Boids flocking · qwen3-235b-a22b-2507
Output saved · Stopped by an API error Cost / run $0.0036 Runtime 184.6s
A model request failed and its retries ran out. The output is the HTML saved before that.
Prompt for this run
Build a full-screen canvas flocking simulation with 150 boids drawn as oriented triangles. 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: - Combine separation, alignment, and cohesion so nearby boids avoid crowding, align their headings, and form moving groups. Triangles point in their direction of travel. - Use bounded speed and steering so motion changes smoothly instead of teleporting. Wrap boids across screen edges. - Boids within a visible or documented cursor-influence radius steer away from the pointer. They resume flocking after the pointer moves away. - Provide Pause/Resume and Reset. Pause freezes simulation state; Reset restores a fresh population of 150 boids. - Fit the canvas to the viewport and keep the population and controls usable after resizing. Verify: Sample the flock at several moments to check coordinated movement and triangles pointing along their travel direction. Try the cursor interaction with available pointer actions, then pause and resume to check that the flock freezes and continues.
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 · 19 messages
Build a full-screen canvas flocking simulation with 150 boids drawn as oriented triangles. 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: - Combine separation, alignment, and cohesion so nearby boids avoid crowding, align their headings, and form moving groups. Triangles point in their direction of travel. - Use bounded speed and steering so motion changes smoothly instead of teleporting. Wrap boids across screen edges. - Boids within a visible or documented cursor-influence radius steer away from the pointer. They resume flocking after the pointer moves away. - Provide Pause/Resume and Reset. Pause freezes simulation state; Reset restores a fresh population of 150 boids. - Fit the canvas to the viewport and keep the population and controls usable after resizing. Verify: Sample the flock at several moments to check coordinated movement and triangles pointing along their travel direction. Try the cursor interaction with available pointer actions, then pause and resume to check that the flock freezes and continues.
index.html (8210 bytes)Successfully wrote to index.html
{"action":"reload"}{"console":["warning: An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing.","error: Assignment to constant variable."],"dom":"Pause\nReset","images":"model does not support images"}
{"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>Boids Flocking Simulation</title> <style> body, html { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; background: #000; display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: Arial, sans-serif; } canvas { display: block; background: #000; border: 1px solid #333; } .controls { position: absolute; top: 10px; left: 10px; z-index: 10; display: flex; gap: 10px; } button { padding: 8px 12px; background: #fff; border: 1px solid #999; border-radius: 4px; cursor: pointer; font-size: 14px; } button:hover { background: #eee; } </style> </head> <body> <div class="controls"> <button id="pauseBtn">Pause</button> <button id="resetBtn">Reset</button> </div> <canvas id="boidsCanvas"></canvas> <script> const canvas = document.getElementById('boidsCanvas'); const ctx = canvas.getContext('2d'); const pauseBtn = document.getElementById('pauseBtn'); const resetBtn = document.getElementById('resetBtn'); // Simulation parameters const numBoids = 150; const separationRadius = 50; const alignmentRadius = 100; const cohesionRadius = 100; const cursorInfluenceRadius = 100; const maxSpeed = 3; const minSpeed = 1; const maxSteerForce = 0.1; let boids = []; let paused = false; let mouse = { x: 0, y: 0 }; // Resize canvas to fit window function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } // Vector math helpers const vec = { add: (a, b) => ({ x: a.x + b.x, y: a.y + b.y }), sub: (a, b) => ({ x: a.x - b.x, y: a.y - b.y }), mult: (v, s) => ({ x: v.x * s, y: v.y * s }), div: (v, s) => ({ x: v.x / s, y: v.y / s }), mag: (v) => Math.sqrt(v.x * v.x + v.y * v.y), normalize: (v) => { const m = vec.mag(v); return m > 0 ? vec.div(v, m) : { x: 0, y: 0 }; }, limit: (v, max) => { const m = vec.mag(v); if (m > max) return vec.mult(vec.normalize(v), max); return v; } }; // Boid class class Boid { constructor() { this.position = { x: Math.random() * canvas.width, y: Math.random() * canvas.height }; this.velocity = { x: (Math.random() - 0.5) * 2, y: (Math.random() - 0.5) * 2 }; this.velocity = vec.normalize(this.velocity); this.velocity = vec.mult(this.velocity, minSpeed + Math.random() * (maxSpeed - minSpeed)); } edges() { if (this.position.x > canvas.width) this.position.x = 0; if (this.position.x < 0) this.position.x = canvas.width; if (this.position.y > canvas.height) this.position.y = 0; if (this.position.y < 0) this.position.y = canvas.height; } separation(boids) { let steer = { x: 0, y: 0 }; let count = 0; for (const other of boids) { const d = vec.mag(vec.sub(this.position, other.position)); if (d > 0 && d < separationRadius) { let diff = vec.sub(this.position, other.position); diff = vec.normalize(diff); diff = vec.div(diff, d); // Weight by distance steer = vec.add(steer, diff); count++; } } if (count > 0) { steer = vec.div(steer, count); } if (vec.mag(steer) > 0) { steer = vec.normalize(steer); steer = vec.mult(steer, maxSpeed); steer = vec.sub(steer, this.velocity); steer = vec.limit(steer, maxSteerForce); } return steer; } alignment(boids) { let sum = { x: 0, y: 0 }; let count = 0; for (const other of boids) { const d = vec.mag(vec.sub(this.position, other.position)); if (d > 0 && d < alignmentRadius) { sum = vec.add(sum, other.velocity); count++; } } if (count > 0) { sum = vec.div(sum, count); sum = vec.normalize(sum); sum = vec.mult(sum, maxSpeed); let steer = vec.sub(sum, this.velocity); steer = vec.limit(steer, maxSteerForce); return steer; } return { x: 0, y: 0 }; } cohesion(boids) { let sum = { x: 0, y: 0 }; let count = 0; for (const other of boids) { const d = vec.mag(vec.sub(this.position, other.position)); if (d > 0 && d < cohesionRadius) { sum = vec.add(sum, other.position); count++; } } if (count > 0) { sum = vec.div(sum, count); return this.seek(sum); } return { x: 0, y: 0 }; } seek(target) { let desired = vec.sub(target, this.position); const d = vec.mag(desired); if (d === 0) return { x: 0, y: 0 }; desired = vec.normalize(desired); if (d < 100) { const m = maxSpeed * (d / 100); desired = vec.mult(desired, m); } else { desired = vec.mult(desired, maxSpeed); } let steer = vec.sub(desired, this.velocity); steer = vec.limit(steer, maxSteerForce); return steer; } flock(boids) { const sep = this.separation(boids); const ali = this.alignment(boids); const coh = this.cohesion(boids); // Weighted combination sep = vec.mult(sep, 2.0); ali = vec.mult(ali, 1.0); coh = vec.mult(coh, 1.0); this.velocity = vec.add(this.velocity, sep); this.velocity = vec.add(this.velocity, ali); this.velocity = vec.add(this.velocity, coh); this.velocity = vec.limit(this.velocity, maxSpeed); } update() { this.position = vec.add(this.position, this.velocity); this.edges(); } draw() { const angle = Math.atan2(this.velocity.y, this.velocity.x); ctx.save(); ctx.translate(this.position.x, this.position.y); ctx.rotate(angle); ctx.fillStyle = '#0f0'; ctx.beginPath(); ctx.moveTo(10, 0); ctx.lineTo(-10, -5); ctx.lineTo(-10, 5); ctx.closePath(); ctx.fill(); ctx.restore(); } repelFrom(pos) { const d = vec.mag(vec.sub(this.position, pos)); if (d < cursorInfluenceRadius) { const steer = vec.sub(this.position, pos); const strength = (cursorInfluenceRadius - d) / cursorInfluenceRadius; steer.x *= strength * 0.05; steer.y *= strength * 0.05; this.velocity = vec.add(this.velocity, steer); this.velocity = vec.limit(this.velocity, maxSpeed); } } } // Initialize function init() { boids = []; for (let i = 0; i < numBoids; i++) { boids.push(new Boid()); } } // Animation loop function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); if (!paused) { for (const boid of boids) { boid.flock(boids); boid.repelFrom(mouse); boid.update(); } } for (const boid of boids) { boid.draw(); } requestAnimationFrame(animate); } // Event listeners window.addEventListener('resize', resizeCanvas); pauseBtn.addEventListener('click', () => { paused = !paused; pauseBtn.textContent = paused ? 'Resume' : 'Pause'; }); resetBtn.addEventListener('click', init); canvas.addEventListener('mousemove', (e) => { mouse.x = e.clientX; mouse.y = e.clientY; }); canvas.addEventListener('mouseleave', () => { mouse.x = -1000; mouse.y = -1000; }); // Start resizeCanvas(); init(); animate(); </script> </body> </html>
{"path":"index.html","edits":[{"oldText":" pauseBtn.addEventListener('click', () => {\n paused = !paused;\n pauseBtn.textContCould not find the exact text in index.html. The old text must match exactly including all whitespace and newlines.
{"path":"index.html","offset":200,"limit":20}const sep = this.separation(boids); const ali = this.alignment(boids); const coh = this.cohesion(boids); // Weighted combination sep = vec.mult(sep, 2.0); ali = vec.mult(ali, 1.0); coh = vec.mult(coh, 1.0); this.velocity = vec.add(this.velocity, sep); this.velocity = vec.add(this.velocity, ali); this.velocity = vec.add(this.velocity, coh); this.velocity = vec.limit(this.velocity, maxSpeed); } update() { this.position = vec.add(this.position, this.velocity); this.edges(); } [81 more lines in file. Use offset=220 to continue.]
{"path":"index.html","offset":220,"limit":40}draw() { const angle = Math.atan2(this.velocity.y, this.velocity.x); ctx.save(); ctx.translate(this.position.x, this.position.y); ctx.rotate(angle); ctx.fillStyle = '#0f0'; ctx.beginPath(); ctx.moveTo(10, 0); ctx.lineTo(-10, -5); ctx.lineTo(-10, 5); ctx.closePath(); ctx.fill(); ctx.restore(); } repelFrom(pos) { const d = vec.mag(vec.sub(this.position, pos)); if (d < cursorInfluenceRadius) { const steer = vec.sub(this.position, pos); const strength = (cursorInfluenceRadius - d) / cursorInfluenceRadius; steer.x *= strength * 0.05; steer.y *= strength * 0.05; this.velocity = vec.add(this.velocity, steer); this.velocity = vec.limit(this.velocity, maxSpeed); } } } // Initialize function init() { boids = []; for (let i = 0; i < numBoids; i++) { boids.push(new Boid()); } } // Animation loop function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); [41 more lines in file. Use offset=260 to continue.]
{"path":"index.html","offset":260,"limit":40}if (!paused) { for (const boid of boids) { boid.flock(boids); boid.repelFrom(mouse); boid.update(); } } for (const boid of boids) { boid.draw(); } requestAnimationFrame(animate); } // Event listeners window.addEventListener('resize', resizeCanvas); pauseBtn.addEventListener('click', () => { paused = !paused; pauseBtn.textContent = paused ? 'Resume' : 'Pause'; }); resetBtn.addEventListener('click', init); canvas.addEventListener('mousemove', (e) => { mouse.x = e.clientX; mouse.y = e.clientY; }); canvas.addEventListener('mouseleave', () => { mouse.x = -1000; mouse.y = -1000; }); // Start resizeCanvas(); init(); animate(); </script> </body> [1 more lines in file. Use offset=300 to continue.]