Space Invaders · nex-n2-pro
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.
thinking
We need need build single-page web app index.html in cwd. Need inspect maybe no files. Need create self-contained canvas Space Invaders playable. Need no external. Need final concise. We can write full html. Need ensure game mechanics: canvas, player cannon moves/shoots, descending grid aliens advance and fire back, destructible bunkers, score, lives. Procedural graphics. Need maybe responsive. Need include instructions, start/restart, keyboard/touch maybe. Need code robust.
Need create index.html. Since user asks build. Need maybe use write. Need produce code. Let's design.
We need think game implementation. Single file. Canvas with pixel-art procedural.
Game mechanics:
- Canvas dimensions maybe 900x700 CSS? internal 900x700? Use responsive scaling with DPR? We can set canvas width/height logical and style. Maybe easier internal 900x700. CSS max-width 96vw.
Game state:
- canvas 900x700.
- world width 900, height 700.
- Player at y=640, x center, width 46 height 26.
- Player bullets upward speed 520 px/s, alien bullets downward speed 260.
- Aliens grid 11 columns x 5 rows. Alien spacing 52 x 42, start x=(900 - (10*52))/2 = 190? 11*? maybe w=38 h=28. Start y=70. Alien speed horizontal 40 + level maybe, descends 24 on edge. Advance direction toggles. Alien fire random.
- Bunkers: 4 bunkers at x 150,330,510,690 y=540 maybe. Each bunker grid of blocks 16x? Need destructible. Represent each bunker as array of small rect cells. Maybe create 12 columns x 8 rows, cell 8x8, with arch shape. Use collision.
- Score: alien points based row. Lives 3. Level increments when all aliens killed; reset aliens faster.
- Game over/win? If aliens reach player/bunker bottom or player lives 0.
Rendering:
- Background starfield, scanlines.
- Draw aliens with pixel shapes depending type and animation. Use fillRect scaled. Use green/cyan/magenta. Maybe procedural.
- Player cannon green.
- Bullets as rects.
- Bunkers with green blocks, remove cells by bullet collisions.
- Explosions particles maybe optional.
- HUD score/lives.
- Overlay start/game over with button? Since canvas only maybe pointer. Need playable. Keyboard controls: left/right move, space shoot, Enter start. Touch/click buttons? Could implement on-screen controls for mobile maybe not necessary but nice. But no external. Need "playable" maybe keyboard.
Need maybe include pause? Could have P.
Need ensure no external assets.
Need write full HTML. Need maybe use requestAnimationFrame delta. Need robust.
Let's outline code.
HTML:
<!DOCTYPE html><html lang="en"><head><meta charset...><title>Canvas Space Invaders</title><style> ... body background radial gradient, canvas image-rendering pixelated, UI overlay maybe. But all CSS in style. Need canvas.
<body>
<div class="wrap">
<h1>Space Invaders</h1>
<p>←/→ Move · Space Shoot · Enter Start/Restart · P Pause</p>
<canvas id="game" width="900" height="700" tabindex="0" aria-label="Space Invaders game"></canvas>
<div class="touch"> maybe buttons? But if using buttons maybe touch controls. Could include left/right/fire buttons styled. Need not external. Use pointer events.
</div>
<script> ... </script>
</body></html>
Need maybe no external network. Use CSS only.
JS:
const canvas = document.getElementById('game'), ctx = canvas.getContext('2d');
const W=canvas.width, H=canvas.height;
let keys = {};
let game = { state:'ready', score:0, lives:3, level:1, player:{x:450,y:642,w:48,h:28,cooldown:0}, aliens:[], alienBullets:[], playerBullets:[], bunkers:[], particles:[], stars:[], dir:1, alienSpeed:30, alienDrop:24, lastShot:0, nextAlienShot:1, message:'Press Enter to start', messageSub:'...', flash:0, shake:0 };
const alienRows=5, cols=11;
function createAliens() { for r 0..4 for c 0..10 type = r; x = (W - (cols-1)*54 - 34)/2 + c*54? y=70 + r*42. w=34 h=26. hp? maybe 1; points = (5-r)*10; phase. }
Need initial x maybe 210? Let's compute: cols=11, spacingX=54, alienW=34. startX=(W - (cols-1)*spacingX)/2 = (900-540)/2=180. If alien drawn centered at x maybe. y=74.
Use alien = {x: startX+c*spacingX, y:72+r*42, r,c,w:34,h:26,points:(5-r)*10, t:0, alive:true};
Need bounding boxes.
Bunkers:
function makeBunker(cx, cy) {
let cells=[];
for row=0; row<8; row++ for col=0; col<14; col++ {
let x=col*8, y=row*8;
// shape: skip top corners, arch bottom center.
let keep = true;
if(row===0 && (col<2 || col>11)) keep=false;
if(row===1 && (col<1 || col>12)) keep=false;
if(row>=5 && col>=5 && col<=8) keep=false; // arch
if(row===7 && (col<2 || col>11)) keep=false;
if (keep) cells.push({x:cx-56+x,y:cy+y,w:8,h:8,hp:2});
}
return cells;
}
bunkers at cy=530, cx maybe 160,340,520,700.
Need collision bullet vs cells:
For player bullet hitting alien or bunker? Player bullets should hit aliens and bunkers? In original, bunkers block both. So check bunkers first? If bullet hits bunker, damage and remove. If bullet hits alien, score. Need if bullet intersects alien and bunker? Bunkers below aliens mostly; maybe if overlap none. We can check alien first then bunker? For player bullet if hits alien score. If hit bunker damage. For alien bullet, only bunker/player. If hit bunker damage. If hit player life.
Alien movement:
Update aliens:
if state playing:
alienMoveTimer maybe simpler:
game.alienMoveTimer -= dt; if <=0:
const alive = aliens.filter
if any at edge with dir: find minX maxX. if maxX >= W-35 && dir>0 or minX <=35 dir<0: dir *= -1; y += drop; speed = Math.min(260, 35 + level*10 + (aliveCount*? inverse maybe)).
else x += dir * speed * stepTime?
Need speed dynamic: game.alienSpeed = 35 + level*8 + (cols*rows - aliveCount)*4 maybe.
Use fixed step every 0.55s, decrease with level/alive. But if edge reached before step, move down. Simpler:
game.alienTimer -= dt;
if game.alienTimer <=0:
const step = 18? Actually grid moves horizontally by 18 per tick.
let alive = aliens.filter alive.
if alive length=0 => next level.
let minX = min x-w/2, maxX = max x+w/2.
if (maxX > W-35 && dir >0 || minX <35 && dir<0) { dir*=-1; alive.forEach(a=>a.y += 24); game.shake=8; }
else alive.forEach(a=>a.x += dir*18);
game.alienTimer = Math.max(0.05, 0.62 - level*0.055 - (55-alive.length)*0.006);
Need if alien y+h/2 >= player.y - 35 -> game over.
Maybe alien bullets:
game.alienShotTimer -= dt; if <=0: choose bottom-most alive alien in random column. For c random 0..cols-1, find alive max y. If found spawn bullet from x, y+h/2, speed = 210+level*20. next timer random 0.6-1.5 - level*0.05 min 0.35. Maybe multiple bullets at higher levels.
Player movement:
let dir = (right?1:0)-(left?1:0); player.x += dir*360*dt; clamp 30..W-30.
player.cooldown -= dt. If space and cooldown <=0 spawn bullet at player.x, player.y-18, cooldown=0.32. Maybe one or two bullets? Original limited. Use one bullet at a time? Could allow 3. Let's limit playerBullets.length<4.
Bullets:
for each bullet x += vx*dt; y += vy*dt.
Remove offscreen.
Collision:
For player bullet:
for each alien alive: if circle/rect intersect: alive=false; score += points; particles; remove bullet; break.
if not removed, check bunker damage.
Need bullet rect: {x,y,w:4,h:14} maybe bullet x center.
rectsIntersect.
Alien bullet:
check bunkers damage; if bullet intersects player rect (x-w/2,y-h/2) => lives--, bullet removed, particles, if lives<=0 game over. Maybe invulnerability 1.5s.
Also if alien bullet hits player while invulnerable skip.
Bunker damage:
function damageBunker(bullet) {
for each cell if rectIntersects(bullet, cell):
cell.hp -= bullet.damage maybe 1; if hp<=0 remove.
// maybe area damage: if bullet intersects, remove nearby cells around with 40% chance? For destructible, create holes. Could remove cell and maybe adjacent cells within 12?
// For player bullet damage maybe 1; alien bullet damage 1.
// To make bunker erosion, when cell damaged, remove it and maybe neighbor cells within radius 14 with chance.
}
Need avoid mutating while iterating? Use for loop backwards.
Particles:
function spawnExplosion(x,y,color,count) push {x,y,vx,vy,life,maxLife,size,color}
Update positions life. Draw with alpha.
Stars:
create 100 stars with x,y,speed,alpha. Update y += speed*dt; wrap. Draw background.
Rendering:
function draw() {
ctx.clearRect...
draw background with gradient? Can create each frame.
draw stars.
draw grid maybe.
draw bunkers, aliens, bullets, player, particles, HUD.
if state ready: overlay translucent with "SPACE INVADERS", instructions, "Press Enter".
if paused: overlay.
if gameover: overlay with score and restart.
Maybe state 'playing','ready','gameover'.
Need state transitions:
function resetGame(full=true) {
score=0,lives=3,level=1, createBunkers, createAliens, bullets empty, particles empty, dir=1, alienTimer=0.6, alienShotTimer=1.2, player.x=W/2, player.cooldown=0, invuln=0, state='playing'; message=''
}
function startLevel() { aliens=createAliens; bullets empty except maybe? alienShotTimer=1; }
Maybe after all aliens killed:
level++; playerBullets=[]; alienBullets=[]; createAliens(); alienShotTimer=1.4; flash=1; message `LEVEL ${level}` maybe.
If state ready: initial createAliens and bunkers in init.
On Enter: if state ready or gameover resetGame.
On P toggle if playing/paused.
Need maybe canvas focus. Add keydown. Space preventDefault.
Need touch controls:
HTML buttons data-control left/right/fire. Add pointerdown/up sets touchControls. On touch fire, call playerShoot.
Maybe also canvas click to focus. But buttons.
Need maybe "procedural graphics only" no external.
Potential issue with "Space Invaders" trademark? It's okay.
Need maybe no network. CSS no fonts external.
Need maybe all in one self-contained file. Good.
Need produce file. But before writing maybe no need inspect. Use write. Need ensure code syntax correct. Let's craft carefully.
Detailed code.
HTML CSS:
:root colors.
body margin 0 min-height 100vh display flex center background #030711 color #d7faff font-family system.
#wrap width min(980px, 100vw) padding 14px box-sizing.
h1 margin...
.controls hint.
canvas display block margin auto width min(100%, 900px); height auto; aspect-ratio 9/7; background #02040a; border 3px solid #2be7ff; box-shadow...
.touch row display flex justify center gap; buttons. @media maybe.
button touch-control style.
Need maybe if user has keyboard, canvas tabindex=0.
JS:
(() => {
'use strict';
const canvas = ...
const ctx = canvas.getContext('2d', { alpha:false });
const W=canvas.width, H=canvas.height;
const keys = Object.create(null);
const touch = {left:false,right:false,fire:false};
const game = { state:'ready', score:0, lives:3, level:1, high: Number(localStorage.getItem('siHigh') || 0), ...}; But no external network. localStorage okay. Need maybe avoid exceptions? Sandbox maybe localStorage available. Could try/catch. Use highScore.
Need if localStorage maybe blocked? Use try.
Maybe high score optional. User asked score/lives, high nice.
Need update high when score > high.
Object:
const player = { x: W/2, y:H-58, w:48,h:28, cooldown:0, invuln:0 };
let aliens=[], bullets=[], alienBullets=[], bunkers=[], particles=[], stars=[];
let alienDir=1, alienTimer=0.6, alienShotTimer=1.2, message='Press Enter to start', messageTimer=0, shake=0, flash=0;
Need maybe "mystery ship" at top? Not requested. Could add but not necessary. Maybe procedural extra: UFO moves across top occasionally for bonus. But user asked aliens. Could include for fun but complexity. Maybe include "mystery saucer" optional. But not necessary.
Need collision helpers:
function rects(a,b) { return a.x < b.x+b.w && a.x+a.w > b.x && a.y < b.y+b.h && a.y+a.h > b.y; }
function bulletRect(b) { return {x:b.x-b.w/2,y:b.y-b.h/2,w:b.w,h:b.h}; }
function alienRect(a) { return {x:a.x-a.w/2,y:a.y-a.h/2,w:a.w,h:a.h}; }
function playerRect() { return {x:player.x-player.w/2,y:player.y-player.h/2,w:player.w,h:player.h}; }
Init:
function buildStars() { stars=[]; for i<120 push {x:Math.random()*W,y:Math.random()*H,r:Math.random()*1.8+0.4,speed:8+Math.random()*24,alpha:0.3+Math.random()*0.7};}
function makeAliens() { aliens=[]; const spacingX=54, spacingY=42; const startX=(W-(COLS-1)*spacingX)/2; const startY=78; for r... aliens.push({x:startX+c*spacingX, y:startY+r*spacingY, w:34,h:26, row:r,col:c, points:(r+1)*10? In original top high. If row 0 top points 30? Actually row values: top 30, second 20, lower 10. Use points=(5-r)*10. animation: Math.random()*Math.PI*2, wiggle:0});}
function makeBunkers() { bunkers=[]; const xs=[155,335,515,695]; for cx of xs bunkers.push(makeBunker(cx,520));}
function resetGame() { game.score=0; game.lives=3; game.level=1; game.state='playing'; bullets=[]; alienBullets=[]; particles=[]; player.x=W/2; player.cooldown=0; player.invuln=1.5; alienDir=1; alienTimer=0.7; alienShotTimer=1.4; message=''; makeBunkers(); makeAliens(); flash=0.8; }
function nextLevel() { game.level++; bullets=[]; alienBullets=[]; particles=[]; player.x=W/2; player.cooldown=0; player.invuln=1; alienDir=1; alienTimer=0.7; alienShotTimer=1.2; makeAliens(); flash=1; message=`LEVEL ${game.level}`; messageTimer=1.2; }
function gameOver() { game.state='gameover'; if high update; message='GAME OVER'; messageTimer=Infinity?; }
Need maybe if localStorage:
let high = 0;
try { high=+localStorage.getItem('spaceInvadersHigh')||0; } catch(e){}
function setHigh()...
Need maybe if high undefined.
Input:
window.addEventListener('keydown', e => {
if (['ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Space','Enter','KeyP'].includes(e.code)) e.preventDefault();
if (e.code==='Enter') { if(game.state!=='playing') resetGame(); }
if (e.code==='KeyP' && (game.state==='playing'||game.state==='paused')) game.state = game.state==='playing'?'paused':'playing';
keys[e.code]=true;
});
window.addEventListener('keyup', e=>{ keys[e.code]=false;});
canvas.addEventListener('click',()=>canvas.focus());
Touch buttons:
document.querySelectorAll('[data-hold]').forEach(btn => {
const key=btn.dataset.hold;
const set=(v)=>{ touch[key]=v; btn.classList.toggle('active',v); if(key==='fire' && v) tryShoot(); };
btn.addEventListener('pointerdown', e=>{e.preventDefault(); btn.setPointerCapture(e.pointerId); set(true);});
const end=... set(false)
});
Need if pointer leaves/cancel.
function tryShoot() { if game.state==='playing' spawnPlayerBullet(); }
But if keydown space, update handles.
Update:
function update(dt) {
if (game.state === 'ready') { updateStars(dt); updateParticles(dt); return; } // maybe stars move
if (game.state === 'paused') { updateParticles(dt); return; }
if (game.state === 'gameover') { updateStars(dt); updateParticles(dt); return; }
if (game.state !== 'playing') return;
updateStars(dt);
const move = (keys.ArrowRight||touch.right?1:0)-(keys.ArrowLeft||touch.left?1:0);
player.x += move*360*dt; player.x = clamp(player.x, 34, W-34);
player.cooldown=Math.max(0,player.cooldown-dt); player.invuln=Math.max(0,player.invuln-dt);
if ((keys.Space||touch.fire) && player.cooldown<=0) shootPlayer();
updateBullets(dt);
updateAliens(dt);
updateParticles(dt);
if (shake>0) shake=Math.max(0,shake-dt*30);
if (flash>0) flash=Math.max(0,flash-dt);
if (messageTimer>0) messageTimer-=dt;
}
Need updateBullets before aliens? Maybe bullets first then aliens. Alien movement after bullets. Alien shot after movement. Good.
shootPlayer:
if bullets.length<3 { bullets.push({x:player.x, y:player.y-player.h/2-4, vx:0, vy:-540,w:4,h:15,kind:'player'}); player.cooldown=0.28; }
Maybe if playerBullets length >=3, cooldown = 0.12.
Need if state ready and space? Could reset? Maybe if state ready and Space resetGame? But instructions Enter. Could make Enter.
Alien shooting:
function updateAliens(dt) {
let alive = aliens.filter(a=>a.alive);
if (!alive.length) { nextLevel(); return; }
const speed = Math.min(260, 35 + game.level*8 + (COLS*ROWS-alive.length)*3.6);
alienTimer -= dt;
if (alienTimer <= 0) {
let minX=Infinity,maxX=-Infinity; alive.forEach...
if ((maxX > W-35 && alienDir>0) || (minX < 35 && alienDir<0)) {
alienDir *= -1;
alive.forEach(a=>a.y += 22);
game.shake=8; spawn?
} else {
alive.forEach(a=>a.x += alienDir*16);
}
alive.forEach(a=>a.wiggle += Math.PI);
const nextDelay = Math.max(0.06, 0.62 - game.level*0.055 - (ROWS*COLS-alive.length)*0.007);
alienTimer = nextDelay;
}
alienShotTimer -= dt;
if (alienShotTimer <=0) fireAlienBullet(alive);
const reached = alive.some(a=>a.y+a.h/2 >= player.y-20);
if (reached) { game.shake=18; gameOver(); }
}
Potential issue if gameOver after nextLevel? Fine.
fireAlienBullet(alive):
const columns = new Map? For random col, bottom-most alive. Simpler:
const candidates = [];
for c=0;c<COLS;c++ { let best=null; for a of alive if a.col===c && (!best||a.y>best.y) best=a; if(best) candidates.push(best);}
if(candidates.length) { const a=candidates[Math.floor(Math.random()*candidates.length)]; alienBullets.push({x:a.x,y:a.y+a.h/2,vx:(Math.random()-0.5)*30,vy:210+game.level*20,w:5,h:14}); }
if(game.level>=3 && Math.random()<0.25) maybe second bullet.
alienShotTimer = rand(0.55,1.4) - game.level*0.06; clamp 0.25.
Need maybe aliens fire from bottom-most.
Update bullets:
for bullets backwards:
b.x += b.vx*dt; b.y += b.vy*dt;
if b.y < -30 || b.y > H+30 || b.x < -30 || b.x>W+30 remove.
if b.kind==='player':
let hitAlien=false;
for aliens: if alive && rects(bulletRect(b), alienRect(a)):
a.alive=false; game.score += a.points; spawnExplosion; hitAlien=true; break;
if hitAlien { remove; continue; }
for bunkers if damageBunker(b) { remove; continue; }
else if b.kind==='alien':
if player.invuln<=0 && rects(bulletRect(b), playerRect()) { playerHit(); remove; continue; }
for bunkers if damageBunker(b) { remove; continue; }
Need if alien bullet hits player and bunker? If bullet intersects player but bunker between? Bunkers at y520, player y642, bullet may pass through bunker first if collision. We check player first, but if bullet in bunker and player? It can't intersect both unless huge. Fine. Maybe check bunkers before player? For fairness, bunker should block. Use check bunkers first then player. But if bullet at player y and bunker y no overlap. Fine.
Need damageBunker returns true if any cell. Use bullet rect.
function damageBunker(b) {
const br=bulletRect(b);
let did=false;
for (const bunker of bunkers) {
for (let i=bunker.length-1; i>=0; i--) {
const cell=bunker[i];
if (rects(br, {x:cell.x,y:cell.y,w:cell.w,h:cell.h})) {
cell.hp -= 1;
if (cell.hp <=0) bunker.splice(i,1);
did=true;
// area damage
const cx=cell.x+cell.w/2, cy=cell.y+cell.h/2;
for (const other of bunker) {
const ox=other.x+other.w/2, oy=other.y+other.h/2;
const d=Math.hypot(ox-cx,oy-cy);
if (d < 14 && Math.random()<0.22) { other.hp -= 1; if(other.hp<=0) remove? Can't splice while iterating? Could mark. Better collect damage list.
}
}
}
}
}
Need avoid mutating bunker while iterating. Use toRemove = new Set? Simpler:
function damageBunker(b) {
const br=bulletRect(b);
for (const bunker of bunkers) {
const hits=[];
for (let i=0; i<bunker.length; i++) {
const cell=bunker[i];
if(rects(...)) hits.push(cell);
}
if(hits.length) {
for (const cell of hits) {
cell.hp -= 1;
if(cell.hp <=0) {
const index=bunker.indexOf(cell); if(index>=0) bunker.splice(index,1);
blastCell(cell);
}
}
for (const cell of hits) {
const cx=cell.x+cell.w/2, cy=...
for (const other of bunker) {
const dist = Math.abs(cx-(other.x+other.w/2)) + Math.abs(cy-(other.y+other.h/2)); // Manhattan
if (dist < 16 && Math.random()<0.18) other.hp -= 1;
}
}
bunker = bunker.filter(cell => cell.hp>0); // But const bunker of bunkers is reference, reassign not update array in bunkers. Need if bunker variable is array reference, can't reassign to update. Use while loop backwards.
}
}
}
Maybe simpler:
for each bunker:
const hitCells=[];
for (let i=bunker.length-1; i>=0; i--) {
if (rects) { hitCells.push({cell:bunker[i], index:i}); bunker.splice(i,1); }
}
if hitCells.length:
spawnExplosion...
for hit of hitCells:
const cx...
for (const other of bunker) {
const d=...
if (d<15 && Math.random()<0.18) other.hp -= 1;
}
for (let i=bunker.length-1;i>=0;i--) if(hp<=0) { spawn; bunker.splice(i,1);}
return true;
return false;
}
Need if hitCells empty return false.
Player hit:
function playerHit() {
if(player.invuln>0) return;
game.lives -= 1;
player.invuln=1.6;
game.shake=14;
spawnExplosion(player.x,player.y,'#7cff6b',30);
alienBullets = alienBullets.filter(b=>b.y < player.y-80); // clear nearby? maybe all.
if (game.lives <=0) { game.lives=0; gameOver(); }
}
Particles:
function spawnExplosion(x,y,color,count=16) { for i count push; }
updateParticles: for backwards p.life -= dt; p.x += p.vx*dt; p.y += p.vy*dt; p.vy += 40*dt; if <=0 splice.
Draw:
function render() {
ctx.save();
if (shake>0) { const s=shake; ctx.translate((Math.random()-0.5)*s, (Math.random()-0.5)*s); }
drawBackground();
drawBunkers();
drawAliens();
drawBullets();
drawPlayer();
drawParticles();
ctx.restore();
drawHud();
if(flash>0) { ctx.fillStyle=`rgba(120,255,220,${Math.min(0.18, flash*0.18)})`; ctx.fillRect(0,0,W,H);}
if(game.state==='ready') drawOverlay(...);
if(game.state==='paused') drawOverlay('PAUSED', 'Press P to resume');
if(game.state==='gameover') drawOverlay('GAME OVER', `Score ${score} · Level ${level} · Press Enter`);
}
Need if state ready, aliens/bunkers initial? At load call makeBunkers(); makeAliens(); draw.
Draw background:
const g=ctx.createLinearGradient(0,0,0,H); fill; draw stars; maybe draw subtle grid horizon.
Need not create gradient each frame? Fine.
Stars update only; draw uses ctx.fillStyle.
drawHud:
ctx.font='18px monospace'; fillText `SCORE ${score}` at 24,28; `HIGH ${high}` center; `LIVES ${lives}` right; `LEVEL ${level}` maybe 24,54.
Need maybe draw small player icons for lives. Could draw text. Maybe draw life icons at right: for i<lives draw mini cannon. But text okay.
Draw bunkers:
for each cell fillRect. Use gradient? maybe fill #44e66d; stroke dark. Since many cells, okay.
Draw aliens:
for each alive:
const frame = Math.floor(performance.now()/300)%2;
drawAlien(a, frame)
Need draw pixel shape. Use ctx.save translate(a.x,a.y); maybe scale = a.w/34 etc. Pixel map array:
For type rows:
row0: [
"001111100",
"011111110",
"110111011",
"111111111",
"010111010",
"010000010",
"100000001"
]
Row1 maybe different.
Need draw using fillRect each "1" cell size 3? But alien dimensions 34x26. Pixel grid 9x7, cell=4, x offset -18. Good.
Use colors by row: top #ff5fd7, second #7cf7ff, lower #7cff6b.
function drawAlien(a, frame) {
const palette = ['#ff5fd7','#7cf7ff','#7cff6b','#ffd166'];
const color=palette[a.row] || '#7cff6b';
const blink = Math.sin((performance.now()/350)+a.x)*? maybe.
const map = (a.row %2===0)? alienSpriteA : alienSpriteB;
if (a.wiggle % (Math.PI*2) > Math.PI) use map2? We can toggle with Math.floor(game.time*4)%2.
ctx.save(); ctx.translate(a.x,a.y); ctx.fillStyle=color; for y rows...
ctx.fillRect(x*4-18, y*4-14,4,4);
ctx.restore();
}
Need if alien row 4 lower. Maybe 7 rows. w=36 h=28.
Draw player:
if invuln and Math.floor(performance.now()/80)%2===0 maybe skip flicker.
ctx.save translate; fillRect shapes:
- base -24,10,48,10
- turret -10,-2,20,12
- barrel -5,-18,10,16
- tip -2,-22,4,6
Maybe color #7cff6b, stroke #d6ff7a.
Draw bullets:
player bullets cyan, alien bullets orange/red.
Draw particles.
Draw overlay:
ctx.save(); ctx.fillStyle='rgba(0,0,0,0.72)'; fillRect(0,0,W,H); draw title large; etc.
Need maybe overlay doesn't cover HUD? Fine.
Need maybe if game.state='ready', aliens drawn behind overlay. Good.
Loop:
let last=performance.now();
function loop(now) {
const dt=Math.min(0.033,(now-last)/1000); last=now;
update(dt); render(); requestAnimationFrame(loop);
}
buildStars(); makeBunkers(); makeAliens(); requestAnimationFrame(loop);
Need maybe high score display. update high after score.
function addScore(points) { score += points; if score>high { high=score; try localStorage...}}
Need maybe when score update in collision.
Need maybe "descend grid of aliens advance and fire back" yes.
Need maybe "destructible bunkers" yes.
Potential issue with keydown keys: e.code for Space is "Space", for arrow "ArrowLeft". Good.
Need prevent repeated Enter? If holding Enter reset repeatedly? In keydown, if Enter and state not playing reset. If state gameover and hold, once. Fine. If state paused, pressing Enter? Currently resets even paused. Maybe okay. But P pause. If state paused and Enter resets? Could be surprising. Better if Enter: if state ready or gameover resetGame(); if state paused maybe resume? Maybe implement:
if e.code==='Enter' { if (game.state==='ready'||game.state==='gameover') resetGame(); else if (game.state==='paused') game.state='playing'; }
Need keys[e.code]=true after? If Enter not in keys. Fine.
Need if e.repeat? For Enter no.
Need maybe if game.state ready and Space? Could not shoot. But maybe user pressing Space after overlay should start? Could support: if (game.state!=='playing' && (e.code==='Enter'||e.code==='Space')) resetGame(); But then Space starts. Fine.
Need maybe if keydown while state ready sets keys.Space true; if reset starts, shoot maybe immediate. okay.
Need maybe if keyup after blur? Add window blur: keys cleared, touch cleared.
Potential issue with localStorage in sandbox? try.
Need maybe no external resources. Fine.
Need maybe canvas dimensions and CSS aspect ratio. Good.
Let's write.
/tmp/ws/index.html (28061 bytes)Successfully wrote 28061 bytes to /tmp/ws/index.html
thinking
Need verify file exists maybe maybe run syntax? Could use bash ls maybe. Also maybe inspect? Need ensure no syntax errors. Could run node? HTML contains JS with browser APIs, but can run maybe extract script and node? Need no need but we can check with grep. Maybe use bash test.
{"command":"ls -l index.html && wc -c index.html","timeout":5}-rw-r--r--. 1 node node 28072 Aug 1 20:51 index.html 28072 index.html
thinking
Done — `/tmp/ws/index.html` is present and contains a fully self-contained, playable Canvas Space Invaders game with procedural graphics, no external assets, player movement/shooting, descending alien grid, alien fire, destructible bunkers, score, lives, levels, and touch/keyboard controls.
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
Fully functional Space Invaders with aliens, bunkers, score/lives, shooting, and game over screen.
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.