Skip to content

Boids Flocking Simulation

Implement Craig Reynolds' Boids algorithm — autonomous agents that exhibit flocking behavior through three simple rules: separation, alignment, and cohesion.

Paste the complete example into main.js in the editor, then click Restart.

The Three Rules

RuleWhat It DoesVisual Effect
SeparationSteer away from nearby boidsPrevents crowding
AlignmentMatch velocity of nearby boidsCreates coordinated movement
CohesionMove toward center of nearby boidsKeeps the flock together

Complete Implementation

javascript
// 1. State Setup
if (!state.init) {
  state.boids = [];
  state.boidCount = 60;
  state.perceptionRadius = 60;
  state.maxSpeed = 120;
  state.maxForce = 40;

  for (let i = 0; i < state.boidCount; i++) {
    state.boids.push({
      x: Math.random() * w,
      y: Math.random() * h,
      vx: (Math.random() - 0.5) * state.maxSpeed,
      vy: (Math.random() - 0.5) * state.maxSpeed,
    });
  }
  state.init = true;
}

// 2. Physics Step — Boids rules
const perception = state.perceptionRadius;
const maxSpeed = state.maxSpeed;
const maxForce = state.maxForce;

for (const boid of state.boids) {
  let sepX = 0, sepY = 0, sepCount = 0;
  let aliX = 0, aliY = 0, aliCount = 0;
  let cohX = 0, cohY = 0, cohCount = 0;

  for (const other of state.boids) {
    if (other === boid) continue;
    const dx = other.x - boid.x;
    const dy = other.y - boid.y;
    const dist = Math.sqrt(dx * dx + dy * dy);

    if (dist < perception) {
      // Separation: steer away from close neighbors
      if (dist < perception * 0.5 && dist > 0) {
        sepX -= dx / dist;
        sepY -= dy / dist;
        sepCount++;
      }
      // Alignment: match average velocity
      aliX += other.vx;
      aliY += other.vy;
      aliCount++;
      // Cohesion: steer toward average position
      cohX += other.x;
      cohY += other.y;
      cohCount++;
    }
  }

  // Compute steering forces
  let fx = 0, fy = 0;

  if (sepCount > 0) {
    fx += (sepX / sepCount) * 1.5;
    fy += (sepY / sepCount) * 1.5;
  }
  if (aliCount > 0) {
    const avgVx = aliX / aliCount;
    const avgVy = aliY / aliCount;
    fx += (avgVx - boid.vx) * 0.05;
    fy += (avgVy - boid.vy) * 0.05;
  }
  if (cohCount > 0) {
    const avgX = cohX / cohCount;
    const avgY = cohY / cohCount;
    fx += (avgX - boid.x) * 0.005;
    fy += (avgY - boid.y) * 0.005;
  }

  // Apply steering force (clamped)
  const fMag = Math.sqrt(fx * fx + fy * fy);
  if (fMag > maxForce) {
    fx = (fx / fMag) * maxForce;
    fy = (fy / fMag) * maxForce;
  }

  boid.vx += fx * dt;
  boid.vy += fy * dt;

  // Clamp speed
  const speed = Math.sqrt(boid.vx * boid.vx + boid.vy * boid.vy);
  if (speed > maxSpeed) {
    boid.vx = (boid.vx / speed) * maxSpeed;
    boid.vy = (boid.vy / speed) * maxSpeed;
  }

  // Update position
  boid.x += boid.vx * dt;
  boid.y += boid.vy * dt;

  // Wrap around edges
  if (boid.x < 0) boid.x = w;
  if (boid.x > w) boid.x = 0;
  if (boid.y < 0) boid.y = h;
  if (boid.y > h) boid.y = 0;
}

// 3. Render Return
return {
  particles: state.boids.map((b) => ({
    x: b.x,
    y: b.y,
    r: 4,
    color: '#8B5CF6',
  })),
  vectors: state.boids.filter((_, i) => i % 4 === 0).map((b) => ({
    x: b.vx * 0.15,
    y: b.vy * 0.15,
    ox: b.x,
    oy: b.y,
    color: '#22C55E',
  })),
  text: {
    'Boids': state.boids.length,
    'Perception': state.perceptionRadius + ' px',
  },
};

📐 MATH CHECK Each rule produces a steering vector. The final force is the weighted sum:

  • Separation: steer = -sum(r̂_ij) / N (normalized away from neighbors)
  • Alignment: steer = v_avg - v_self (match group velocity)
  • Cohesion: steer = p_avg - p_self (move toward group center)

Weights (1.5, 0.05, 0.005) are tuned for visual appeal. Increase separation weight to spread the flock; increase cohesion weight to cluster tightly.

Parameter Tuning

ParameterEffectDefaultTry
boidCountNumber of agents6030, 100, 200
perceptionRadiusHow far each boid "sees"6040, 100, 150
maxSpeedMaximum velocity12060, 200
maxForceMaximum steering acceleration4020, 80
Separation weightAvoid crowding1.50.5, 3.0
Alignment weightMatch direction0.050.01, 0.1
Cohesion weightMove toward center0.0050.001, 0.02

💡 PRO TIP Separation should always have the highest weight. If boids overlap or cluster too tightly, increase it. If the flock flies apart, increase cohesion. If the flock rotates instead of moving forward, increase alignment.

Edge Wrapping vs. Bounding

The example uses toroidal wrapping (boids wrap from one edge to the opposite). For bounding behavior instead:

javascript
// Replace the wrap logic with:
const margin = 50;
let turnX = 0, turnY = 0;
if (boid.x < margin) turnX = 1;
if (boid.x > w - margin) turnX = -1;
if (boid.y < margin) turnY = 1;
if (boid.y > h - margin) turnY = -1;

boid.vx += turnX * maxForce * dt;
boid.vy += turnY * maxForce * dt;

This makes boids turn away from edges, creating a contained flock.

Extending This Example

  • Add predator avoidance: spawn a predator on click, boids flee from it
  • Implement food seeking: boids cluster toward a food source
  • Add different species with different behaviors (larger boids dominate)
  • Plot the center of mass trajectory over time
  • Add obstacle avoidance with circles the boids must steer around

Same Result with ai.flock()

The whole simulation above collapses to a few lines with the ai API:

javascript
import ai from 'ai';

if (!state.flock) {
  state.flock = Array.from({ length: 60 }, () =>
    ai.agent({
      x: Math.random() * w, y: Math.random() * h,
      maxSpeed: 120, maxForce: 45,
      brain: (me, sense) =>
        ai.flock(me, ai.neighbors(state.flock, me, 60),
          { perception: 60, sep: 1.5, ali: 1, coh: 1, maxForce: 45 }),
    })
  );
}

for (const b of state.flock) {
  ai.think(b, ai.sense(b, w, h, time), dt);
  ai.wrap(b, w, h);
}

return {
  particles: state.flock.map((b) => ({ x: b.x, y: b.y, r: 4, color: '#8B5CF6' })),
  text: { Boids: state.flock.length },
};

The brain decides the steering force; ai.flock computes separation + alignment + cohesion with the same weights you tuned above. Add cursor interaction for free — see the AI cookbook.