oneshotlm
Compare all models on this prompt →

Space Invaders · mercury-2

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.

⛨ sandboxed Open in new tab ↗
transcript · 6 messages
user

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.

assistant
write index.html (6675 bytes)
toolResult

Successfully wrote 6675 bytes to index.html

assistant
read {"path":"index.html","limit":2000}
toolResult

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Space Invaders</title> <style> body { margin:0; background:#000; overflow:hidden; } canvas { display:block; margin:auto; background:#111; } #ui { position:absolute; top:10px; left:10px; color:#0f0; font-family:monospace; font-size:16px; } </style> </head> <body> <canvas id="game" width="480" height="640"></canvas> <div id="ui"></div> <script> (() => { const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); const UI = document.getElementById('ui'); const W = canvas.width, H = canvas.height; // Game state const player = { x: W/2, y: H-30, w: 30, h: 10, speed: 4, cooldown: 0 }; const bullets = []; const alienBullets = []; const aliens = []; const bunkers = []; let score = 0, lives = 3, level = 1, gameOver = false; // Init aliens grid function initAliens() { aliens.length = 0; const rows = 5, cols = 11; const alienW = 30, alienH = 20, gap = 10; const offsetX = (W - (cols*alienW + (cols-1)*gap))/2; const offsetY = 40; for(let r=0;r<rows;r++){ for(let c=0;c<cols;c++){ aliens.push({ x: offsetX + c*(alienW+gap), y: offsetY + r*(alienH+gap), w: alienW, h: alienH, alive: true, type: r%2 // 0: top rows, 1: bottom rows (different color) }); } } alienDirection = 1; // 1 right, -1 left alienSpeed = 0.5 + level*0.1; alienDescend = 10; alienFireTimer = 0; } // Init bunkers function initBunkers(){ bunkers.length = 0; const bunkerW = 60, bunkerH = 30, gap = 80; const startX = (W - (3*bunkerW + 2*gap))/2; const y = H-120; for(let i=0;i<3;i++){ bunkers.push({x:startX + i*(bunkerW+gap), y, w:bunkerW, h:bunkerH, health:3}); } } initAliens(); initBunkers(); // Input handling const keys = {}; window.addEventListener('keydown', e=>{keys[e.code]=true;}); window.addEventListener('keyup', e=>{keys[e.code]=false;}); function update(){ if(gameOver) return; // Player movement if(keys['ArrowLeft']) player.x -= player.speed; if(keys['ArrowRight']) player.x += player.speed; player.x = Math.max(0, Math.min(W-player.w, player.x)); // Shooting if(keys['Space'] && player.cooldown<=0){ bullets.push({x:player.x+player.w/2-2, y:player.y-5, w:4, h:10, dy:-6}); player.cooldown = 15; // frames } if(player.cooldown>0) player.cooldown--; // Update bullets for(let i=bullets.length-1;i>=0;i--){ const b = bullets[i]; b.y += b.dy; if(b.y + b.h < 0) bullets.splice(i,1); } for(let i=alienBullets.length-1;i>=0;i--){ const b = alienBullets[i]; b.y += b.dy; if(b.y > H) alienBullets.splice(i,1); } // Move aliens let edge = false; for(const a of aliens){ if(!a.alive) continue; a.x += alienDirection * alienSpeed; if(a.x <=0 || a.x + a.w >= W) edge = true; } if(edge){ alienDirection *= -1; for(const a of aliens){ if(a.alive) a.y += alienDescend; } } // Alien fire alienFireTimer--; if(alienFireTimer<=0){ // pick random alive alien from bottom row of each column const columns = {}; for(const a of aliens){ if(a.alive){ const col = Math.floor(a.x); if(!columns[col]||a.y>columns[col].y) columns[col]=a; } } const candidates = Object.values(columns); if(candidates.length){ const shooter = candidates[Math.floor(Math.random()*candidates.length)]; alienBullets.push({x:shooter.x+shooter.w/2-2, y:shooter.y+shooter.h, w:4, h:10, dy:4}); } alienFireTimer = 60 - level*5; // faster as level increases } // Collision detection // Player bullet vs aliens for(let i=bullets.length-1;i>=0;i--){ const b = bullets[i]; for(const a of aliens){ if(!a.alive) continue; if(b.x < a.x + a.w && b.x + b.w > a.x && b.y < a.y + a.h && b.y + b.h > a.y){ a.alive = false; bullets.splice(i,1); score += 10; break; } } } // Player bullet vs bunkers for(let i=bullets.length-1;i>=0;i--){ const b = bullets[i]; for(const bu of bunkers){ if(bu.health<=0) continue; if(b.x < bu.x + bu.w && b.x + b.w > bu.x && b.y < bu.y + bu.h && b.y + b.h > bu.y){ bu.health--; bullets.splice(i,1); break; } } } // Alien bullet vs player for(let i=alienBullets.length-1;i>=0;i--){ const b = alienBullets[i]; if(b.x < player.x + player.w && b.x + b.w > player.x && b.y < player.y + player.h && b.y + b.h > player.y){ alienBullets.splice(i,1); lives--; if(lives<=0){ gameOver=true; } } } // Alien bullet vs bunkers for(let i=alienBullets.length-1;i>=0;i--){ const b = alienBullets[i]; for(const bu of bunkers){ if(bu.health<=0) continue; if(b.x < bu.x + bu.w && b.x + b.w > bu.x && b.y < bu.y + bu.h && b.y + b.h > bu.y){ bu.health--; alienBullets.splice(i,1); break; } } } // Aliens reaching bottom for(const a of aliens){ if(a.alive && a.y + a.h >= player.y){ gameOver = true; } } // Check level cleared if(aliens.every(a=>!a.alive)){ level++; initAliens(); } } function draw(){ ctx.clearRect(0,0,W,H); // Draw player ctx.fillStyle = '#0f0'; ctx.fillRect(player.x, player.y, player.w, player.h); // Draw bullets ctx.fillStyle = '#ff0'; for(const b of bullets) ctx.fillRect(b.x,b.y,b.w,b.h); ctx.fillStyle = '#f00'; for(const b of alienBullets) ctx.fillRect(b.x,b.y,b.w,b.h); // Draw aliens for(const a of aliens){ if(!a.alive) continue; ctx.fillStyle = a.type===0 ? '#0ff' : '#ff0'; ctx.fillRect(a.x,a.y,a.w,a.h); } // Draw bunkers for(const bu of bunkers){ if(bu.health<=0) continue; const hue = 120 - (3-bu.health)*30; // green to red ctx.fillStyle = `hsl(${hue},100%,50%)`; ctx.fillRect(bu.x,bu.y,bu.w,bu.h); } // UI UI.textContent = `Score: ${score} Lives: ${lives} Level: ${level}`; if(gameOver){ ctx.fillStyle = 'rgba(0,0,0,0.7)'; ctx.fillRect(0,0,W,H); ctx.fillStyle = '#fff'; ctx.font = '48px monospace'; ctx.textAlign = 'center'; ctx.fillText('GAME OVER', W/2, H/2); } } function loop(){ update(); draw(); if(!gameOver) requestAnimationFrame(loop); } requestAnimationFrame(loop); })(); </script> </body> </html>

assistant

The **Space Invaders** game has been created as a single self‑contained `index.html` file. It includes: * A canvas with procedural graphics (no external assets). * Player cannon that moves left/right and shoots. * A descending grid of aliens that move, bounce off walls, descend, and fire back. * Destructible bunkers with health. * Score, lives, and level UI. * Game‑over handling and level progression. You can open `index.html` in any modern browser to play the game. Enjoy!

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

modelmercury-2
vendorInception
harnesspi-agent
generated2026-08-01

EVALUATION

3/5 working

Space Invaders with aliens, bunkers, ship, and score/lives HUD; leads to a Game Over screen after WASD 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.

idle motion6.3%
on click7.9%
on drag7.6%
on wheel7.3%
on enter + space7.0%
on arrow keys9.9%
on w/a/s/d9.4%
frame spread35.5 / 255
console errors0
js errors none

METRICS

runtime6.4s
tokens in12k
tokens out2.6k
cached2.4k
cost / run$0.0043