Boids flocking · deepseek-v3.2-exp
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 · 20 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
I'll create a single-page web app with a boids flocking simulation. I'll write it into index.html with CSS and JS inline. Let's first check the current directory.
{"command":"pwd && ls -la"}/tmp/ws total 0 drwxr-sr-x. 2 node node 6 Aug 1 19:44 . drwxrwsrwx. 3 root node 16 Aug 1 19:44 ..
thinking
Now I'll create index.html with the simulation. I'll design it to be full-screen, with boids as triangles, three behaviors (separation, alignment, cohesion) plus flee from mouse cursor. I'll use HTML5 canvas and requestAnimationFrame. Let's
thinking
Now I'll write the index.html file with the simulation. I'll design a full-screen canvas, implement boids with three behaviors (separation, alignment, cohesion), plus flee from mouse cursor. I'll use oriented triangles, smooth steering with velocity and acceleration, and wrap-around edges. I'll also add some UI controls for parameters and maybe a toggle for mouse interaction. Let's create the file.
index.html (21870 bytes)Successfully wrote 21870 bytes to index.html
thinking
Now let's verify the file exists and maybe open it in a browser to test. Since we can't open a browser directly, we can at least check the file size and maybe serve it with a simple HTTP server? But we can just ensure it's valid. Let's read a snippet to confirm.
{"path":"index.html","offset":1,"limit":10}<!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; [599 more lines in file. Use offset=11 to continue.]
thinking
Great. The file is created. Let's also check that the canvas container height calculation works. We'll adjust the JS to ensure canvas fills the remaining space after header and footer. We used offsetHeight which may be zero before rendering? It's fine. We'll also add a small safety to recalc on load. But the current script already does on resize. We'll also add a load event to set initial canvas size. Let's update the script to handle load. We'll edit the file. Let's find the part where we set width and height initially. We'll add a function to update canvas size and call it on load and resize. Let's read the JS portion. Let's search for "width = canvas.width". We'll edit with a function. Let's do an edit. First, I'll locate the line. Let's read more lines.
{"path":"index.html","offset":120,"limit":50}.controls { flex-wrap: wrap; gap: 1rem; } .control-group { min-width: 100px; } } </style> </head> <body> <div id="header"> <h1>Boids Flocking Simulation</h1> <div class="controls"> <div class="control-group"> <label for="separation">Separation</label> <input type="range" id="separation" min="0" max="2" step="0.05" value="1.5"> <div class="value" id="separationValue">1.50</div> </div> <div class="control-group"> <label for="alignment">Alignment</label> <input type="range" id="alignment" min="0" max="2" step="0.05" value="1.0"> <div class="value" id="alignmentValue">1.00</div> </div> <div class="control-group"> <label for="cohesion">Cohesion</label> <input type="range" id="cohesion" min="0" max="2" step="0.05" value="1.0"> <div class="value" id="cohesionValue">1.00</div> </div> <div class="control-group"> <label for="mouseFlee">Mouse Flee</label> <input type="range" id="mouseFlee" min="0" max="3" step="0.1" value="1.5"> <div class="value" id="mouseFleeValue">1.5</div> </div> <button id="toggleMouse" class="active">Mouse Interaction ON</button> <div class="stats"> <div class="stat">Boids: <span id="boidCount">150</span></div> <div class="stat">FPS: <span id="fps">0</span></div> </div> </div> </div> <div id="canvasContainer"> <canvas id="canvas"></canvas> </div> <div id="footer"> <p>Boids algorithm with separation, alignment, cohesion, and mouse avoidance. Move cursor near boids to see them flee. | <a href="https://en.wikipedia.org/wiki/Boids" target="_blank">Learn about boids</a></p> </div> <script> [440 more lines in file. Use offset=170 to continue.]
thinking
Let's find the JS section where canvas dimensions are set. Let's search from line 170 onward. Let's read a chunk.
{"path":"index.html","offset":170,"limit":100}// Canvas setup const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let width = canvas.width = window.innerWidth; let height = canvas.height = window.innerHeight - document.getElementById('header').offsetHeight - document.getElementById('footer').offsetHeight; // Parameters let separationWeight = 1.5; let alignmentWeight = 1.0; let cohesionWeight = 1.0; let mouseFleeWeight = 1.5; let mouseInteraction = true; // Boid settings const BOID_COUNT = 150; const MAX_SPEED = 4; const MAX_FORCE = 0.2; const PERCEPTION_RADIUS = 60; const SEPARATION_RADIUS = 30; const BOID_SIZE = 10; const EDGE_MARGIN = 50; const EDGE_TURN_FACTOR = 0.2; // Mouse position let mouseX = width / 2; let mouseY = height / 2; let mouseRadius = 100; // Boids array let boids = []; // FPS tracking let fps = 0; let lastTimestamp = 0; let frameCount = 0; // Boid class class Boid { constructor(x, y) { this.position = { x, y }; this.velocity = { x: (Math.random() - 0.5) * MAX_SPEED, y: (Math.random() - 0.5) * MAX_SPEED }; this.acceleration = { x: 0, y: 0 }; this.color = `hsl(${Math.random() * 60 + 180}, 80%, 60%)`; } // Apply force to acceleration applyForce(force) { this.acceleration.x += force.x; this.acceleration.y += force.y; } // Update position based on velocity and acceleration update() { // Add acceleration to velocity this.velocity.x += this.acceleration.x; this.velocity.y += this.acceleration.y; // Limit speed const speed = Math.sqrt(this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y); if (speed > MAX_SPEED) { this.velocity.x = (this.velocity.x / speed) * MAX_SPEED; this.velocity.y = (this.velocity.y / speed) * MAX_SPEED; } // Update position this.position.x += this.velocity.x; this.position.y += this.velocity.y; // Reset acceleration this.acceleration.x = 0; this.acceleration.y = 0; // Apply edge avoidance this.edges(); } // Wrap around edges or steer away edges() { const turn = EDGE_TURN_FACTOR; if (this.position.x < EDGE_MARGIN) { this.velocity.x += turn; } if (this.position.x > width - EDGE_MARGIN) { this.velocity.x -= turn; } if (this.position.y < EDGE_MARGIN) { this.velocity.y += turn; } if (this.position.y > height - EDGE_MARGIN) { this.velocity.y -= turn; } } // Calculate separation steering force separation(boids) { let steering = { x: 0, y: 0 }; let total = 0; [340 more lines in file. Use offset=270 to continue.]
thinking
Now we need to adjust the canvas size initialization to ensure it's correct after DOM load. We'll add a function `updateCanvasSize` and call it on load and resize. Also we need to recalc mouse position on resize. Let's edit the file. We'll replace the lines from "// Canvas setup" to the line after height assignment. We'll also add a function later and call it. Let's do an edit with multiple changes. First, find the exact old text: "// Canvas setup\n const canvas = document.getElementById('canvas');\n const ctx = canvas.getContext('2d');\n let width = canvas.width = window.innerWidth;\n let height = canvas.height = window.innerHeight - document.getElementById('header').offsetHeight - document.getElementById('footer').offsetHeight;"
We'll replace with a function and call it. Let's also add a variable for ctx and canvas as before. We'll keep the same variable declarations but move assignment inside function. Let's write new text. We'll also add a load event listener. We'll do this in one edit. Let's also adjust the resize event to call the same function. We'll need to modify the resize event later. We'll locate the resize event listener later and adjust. Let's first replace the block. Let's read a bit more to ensure we have the exact text. Let's get lines 170-174 exactly. Let's read with cat? Use bash to get exact lines. Let's do:{"command":"sed -n '170,174p' index.html"}// Canvas setup const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let width = canvas.width = window.innerWidth; let height = canvas.height = window.innerHeight - document.getElementById('header').offsetHeight - document.getElementById('footer').offsetHeight;
thinking
Great. Now we need to replace from line 170 to line 174 inclusive. Let's also include the next line (line 175 is empty? Let's check line 175). Let's read 170-180.
{"command":"sed -n '170,180p' index.html"}// Canvas setup const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let width = canvas.width = window.innerWidth; let height = canvas.height = window.innerHeight - document.getElementById('header').offsetHeight - document.getElementById('footer').offsetHeight; // Parameters let separationWeight = 1.5; let alignmentWeight = 1.0; let cohesionWeight = 1.0; let mouseFleeWeight = 1.5;
thinking
We'll replace from "// Canvas setup" to the line before "// Parameters". That's lines 170-174. We'll replace with a new block. Let's write the new block: we'll keep canvas and ctx as const, but width and height as let. We'll define a function updateCanvasSize that updates width, height, canvas dimensions, and also mouseX/Y if needed. We'll call it immediately. Let's do:
```
// Canvas setup
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let width, height;
function updateCanvasSize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight - document.getElementById('header').offsetHeight - document.getElementById('footer').offsetHeight;
// Adjust mouse position to stay within bounds
mouseX = Math.min(Math.max(mouseX, 0), width);
mouseY = Math.min(Math.max(mouseY, 0), height);
}
updateCanvasSize();
```
Now we need to also adjust the resize event listener later. Let's find the resize event listener. Let's search for "window.addEventListener('resize'". We'll read from line maybe 400 onward. Let's grep.{"command":"grep -n \"window.addEventListener\" index.html"}564: window.addEventListener('resize', () => {
thinking
Let's look at lines around 564. Let's read from 560 to 580.
{"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> * { margin: 0; padding: 0; box-sizing: border-box; overflow: hidden; } body { background: #000; color: #fff; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; height: 100vh; display: flex; flex-direction: column; } #header { padding: 1rem; background: rgba(20, 20, 30, 0.8); border-bottom: 1px solid #333; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } h1 { font-size: 1.8rem; font-weight: 300; } .controls { display: flex; gap: 1.5rem; align-items: center; } .control-group { display: flex; flex-direction: column; gap: 0.3rem; } label { font-size: 0.8rem; color: #aaa; } input[type="range"] { width: 120px; height: 6px; background: #333; border-radius: 3px; outline: none; -webkit-appearance: none; } input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: #4af; cursor: pointer; } .value { font-size: 0.9rem; color: #4af; min-width: 40px; } #canvasContainer { flex: 1; position: relative; } canvas { display: block; width: 100%; height: 100%; } #footer { padding: 0.8rem; background: rgba(20, 20, 30, 0.8); border-top: 1px solid #333; font-size: 0.8rem; color: #888; text-align: center; flex-shrink: 0; } #footer a { color: #4af; text-decoration: none; } #toggleMouse { background: #333; color: #fff; border: 1px solid #555; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; font-size: 0.9rem; transition: background 0.2s; } #toggleMouse:hover { background: #444; } #toggleMouse.active { background: #4af; color: #000; } .stats { display: flex; gap: 1rem; font-size: 0.9rem; color: #aaa; } .stat span { color: #4af; font-weight: bold; } @media (max-width: 900px) { .controls { flex-wrap: wrap; gap: 1rem; } .control-group { min-width: 100px; } } </style> </head> <body> <div id="header"> <h1>Boids Flocking Simulation</h1> <div class="controls"> <div class="control-group"> <label for="separation">Separation</label> <input type="range" id="separation" min="0" max="2" step="0.05" value="1.5"> <div class="value" id="separationValue">1.50</div> </div> <div class="control-group"> <label for="alignment">Alignment</label> <input type="range" id="alignment" min="0" max="2" step="0.05" value="1.0"> <div class="value" id="alignmentValue">1.00</div> </div> <div class="control-group"> <label for="cohesion">Cohesion</label> <input type="range" id="cohesion" min="0" max="2" step="0.05" value="1.0"> <div class="value" id="cohesionValue">1.00</div> </div> <div class="control-group"> <label for="mouseFlee">Mouse Flee</label> <input type="range" id="mouseFlee" min="0" max="3" step="0.1" value="1.5"> <div class="value" id="mouseFleeValue">1.5</div> </div> <button id="toggleMouse" class="active">Mouse Interaction ON</button> <div class="stats"> <div class="stat">Boids: <span id="boidCount">150</span></div> <div class="stat">FPS: <span id="fps">0</span></div> </div> </div> </div> <div id="canvasContainer"> <canvas id="canvas"></canvas> </div> <div id="footer"> <p>Boids algorithm with separation, alignment, cohesion, and mouse avoidance. Move cursor near boids to see them flee. | <a href="https://en.wikipedia.org/wiki/Boids" target="_blank">Learn about boids</a></p> </div> <script> // Canvas setup const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let width = canvas.width = window.innerWidth; let height = canvas.height = window.innerHeight - document.getElementById('header').offsetHeight - document.getElementById('footer').offsetHeight; // Parameters let separationWeight = 1.5; let alignmentWeight = 1.0; let cohesionWeight = 1.0; let mouseFleeWeight = 1.5; let mouseInteraction = true; // Boid settings const BOID_COUNT = 150; const MAX_SPEED = 4; const MAX_FORCE = 0.2; const PERCEPTION_RADIUS = 60; const SEPARATION_RADIUS = 30; const BOID_SIZE = 10; const EDGE_MARGIN = 50; const EDGE_TURN_FACTOR = 0.2; // Mouse position let mouseX = width / 2; let mouseY = height / 2; let mouseRadius = 100; // Boids array let boids = []; // FPS tracking let fps = 0; let lastTimestamp = 0; let frameCount = 0; // Boid class class Boid { constructor(x, y) { this.position = { x, y }; this.velocity = { x: (Math.random() - 0.5) * MAX_SPEED, y: (Math.random() - 0.5) * MAX_SPEED }; this.acceleration = { x: 0, y: 0 }; this.color = `hsl(${Math.random() * 60 + 180}, 80%, 60%)`; } // Apply force to acceleration applyForce(force) { this.acceleration.x += force.x; this.acceleration.y += force.y; } // Update position based on velocity and acceleration update() { // Add acceleration to velocity this.velocity.x += this.acceleration.x; this.velocity.y += this.acceleration.y; // Limit speed const speed = Math.sqrt(this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y); if (speed > MAX_SPEED) { this.velocity.x = (this.velocity.x / speed) * MAX_SPEED; this.velocity.y = (this.velocity.y / speed) * MAX_SPEED; } // Update position this.position.x += this.velocity.x; this.position.y += this.velocity.y; // Reset acceleration this.acceleration.x = 0; this.acceleration.y = 0; // Apply edge avoidance this.edges(); } // Wrap around edges or steer away edges() { const turn = EDGE_TURN_FACTOR; if (this.position.x < EDGE_MARGIN) { this.velocity.x += turn; } if (this.position.x > width - EDGE_MARGIN) { this.velocity.x -= turn; } if (this.position.y < EDGE_MARGIN) { this.velocity.y += turn; } if (this.position.y > height - EDGE_MARGIN) { this.velocity.y -= turn; } } // Calculate separation steering force separation(boids) { let steering = { x: 0, y: 0 }; let total = 0; for (let other of boids) { const dx = this.position.x - other.position.x; const dy = this.position.y - other.position.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0 && distance < SEPARATION_RADIUS) { // Weight by distance (closer = stronger) const diffX = dx / distance; const diffY = dy / distance; steering.x += diffX / distance; steering.y += diffY / distance; total++; } } if (total > 0) { steering.x /= total; steering.y /= total; // Normalize and scale to max speed const mag = Math.sqrt(steering.x * steering.x + steering.y * steering.y); if (mag > 0) { steering.x = (steering.x / mag) * MAX_SPEED; steering.y = (steering.y / mag) * MAX_SPEED; } // Steering = desired - current velocity steering.x -= this.velocity.x; steering.y -= this.velocity.y; // Limit force const forceMag = Math.sqrt(steering.x * steering.x + steering.y * steering.y); if (forceMag > MAX_FORCE) { steering.x = (steering.x / forceMag) * MAX_FORCE; steering.y = (steering.y / forceMag) * MAX_FORCE; } } return steering; } // Calculate alignment steering force alignment(boids) { let steering = { x: 0, y: 0 }; let total = 0; for (let other of boids) { const dx = this.position.x - other.position.x; const dy = this.position.y - other.position.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0 && distance < PERCEPTION_RADIUS) { steering.x += other.velocity.x; steering.y += other.velocity.y; total++; } } if (total > 0) { steering.x /= total; steering.y /= total; // Normalize and scale to max speed const mag = Math.sqrt(steering.x * steering.x + steering.y * steering.y); if (mag > 0) { steering.x = (steering.x / mag) * MAX_SPEED; steering.y = (steering.y / mag) * MAX_SPEED; } // Steering = desired - current velocity steering.x -= this.velocity.x; steering.y -= this.velocity.y; // Limit force const forceMag = Math.sqrt(steering.x * steering.x + steering.y * steering.y); if (forceMag > MAX_FORCE) { steering.x = (steering.x / forceMag) * MAX_FORCE; steering.y = (steering.y / forceMag) * MAX_FORCE; } } return steering; } // Calculate cohesion steering force cohesion(boids) { let steering = { x: 0, y: 0 }; let total = 0; for (let other of boids) { const dx = this.position.x - other.position.x; const dy = this.position.y - other.position.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0 && distance < PERCEPTION_RADIUS) { steering.x += other.position.x; steering.y += other.position.y; total++; } } if (total > 0) { steering.x /= total; steering.y /= total; // Desired vector towards average position steering.x -= this.position.x; steering.y -= this.position.y; // Normalize and scale to max speed const mag = Math.sqrt(steering.x * steering.x + steering.y * steering.y); if (mag > 0) { steering.x = (steering.x / mag) * MAX_SPEED; steering.y = (steering.y / mag) * MAX_SPEED; } // Steering = desired - current velocity steering.x -= this.velocity.x; steering.y -= this.velocity.y; // Limit force const forceMag = Math.sqrt(steering.x * steering.x + steering.y * steering.y); if (forceMag > MAX_FORCE) { steering.x = (steering.x / forceMag) * MAX_FORCE; steering.y = (steering.y / forceMag) * MAX_FORCE; } } return steering; } // Flee from mouse cursor fleeFromMouse() { const dx = this.position.x - mouseX; const dy = this.position.y - mouseY; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < mouseRadius) { // Desired velocity away from mouse let steering = { x: 0, y: 0 }; if (distance > 0) { steering.x = dx / distance; steering.y = dy / distance; } else { steering.x = Math.random() - 0.5; steering.y = Math.random() - 0.5; } // Scale to max speed steering.x *= MAX_SPEED * 1.5; steering.y *= MAX_SPEED * 1.5; // Steering = desired - current velocity steering.x -= this.velocity.x; steering.y -= this.velocity.y; // Limit force const forceMag = Math.sqrt(steering.x * steering.x + steering.y * steering.y); if (forceMag > MAX_FORCE * 2) { steering.x = (steering.x / forceMag) * MAX_FORCE * 2; steering.y = (steering.y / forceMag) * MAX_FORCE * 2; } return steering; } return { x: 0, y: 0 }; } // Flock behavior flock(boids) { const sep = this.separation(boids); const ali = this.alignment(boids); const coh = this.cohesion(boids); let flee = { x: 0, y: 0 }; if (mouseInteraction) { flee = this.fleeFromMouse(); } // Apply weights sep.x *= separationWeight; sep.y *= separationWeight; ali.x *= alignmentWeight; ali.y *= alignmentWeight; coh.x *= cohesionWeight; coh.y *= cohesionWeight; flee.x *= mouseFleeWeight; flee.y *= mouseFleeWeight; this.applyForce(sep); this.applyForce(ali); this.applyForce(coh); this.applyForce(flee); } // Draw boid as oriented triangle draw() { ctx.save(); ctx.translate(this.position.x, this.position.y); // Rotate to face direction of velocity const angle = Math.atan2(this.velocity.y, this.velocity.x); ctx.rotate(angle); // Draw triangle ctx.fillStyle = this.color; ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(BOID_SIZE, 0); ctx.lineTo(-BOID_SIZE, BOID_SIZE / 2); ctx.lineTo(-BOID_SIZE, -BOID_SIZE / 2); ctx.closePath(); ctx.fill(); ctx.stroke(); // Optional: draw perception radius (debug) // ctx.strokeStyle = 'rgba(255,255,255,0.05)'; // ctx.beginPath(); // ctx.arc(0, 0, PERCEPTION_RADIUS, 0, Math.PI * 2); // ctx.stroke(); ctx.restore(); } } // Initialize boids function initBoids() { boids = []; for (let i = 0; i < BOID_COUNT; i++) { boids.push(new Boid( Math.random() * width, Math.random() * height )); } } // Animation loop function animate(timestamp) { // Calculate FPS frameCount++; if (timestamp - lastTimestamp >= 1000) { fps = Math.round((frameCount * 1000) / (timestamp - lastTimestamp)); document.getElementById('fps').textContent = fps; frameCount = 0; lastTimestamp = timestamp; } // Clear canvas with a subtle fade effect ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; ctx.fillRect(0, 0, width, height); // Update and draw each boid for (let boid of boids) { boid.flock(boids); boid.update(); boid.draw(); } // Draw mouse radius (if interaction on) if (mouseInteraction) { ctx.strokeStyle = 'rgba(255, 100, 100, 0.3)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(mouseX, mouseY, mouseRadius, 0, Math.PI * 2); ctx.stroke(); // Draw cursor ctx.fillStyle = 'rgba(255, 100, 100, 0.7)'; ctx.beginPath(); ctx.arc(mouseX, mouseY, 5, 0, Math.PI * 2); ctx.fill(); } requestAnimationFrame(animate); } // Event listeners function setupEventListeners() { // Mouse move canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); mouseX = e.clientX - rect.left; mouseY = e.clientY - rect.top; }); // Touch move canvas.addEventListener('touchmove', (e) => { e.preventDefault(); const rect = canvas.getBoundingClientRect(); mouseX = e.touches[0].clientX - rect.left; mouseY = e.touches[0].clientY - rect.top; }, { passive: false }); // Window resize window.addEventListener('resize', () => { width = canvas.width = window.innerWidth; height = canvas.height = window.innerHeight - document.getElementById('header').offsetHeight - document.getElementById('footer').offsetHeight; // Reset boids to keep them on screen initBoids(); }); // Sliders document.getElementById('separation').addEventListener('input', (e) => { separationWeight = parseFloat(e.target.value); document.getElementById('separationValue').textContent = separationWeight.toFixed(2); }); document.getElementById('alignment').addEventListener('input', (e) => { alignmentWeight = parseFloat(e.target.value); document.getElementById('alignmentValue').textContent = alignmentWeight.toFixed(2); }); document.getElementById('cohesion').addEventListener('input', (e) => { cohesionWeight = parseFloat(e.target.value); document.getElementById('cohesionValue').textContent = cohesionWeight.toFixed(2); }); document.getElementById('mouseFlee').addEventListener('input', (e) => { mouseFleeWeight = parseFloat(e.target.value); document.getElementById('mouseFleeValue').textContent = mouseFleeWeight.toFixed(1); }); // Toggle mouse interaction document.getElementById('toggleMouse').addEventListener('click', () => { mouseInteraction = !mouseInteraction; const btn = document.getElementById('toggleMouse'); if (mouseInteraction) { btn.textContent = 'Mouse Interaction ON'; btn.classList.add('active'); } else { btn.textContent = 'Mouse Interaction OFF'; btn.classList.remove('active'); } }); } // Initialize and start initBoids(); setupEventListeners(); animate(0); </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
Boids flock and flee mouse cursor smoothly with sliders and stats, matching prompt well; keyboard has little effect but that's expected.
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.