Skip to content

Instantly share code, notes, and snippets.

@mikaelhadler
Created June 3, 2025 14:29
Show Gist options
  • Save mikaelhadler/f38ff244e0c5b5e5217957888db4ff0d to your computer and use it in GitHub Desktop.
Save mikaelhadler/f38ff244e0c5b5e5217957888db4ff0d to your computer and use it in GitHub Desktop.
space-invaders
{
"name": "ink-space-invaders",
"type": "module",
"private": true,
"scripts": {
"start": "tsx space_invaders.jsx"
},
"dependencies": {
"ink": "^6.0.0",
"react": "^19.1.0"
},
"devDependencies": {
"tsx": "^4.7.0"
}
}
#!/usr/bin/env node
import React, { useState, useEffect } from 'react';
import { render, Text, useInput, Box, useApp } from 'ink';
const WIDTH = 30;
const HEIGHT = 10;
const TICK_RATE = 200;
const createAliens = () => {
const aliens = [];
for (let y = 0; y < 2; y++) {
for (let x = 2; x < WIDTH - 2; x += 4) {
aliens.push({ x, y });
}
}
return aliens;
};
const App = () => {
const [shipX, setShipX] = useState(Math.floor(WIDTH / 2));
const [bullets, setBullets] = useState([]);
const [aliens, setAliens] = useState(createAliens());
const [tick, setTick] = useState(0);
const [gameOver, setGameOver] = useState(false);
const { exit } = useApp();
useInput((input, key) => {
if (input === 'q') exit();
if (key.leftArrow) setShipX(x => Math.max(0, x - 1));
if (key.rightArrow) setShipX(x => Math.min(WIDTH - 1, x + 1));
if (input === ' ') {
setBullets(b => [...b, { x: shipX, y: HEIGHT - 2 }]);
}
});
useEffect(() => {
const timer = setInterval(() => {
setTick(t => t + 1);
}, TICK_RATE);
return () => clearInterval(timer);
}, []);
useEffect(() => {
// move bullets
setBullets(bullets =>
bullets
.map(b => ({ ...b, y: b.y - 1 }))
.filter(b => b.y >= 0)
);
// check collisions
setAliens(aliens =>
aliens.filter(a => {
const hit = bullets.some(b => b.x === a.x && b.y === a.y);
return !hit;
})
);
// lose condition
if (aliens.some(a => a.y >= HEIGHT - 2)) {
setGameOver(true);
setTimeout(exit, 3000);
}
}, [tick]);
const renderRow = y => {
let row = '';
for (let x = 0; x < WIDTH; x++) {
if (aliens.some(a => a.x === x && a.y === y)) {
row += 'πŸ‘Ύ';
} else if (bullets.some(b => b.x === x && b.y === y)) {
row += '|';
} else if (y === HEIGHT - 1 && x === shipX) {
row += 'πŸš€';
} else {
row += ' ';
}
}
return row;
};
return (
<Box flexDirection="column">
<Text>← β†’ to move, space to shoot, q to quit</Text>
{[...Array(HEIGHT).keys()].map(y => (
<Text key={y}>{renderRow(y)}</Text>
))}
{gameOver && <Text color="red">πŸ’₯ Game Over!</Text>}
</Box>
);
};
render(<App />);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment