Asteroids · deepseek-v3.2
Output generated Cost / run $0.0381 Runtime 292.9s
Prompt for this run
Build a playable Asteroids-style game on a canvas using procedural vector graphics. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. No third-party libraries are provided. Required behavior: - Use Left/Right to rotate the ship, Up to thrust, and Space to fire. Show the controls. The ship retains momentum and wraps around every screen edge; asteroids also wrap. - Spawn moving large asteroids. A bullet hit splits a large asteroid into smaller pieces; hitting a smallest piece removes it. Hits increase the score. - Start with three lives. A ship-asteroid collision removes one life, respawns the ship, and briefly protects it from immediate repeated damage. - Clearing all asteroids starts another wave. Losing all lives shows Game Over and a Restart control that resets score, lives, and the arena. - Keep projectile lifetime bounded and prevent gameplay keys from scrolling the page. Verify: Test rotation, thrust, and firing near the initial spawn. Check that the ship changes heading, gains momentum, and emits moving projectiles. Sample the scene to inspect screen wrapping when an object crosses an edge.
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
METRICS
Transcript · 39 messages
Build a playable Asteroids-style game on a canvas using procedural vector graphics. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. No third-party libraries are provided. Required behavior: - Use Left/Right to rotate the ship, Up to thrust, and Space to fire. Show the controls. The ship retains momentum and wraps around every screen edge; asteroids also wrap. - Spawn moving large asteroids. A bullet hit splits a large asteroid into smaller pieces; hitting a smallest piece removes it. Hits increase the score. - Start with three lives. A ship-asteroid collision removes one life, respawns the ship, and briefly protects it from immediate repeated damage. - Clearing all asteroids starts another wave. Losing all lives shows Game Over and a Restart control that resets score, lives, and the arena. - Keep projectile lifetime bounded and prevent gameplay keys from scrolling the page. Verify: Test rotation, thrust, and firing near the initial spawn. Check that the ship changes heading, gains momentum, and emits moving projectiles. Sample the scene to inspect screen wrapping when an object crosses an edge.
thinking
I'll start by examining the current directory to see if there's anything there, then build the game. Let's first list files.
{"command":"ls -la"}total 12 drwx------ 3 node node 4096 Oct 9 11:11 . drwxrwxrwt 6 root root 4096 Oct 9 11:11 .. drwxr-xr-x 3 node node 4096 Oct 9 11:11 .home
thinking
So empty. I'll create index.html with the game. Let's start writing the file. I'll create a single HTML file with canvas, CSS, JS. Use vector graphics (drawing lines and polygons). I'll implement standard Asteroids mechanics. I'll structure: - HTML: canvas, controls display, UI for lives, score, game over. - CSS: center canvas, basic styling. - JS: Game loop, entities (Ship, Asteroid, Bullet), keyboard handling, collision detection, wrapping, spawning. Let's write the file.
index.html (11591 bytes)Successfully wrote to index.html
thinking
Now I need to verify the file exists and test in browser. Let's first check the file size and maybe fix a couple of errors: ASTEROID_RADIUS_SMALL = A10; that's a typo. Should be numeric. Let's edit the file. Let's read it first.
{"path":"index.html"}<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Asteroids</title> <style> body { margin:赏识0; padding: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #000; color: #0f0; font-family: monospace; overflow: hidden; } #gameContainer { text-align: center; } #gameCanvas { display: block; background: #111; border: 2px solid #0f0; margin: 10px auto; } #ui { margin: 10px; font-size: 18px; display: flex; justify-content: space-around; max-width: 800px; } #controls { margin: 10px; font-size: 14px; color: #ccc; } #gameOver { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.8); padding: interv20px; border: 2px solid #0f0; color: #0f0; z-index: 100; display: none; } button { background: #000; color: #00ff00; border: 1px solid #00ff00; padding: 10px 20px; cursor: pointer; font-family: monospace; margin: 5px; } button:hover { background: #00ff00; color: #000; } </style> </head> <body> <div id="gameContainer"> <div id="ui"> <div>Score: <span id="score">0</span></div> <div>Lives: <span id="lives">3</span></div> <div>Wave: <span id="wave">1</span></div> </div> <canvas id="gameCanvas" width="800" height="600"></canvas> <div id="controls"> <p>Controls: Left/Right arrows to rotate, Up arrow to thrust, Space to fire</p> </div> <div id="gameOver"> <h2>Game Over</h2> <p>Final Score: <span id="finalScore">0</span></p> <button id="restartButton">Restart</button> </div> </div> <script> // Game constants const CANVAS_WIDTH = 800; const CANVAS_HEIGHT = 600; const SHIP_SIZE = 20; const SHIP_SPEED = 0.1; const ROTATION_SPEED = 0.05; const BULLET_SPEED = 8; const BULLET_LIFETIME = 60; // frames const ASTEROID_RADIUS_LARGE = 40; const ASTEROID_RADIUS_MEDIUM = 20; const ASTEROID_RADIUS_SMALL = A10; const ASTEROID_SPEED_RANGE = 1; const ASTEROIDS_PER_WAVE = 5; const IMMUNITY_TIME = 120; // frames after respawn const FRICTION = 0.99; // Game state let canvas, ctx; let ship; let asteroids = []; let bullets = []; let score = 0; let lives = 3; let wave = 1; let gameOver = false; let keys = {}; let immunity = 0; // Initialize function init() { canvas = document.getElementById('gameCanvas'); ctx = canvas.getContext('2d'); ship = new Ship(); spawnWave(); requestAnimationFrame(gameLoop); } // Ship class function Ship() { this.x = CANVAS_WIDTH / 2; this.y = CANVAS_HEIGHT / 2; this.vx = 0; this.vy = 0; this.angle = 0; // radians this.radius = SHIP_SIZE; this.thrusting = false; this.draw = function() { ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle); ctx.strokeStyle = immunity > 0 ? '#888' : '#fff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(SHIP_SIZE, 0); ctx.lineTo(-SHIP_SIZE / 2, -SHIP_SIZE / 2); ctx.lineTo(-SHIP_SIZE / 2, SHIP_SIZE / 2); ctx.closePath(); ctx.stroke(); if (this.thrusting && immunity === 0) { ctx.strokeStyle = '#f00'; ctx.beginPath(); ctx.moveTo(-SHIP_SIZE / 2, 0); ctx.lineTo(-SHIP_SIZE * 1.5, 0); ctx.stroke(); } ctx.restore(); }; this.update = function() { // rotation if (keys[37]) this.angle -= ROTATION_SPEED; // left if (keys[39]) this.angle += ROTATION_SPEED; // right // thrust this.thrusting = false; if (keys[38]) { this.vx += Math.cos(this.angle) * SHIP_SPEED; this.vy += Math.sin(this.angle) * SHIP_SPEED; this.thrusting = true; } // apply friction this.vx *= FRICTION; this.vy *= FRICTION; // move this.x += this.vx; this.y += this.vy; // wrap if (this.x < 0) this.x = CANVAS_WIDTH; if (this.x > CANVAS_WIDTH) this.x = 0; if (this.y < 0) this.y = CANVAS_HEIGHT; if (this.y > CANVAS_HEIGHT) this.y = 0; }; } // Asteroid class function Asteroid(radius, x, y) { this.radius = radius; this.x = x !== undefined ? x : Math.random() * CANVAS_WIDTH; this.y = y !== undefined ? y : Math.random() * CANVAS_HEIGHT; this.vx = (Math.random() - 0.5) * ASTEROID_SPEED_RANGE * 2; this.vy = (Math.random() - 0.5) * ASTEROID_SPEED_RANGE * 2; this.angle = Math.random() * Math.PI * 2; this.rotationSpeed = (Math.random() - 0.5) * 0.02; this.points = Math.floor(Math.random() * 6) + 5; // irregular shape this.draw = function() { ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle); ctx.strokeStyle = '#0f0'; ctx.lineWidth = 2; ctx.beginPath(); for (let i = 0; i < this.points; i++) { const a = (i / this.points) * Math.PI * 2; const r = this.radius * (0.7 + 0.3 * Math.sin(i * 1.7)); const px = Math.cos(a) * r; const py = Math.sin(a) * r; if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py); } ctx.closePath(); ctx.stroke(); ctx.restore(); }; this.update = function() { this.x += this.vx; this.y += this.vy; this.angle += this.rotationSpeed; // wrap if (this.x < -this.radius) this.x = CANVAS_WIDTH + this.radius; if (this.x > CANVAS_WIDTH + this.radius) this.x = -this.radius; if (this.y < -this.radius) this.y = CANVAS_HEIGHT + this.radius; if (this.y > CANVAS_HEIGHT + this.radius) this.y = -this.radius; }; this.split = function() { if (this.radius === ASTEROID_RADIUS_SMALL) { return []; // disappears } const newRadius = this.radius === ASTEROID_RADIUS_LARGE ? ASTEROID_RADIUS_MEDIUM : ASTEROID_RADIUS_SMALL; const a1 = new Asteroid(newRadius, this.x, this.y); const a2 = new Asteroid(newRadius, this.x, this.y); // give them random velocities a1.vx = (Math.random() - 0.5) * ASTEROID_SPEED_RANGE * 3; a1.vy = (Math.random() - 0.5) * ASTEROID_SPEED_RANGE * 3; a2.vx = (Math.random() - 0.5) * ASTEROID_SPEED_RANGE * 3; a2.vy = (Math.random() - 0.5) * ASTEROID_SPEED_RANGE * 3; return [a1, a2]; }; } // Bullet class function Bullet(x, y, angle) { this.x = x; this.y = y; this.vx = Math.cos(angle) * BULLET_SPEED; this.vy = Math.sin(angle) * BULLET_SPEED; this.life = BULLET_LIFETIME; this.radius = 2; this.draw = function() { ctx.fillStyle = '#ff0'; ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fill(); }; this.update = function() { this.x += this.vx; this.y += this.vy; this.life--; // wrap if (this.x < 0) this.x = CANVAS_WIDTH; if (this.x > CANVAS_WIDTH) this.x = 0; if (this.y < 0) this.y = CANVAS_HEIGHT; if (this.y > CANVAS_HEIGHT) this.y = 0; }; } // Spawn a wave of asteroids function spawnWave() { asteroids = []; for (let i = 0; i < ASTEROIDS_PER_WAVE; i++) { asteroids.push(new Asteroid(ASTEROID_RADIUS_LARGE)); } } // Check collisions function checkCollisions() { // bullets vs asteroids for (let i = bullets.length - 1; i >= 0; i--) { const b = bullets[i]; for (let j = asteroids.length - 1; j >= 0; j--) { const a = asteroids[j]; const dx = b.x - a.x; const dy = b.y - a.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < b.radius + a.radius) { // hit bullets.splice(i, 1); score += 100; const newAsteroids = a.split(); asteroids.splice(j, 1); asteroids.push(...newAsteroids); updateUI(); break; } } } // ship vs asteroids if (immunity === 0) { for (let i = asteroids.length - 1; i >= 0; i--) { const a = asteroids[i]; const dx = ship.x - a.x; const dy = ship.y - a.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < ship.radius + a.radius) { // collision lives--; updateUI(); if (lives <= 0) { gameOver = true; document.getElementById('gameOver').style.display = 'block'; document.getElementById('finalScore').textContent = score; } else { ship = new Ship(); immunity = IMMUNITY_TIME; } break; } } } } // Update UI function updateUI() { document.getElementById('score').textContent = score; document.getElementById('lives').textContent = lives; document.getElementById('wave').textContent = wave; } // Game loop function gameLoop() { // clear canvas ctx.fillStyle = '#111'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); // update entities if (!gameOver) { ship.update(); if (immunity > 0) immunity--; asteroids.forEach(a => a.update()); bullets.forEach(b => b.update()); // remove dead bullets bullets = bullets.filter(b => b.life > 0); checkCollisions(); // check if wave cleared if (asteroids.length === 0) { wave++; updateUI(); spawnWave(); } } // draw asteroids.forEach(a => a.draw()); bullets.forEach(b => b.draw()); ship.draw(); // draw immunity indicator if (immunity > 0) { ctx.strokeStyle = '#f00'; ctx.beginPath(); ctx.arc(ship.x, ship.y, SHIP_SIZE + 5, 0, Math.PI * 2); ctx.stroke(); } requestAnimationFrame(gameLoop); } // Event listeners window.addEventListener('keydown', e => { keys[e.keyCode] = true; // prevent arrow keys from scrolling if ([37, 38, 39, 40, 32].includes(e.keyCode)) { e.preventDefault(); } // fire bullet if (e.keyCode === 32 && !gameOver) { bullets.push(new Bullet(ship.x, ship.y, ship.angle)); } }); window.addEventListener('keyup', e => { keys[e.keyCode] = false; }); // Restart button document.getElementById('restartButton').addEventListener('click', () => { score = 0; lives = 3; wave = 1; gameOver = false; bullets = []; ship = new Ship(); spawnWave(); immunity = 0; updateUI(); document.getElementById('gameOver').style.display = 'none'; }); // Start game init(); </script> </body> </html>
thinking
I see a few issues: 1. ASTEROID_RADIUS_SMALL = A10; should be 10. 2. CSS margin:赏识0; and padding: interv20px; weird typos. 3. ASTEROID_SPEED_RANGE = 1; maybe a bit low; we can keep. 4. The ship drawing: I used SHIP_SIZE for moveTo (SHIP_SIZE,0) but the other points are -SHIP_SIZE/2. That's okay. 5. Immunity indicator draws over ship even when immunity > 0? That's fine. 6. Bullet spawning should originate from ship tip, not center. Let's adjust. Let's edit the file. We'll fix the constants and CSS. Let's edit in one go. We'll need to change lines: CSS margin:赏识0; padding: interv20px; and ASTEROID_RADIUS_SMALL = A10;. Let's also adjust bullet spawn: we want bullet to start at ship's tip (front). The ship points right (angle 0). The tip is at (SHIP_SIZE,0) relative to ship center. So we can compute tipX = ship.x + Math.cos(ship.angle) * SHIP_SIZE; tipY similarly. We'll also increase bullet lifetime a bit? Keep as is. Also, maybe increase asteroid speed range and spawn count. Let's do edits. First, create a corrected version. Use edit with multiple changes. Let's locate exact lines. Let's find line numbers? We'll just replace the specific strings. First, margin:赏识0; should be margin: 0; but there's also a CSS property padding: interv20px; should be padding: 20px;. Let's edit. We'll also adjust ASTEROID_RADIUS_SMALL. Let's do separate edits. I'll do CSS first. The oldText includes the whole block? Safer to replace exact lines. Search for "margin:赏识0;" and "padding: interv20px;". Let's also look at the surrounding lines. Let's make a single edit with two changes: one for margin, one for padding. But note: edit's oldText must be unique and non-overlapping. They are separate lines, so fine. Let's do:
{"path":"index.html","edits":[{"oldText":" margin:赏识0;","newText":" margin: 0;"},{"oldText":" padding: inteSuccessfully replaced 2 block(s) in index.html.
thinking
Now fix ASTEROID_RADIUS_SMALL = A10; change to 10. Let's locate line. Let's read a snippet to confirm. Let's search for "ASTEROID_RADIUS_SMALL". We'll replace that line.
We'll also adjust ASTEROID_SPEED_RANGE maybe increase to 2? Let's keep as is for now. We'll also adjust ASTEROIDS_PER_WAVE to 5 (okay). Let's also adjust SHIP_SPEED maybe increase? Actually 0.1 per frame is okay.
Now bullet spawn location. We'll change line where bullets.push(new Bullet(ship.x, ship.y, ship.angle)); We'll compute tip.
Let's edit that line and also maybe adjust bullet speed.
We'll also need to ensure bullets don't spawn when ship is immune? Not necessary.
Let's first change constant. Let's find exact line: "const ASTEROID_RADIUS_SMALL = A10;". Let's replace with "const ASTEROID_RADIUS_SMALL = 10;". Let's do that.
Now bullet spawn: we need to modify the keydown event listener.
Let's read around that line: "if (e.keyCode === 32 && !gameOver) { bullets.push(new Bullet(ship.x, ship.y, ship.angle)); }". We'll replace with calculation.
Let's also adjust bullet lifetime maybe 90 frames? Keep as is.
Now also need to ensure bullets don't collide with ship immediately after spawn (they spawn ahead). That's fine.
Let's implement a simple offset: ship tip plus a small extra to avoid collision.
Let's do edits. First find ASTEROID_RADIUS_SMALL line. Let's search via bash for line numbers? Let's use grep.{"command":"grep -n \"ASTEROID_RADIUS_SMALL\" index.html"}96:const ASTEROID_RADIUS_SMALL = A10; 218: if (this.radius === ASTEROID_RADIUS_SMALL) { 221: const newRadius = this.radius === ASTEROID_RADIUS_LARGE ? ASTEROID_RADIUS_MEDIUM : ASTEROID_RADIUS_SMALL;
thinking
Great. Line 96. Let's edit that line. Let's also maybe adjust ASTEROID_SPEED_RANGE line? Not needed. Now bullet spawn line: Let's find "keydown". Let's grep for "keydown".
{"command":"grep -n \"keydown\" index.html"}361:window.addEventListener('keydown', e => {
thinking
Read around line 361. Let's read lines around it. Let's read from line的有吗? Let's read from line 355 to 370. Let's use sed? Better to read the file from offset. Use read with offset.
{"path":"index.html","offset":350,"limit":30}if (immunity > 0) { ctx.strokeStyle = '#f00'; ctx.beginPath(); ctx.arc(ship.x, ship.y, SHIP_SIZE + 5, 0, Math.PI * 2); ctx.stroke(); } requestAnimationFrame(gameLoop); } // Event listeners window.addEventListener('keydown', e => { keys[e.keyCode] = true; // prevent arrow keys from scrolling if ([37, 38, 39, 40, 32].includes(e.keyCode)) { e.preventDefault(); } // fire bullet if (e.keyCode === 32 && !gameOver) { bullets.push(new Bullet(ship.x, ship.y, ship.angle)); } }); window.addEventListener('keyup', e => { keys[e.keyCode] = false; }); // Restart button document.getElementById('restartButton').addEventListener('click', () => { score = 0; lives = 3; [15 more lines in file. Use offset=380 to continue.]
thinking
Now we need to replace the bullet spawning line. Let's edit exactly that line. We'll compute tipX and tipY. We'll do: const tipX = ship.x + Math.cos(ship.angle) * SHIP_SIZE; const tipY = ship.y + Math.sin(ship.angle) * SHIP_SIZE; bullets.push(new Bullet(tipX, tipY, ship.angle)); We'll replace the line. Now also adjust bullet speed maybe increase to 10? Let's keep BULLET_SPEED = 8. Let's also adjust asteroid splitting velocities to be more random. But first, let's apply the two edits: constant and bullet spawn. Let's do constant edit:
{"path":"index.html","edits":[{"oldText":"const ASTEROID_RADIUS_SMALL = A10;","newText":"const ASTEROID_RADIUS_SMALL = 10;"}]}Successfully replaced 1 block(s) in index.html.
thinking
Now bullet spawn line. Let's get exact line: " bullets.push(new Bullet(ship.x, ship.y, ship.angle));". Let's replace with new lines. We'll keep the surrounding lines. We'll replace from " bullets.push(new Bullet(ship.x, ship.y, ship.angle));" to something else. Let's write new text with proper indentation. We'll also add a small offset maybe 5 pixels extra. Let's compute tip offset SHIP_SIZE + 5. Let's do: " const tipX = ship.x + Math.cos(ship.angle) * (SHIP_SIZE + 5);\n const tipY = ship.y + Math.sin(ship.angle) * (SHIP_SIZE + 5);\n bullets.push(new Bullet(tipX, tipY, ship.angle));" Now edit. Let's ensure we match exactly the line. Let's read the exact line again: it's preceded by " // fire bullet". Let's read from 360 to 370 again to be safe. Let's output exact characters. Let's capture with cat? Use bash to extract line.
{"command":"sed -n '367,367p' index.html"}// fire bullet
thinking
That's not right. Let's count lines: we need line numbers after previous edit changed line numbers. Let's just grep with context. Let's do: `grep -n "bullets.push(new Bullet" index.html`
{"command":"grep -n \"bullets.push(new Bullet\" index.html"}369: bullets.push(new Bullet(ship.x, ship.y, ship.angle));
thinking
Great. Now we need the exact line at line 369. Let's get that line and verify there's no extra spaces. Let's read line 369 and surrounding lines to be safe.
{"path":"index.html","offset":365,"limit":10}e.preventDefault(); } // fire bullet if (e.keyCode === 32 && !gameOver) { bullets.push(new Bullet(ship.x, ship.y, ship.angle)); } }); window.addEventListener('keyup', e => { keys[e.keyCode] = false; }); [20 more lines in file. Use offset=375 to continue.]
thinking
Now we have the exact line. Let's replace it. We'll also maybe adjust bullet lifetime constant. Let's keep. Now edit:
{"path":"index.html","edits":[{"oldText":" bullets.push(new Bullet(ship.x, ship.y, ship.angle));","newText":" const tipX = shiSuccessfully replaced 1 block(s) in index.html.
thinking
Now let's test the game in browser. We'll load index.html and inspect initial output and console. Use browser tool. Let's start with reloading the page.
{"action":"reload"}{"console":["warning: An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing."],"dom":"Score: 0\nLives: 3\nWave: 1\n\nControls: Left/Right arrows to rotate, Up arrow to thrust, Space to fire","images":"model does not support images"}
thinking
Now we need to see the canvas; we can take a screenshot to see if asteroids and ship are drawn. Let's take screenshot.
{"action":"screenshot"}{"console":["warning: An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing."],"dom":"Score: 0\nLives: 2\nWave: 1\n\nControls: Left/Right arrows to rotate, Up arrow to thrust, Space to fire","images":"model does not support images"}
thinking
Lives decreased? Something happened. Probably ship collided with asteroid immediately due to spawn overlap. Indeed we spawned asteroids randomly and ship at center; they could overlap. Need to ensure asteroids spawn away from ship. Let's modify spawnWave to avoid spawning within safe radius of ship. Also when respawning ship after loss, we should ensure no immediate collision. We'll implement a simple check: while any asteroid within safe distance, move asteroid away or regenerate. Simpler: we can spawn asteroids at random positions but at least a certain distance from ship. Let's define SAFE_DISTANCE = 100. Also, after losing a life, we need to respawn ship away from asteroids. We'll also set immunity. Let's adjust spawnWave and also when ship respawn we might want to reposition ship away from asteroids. Let's edit the file. We'll add a function to place asteroid with safe distance. We'll also adjust the ship constructor to maybe pick a random position? Not needed; we can just move ship away from asteroids after respawn. Let's do changes: 1. Add SAFE_DISTANCE = 100 constant. 2. In Asteroid constructor, if x and y are not provided, we'll generate random position but ensure distance from ship (which is at center initially). We'll pass shipX, shipY parameters to check. We'll need to know ship position at wave start. In spawnWave we can loop until position is safe. 3. In spawnWave, we'll create asteroids using new Asteroid(radius) with optional x,y? We'll implement a helper function getSafePosition(). Let's edit the JavaScript section. We'll replace the spawnWave function. Also modify the ship respawn: we can simply reset ship to center; but if there are asteroids overlapping, immunity should protect but we still want to avoid overlapping visuals. We'll keep immunity and maybe also move ship away if overlapping? Simpler: we can move ship to center and push away any asteroids that are too close. We'll implement a function pushAsteroidsAwayFromShip. Given time, I'll implement safe spawn for asteroids only at wave start. Let's read the current spawnWave function. Let's get its content. Let's search for "function spawnWave". Let's grep with line numbers.
{"command":"grep -n \"function spawnWave\" index.html"}260:function spawnWave() {
thinking
Now read lines around 260-270. Let's read.
{"path":"index.html","offset":255,"limit":20}if (this.y > CANVAS_HEIGHT) this.y = 0; }; } // Spawn a wave of asteroids function spawnWave() { asteroids = []; for (let i = 0; i < ASTEROIDS_PER_WAVE; i++) { asteroids.push(new Asteroid(ASTEROID_RADIUS_LARGE)); } } // Check collisions function checkCollisions() { // bullets vs asteroids for (let i = bullets.length - 1; i >= 0; i--) { const b = bullets[i]; for (let j = asteroids.length - 1; j >= 0; j--) { const a = asteroids[j]; const dx = b.x - a.x; [122 more lines in file. Use offset=275 to continue.]
thinking
We need to replace this with safe spawning. Let's also add SAFE_DISTANCE constant near other constants. First, add constant. Let's find line where constants start (maybe line 80). Let's read from line 70 to 110.
{"path":"index.html","offset":70,"limit":40}<div id="ui"> <div>Score: <span id="score">0</span></div> <div>Lives: <span id="lives">3</span></div> <div>Wave: <span id="wave">1</span></div> </div> <canvas id="gameCanvas" width="800" height="600"></canvas> <div id="controls"> <p>Controls: Left/Right arrows to rotate, Up arrow to thrust, Space to fire</p> </div> <div id="gameOver"> <h2>Game Over</h2> <p>Final Score: <span id="finalScore">0</span></p> <button id="restartButton">Restart</button> </div> </div> <script> // Game constants const CANVAS_WIDTH = 800; const CANVAS_HEIGHT = 600; const SHIP_SIZE = 20; const SHIP_SPEED = 0.1; const ROTATION_SPEED = 0.05; const BULLET_SPEED = 8; const BULLET_LIFETIME = 60; // frames const ASTEROID_RADIUS_LARGE = 40; const ASTEROID_RADIUS_MEDIUM = 20; const ASTEROID_RADIUS_SMALL = 10; const ASTEROID_SPEED_RANGE = 1; const ASTEROIDS_PER_WAVE = 5; const IMMUNITY_TIME = 120; // frames after respawn const FRICTION = 0.99; // Game state let canvas, ctx; let ship; let asteroids = []; let bullets = []; let score = 0; let lives = 3; let wave = 1; [287 more lines in file. Use offset=110 to continue.]
thinking
Now add SAFE_DISTANCE = 150 after ASTEROIDS_PER_WAVE maybe. Let's edit that line. We'll replace "const ASTEROIDS_PER_WAVE = 5;" with "const ASTEROIDS_PER_WAVE = 5;\nconst SAFE_DISTANCE = 150;" but we need to keep the line unique. Let's do a separate edit. Let's find the exact line "const ASTEROIDS_PER_WAVE = 5;". Let's grep again.
{"command":"grep -n \"ASTEROIDS_PER_WAVE = 5\" index.html"}98:const ASTEROIDS_PER_WAVE = 5;