Space Invaders · kat-coder-air-v2.5
Build a playable Space Invaders game on a canvas: a player cannon that moves and shoots, a descending grid of aliens that advance and fire back, destructible bunkers, score, and lives. Procedural graphics only, no external assets.
transcript · 16 messages
Build a playable Space Invaders game on a canvas: a player cannon that moves and shoots, a descending grid of aliens that advance and fire back, destructible bunkers, score, and lives. Procedural graphics only, no external assets.
/tmp/ws/index.html (15001 bytes)Successfully wrote 15001 bytes to /tmp/ws/index.html
/tmp/ws/index.html (14996 bytes)Successfully wrote 14996 bytes to /tmp/ws/index.html
{"command":"ls -la /tmp/ws/index.html"}-rw-r--r--. 1 node node 15068 Aug 1 20:50 /tmp/ws/index.html
The file is written. Let me verify it's valid HTML by checking the structure.
{"command":"head -5 /tmp/ws/index.html && echo \"---\" && tail -5 /tmp/ws/index.html"}<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> --- } loop(); </script> </body> </html>
I notice the alien animation doesn't actually vary between frames. Let me fix that and a couple other issues.
{"limit":100,"offset":1,"path":"/tmp/ws/index.html"}<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Space Invaders</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #0a0a0a; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; font-family: 'Courier New', monospace; } canvas { background: #000; border: 2px solid #333; image-rendering: pixelated; } </style> </head> <body> <canvas id="c"></canvas> <script> const canvas = document.getElementById('c'); const ctx = canvas.getContext('2d'); const W = 640, H = 520; canvas.width = W; canvas.height = H; // ── Audio (procedural, no external assets) ── const AudioCtx = window.AudioContext || window.webkitAudioContext; let audioCtx; function ensureAudio() { if (!audioCtx) audioCtx = new AudioCtx(); } function tone(freq, dur, vol=0.1, type='square') { if (!audioCtx) return; const o = audioCtx.createOscillator(); const g = audioCtx.createGain(); o.type = type; o.frequency.value = freq; g.gain.setValueAtTime(vol, audioCtx.currentTime); g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + dur); o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + dur); } function sfxShoot() { tone(880, 0.08, 0.08); } function sfxHit() { tone(220, 0.15, 0.12, 'sawtooth'); } function sfxExplode(){ tone(80, 0.3, 0.15, 'sawtooth'); } function sfxAlienHit(){ tone(440, 0.1, 0.08, 'triangle'); } function sfxPlayerHit(){ tone(100, 0.4, 0.15, 'sawtooth'); } function sfxWin() { [523,659,784,1047].forEach((f,i)=>setTimeout(()=>tone(f,0.2,0.1,'square'),i*100)); } // ── Game State ── let state, score, lives, level, frame; let player, aliens, bullets, alienBullets, bunkers, particles, stars; let alienDir, alienSpeed, alienMoveTimer, alienShootTimer; let shakeTimer, shakeIntensity; function initGame() { score = 0; lives = 3; level = 1; initLevel(); state = 'playing'; } function initLevel() { frame = 0; player = { x: W/2, y: H-40, w: 40, h: 20, speed: 5, cooldown: 0 }; bullets = []; alienBullets = []; particles = []; alienDir = 1; alienSpeed = 1 + level * 0.3; alienMoveTimer = 0; alienShootTimer = 0; shakeTimer = 0; shakeIntensity = 0; // Stars stars = []; for (let i = 0; i < 80; i++) { stars.push({ x: Math.random()*W, y: Math.random()*(H-80), s: Math.random()*1.5+0.5, b: Math.random() }); } // Aliens: 5 rows x 11 columns aliens = []; const rows = 5, cols = 11; const spacingX = 48, spacingY = 38; const startX = 60, startY = 60; for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { let type, points; if (r === 0) { type = 'top'; points = 30; } else if (r <= 2) { type = 'mid'; points = 20; } else { type = 'bot'; points = 10; } aliens.push({ x: startX + c * spacingX, y: startY + r * spacingY, w: 30, h: 24, [457 more lines in file. Use offset=101 to continue.]
Let me rewrite the file with proper two-frame alien animations and some other improvements.
/tmp/ws/index.html (15268 bytes)Successfully wrote 15268 bytes to /tmp/ws/index.html
{"command":"wc -l /tmp/ws/index.html && tail -3 /tmp/ws/index.html"}579 /tmp/ws/index.html </script> </body> </html>
The file is complete and valid. Here's what's in `/tmp/ws/index.html`: **Gameplay:** - Player cannon moves left/right with arrow keys or A/D, shoots with Space or ↑ - 5×11 grid of aliens in 3 types (red top, green mid, blue bot) with different point values (30/20/10) - Aliens march side-to-side and descend when hitting edges, speeding up as fewer remain - Aliens fire back at the player - 4 destructible bunkers made of pixel blocks with arched doorways - 5 levels of increasing difficulty, then a win screen **Features:** - Procedural pixel-art alien sprites with two-frame walking animation - Particle explosion effects on hits - Screen shake when player is hit - Twinkling star field background - Procedural sound effects (shoot, hit, explode, win fanfare) via Web Audio API - HUD with score, level, and lives displayed as mini ships - Start screen, game over, and victory overlays
TAKE IT TO CODE
A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.
RUN
EVALUATION
Space Invaders with menu, alien grid, cannon, bunkers, score/lives; keyboard starts game and moves ship/aliens, but pointer inputs do nothing.
Vision grade · anthropic/claude-sonnet-5
MEASUREMENTS
Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.