oneshotlm
← Back to comparison

Boids flocking · kimi-k2.7-code

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.

⛨ sandboxed
transcript · 4 messages
user

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.

assistant
thinking
The user wants a full-screen flocking simulation (boids) with separation, alignment, and cohesion. The boids should be drawn as oriented triangles, steer smoothly, and flee from the mouse cursor. Everything must be in a single `index.html` file with inline CSS/JS, no external resources.

We need to produce the file. Let's design:

- HTML full screen canvas, no scrollbars.
- CSS: margin 0, padding 0, overflow hidden, background color.
- JS: Boid class with position, velocity, acceleration. Update using rules:
  - Separation: steer away from nearby boids.
  - Alignment: steer towards average velocity of neighbors.
  - Cohesion: steer towards average position of neighbors.
  - Flee mouse: steer away from cursor.
  - Boundaries: wrap around or turn away. Full-screen wrap can be nice. But "flee from mouse cursor". Could also turn away from screen edges? Wrapping avoids edge clumping. User asked full-screen, boids fleeing mouse. Use wrapping for seamless.
- Drawing: triangle oriented to velocity. Use canvas transforms. Draw as filled triangle.
- Parameters: sliders? User didn't request UI, but can include hidden or simple controls? Since single-file self-contained, maybe minimal controls at top? But "no external resources" doesn't preclude simple UI. Better to keep clean full-screen simulation. We can add simple clickable info. But not necessary. Maybe include small instruction text overlay? However "full-screen" suggests minimal. Could still include a small title overlay. But user didn't ask. I think just pure canvas. Could add parameters at top of JS for tuning.

- Interactions: mouse move tracked globally; fleeing triggered when mouse active. Use pointer position. On touch too.

- Performance: number of boids e.g., 150-200. Use squared distances. Neighbor radius ~50. Separation radius ~25. Use max force, max speed.

- Smooth steering: use acceleration and limit; accumulate forces with weights.

- Implementation details:
  - `Boid` class methods: `flock(boids)`, `update()`, `edges()`, `show(ctx)`, `applyForce(force)`.
  - `Flock` array.
  - Rules functions: `separate`, `align`, `cohesion`, `flee`.
  - For each boid, loop all boids O(N^2). For 200 boids fine.

- Flee: compute vector boid->mouse, distance; if within flee radius, scale force inverse proportional maybe. Add weight.

- Smooth triangle: draw isosceles triangle pointing along velocity: maybe `translate(x,y); rotate(angle); beginPath; moveTo(10,0); lineTo(-6,5); lineTo(-6,-5); closePath; fill`.

- Colors: maybe white triangles with slight blue hue, on dark background. Add subtle trails? Could leave no trails.

- Responsive: resize canvas to window innerWidth/innerHeight; on resize reset canvas size. If wrapping, no issue.

- Code style: plain JS, ES6 classes. Use requestAnimationFrame loop.

- Provide final concise summary and file path.

Potential improvements: use devicePixelRatio for crisp rendering on high DPI. Set canvas width/height to CSS size * DPR, scale ctx. This is good. Use `window.devicePixelRatio || 1`. Mouse coordinates must be scaled accordingly.

Let's write the index.html. Need to ensure no external resources, all inline.

Let's code.

