Space Invaders · nex-n2-mini
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 · 10 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
We need respond by creating index.html in /tmp/ws. Need implement playable Space Invaders canvas single file no external resources. Need inspect maybe empty. We need use tools. Need maybe create file. Need be careful final concise. We need implement game robust. We can write single HTML with CSS/JS. Need no external assets. Need playable canvas: player cannon moves/shoots, descending grid aliens advance/fire back, destructible bunkers, score/lives. Procedural graphics. Need perhaps include instructions, start/restart, pause, sound? no external. Need maybe responsive.
Need create index.html. We can write file. Need maybe maybe use canvas and JS. Let's design.
We can write self-contained HTML. Features:
- Canvas 800x600.
- Game loop with requestAnimationFrame, update dt.
- Player at bottom, cannon moves left/right with keyboard arrows/A/D, shoot space. Touch controls maybe buttons.
- Aliens grid descending, move horizontally, advance down if hit edge. Alien fire back with random interval.
- Bunkers destructible made of blocks cells, can be destroyed by bullets.
- Score/lives display.
- Procedural graphics.
Need implement collision.
Need maybe include canvas 800x600, CSS centered.
Let's craft code. Need all in one file.
Let's think game mechanics.
Constants:
WIDTH=800 HEIGHT=600.
PLAYER width 44 height 16, speed 360 px/s.
Aliens 11 columns x 5 rows maybe 11*5, alienW 34 alienH 24 gapX 12 gapY 14. Starting x center.
Alien direction 1, speed 35 initial, increment per level.
Alien positions with origin.
Bunkers: 4 bunkers each 18 columns x 10 rows? Blocks 8x8 gap 2. Bunker x positions [80, 230, 380, 530] y 500? Actually bunker top 505? player at 560. Need bunker above player, bottom 560. Use block size 8, spacing 2, w = cols*bs + (cols-1)*gap = 18*8+17*2=178. y = 500. Player y=562, height=16, bunker top=500 bottom=562. Good. Bunker blocks array.
Player bullets array {x,y,w,h,vy}; alien bullets {x,y,w,h,vy}. Player y = height-36.
Bunkers collision: bullet vs block AABB. Remove block, bullet maybe destroyed. Alien vs bunker: if alien touches destroyed bunker, game over? Classic. We'll implement if alien reaches bunker top or bottom? If alien bullet kills player game over. If alien reaches bunker top, game over.
Aliens:
columns = 11, rows=5. alienRects array with x,y,w,h alive.
startX = (W - totalW)/2. totalW = cols*AW + (cols-1)*AGAP. startY = 75.
movement: each frame if any alive? dx = dir*speed*dt. move all alive. if minX < 25 or maxX > W-25 direction flips and move down 18, speed increment. maybe "descending grid advance".
Alien fire: timer; fire when timer<=0. Choose shooter bottommost per column (alive), random among columns with aliens. Fire one bullet at speed 210. maybe if multiple aliens in same column choose lowest.
Player shooting: cooldown 0.28s. Space/Keydown once. bullet speed 520.
Bunkers:
create blocks array with x,y,w=bs,h=bs, alive=true. For each bunker. Collision bullet with bunker:
- bullet x,y center? Use AABB. If bullet intersects, remove block and bullet. For alien bullet maybe same. For player bullet if hits alien first? order.
- Draw bunker as block with random color variations.
Scoring:
- each alien destroyed score maybe 10 + row bonus? rows top 50, bottom 10. Level clear -> spawn next with higher speed, fewer? Keep score.
Lives:
3 lives. If alien bullet hits player -> lose life, reset player, maybe respawn bunker? Maybe after hit reset bullets, player invulnerable 1.5s. If lives <=0 game over.
- Also if alien reaches bunker -> lose life? or game over? User asks lives. We'll implement lives lost if alien touches bunker (or bottom), reset player and clear bullets; if lives >0 continue. If alien reaches bottom -> lose life and reset.
Game states:
state = 'title' | 'playing' | 'paused' | 'gameover' | 'levelclear'? Simpler: start overlay. Press Space/Enter starts.
- Initial title with score/lives? Reset game.
- update only playing.
- paused with P.
- game over state with restart.
- Level clear: after all aliens destroyed, score bonus, level++, spawn aliens, maybe reset bunker damage? Could preserve bunker damage. Need maybe not too hard.
- Overlay text.
Need maybe handle focus. Keyboard events prevent default for arrows/space.
Touch controls:
- On-screen buttons for left/right/shoot maybe optional. User didn't ask but helpful. No external.
- Buttons call set keys. We can include.
- Canvas click maybe start/restart? Press Enter.
Need maybe game loop:
let last = 0;
function loop(t) { let dt = Math.min((t-last)/1000,0.033); last=t; if(state==='playing') update(dt); draw(); requestAnimationFrame(loop); }
Need draw title.
Implementation details:
HTML:
<!doctype html>
<html lang="en">
<head><meta charset=utf-8><meta name=viewport...><title>Canvas Space Invaders</title><style>...</style></head>
<body>
<div class=shell>
<h1>Space Invaders</h1>
<canvas id=game width=800 height=600 tabindex="0"></canvas>
<div class=help>...</div>
<div class=controls>buttons...
</div>
<script>
(() => { 'use strict'; ...})();
</script>
</body></html>
CSS:
body gradient, color, display flex, etc. canvas border, image-rendering maybe not.
JS:
const canvas = document.getElementById('game'), ctx = canvas.getContext('2d');
const DPR maybe? Canvas fixed 800x600. CSS width min(100vw, 960px). But coordinate system fixed.
Need maybe handle touch buttons:
const controls = {left:false,right:false,shoot:false};
function setControl(name, pressed) ...
Button mousedown/touchstart pointerdown/up/cancel.
document keydown/up.
Need resetGame:
score=0; lives=3; level=1; state='playing'; player reset; bullets arrays; aliens create; bunker reset; timers.
Maybe start screen initial state = 'title', but draw uses reset? Could call setupGame(false)? Let's define:
let score=0,lives=3,level=1,state='title'; let player = createPlayer(); let aliens=[],playerBullets=[],alienBullets=[],bunkers=[],...
function newGame() { score=0; lives=3; level=1; state='playing'; setupPlayer(); setupBunkers(); setupAliens(); timers=0; last=performance.now(); }
function setupAliens() {...}
function setupBunkers() { bunkerBlocks=[]; bunker positions; }
function resetPlayer() { player.x = W/2 - player.w/2; player.y = H-38; player.invuln=1.5; }
Need if lives lost:
function loseLife(reason) { lives--; clear bullets? playerBullets=[] maybe alienBullets=[]; if(lives<=0) state='gameover'; else { resetPlayer(); timers? alienFireTimer = Math.max(alienFireTimer,0.6); } }
If alien reaches bunker, lose life. But if aliens already descending, reset all alien bullets and maybe remove all alien bullets. Keep aliens? In classic if they touch bunker, lose life and reset player, maybe aliens stay? Simpler lose life and reset player/bullets, keep aliens? But if aliens are in bunker, they might still be there, player can shoot them. We can maybe reset aliens to top after losing life due to bunker? But then alien bullet kill? Need not. We'll implement loseLife resets player and bullets but keeps aliens; however if aliens still in bunker, they might not move? They can still move, maybe immediate collision. Fine.
Need game over if alien reaches bottom:
function aliensReachedBottom() { for alive if y+height>H-80 maybe loseLife('Aliens breached bunker') } In update after movement.
Need alien fire:
function chooseAlienShooter() {
const columns = new Array(cols).fill(null);
for each alive a: if !columns[col] || a.y > columns[col].y then columns[col]=a
const shooters = columns.filter(Boolean);
if (shooters.length) return shooters[Math.floor(Math.random()*shooters.length)];
return null;
}
fire:
let shooter=chooseAlienShooter(); if shooter { alienBullets.push({x: shooter.x+shooter.w/2-3, y: shooter.y+shooter.h, w:6,h:12,vy:190+level*12}); }
Need alien movement:
let alienDir=1, alienSpeed=32+level*4, alienDrop=16.
update:
if aliens alive:
let minX = min alive.x; maxX = max alive.x+alienW
let dx=alienSpeed*dt*alienDir
let nextMin = minX+dx; nextMax=maxX+dx
if(nextMin<24 || nextMax>W-24) { alienDir *= -1; for alive alive.y += alienDrop; // maybe shift x? no
alienSpeed += 6;
}
for alive alive.x += dx.
if minX < 20 or maxX > W-20 after move, maybe flip and drop. Need avoid repeated. Use edge collision before move.
Also if any alive y+height > bunker top + 20? loseLife.
Maybe if alive y+height > H-85? loseLife.
Need player controls:
let keys = {left:false,right:false,shoot:false};
In update:
if keys.left player.x -= speed*dt; if right +=. if playerBullets.length===0 and keys.shoot maybe continuous? Player cannon shoots while holding. We can allow continuous with cooldown. But maybe "shoot" should fire while holding. Use player.shootCooldown. If keys.shoot && cooldown<=0 then fire. Need maybe initial cooldown=0.
Need prevent shooting if state not playing.
if player.invuln >0 decrement.
if player.shootCooldown >0 decrement.
Bullets:
for each p: p.y += p.vy*dt; p.active=true.
Alien bullets same.
Collisions:
- Player bullets with aliens:
for pb of playerBullets:
if pb.active:
for a of aliens: if alive && a.active && rectOverlap(pb,a): a.active=false; pb.active=false; score += 10 + (rows-1-a.row)*5; maybe if level clear after all active false.
- Alien bullets with player:
if player.invuln <=0 && rectOverlap(ab, player): ab.active=false; loseLife('Hit by alien fire');
- Bullets with bunkers:
For each bullet and bunker block. Need optimize but small. Could do:
function collideBulletWithBunkers(b) {
for each bunker in bunkers:
for block of bunker.blocks:
if block.alive && rectOverlap(b, block) { block.alive=false; b.active=false; return true; }
}
}
Call for playerBullets and alienBullets. Need maybe bullet dimensions.
- Alien bullets with bunkers before player. Player bullets with bunker before alien? In classic player bullet hits bunker before alien if bunker between; order: bunker collisions for player bullets first then aliens. But if bullet passes through bunker, should destroy bunker. If alien behind bunker, blocked. So do bunker first for player bullets, then aliens. For alien bullets, bunker first then player.
- Aliens with bunkers: if alive and a.y + a.h >= bunkerTop? loseLife.
- Player with aliens? If alien collides player, lose life.
- Level clear: if no alive aliens after bullet collisions:
score += lives*100 + level*200; state maybe 'levelclear'; show overlay? Then spawn after delay. Could implement state='levelclear'; levelClearTimer=2.0; draw overlay; update decrements; after timer setupAliens level++ and state='playing'. Need preserve bunker damage.
Need no immediate game over after all aliens destroyed. In update:
if state === 'levelclear' { levelClearTimer -= dt; if <=0 { level++; setupAliens(); state='playing'; } return; }
But if setupAliens sets state? no.
Need maybe score bonus.
Need draw:
ctx.clearRect; background black gradient maybe. Draw stars.
Draw score/lives at top:
ctx.fillStyle '#e8f7ff'; ctx.font '16px monospace'; ctx.fillText(`SCORE ${score}` 20 26; `LEVEL ${level}`; `LIVES ${lives}`)
Maybe draw alien count?
Draw bunkers:
for bunker of bunkers: for blocks if alive draw. Could draw individual blocks with slight color based on x,y.
Draw player:
if invuln flicker: if invuln >0 && Math.floor(invuln*12)%2===0 skip? But still maybe draw shield.
Draw player cannon as pixel shape:
ctx.save translate center. fillStyle '#38d5ff';
body: rectangle x-10 y-12 w20 h10; triangle nose? Use path.
Draw using procedural shapes. Maybe fillRect and path.
Draw alien:
Pixel style with fillRect blocks. Function drawAlien(a, color). Could draw using rectangles:
body fillStyle color; x+4 y+4 w26 h12; top x+8 y+0 w18 h8; legs x+2 y+16 w8 h6; x+24 etc; eyes black.
Draw bullets: rectangles.
Draw explosions? Could add particle effects? Not necessary but nice. Procedural graphics. Maybe add small particles on bunker destroyed/alien. But not too complex. Could implement particle array for visual.
Need maybe "procedural graphics only" means no external assets; drawing shapes okay.
Draw overlay:
if state title: semi-transparent panel with "SPACE INVADERS", instructions.
if paused: "PAUSED - P to resume".
if gameover: "GAME OVER", "Press Enter/Space to restart".
if levelclear: "LEVEL CLEAR", "+bonus", "Next level..."
Need maybe draw game over score.
Need if state title, maybe draw demo? Could draw title overlay and maybe no game? draw current state with initial? We can call setupBunkers, setupPlayer, setupAliens before loop but state title; draw game behind.
Need start/restart:
function beginOrRestart() { if state==='title'||state==='gameover'||state==='levelclear'? Actually levelclear auto. If state==='playing' maybe start? If paused resume. if state==='paused' state='playing'; else newGame(); canvas.focus(); }
keydown:
if Space:
if state==='title'||state==='gameover' beginOrRestart()
else if state==='playing' keys.shoot=true; prevent
if Enter same
if P toggles pause
if state==='playing' and (ArrowLeft/KeyA) keys.left=true etc.
keyup set false.
Need prevent default for keys.
Maybe if state not playing and arrow keys? no.
Need maybe handle focus after click:
canvas.addEventListener('pointerdown', () => canvas.focus());
Need maybe update on mobile:
touch controls. Buttons with data-control. pointerdown set true; pointerup false; pointercancel; pointerleave.
Need maybe no external resources. Good.
Need write file. Use write.
/tmp/ws/index.html (25723 bytes)Successfully wrote 25723 bytes to /tmp/ws/index.html
thinking
Need maybe validate file. Could run maybe no external. Need inspect maybe maybe bug in levelclear bonus text: uses state.score - (state.score - (lives*100+level*200)) equals bonus, but after score already updated and before state.level++ so okay. But if state.score variable after bonus, expression okay. Could simplify but fine. Need maybe issue with updateAliens: if aliens reach bunker and loseLife, state.mode maybe gameover/paused? Then updateBullets still runs after updateAliens even if gameover, maybe okay. But if loseLife resets player and alienBullets cleared, then updateBullets filters. updateCollisions sees mode not playing maybe? It has if state.mode === PLAYING and player.invuln; then for aliens rectOverlap player maybe if gameover no. Good. But if loseLife due bunker and lives >0, state.mode still playing. Then updateCollisions can continue, maybe aliens still overlapping player? Since player reset and aliens at bunker, could immediately overlap? If aliens reached bunker top ~502, player y ~558, alien h 24, player h 18, no overlap; okay. But if aliens are much lower maybe H-85 bottom: loseLife due bottom, aliens reset? Actually loseLife due bottom if a.y+a.h >= H-85; with H=600, threshold 515. That's near bunker top. okay.
Potential issue: In newGame, setupAliens uses state.level=1; setupBunkers random every new game. okay.
Potential issue: state.mode LEVELCLEAR, update returns? In update, after updateAliens updateBullets updateCollisions; updateCollisions sets state.mode LEVELCLEAR. Then after if state.mode LEVELCLEAR, timer. Good.
Potential issue: drawOverlay levelclear bonus line uses expression maybe weird. Could be okay.
Potential issue: pressing Space starts and sets keys.shoot true; in same keydown if mode playing? Actually if mode title, restartIfNeeded; doesn't set keys.shoot. On keyup keys.shoot false? But keydown didn't set, okay. If holding Space after start, keyup might not fire? Next keydown maybe. Fine.
Potential issue: On mobile, pressing Fire button starts game but doesn't set keys.shoot true? It calls restartIfNeeded but not setControl('shoot', true). Button down after state playing maybe no fire until release? Actually press: setControl(control,true) after restartIfNeeded? Code:
if control !== pause setControl true; if control === shoot && state.mode !== playing restartIfNeeded();
{"command":"ls -l /tmp/ws/index.html && node --check /tmp/ws/index.html","timeout":5}-rw-r--r--. 1 node node 25741 Aug 1 20:50 /tmp/ws/index.html node:internal/modules/esm/get_format:236 throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath); ^ TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".html" for /tmp/ws/index.html at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:236:9) at defaultGetFormat (node:internal/modules/esm/get_format:262:36) at checkSyntax (node:internal/main/check_syntax:67:20) { code: 'ERR_UNKNOWN_FILE_EXTENSION' } Node.js v24.18.1 Command exited with code 1
thinking
Node check invalid. Need maybe use python to strip script? Or no need. But maybe we should validate syntax by extracting script? Use python regex? Or maybe use npx? Not external. Could use node -e to read and new Function(script). Need ensure no DOM references at parse. We can extract script text and new Function.
{"command":"python3 - <<'PY'\nfrom pathlib import Path\nimport re\nhtml = Path('/tmp/ws/index.html').read_text()\nscripts = re.findall(r'<sc/bin/bash: line 1: python3: command not found Command exited with code 127
thinking
No python3. Use node script to extract.
{"command":"node - <<'NODE'\nconst fs = require('fs');\nconst html = fs.readFileSync('/tmp/ws/index.html', 'utf8');\nconst scripts = html.mascript 1 syntax ok (21748 chars)
thinking
Need maybe consider maybe "current directory: /tmp/ws" and "End with index.html present" yes. Need final concise mention created /tmp/ws/index.html. But maybe should mention no external resources. Fine. Need maybe maybe check file exists. Already did. Final.
Created `/tmp/ws/index.html` with a self-contained playable canvas Space Invaders game: - Player cannon movement and shooting - Descending advancing alien grid - Alien return fire - Destructible procedural bunkers - Score, level, and lives - Pause/restart controls - No external assets, CSS, JS, or network dependencies
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, aliens, bunkers, ship, and projectiles responds correctly to keyboard but not mouse 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.