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.
transcript · 6 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.
index.html (6675 bytes)Successfully wrote 6675 bytes to index.html
{"path":"index.html","limit":2000}<!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>
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
EVALUATION
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.