Plan:
```
<!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; }
  html, body { width:100%; height:100%; overflow:hidden; background:#0b1020; }
  canvas { display:block; }
  .hint { position:absolute; top:10px; left:10px; color:rgba(255,255,255,0.4); font-family:sans-serif; font-size:12px; pointer-events:none; user-select:none; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div class="hint">Move your cursor to scare the flock.</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let width, height, dpr;
let mouse = { x: -1000, y: -1000, active: false };
let boids = [];

const CONFIG = {
  count: 160,
  neighborDist: 60,
  separationDist: 24,
  fleeDist: 180,
  maxSpeed: 4,
  maxForce: 0.08,
  fleeWeight: 2.5,
  sepWeight: 1.8,
  aliWeight: 1.0,
  cohWeight: 1.0,
  color: '#7ec8e3'
};

function resize() {
  dpr = window.devicePixelRatio || 1;
  width = window.innerWidth;
  height = window.innerHeight;
  canvas.style.width = width + 'px';
  canvas.style.height = height + 'px';
  canvas.width = Math.floor(width * dpr);
  canvas.height = Math.floor(height * dpr);
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}

class Boid {
  constructor() {
    this.position = { x: Math.random()*width, y: Math.random()*height };
    const angle = Math.random()*Math.PI*2;
    this.velocity = { x: Math.cos(angle)*CONFIG.maxSpeed, y: Math.sin(angle)*CONFIG.maxSpeed };
    this.acceleration = { x:0, y:0 };
  }
  edges() {
    if (this.position.x < -10) this.position.x = width + 10;
    else if (this.position.x > width + 10) this.position.x = -10;
    if (this.position.y < -10) this.position.y = height + 10;
    else if (this.position.y > height + 10) this.position.y = -10;
  }
  applyForce(force) {
    this.acceleration.x += force.x;
    this.acceleration.y += force.y;
  }
  flock(all) {
    let sep = {x:0,y:0}, ali = {x:0,y:0}, coh = {x:0,y:0};
    let sepCount=0, aliCount=0, cohCount=0;
    for (let other of all) {
      if (other === this) continue;
      let dx = other.position.x - this.position.x;
      let dy = other.position.y - this.position.y;
      let d2 = dx*dx + dy*dy;
      if (d2 > 0 && d2 < CONFIG.separationDist*CONFIG.separationDist) {
        let d = Math.sqrt(d2);
        sep.x -= dx/d;
        sep.y -= dy/d;
        sepCount++;
      }
      if (d2 > 0 && d2 < CONFIG.neighborDist*CONFIG.neighborDist) {
        ali.x += other.velocity.x;
        ali.y += other.velocity.y;
        aliCount++;
        coh.x += other.position.x;
        coh.y += other.position.y;
        cohCount++;
      }
    }
    if (sepCount>0) {
      sep.x /= sepCount; sep.y /= sepCount;
      this.steerTo(sep, CONFIG.sepWeight, sep);
    }
    if (aliCount>0) {
      ali.x /= aliCount; ali.y /= aliCount;
      this.steerTo(ali, CONFIG.aliWeight, ali);
    }
    if (cohCount>0) {
      coh.x /= cohCount; coh.y /= cohCount;
      let desired = {x: coh.x - this.position.x, y: coh.y - this.position.y};
      this.steerTo(desired, CONFIG.cohWeight, coh);
    }
    // flee mouse
    let mdx = this.position.x - mouse.x;
    let mdy = this.position.y - mouse.y;
    let md2 = mdx*mdx + mdy*mdy;
    if (mouse.active && md2 > 0 && md2 < CONFIG.fleeDist*CONFIG.fleeDist) {
      let md = Math.sqrt(md2);
      let flee = {x: mdx/md, y: mdy/md};
      let strength = (1 - md/CONFIG.fleeDist) * CONFIG.fleeWeight;
      this.steerTo(flee, strength, flee);
    }
  }
  steerTo(vector, weight, target) {
    // Normalize and scale to maxSpeed
    let mag = Math.hypot(vector.x, vector.y);
    if (mag === 0) return;
    let desired = { x: vector.x/mag*CONFIG.maxSpeed, y: vector.y/mag*CONFIG.maxSpeed };
    let steer = { x: desired.x - this.velocity.x, y: desired.y - this.velocity.y };
    let smag = Math.hypot(steer.x, steer.y);
    if (smag > CONFIG.maxForce) {
      steer.x = steer.x/smag*CONFIG.maxForce;
      steer.y = steer.y/smag*CONFIG.maxForce;
    }
    steer.x *= weight; steer.y *= weight;
    this.applyForce(steer);
  }
  update() {
    this.velocity.x += this.acceleration.x;
    this.velocity.y += this.acceleration.y;
    let speed = Math.hypot(this.velocity.x, this.velocity.y);
    if (speed > CONFIG.maxSpeed) {
      this.velocity.x = this.velocity.x/speed*CONFIG.maxSpeed;
      this.velocity.y = this.velocity.y/speed*CONFIG.maxSpeed;
    }
    this.position.x += this.velocity.x;
    this.position.y += this.velocity.y;
    this.acceleration.x = 0;
    this.acceleration.y = 0;
    this.edges();
  }
  draw(ctx) {
    let angle = Math.atan2(this.velocity.y, this.velocity.x);
    ctx.save();
    ctx.translate(this.position.x, this.position.y);
    ctx.rotate(angle);
    ctx.fillStyle = CONFIG.color;
    ctx.beginPath();
    ctx.moveTo(10, 0);
    ctx.lineTo(-6, 5);
    ctx.lineTo(-3, 0);
    ctx.lineTo(-6, -5);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }
}

function init() {
  resize();
  boids = [];
  for (let i=0; i<CONFIG.count; i++) boids.push(new Boid());
}

function animate() {
  ctx.clearRect(0,0,width,height);
  // maybe background fill not needed because body background
  for (let b of boids) {
    b.flock(boids);
    b.update();
    b.draw(ctx);
  }
  requestAnimationFrame(animate);
}

window.addEventListener('resize', () => { resize(); });
window.addEventListener('mousemove', e => { mouse.x = e.clientX; mouse.y = e.clientY; mouse.active = true; });
window.addEventListener('mouseleave', () => { mouse.active = false; });
window.addEventListener('touchmove', e => { mouse.x = e.touches[0].clientX; mouse.y = e.touches[0].clientY; mouse.active = true; }, {passive:true});
window.addEventListener('touchend', () => { mouse.active = false; });

init();
animate();
</script>
</body>
</html>
```

