Asteroids · gemini-3.1-flash-lite
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 · 4 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
**Developing Core Mechanics** I'm currently focused on implementing the fundamental elements for our Asteroids game. The ship's movement, including thrust and rotation, is taking shape, and I'm also starting to integrate the shooting mechanism. Next, I'll be tackling screen wrapping and the initial asteroid behavior. **Implementing Core Game Logic** I'm now shifting my focus to integrating the score and lives systems. The core game loop is well-defined with `requestAnimationFrame`, and I'm refining the entity structures for ship, bullets, and asteroids. Collision detection using circular approximations and vector math is a key area I'm actively working on.
index.html (5392 bytes)Successfully wrote 5392 bytes to index.html
This single-file solution implements the Asteroids game using HTML5 Canvas and procedural vector drawing. It includes: * **Vector Physics:** Thrust movement, rotation, and momentum for the ship. * **Game Loop:** The game runs in a `requestAnimationFrame` loop. * **Procedural Asteroids:** Asteroids are generated as random polygons that wrap around the screen. * **Collision Detection:** Circular math resolves ship-asteroid and bullet-asteroid interactions. * **Mechanics:** Shooting triggers asteroid splitting, and the ship has lives and persistent score tracking. You can open `index.html` in any modern web browser to play. **Controls:** * **Arrow Keys:** Rotate and Thrust. * **Spacebar:** Shoot. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Asteroids Vector Game</title> <style> body { margin: 0; background: #000; color: #fff; overflow: hidden; font-family: monospace; } canvas { display: block; } #ui { position: absolute; top: 10px; left: 10px; pointer-events: none; } </style> </head> <body> <div id="ui"> Score: <span id="score">0</span><br> Lives: <span id="lives">3</span> </div> <canvas id="game"></canvas> <script> const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); const scoreEl = document.getElementById('score'); const livesEl = document.getElementById('lives'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; let score = 0, lives = 3, gameOver = false; const keys = {}; window.addEventListener('keydown', e => keys[e.code] = true); window.addEventListener('keyup', e => keys[e.code] = false); const ship = { x: canvas.width / 2, y: canvas.height / 2, dx: 0, dy: 0, angle: -Math.PI / 2, bullets: [], lastShot: 0 }; let asteroids = []; function createAsteroid(x, y, radius, level = 3) { const vertices = []; const offset = 8; for (let i = 0; i < 8; i++) { vertices.push({ x: (radius + Math.random() * offset - offset / 2) * Math.cos(i / 8 * 2 * Math.PI), y: (radius + Math.random() * offset - offset / 2) * Math.sin(i / 8 * 2 * Math.PI) }); } return { x, y, dx: (Math.random() - 0.5) * 2, dy: (Math.random() - 0.5) * 2, radius, vertices, level }; } for(let i=0; i<5; i++) asteroids.push(createAsteroid(Math.random()*canvas.width, Math.random()*canvas.height, 40)); function drawVector(x, y, vertices, angle = 0) { ctx.beginPath(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; for (let i = 0; i < vertices.length; i++) { const px = x + vertices[i].x * Math.cos(angle) - vertices[i].y * Math.sin(angle); const py = y + vertices[i].x * Math.sin(angle) + vertices[i].y * Math.cos(angle); if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py); } ctx.closePath(); ctx.stroke(); } function update() { if (gameOver) return; if (keys['ArrowLeft']) ship.angle -= 0.1; if (keys['ArrowRight']) ship.angle += 0.1; if (keys['ArrowUp']) { ship.dx += Math.cos(ship.angle) * 0.2; ship.dy += Math.sin(ship.angle) * 0.2; } if (keys['Space'] && Date.now() - ship.lastShot > 250) { ship.bullets.push({ x: ship.x + Math.cos(ship.angle)*20, y: ship.y + Math.sin(ship.angle)*20, dx: Math.cos(ship.angle)*7, dy: Math.sin(ship.angle)*7, life: 60 }); ship.lastShot = Date.now(); } ship.x = (ship.x + ship.dx + canvas.width) % canvas.width; ship.y = (ship.y + ship.dy + canvas.height) % canvas.height; ship.dx *= 0.99; ship.dy *= 0.99; ship.bullets.forEach((b, i) => { b.x += b.dx; b.y += b.dy; b.life--; if(b.life <= 0) ship.bullets.splice(i, 1); }); asteroids.forEach((a, i) => { a.x = (a.x + a.dx + canvas.width) % canvas.width; a.y = (a.y + a.dy + canvas.height) % canvas.height; ship.bullets.forEach((b, bi) => { if (Math.hypot(a.x - b.x, a.y - b.y) < a.radius) { if (a.level > 1) { asteroids.push(createAsteroid(a.x, a.y, a.radius / 2, a.level - 1)); asteroids.push(createAsteroid(a.x, a.y, a.radius / 2, a.level - 1)); } asteroids.splice(i, 1); ship.bullets.splice(bi, 1); score += 100; scoreEl.innerText = score; } }); if (Math.hypot(a.x - ship.x, a.y - ship.y) < a.radius) { lives--; livesEl.innerText = lives; if (lives <= 0) gameOver = true; ship.x = canvas.width/2; ship.y = canvas.height/2; ship.dx = 0; ship.dy = 0; } }); if (asteroids.length === 0) for(let i=0; i<6; i++) asteroids.push(createAsteroid(Math.random()*canvas.width, Math.random()*canvas.height, 40)); ctx.fillStyle = '#000'; ctx.fillRect(0, 0, canvas.width, canvas.height); drawVector(ship.x, ship.y, [{x:20, y:0}, {x:-10, y:10}, {x:-5, y:0}, {x:-10, y:-10}], ship.angle); ship.bullets.forEach(b => { ctx.fillStyle='#fff'; ctx.fillRect(b.x, b.y, 2, 2); }); asteroids.forEach(a => drawVector(a.x, a.y, a.vertices)); requestAnimationFrame(update); } update(); </script> </body> </html> ```
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
Ship and asteroids render and drift, but ship never rotates/thrusts/shoots in response to any input; controls appear non-functional.
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.