Asteroids · nex-n2-pro
Build a playable Asteroids game on a canvas: a thrust-and-rotate vector ship that shoots and wraps around the screen edges, splitting asteroids into smaller pieces, with score and lives. Procedural vector graphics only, no external assets.
transcript · 8 messages
Build a playable Asteroids game on a canvas: a thrust-and-rotate vector ship that shoots and wraps around the screen edges, splitting asteroids into smaller pieces, with score and lives. Procedural vector graphics only, no external assets.
thinking
We need respond by building single-page web app in index.html current dir. Need inspect maybe empty. Need create self-contained HTML with CSS JS. Need implement playable Asteroids game canvas. Need ensure no external assets. Need end with index.html present. Need maybe use tools. Need produce concise final. But first need inspect cwd maybe.
{"command":"ls","timeout":1000}(no output)
thinking
Need create file. Need think implementation. Single HTML. Need playable Asteroids: canvas, vector graphics, controls, score, lives, ship thrust rotate, shooting, wrap, splitting asteroids, procedural. Need maybe include instructions, pause/restart, game over, high score? Need robust.
Need write index.html. Need all inline CSS JS. Use canvas. Need no external. Need perhaps high DPI support, responsive. Need keyboard and touch? Could add mobile controls. Need score/lives display. Need vector ship, asteroids irregular polygons, bullets. Collision, particles. Need wrapping edges. Need splitting asteroids into smaller pieces. Need lives. Need game states.
Let's design.
HTML:
<!DOCTYPE html><html lang="en"><head><meta charset...><title>Asteroids</title><style>... body black, canvas full viewport maybe plus HUD overlay. </style></head><body><canvas id="game"></canvas><div id="hud">...</div><div id="message">... click/tap to start ...</div><script> ... </script></body></html>
Need maybe canvas fixed full screen. Use JS.
Game constants:
const TAU = Math.PI*2;
const ship = {x,y,angle,vx,vy,radius:14, thrusting:false, invincible:3, lives:3, reload:0};
bullets array {x,y,vx,vy,life};
asteroids array {x,y,vx,vy,radius,vertices,angle,spin,hits?}
particles array.
score, level, highScore from localStorage? Is localStorage okay no network. Could use but maybe not necessary. Use localStorage.
keys object.
canvas resize: set size to window innerWidth/Height * dpr, ctx.scale(dpr,dpr), width=innerWidth,height=innerHeight. Need CSS.
Game mechanics:
- ship starts center.
- angle in radians; rotate left/right A/D or ArrowLeft/Right. thrust W/Up. shoot Space.
- friction maybe 0.995 to damp; no drag? Asteroids has inertia. Use damping 0.995.
- wrap positions: x=(x+w)%w; y=(y+h)%h.
- bullet speed 650 px/s, life 1s, max bullets 8.
- spawnAsteroid(radius): random position not too close to ship? At level start spawn. Asteroids have radius 55/35/18 maybe. Irregular vertices: generate n 8-12 with random radial factors 0.65-1.2, store normalized? Need draw polygon with radius*factor. For splitting, create 2 smaller asteroids with radius parent.radius*0.45 (or 0.55). If radius > 18 split into 2, else remove. Score: 20/50/100 based radius.
- collision: circle approximation. ship invincible. If collision with asteroid, lives--, reset ship, particles; if lives<=0 gameover. Need maybe clear asteroids? On death maybe explosion.
- bullets collision: if dist < asteroid.radius + bullet.r then remove bullet, splitAsteroid.
- Screen wrap also for bullets maybe wrap; if bullet life expired remove.
- Need avoid spawning too close to ship on new level? Generate x,y with random, if dist < 120 from ship, retry.
- Level progression: if asteroids empty, level++ spawn level+3 asteroids.
Need HUD overlay: score, lives icons, level, high, controls. Use DOM update. Maybe <div id="hud">.
Need message overlay:
- initial: "ASTEROIDS", "Click/Tap or Press Enter to Start", "Arrow keys/WASD to move, Space to shoot"
- gameover: "GAME OVER", "Press Enter/Click to Restart"
- paused maybe P.
Need state: 'title','playing','gameover','paused'.
Need handle click/touch start/resume. If playing and click maybe no effect. Need keyboard Enter toggles.
Need high score localStorage maybe.
Need implement game loop with requestAnimationFrame.
dt = min((now-last)/1000, 1/30)
if playing update.
Need vector graphics:
- Background stars maybe procedural. Generate stars array with x,y,size,alpha. On resize regenerate? Use random.
- draw:
ctx.fillStyle black; ctx.fillRect.
draw stars.
draw bullets as small lines or circles.
draw asteroids: path through vertices, stroke, maybe fill translucent, line join.
draw ship:
if invincible blink. Path: nose at angle, left back, right back, maybe flame if thrusting. Use line stroke.
particles: small fading.
- Need maybe screen wrap visual? no.
Need touch controls? User asked playable. Could include keyboard only. But maybe mobile. We can add on-screen controls for touch. But no external. Could create buttons overlay bottom left/right for rotate/thrust/fire. Need avoid interfering. Maybe simpler: canvas pointer to start, keyboard. But "playable" maybe keyboard enough. We can include optional touch controls hidden on coarse pointer. Need implement.
Touch controls:
<div id="touchControls">
<button data-control="left">↶</button>
<button data-control="right">↷</button>
<button data-control="thrust">▲</button>
<button data-control="fire">●</button>
</div>
CSS @media (hover:none), (pointer:coarse) display. JS pointerdown/up set touchControls map. Update uses keys.left = keys.left || touch.left. Need prevent default. Could add.
Need maybe no external resources. Fine.
Need code quality. Need write index.html. Use write. Need ensure not too long but robust.
Let's craft code. Need maybe localStorage safe.
Detailed JS:
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const hud = document.getElementById('hud');
const overlay = document.getElementById('overlay');
let W=0,H=0,dpr=1;
const keys = {};
const touch = {left:false,right:false,thrust:false,fire:false};
let state='title';
let ship,lives,score,level,high,bullets,asteroids,particles,stars,last=0;
const TWO_PI=Math.PI*2;
function clamp(v,min,max)...
function rand(min,max), chance.
resize() { W=window.innerWidth; H=window.innerHeight; dpr=Math.min(devicePixelRatio||1,2); canvas.width=Math.floor(W*dpr); canvas.height=Math.floor(H*dpr); canvas.style.width=W+'px'; canvas.style.height=H+'px'; ctx.setTransform(dpr,0,0,dpr,0,0); makeStars();}
Need setTransform each resize. In draw maybe after resize.
makeStars: stars = Array.from({length: Math.floor(W*H/9000)}, () => ({x:rand(0,W),y:rand(0,H),r:rand(.35,1.4),a:rand(.25,.9),s:rand(10,60)}));
drawStars(dt): update x += s*dt wrap, alpha twinkle.
initGame() {
score=0; level=1; lives=3; bullets=[]; asteroids=[]; particles=[]; ship=...
spawnAsteroids(level+2); state='playing'; hide overlay.
}
resetShip() { ship.x=W/2; y=H/2; vx=vy=0; angle=-Math.PI/2; invincible=3; reload=0; }
newLevel() { level++; bullets=[]; particles=[]; resetShip(); spawnAsteroids(level+2); show overlay? maybe "LEVEL X" briefly? Could set levelFlash = 2. Need overlay only for game over/title. Could draw message? Maybe not.
Need spawnAsteroids(n): for i n push createAsteroid(randomLargeRadius(), random pos not close).
function createAsteroid(r,x,y) { const vertices=[]; const count=...; for ... vertices.push(rand(.62,1.18)); return {x,y,vx:rand(-40,40),vy..., radius:r, vertices, angle:rand(0,TWO_PI), spin:rand(-.8,.8)};}
function splitAsteroid(ast) {
const points = ast.radius > 34 ? 100 : ast.radius > 22 ? 50 : 20;
score += points; high update.
if (ast.radius > 22) {
const count=2; for i 0..1 {
const a=ast.angle + (i?1:-1)*rand(.5,1.2);
const speed=80 + rand(0,80);
const child=createAsteroid(ast.radius*0.5, ast.x, ast.y);
child.vx = ast.vx + Math.cos(a)*speed;
child.vy = ast.vy + Math.sin(a)*speed;
asteroids.push(child);
}
}
addExplosion(ast.x,ast.y, ast.radius>22?'#fff':'#88f', ast.radius*.35, ast.radius>22?18:10)
}
Need score based on radius:
function asteroidValue(r) { return r > 34 ? 20 : r > 22 ? 50 : 100; }
Maybe large 20, medium 50, small 100.
Need if asteroid destroyed, remove.
update(dt):
if state!='playing' return maybe draw still.
const left = keys.ArrowLeft||keys.a||touch.left? But keys object lower? Need normalize.
keydown: e.key maybe "ArrowLeft","a","A"; use keys[e.code]? Use code for layout. keys[e.code]=true. Then check keys['ArrowLeft']? Actually code for arrow left is 'ArrowLeft', for A 'KeyA'. Use isDown('ArrowLeft','KeyA').
keydown prevent default for game keys.
if e.code==='Enter'...
if e.code==='KeyP' toggle pause.
Need if state title/gameover and Enter start.
if state playing and P toggles state paused; show overlay.
keyup set false.
update:
const rotate = (isDown('ArrowLeft','KeyA')?-1:0)+(isDown('ArrowRight','KeyD')?1:0)
ship.angle += rotate*ship.turnRate*dt
thrusting = isDown('ArrowUp','KeyW')||touch.thrust
if thrusting: ship.vx += cos(angle)*ship.accel*dt; etc; add thruster particles occasionally (random).
fire = isDown('Space')||touch.fire
if fire && ship.reload<=0 && bullets.length<max: shoot; ship.reload=.18; maybe recoil.
ship.reload -= dt
ship.vx *= Math.pow(0.995, dt*60)? Need frame independent. const damping=Math.pow(0.995,dt*60); multiply.
ship.x += vx*dt; wrap
ship.invincible -= dt; ship.invincible = max.
bullets: update x,y, life; wrap; filter.
asteroids update x,y,angle += spin*dt; wrap.
particles update vx,vy,life, alpha.
collisions:
for bullets vs asteroids:
for i bullets.length-1..0:
for j asteroids.length-1..0:
if distanceSq < (a.radius+bullet.r)^2:
remove bullet; destroyAsteroid(j); break.
Need if bullet removed, no more.
Ship collision:
if ship.invincible<=0:
for ast of asteroids:
const dist = distWrap? Asteroids wrap; collision across edges? Need distance accounting wrap. Use wrapDistance function: dx=Math.abs(ship.x-ast.x); if dx>W/2 dx=W-dx; same y; if dx*dx+dy*dy < (ship.radius+ast.radius*.8)^2. Then shipHit(ast). break.
Need if collision, maybe asteroid continues? Could leave.
shipHit:
addExplosion(ship.x,ship.y,'#7ff',14,40); lives--; if lives<=0 { state='gameover'; updateHigh(); show overlay; } else { resetShip(); maybe clear nearby? }
Need if lives <= 0, maybe asteroids remain but state gameover.
Need update high.
Need draw:
function draw() {
ctx.clearRect(0,0,W,H);
ctx.fillStyle='#02040a'; fillRect.
drawStars(0?) If drawStars includes update maybe separate. Need avoid updating twice. Use drawStars maybe.
drawBullets, drawAsteroids, drawParticles, drawShip.
if state==='paused' maybe overlay.
}
Loop:
function loop(t) {
const dt=Math.min(0.033,(t-last)/1000 || 0);
last=t;
if (state==='playing') update(dt);
draw();
requestAnimationFrame(loop);
}
Need drawStars maybe update star positions if playing? Could update always with dt. But if paused maybe no. Use drawStars(dt) only when playing? We can update in draw always maybe okay. But for gameover maybe stars drift. Fine.
Need HUD update:
hud.innerHTML = `<div>Score <span>${score}</span></div>...`
Need maybe every frame. Lives icons: '◆'.repeat(lives). Need high.
Could use textContent simpler:
document.getElementById('score').textContent = score; etc.
HTML:
<div id="hud">
<div class="stat">Score <b id=score>0</b></div>
<div class="stat">Lives <b id=lives>◆ ◆ ◆</b></div>
<div class="stat">Level <b id=level>1</b></div>
<div class="stat">Best <b id=best>0</b></div>
</div>
<div id="overlay">...</div>
<div id=touchControls>...
Need maybe keyboard focus? Body.
Need overlay content dynamic:
function showOverlay(title, lines) { overlay.classList.remove('hidden'); overlay.querySelector('h1').textContent=title; overlay.querySelector('.sub').innerHTML = lines.map... }
Initial overlay. Need event click on overlay? document.addEventListener('pointerdown', start if state title/gameover; if paused maybe resume? Maybe click anywhere on canvas not buttons. But buttons pointerdown could also trigger; prevent default and stop? Touch buttons call e.stopPropagation maybe. For pointerdown on document maybe if state playing no.
Need on pointerdown:
if (state==='title'||state==='gameover') startGame();
else if (state==='paused') resumeGame();
Need if clicked touch button while paused? It would resume maybe. okay.
Need if initial overlay click start.
Need maybe canvas tabindex? body.
Need high score:
try high = Number(localStorage.getItem('asteroidsHigh')||0) catch.
function saveHigh()...
Need update best.
Need maybe use safe area.
Need handle resize preserving positions? Could scale positions by new/old. In resize: oldW, oldH; if W/H, scale ship/asteroids/bullets/particles positions. But if called before init? Need.
function resize() { const oldW=W, oldH=H; W=...; if (oldW && oldH) { const sx=W/oldW; ... positions *= } makeStars(); }
Need dpr.
Need maybe on mobile, canvas no scrolling. CSS body overflow hidden; touch-action none.
Need perhaps "Procedural vector graphics only" no images. Fine.
Need maybe "Asteroids" all in one file.
Potential issues:
- Use ctx.setTransform(dpr,0,0,dpr,0,0) after resize, then clearRect uses CSS coordinates. Good.
- When drawing, if canvas resized, transform persists? setTransform called. Need no scale elsewhere.
- In draw, ctx.clearRect(0,0,W,H) with transform. Good.
- For lineCap etc.
Need maybe add "mines"? no.
Need write file.
/tmp/ws/index.html (27507 bytes)Successfully wrote 27507 bytes to /tmp/ws/index.html
thinking
Need maybe verify file present and maybe maybe run? Could use grep? Need ensure no external. Need maybe use bash ls and maybe maybe HTML validation? We can run a quick grep for http?
{"command":"ls -l index.html && grep -E \"https?://|<link|<script src|<img\" -n index.html || true","timeout":1000}-rw-r--r--. 1 node node 27531 Aug 1 19:45 index.html
thinking
Created `/tmp/ws/index.html` — a self-contained, playable canvas Asteroids game with procedural vector graphics only.
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
Vector Asteroids with menu screen; click starts game, ship and asteroids render, drifting and responding to inputs but ship rotation/thrust unclear.
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.