Need to double-check the `steerTo` method: I pass vector and target, but target not used. Actually can simplify. The separation vector `sep` is average of normalized away vectors. Then steer to that direction. I wrote `this.steerTo(sep, CONFIG.sepWeight, sep);` The method signature takes `vector` and uses it; target unused. OK but can clean.

For alignment: `ali` is average velocity; we want steer towards average heading. So vector=ali. Good.

For cohesion: desired vector = center - position. Good.

For flee: direction away normalized. Good.

Potential issue: `steerTo` normalizes vector and sets desired max speed, then subtract velocity and limit. But if we pass `sep` which is average unit away direction, desired = that unit * maxSpeed. Good.

However for alignment, average velocity might be small magnitude; still steerTo normalizes, so aligns heading but ignores magnitude. That's standard.

One subtle issue: `this.steerTo` uses `target` parameter unnecessary. Could remove. Also `flock` uses `target` argument identical. Could simplify to `this.applySteer(sep, CONFIG.sepWeight)`. But code works.

Performance: O(N^2) with count 160 fine.

Mouse flee: It calls steerTo with `flee` unit vector and strength weight. Since `steerTo` multiplies weight after limiting maxForce, strength could exceed 1 causing large force. FleeWeight 2.5; good.

Smoothness: acceleration limited per rule. But because multiple forces added, total acceleration can exceed maxForce; acceptable. Could scale combined force, but not necessary. The maxSpeed cap ensures smooth movement.

Maybe make triangle shape nicer: We used line to (-3,0) creating a small notch? Actually path: moveTo(10,0); lineTo(-6,5); lineTo(-3,0); lineTo(-6,-5); closePath. That creates a chevron shape with a notch. Better simple triangle: moveTo(9,0); lineTo(-5,4); lineTo(-5,-4); closePath. The notch isn't bad but might look like arrow. Oriented triangle could be just triangle. Let's use simple triangle.

Color: perhaps slight transparency? `rgba(126,200,227,0.9)`.

Maybe add motion trails? Not needed. Maybe background fill each frame with low alpha to create trails. User wants drawn as oriented triangles steering smoothly. Trails optional. Use `ctx.fillStyle='rgba(11,16,32,0.35)'; ctx.fillRect(...)` each frame to produce trails. This looks nice and smooth. But clearRect + no trails is also fine. Trails can enhance smooth visual. Let's add subtle trails: `ctx.fillStyle = 'rgba(11,16,32,0.2)'; ctx.fillRect(0,0,width,height);` Body background same color. This leaves trails. However if user moves mouse, trails might look like smearing. Flocking sims often use trails. I think it's okay but might obscure. Maybe not include to keep crisp. I'll keep clearRect for crisp triangles.

Responsive: On resize, if width changes, boids outside will wrap next update. Fine.

Let's also handle `pointermove` to capture mouse and touch; but use separate listeners. Fine.

Let's improve code quality: define `applySteer` without unused target. Use `let` but maybe use const where appropriate. No need.

Potential issue with `steerTo` modifying passed object: It doesn't modify; it uses to create desired and steer. Fine.

Let's finalize file. But before writing, we can consider adding sliders to adjust weights? Not requested. Avoid UI to keep full-screen.

Let's also ensure `maxForce` and `maxSpeed` units in CSS pixels. Good.

One more consideration: `ctx.setTransform(dpr,...)` after resize; on initial resize. Good. Since we clear canvas using width/height in CSS pixels, with setTransform, clearRect uses CSS coordinates, clears full canvas. Good.

Let's make triangle dimensions scale with dpr? Since ctx scaled by dpr, drawing in CSS coordinates; triangle size in CSS px. Fine.

Now write file.

Let's create /workspace/index.html. Use write tool.

Also final response: mention built index.html with separation/alignment/cohesion and mouse flee.

Proceed.
write /workspace/index.html (7190 bytes)
toolResult

Successfully wrote 7190 bytes to /workspace/index.html

assistant
thinking
The file is written. Provide concise summary. Ensure final answer mentions self-contained index.html, no external resources.

Created `/workspace/index.html` — a self-contained, full-screen boids simulation with: - **Separation**, **alignment**, and **cohesion** rules with tunable weights - Smooth steering via per-boid acceleration and velocity limiting - Boids rendered as oriented triangles pointing in their direction of travel - Mouse-cursor repulsion/flee behavior (also works with touch) - Responsive, high-DPI canvas with no scrollbars - No external resources, CDNs, or network requests — everything is inline Open `index.html` in a browser and move your cursor to scatter the flock.

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

modelkimi-k2.7-code
vendorMoonshot · Kimi
harnesspi-agent
generated2026-07-07

METRICS

latency149.5s
tokens in6.0k
tokens out5.8k
cached2.0k
cost / run$0.0250