Boids flocking · laguna-m.1
Create a full-screen flocking simulation of boids using separation, alignment, and cohesion, drawn as oriented triangles that steer smoothly and flee from the mouse cursor.
transcript · 8 messages
Create a full-screen flocking simulation of boids using separation, alignment, and cohesion, drawn as oriented triangles that steer smoothly and flee from the mouse cursor.
thinking
The user wants me to create a full-screen flocking simulation (boids) in a single index.html file. Let me break down the requirements: 1. Full-screen - should use the canvas to fill the entire viewport 2. Flocking simulation with boids (separation, alignment, cohesion) 3. Drawn as oriented triangles - each boid should be a triangle pointing in its direction of movement 4. Smooth steering - nice smooth movement 5. Flee from mouse cursor - boids should avoid the cursor 6. Self-contained - all CSS and JS in the HTML file, no external resources Let me create this simulation. I'll use: - HTML5 Canvas for rendering - Boids algorithm with proper flocking behavior - Mouse position detection for flee behavior - Triangle sprites that rotate based on velocity - Smooth animation with requestAnimationFrame I'll create a comprehensive flocking simulation with: - A good number of boids (maybe 100-200) - Proper separation, alignment, and cohesion forces - Mouse fleeing behavior - Edge wrapping or bouncing - Nice visual styling
I'll create a full-screen flocking simulation with boids drawn as oriented triangles that flee from the mouse cursor. This will be a complete self-contained HTML file.
/workspace/index.html (11466 bytes)Successfully wrote 11466 bytes to /workspace/index.html
thinking
The file has been created successfully. Let me verify it was written correctly by reading it.
The flocking simulation has been created. Let me verify the file:
{"path":"/workspace/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> * { margin: 0; padding: 0; overflow: hidden; } body { background: linear-gradient(135deg, #0c0e20, #1a1033); height: 100vh; width: 100vw; } canvas { display: block; } #info { position: fixed; top: 20px; left: 20px; color: rgba(255, 255, 255, 0.7); font-family: 'Courier New', monospace; font-size: 14px; pointer-events: none; text-shadow: 0 0 10px rgba(0, 0, 0, 0.5); } #stats { position: fixed; bottom: 20px; left: 20px; color: rgba(255, 255, 255, 0.5); font-family: 'Courier New', monospace; font-size: 12px; pointer-events: none; } .cursor-indicator { position: fixed; width: 40px; height: 40px; border: 2px solid rgba(255, 100, 100, 0.5); border-radius: 50%; transform: translate(-50%, -50%); pointer-events: none; z-index: 10; display: none; } </style> </head> <body> <canvas id="canvas"></canvas> <div id="info">Flocking Boids Simulation<br>Move mouse to see them flee!</div> <div id="stats">Boids: <span id="count">200</span></div> <div class="cursor-indicator" id="cursorIndicator"></div> <script> // Canvas setup const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const countEl = document.getElementById('count'); const cursorIndicator = document.getElementById('cursorIndicator'); function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Mouse position tracking const mouse = { x: -1000, y: -1000, radius: 100 }; document.addEventListener('mousemove', (e) => { mouse.x = e.clientX; mouse.y = e.clientY; cursorIndicator.style.display = 'block'; cursorIndicator.style.left = e.clientX + 'px'; cursorIndicator.style.top = e.clientY + 'px'; }); document.addEventListener('mouseleave', () => { mouse.x = -1000; mouse.y = -1000; cursorIndicator.style.display = 'none'; }); // Vector utility class class Vector { constructor(x = 0, y = 0) { this.x = x; this.y = y; } add(v) { this.x += v.x; this.y += v.y; return this; } sub(v) { this.x -= v.x; this.y -= v.y; return this; } mul(s) { this.x *= s; this.y *= s; return this; } div(s) { this.x /= s; this.y /= s; return this; } mag() { return Math.sqrt(this.x * this.x + this.y * this.y); } setMag(s) { const m = this.mag(); if (m > 0) { this.x = (this.x / m) * s; this.y = (this.y / m) * s; } return this; } normalize() { const m = this.mag(); if (m > 0) { this.x /= m; this.y /= m; } return this; } static sub(v1, v2) { return new Vector(v1.x - v2.x, v1.y - v2.y); } static add(v1, v2) { return new Vector(v1.x + v2.x, v1.y + v2.y); } static mul(v, s) { return new Vector(v.x * s, v.y * s); } static div(v, s) { return new Vector(v.x / s, v.y / s); } static dist(v1, v2) { return Vector.sub(v1, v2).mag(); } } // Boid class class Boid { constructor() { this.position = new Vector(Math.random() * canvas.width, Math.random() * canvas.height); const angle = Math.random() * Math.PI * 2; const speed = Math.random() * 2 + 1; this.velocity = new Vector(Math.cos(angle) * speed, Math.sin(angle) * speed); this.acceleration = new Vector(); this.maxSpeed = 3; this.maxForce = 0.05; this.size = 8; } update(boids) { // Calculate flocking behaviors const sep = this.separate(boids); const ali = this.align(boids); const coh = this.cohere(boids); const fle = this.flee(mouse); // Apply weights sep.mul(1.8); ali.mul(1.0); coh.mul(1.0); fle.mul(2.0); // Accumulate acceleration this.acceleration.add(sep).add(ali).add(coh).add(fle); // Update velocity and position this.velocity.add(this.acceleration); this.velocity.limit(this.maxSpeed); this.position.add(this.velocity); this.acceleration.mul(0); // Wrap around edges this.wrapEdges(); } wrapEdges() { if (this.position.x < 0) this.position.x = canvas.width; if (this.position.x > canvas.width) this.position.x = 0; if (this.position.y < 0) this.position.y = canvas.height; if (this.position.y > canvas.height) this.position.y = 0; } separate(boids) { const steer = new Vector(); let count = 0; const perception = 30; for (const other of boids) { const d = Vector.dist(this.position, other.position); if (other !== this && d > 0 && d < perception) { const diff = Vector.sub(this.position, other.position); diff.normalize(); diff.div(d); steer.add(diff); count++; } } if (count > 0) { steer.div(count); steer.normalize(); steer.mul(this.maxSpeed); steer.sub(this.velocity); steer.limit(this.maxForce); } return steer; } align(boids) { const sum = new Vector(); let count = 0; const perception = 50; const range = 100; for (const other of boids) { const d = Vector.dist(this.position, other.position); if (other !== this && d > 0 && d < perception) { sum.add(other.velocity); count++; } } if (count > 0) { sum.div(count); sum.setMag(this.maxSpeed); const steer = Vector.sub(sum, this.velocity); steer.limit(this.maxForce); return steer; } return new Vector(); } cohere(boids) { const sum = new Vector(); let count = 0; const perception = 50; const range = 100; for (const other of boids) { const d = Vector.dist(this.position, other.position); if (other !== this && d > 0 && d < perception) { sum.add(other.position); count++; } } if (count > 0) { sum.div(count); return this.seek(sum); } return new Vector(); } seek(target) { const desired = Vector.sub(target, this.position); const d = desired.mag(); if (d > 0) { desired.normalize(); desired.mul(this.maxSpeed); const steer = Vector.sub(desired, this.velocity); steer.limit(this.maxForce); return steer; } return new Vector(); } flee(target) { const d = Vector.dist(this.position, target); if (d < mouse.radius) { const desired = Vector.sub(this.position, target); desired.normalize(); desired.mul(this.maxSpeed); const steer = Vector.sub(desired, this.velocity); steer.limit(this.maxForce * 3); return steer; } return new Vector(); } draw() { const angle = Math.atan2(this.velocity.y, this.velocity.x); ctx.save(); ctx.translate(this.position.x, this.position.y); ctx.rotate(angle); // Create gradient for glow effect const speed = this.velocity.mag(); const hue = 200 + Math.min(speed * 50, 60); // Draw triangle boid ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(-this.size, -this.size/2); ctx.lineTo(-this.size, this.size/2); ctx.closePath(); // Triangle fill with gradient const gradient = ctx.createLinearGradient(-this.size, 0, 0, 0); gradient.addColorStop(0, `hsla(${hue}, 80%, 60%, 0.8)`); gradient.addColorStop(1, `hsla(${hue}, 100%, 70%, 1)`); ctx.fillStyle = gradient; ctx.strokeStyle = `hsla(${hue}, 100%, 80%, 0.9)`; ctx.lineWidth = 1.5; ctx.fill(); ctx.stroke(); // Add glow effect ctx.shadowBlur = 15; ctx.shadowColor = `hsla(${hue}, 100%, 70%, 0.6)`; ctx.fill(); ctx.restore(); } } // Vector limit method Vector.prototype.limit = function(max) { if (this.mag() > max) { this.setMag(max); } return this; }; // Initialize boids const boids = []; const numBoids = 200; for (let i = 0; i < numBoids; i++) { boids.push(new Boid()); } countEl.textContent = numBoids; // Animation loop function animate() { // Semi-transparent fill for trail effect ctx.fillStyle = 'rgba(12, 14, 32, 0.1)'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Update and draw boids for (const boid of boids) { boid.update(boids); boid.draw(); } requestAnimationFrame(animate); } animate(); </script> </body> </html>
thinking
The file looks correct. Let me verify it exists and check the file size.
{"command":"ls -la /workspace/index.html"}-rw-r--r-- 1 node node 11466 Jul 22 16:48 /workspace/index.html
thinking
The file is created successfully. Let me verify it's valid HTML by checking if it can be parsed.
The flocking simulation has been created at `/workspace/index.html`. It includes: - **200 boids** drawn as oriented triangles (pointing in their direction of travel) - **Three core flocking behaviors**: - **Separation** - boids avoid crowding neighbors (perception radius: 30px) - **Alignment** - boids steer toward average heading of neighbors (perception: 50px) - **Cohesion** - boids steer toward center of neighbors (perception: 50px) - **Mouse flee behavior** - boids flee from the cursor when within 100px radius - **Full-screen canvas** with dark gradient background - **Smooth trails** using semi-transparent fade - **Colorful glowing triangles** with speed-based hue variation - **Responsive** - resizes with window - **No external dependencies** - fully self-contained with inline CSS and JS
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
Boids flock smoothly as glowing purple triangles with organic clustering; mouse cursor circle shown but flee effect subtle.
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.