A faithful recreation of the classic 1978 arcade game Space Invaders, built entirely with web technologies.
This is a complete, self-contained Space Invaders clone featuring the iconic alien formation, destructible bunkers, bonus UFO, and progressive difficulty — all running directly in your browser with authentic retro sound effects.
- Three distinct alien types — Squid (top), Crab (middle), and Octopus (bottom)
- Pixel art sprites — Authentic retro-style graphics with animated sprites
- Destructible bunkers — Four shields that erode from both player and enemy fire
- Bonus UFO — Randomly appears for bonus points (50–300)
- Particle explosions — Classic arcade-style particle effects
- Procedural sound effects — All sounds generated via Web Audio API (no audio files needed)
- Progressive difficulty — Aliens speed up as fewer remain and each level increases challenge
- Starfield background — Twinkling stars for atmosphere
This game is built as a single-file, zero-dependency web application using:
| Technology | Purpose |
|---|---|
| HTML5 Canvas | 2D rendering for all game graphics |
| CSS3 | UI styling and layout (start screen, game over) |
| Vanilla JavaScript | All game logic, state management, and rendering |
| Web Audio API | Procedural sound generation (no external files) |
| RequestAnimationFrame | Smooth 60fps game loop |
space-invaders/
└── index.html # Complete game in a single file
├── <style> # Game UI and layout CSS
├── <canvas> # Main game rendering surface
├── Start screen # HTML overlay for title screen
├── Game Over screen # HTML overlay for game over state
└── <script> # All game logic (~700 lines)
The entire game fits in a single index.html file — no build tools, no dependencies, no installation. Just open it in a browser.
The game follows the classic update-render cycle:
┌─────────────────────────────────────────────┐
│ requestAnimationFrame │
└──────────────────┬──────────────────────────┘
│
┌───────▼────────┐
│ UPDATE() │ ← Game logic
├────────────────┤ - Player movement
│ │ - Alien marching
│ │ - Bullet physics
│ │ - Collision detection
│ │ - Particle updates
│ │ - Game state transitions
└───────┬────────┘
│
┌───────▼────────┐
│ RENDER() │ ← Drawing
├────────────────┤ - Clear canvas
│ │ - Draw starfield
│ │ - Draw bunkers
│ │ - Draw player
│ │ - Draw aliens
│ │ - Draw bullets
│ │ - Draw particles
│ │ - Draw UFO
└───────┬────────┘
│
┌───────▼────────┐
│ Repeat │
└────────────────┘
The game tracks its state with a single gameState variable:
let gameState = 'start'; // 'start' | 'playing' | 'gameOver'State transitions are triggered by player input (Space key) and game events (player death, level complete).
Entities (player, aliens, bullets, UFO, bunkers) are simple objects with position, size, and behavior properties:
// Player
let player = { x: W / 2, y: H - 50, width: 40, height: 24 };
// Aliens (array of objects)
let aliens = [
{ x: 40, y: 80, width: 28, height: 20, type: 0, alive: true },
// ... more aliens
];
// Bullets (shared array, distinguished by flag)
let playerBullets = [
{ x: 398, y: 550, width: 4, height: 12 }, // player bullet
{ x: 100, y: 200, width: 4, height: 12, isEnemy: true } // enemy bullet
];This lightweight approach avoids the complexity of full ECS while keeping entities easily iterable and serializable.
Graphics use Canvas 2D API with pixelated rendering:
canvas {
image-rendering: pixelated; /* Crisp pixels, no smoothing */
}Sprites are defined as pixel grid arrays — coordinate pairs that map directly to drawn rectangles:
const squidSprite = [
[3,0],[4,0],[5,0], // Top row of pixels
[2,1],[3,1],[4,1],[5,1],[6,1], // Next row...
// ... more rows
];
function drawPixelSprite(data, x, y, size, color) {
ctx.fillStyle = color;
data.forEach(([px, py]) => {
ctx.fillRect(x + px * size, y + py * size, size, size);
});
}This approach:
- Eliminates image file dependencies
- Allows procedural sprite generation
- Makes sprite editing trivial (just edit arrays)
- Keeps the file self-contained
Two collision methods are used:
Rectangle-Rectangle (AABB) — For static checks:
function rectCollision(a, b) {
return a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y;
}Line-Rectangle — To prevent "tunneling" when fast bullets skip over thin targets:
function lineRectCollision(bullet, topY, bottomY, rect) {
// Check if the bullet's path (previous → current position)
// intersects the target rectangle
}The line-based check traces the bullet's previous position to its current position, catching any collisions that would otherwise be missed at high speeds.
All sounds are generated procedurally using the Web Audio API:
function playShoot() {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'square';
osc.frequency.setValueAtTime(880, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(440, audioCtx.currentTime + 0.1);
// ... envelope and connection
}Sound effects include:
- Player shoot — Square wave descending tone
- Alien march — Classic 4-note descending sequence
- Alien hit — Sawtooth wave crash
- Player hit — Dramatic descending wail
- UFO approach — Sine wave hover
- UFO hit — Rapid descending blips
- Level complete — Ascending fanfare
Each sound uses an ADSR-like envelope for authentic retro character.
Aliens follow a simple but engaging behavior pattern:
- March — All aliens move horizontally together
- Drop — When any alien hits the screen edge, the entire formation shifts down
- Accelerate — As fewer aliens remain, the formation speeds up (classic tension mechanic)
- Shoot — Aliens randomly fire from bottom-row columns
- Escalate — Shooting frequency increases with level and remaining alien count
// Speed increases as aliens are destroyed
const speedMultiplier = 1 + (1 - aliveAliens.length / aliens.length) * 2;
// Shooting rate increases with difficulty
const shootRate = Math.max(30, 120 - level * 15 -
(1 - aliveAliens.length / aliens.length) * 60);| Constant | Value | Purpose |
|---|---|---|
| Canvas size | 800 × 600 | Game world resolution |
| Alien rows | 5 | Number of alien rows |
| Alien columns | 11 | Number of alien columns |
| Total aliens | 55 | 5 × 11 formation |
| Player lives | 3 | Starting lives |
| Bunkers | 4 | Defendable shields |
| Bunker health | 100 | Degrades on hit |
| Type | Points | Color | Shape | Animation |
|---|---|---|---|---|
| 0 (Squid) | 30 × level | Red | 9×8 pixels | Tentacle wave |
| 1 (Crab) | 20 × level | Orange | 13×13 pixels | Claw raise/lower |
| 2 (Octopus) | 10 × level | Green | 13×13 pixels | Tentacle spread |
I chose vanilla HTML/Canvas/JavaScript for several reasons:
- No installation required — Works in any modern browser
- No build tools — No Webpack, Vite, npm, or package.json
- No dependencies — Pure, self-contained file
- Instant playability — Double-click and play
- Canvas 2D API was designed for exactly this use case
- Pixel manipulation is straightforward with
fillRect requestAnimationFrameprovides smooth 60fpsimage-rendering: pixelatedgives crisp retro graphics
- Runs on any device with a browser (desktop, tablet, phone)
- No platform-specific binaries
- Shareable via a single file or URL
- Demonstrates core game dev concepts without framework complexity
- Easy to modify and experiment with
- Forces understanding of the fundamentals: game loop, state, rendering, input
- Canvas 2D is fast enough for 55 animated sprites at 60fps
- No overhead from virtual DOM or framework re-renders
- Direct pixel access for maximum control
| Option | Why Not |
|---|---|
| Python/Pygame | Requires Python installation; not shareable via URL |
| React/Vue | Framework overhead unnecessary for simple 2D rendering |
| WebGL | Overkill for simple 2D; steep learning curve |
| Game engines (Unity, Godot) | Heavy toolchain; overkill for a single-file clone |
| Key | Action |
|---|---|
| ← → or A D | Move ship left/right |
| Space | Shoot / Start / Restart |
- Open
index.htmlin any modern browser - Press Space to start
- Destroy all aliens to advance to the next level
- Don't let aliens reach the bottom!
- Power-ups (spread shot, shield, slow-motion)
- High score persistence (localStorage)
- More alien patterns (V-formation, circle)
- Background music
- Touch controls for mobile
- Sound toggle
- Multiple ship types
- Canvas resolution: 800 × 600 pixels
- Sprite size: 3px per pixel (scaled from 13×13 pixel sprites)
- Frame rate: 60fps via
requestAnimationFrame - Audio sample rate: Browser default (typically 44.1kHz)
- File size: ~30KB (entire game)
This project was developed using pi, a minimal terminal-based coding agent harness that extends LLM capabilities through a powerful TypeScript extension system.
| Property | Value |
|---|---|
| Provider | llama.cpp (self-hosted server at http://100.88.205.86:8080) |
| Model | Qwen3.6-35B-A3B-MTP-GGUF (quantized to UD-Q5_K_M) |
| Thinking Level | Medium |
| Framework | pi — minimal, extensible coding agent |
pi is a terminal coding harness that ships with powerful defaults (read, write, edit, bash tools) but skips features like sub-agents and plan mode. Instead, it is aggressively extensible — capabilities are added via TypeScript extensions, skills, prompt templates, and pi packages. It supports multiple providers (Anthropic, OpenAI, Google, xAI, and many more via API keys or subscriptions) as well as self-hosted llama.cpp servers.
1. pi-llama-cpp (npm: pi-llama-cpp)
Enables llama.cpp integration for pi. Supports router, single, and legacy models across multiple servers. This is how the self-hosted Qwen model is made available to pi as a provider.
2. searxng-suite (npm: @blazer2k/searxng-suite)
Provides SearxNG-based web search with category filtering and multi-format URL extraction. Registers the web_search and web_extract tools, enabling the agent to look up information during development.
| Aspect | Reason |
|---|---|
| Self-hosted model | Full control over model choice, no API costs, no rate limits |
| pi over Claude Code | More extensible, no vendor lock-in, runs locally, open source |
| TypeScript extensions | Full system access, custom tools, UI components, event hooks |
| llama.cpp | Efficient GGUF inference, runs on any hardware, mature ecosystem |
Built with ❤ and vanilla JavaScript. No game engines were harmed in the making of this game.
