Space Invaders · qwen3.8-27b
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 · 14 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.
thinking
Create a single index.html containing a Canvas Space Invaders game. Make it complete: player movement (arrow keys), shooting, a grid of aliens that moves side to side and descends, aliens shooting back, destructible bunkers (made of small destructible blocks), score, lives, waves, game over, start screen, procedural pixel-art sprites drawn with rectangles. I'll write the file.
/tmp/ws/index.html (17385 bytes)Successfully wrote 17385 bytes to /tmp/ws/index.html
thinking
Let's clean up a few rough spots: the alien speed calculation has some messy placeholder lines left over. Also, there's some weird code in buildBunkers (the `r === 4 && ... && r === 4` condition, and `r === 3 && ... && r === 4` which will never be true). Let's simplify the bunker shape. On top of that, `alienStepX` gets assigned inside the update — that's fine but a bit awkward. Also the restart condition logic is convoluted. Let's fix these.
Let me clean up a few convoluted spots (alien speed calc and bunker shape logic):
{"path":"/tmp/ws/index.html","edits":[{"oldText":" let alive = true;\n if (r === 0 && (c < 2 || c > BUNKER_COLS - 3)) alivSuccessfully replaced 2 block(s) in /tmp/ws/index.html.
thinking
Also fix the alien movement section: `const stepW = alienStepX = 12 * (wave > 2 ? 1 : 1);` is silly. Also the descent freeze logic `alienDir = 0` — if it's 0, `a.x += stepW * alienDir` becomes 0, and the hitEdge check is... actually, when alienDir=0, hitEdge will always be false, so no descent happens either (descent only happens on edge hit). That's fine, but edge behavior: if alienDir is 0, no movement, no descent — aliens frozen in the air. But they might be overlapping with the player... The real classic behavior: game over when aliens reach the player. Simpler: if maxY >= player.y, endGame. Let's do that instead. Also, the `alienStepX` variable is declared but only used there; simplify it. Also, the Enter/R restart logic is convoluted. Simplify: Enter restarts on game over; R restarts at any time.
{"edits":[{"newText":" const b = alienAlive();\n if (b.count === 0) break;\n const stepW = 12;\n let hitEdge = (alienDirSuccessfully replaced 4 block(s) in /tmp/ws/index.html.
thinking
Also, let me verify: in update, when the player dies and lives>0, we clear alienBullets — no problem. Also, the one-shot logic for " " on the keyboard: setting keys[" "]=false on fire prevents auto-fire, but keyup for " " sets false anyway; holding down the space key fires only once (no keydown repeat due to repeat? keydown repeats and sets keys[e.key]=true again → refires. Actually keydown repeats, so if you keep holding it, it will repeatedly fire as long as only one bullet is on screen... we're setting keys to false on fire, but the repeat event re-sets it to true. Net effect: while held, it fires at a rate limited by bullet count (max 1 on screen). This is acceptable / good behavior. Quick syntax check with node.
{"command":"cd /tmp/ws && node -e \"\nconst html = require('fs').readFileSync('index.html','utf8');\nconst m = html.match(/<script>([\\s\\S]syntax OK
thinking
Syntax is OK (the first node -e was a bit hacky, but it passed). Let me try running a cleaner check: extract the script and node --check it.
{"command":"cd /tmp/ws && sed -n '/<script>/,/<\\/script>/p' index.html | sed '1d;$d' > /tmp/check.js && node --check /tmp/check.js && echo PASS -rw-r--r--. 1 node node 16621 Aug 15 14:00 index.html
thinking
Also, quickly simulate the game logic headlessly to catch runtime errors (e.g., the aliens object being used before startGame — the input handler references aliens but is registered after startGame, so no problem). Should I do a quick headless run in node using DOM stubs? It might be worth it for catching runtime errors.
Let me do a quick headless runtime test to catch any logic errors:
{"command":"cd /tmp/ws && cat > /tmp/sim.js <<'EOF'\n// minimal DOM stubs\nconst ctxStub = new Proxy({}, { get: (t,p) => (p==='globalAlpha'|SIM OK — no runtime errors over 60s of simulated play
thinking
Runtime simulation passed. This includes restarts (r), pause (p), shooting, and movement. The game works. Done — I'll wrap it up concisely.
Done. `index.html` is in place (single self-contained file, no external resources, syntax-checked and run through a 60-second headless simulation of gameplay with no runtime errors). **What's in the game:** - **Player cannon** — arrow keys (or A/D) to move, Space to shoot (one bullet on screen at a time, classic-style), all drawn procedurally as rectangles - **Alien grid** — 5 rows × 11 aliens (squid/crab/octo) with 2-frame animated pixel-bitmap sprites in distinct colors, moving in the classic stepwise side-to-side pattern, descending 14px on each direction change, and accelerating as their numbers drop - **Alien fire** — random bottom-of-column aliens fire jagged orange bolts back at you - **Destructible bunkers** — 4 shield bunkers built from 13×6 brick grids with a trapezoid shape and bottom notch; bullets erode them brick-by-brick with a splash-radius damage model - **Score & lives** — 30/20/10 points by alien type, +100 wave bonus, 3 lives shown as mini-cannons, persistent high score, wave counter - **Extras** — particle explosion effects, wave progression (new waves start lower and faster), READY/WAVE announcements, pause (P), restart (R or Enter after game over), game-over screen with final score Just open `index.html` in a browser to play.
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 descending aliens, bunkers, score/lives HUD, player cannon movement, and a READY prompt appearing after input.
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.