Boids Flocking Simulation using the Boids algorithm created by Craig Reynolds in 1986, simulating the flocking behavior of birds through emergent complexity from simple rules.
---
format: typebulb/v1
name: Birds
---
**code.tsx**
```tsx
import React from "react";
import { createRoot } from "react-dom/client";
import { useEffect, useRef } from "react";
type Vec2 = { x: number; y: number };
const vec = {
sub: (a: Vec2, b: Vec2): Vec2 => ({ x: a.x - b.x, y: a.y - b.y }),
add: (a: Vec2, b: Vec2): Vec2 => ({ x: a.x + b.x, y: a.y + b.y }),
scale: (v: Vec2, s: number): Vec2 => ({ x: v.x * s, y: v.y * s }),
mag: (v: Vec2) => Math.sqrt(v.x * v.x + v.y * v.y),
norm: (v: Vec2): Vec2 => { const m = vec.mag(v); return m > 0 ? vec.scale(v, 1 / m) : { x: 0, y: 0 }; },
limit: (v: Vec2, max: number): Vec2 => { const m = vec.mag(v); return m > max ? vec.scale(vec.norm(v), max) : v; }
};
class Boid {
pos: Vec2;
vel: Vec2;
acc: Vec2 = { x: 0, y: 0 };
flapPhase = 0;
flapTimer = 50 + Math.random() * 100;
isFlapping = false;
constructor(x: number, y: number) {
this.pos = { x, y };
const angle = Math.random() * Math.PI * 2;
this.vel = { x: Math.cos(angle) * 2, y: Math.sin(angle) * 2 };
}
steer(boids: Boid[], radius: number, fn: (o: Boid, d: number) => Vec2, seek = false): Vec2 {
let sum = { x: 0, y: 0 }, count = 0;
for (const o of boids) {
const d = vec.mag(vec.sub(this.pos, o.pos));
if (d > 0 && d < radius) { const v = fn(o, d); sum.x += v.x; sum.y += v.y; count++; }
}
if (!count) return { x: 0, y: 0 };
const avg = vec.scale(sum, 1 / count);
const target = seek ? vec.sub(avg, this.pos) : avg;
return vec.mag(target) ? vec.limit(vec.sub(vec.scale(vec.norm(target), 3), this.vel), 0.03) : { x: 0, y: 0 };
}
flock(boids: Boid[]) {
const sep = this.steer(boids, 55, (o, d) => vec.scale(vec.norm(vec.sub(this.pos, o.pos)), ((55 - d) / 55) ** 2));
const ali = this.steer(boids, 90, o => o.vel);
const coh = this.steer(boids, 90, o => o.pos, true);
this.acc = vec.add(this.acc, vec.add(vec.scale(sep, 1.3), vec.add(vec.scale(ali, 1.2), coh)));
}
update(w: number, h: number) {
this.vel = vec.limit(vec.add(this.vel, this.acc), 3);
this.pos = vec.add(this.pos, this.vel);
this.acc = { x: 0, y: 0 };
this.pos.x = (this.pos.x + w) % w;
this.pos.y = (this.pos.y + h) % h;
this.flapTimer--;
if (this.isFlapping) {
this.flapPhase += 0.5;
if (this.flapTimer <= 0) { this.isFlapping = false; this.flapTimer = 60 + Math.random() * 120; }
} else if (this.flapTimer <= 0) {
this.isFlapping = true; this.flapTimer = 20 + Math.random() * 30; this.flapPhase = 0;
}
}
draw(ctx: CanvasRenderingContext2D) {
ctx.save();
ctx.translate(this.pos.x, this.pos.y);
ctx.rotate(Math.atan2(this.vel.y, this.vel.x));
const f = this.isFlapping ? Math.sin(this.flapPhase) : 0;
const t = 1 - f * 0.4, m = 1 - f * 0.2;
ctx.beginPath();
ctx.moveTo(10, 0);
for (const [x, y] of [[2, -2], [-4, -14 * t], [-6, -10 * m], [-2, -2], [-8, 0], [-2, 2], [-6, 10 * m], [-4, 14 * t], [2, 2]])
ctx.lineTo(x, y);
ctx.closePath();
ctx.fill();
ctx.restore();
}
}
function App() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const boidsRef = useRef<Boid[]>([]);
useEffect(() => {
const canvas = canvasRef.current!, ctx = canvas.getContext("2d")!;
const resize = () => { canvas.width = innerWidth; canvas.height = innerHeight; };
resize();
addEventListener("resize", resize);
if (!boidsRef.current.length)
boidsRef.current = Array.from({ length: 100 }, () => new Boid(Math.random() * canvas.width, Math.random() * canvas.height));
let frameId: number;
const animate = () => {
const style = getComputedStyle(document.documentElement);
ctx.fillStyle = style.getPropertyValue("--bg-color");
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = style.getPropertyValue("--bird-color");
for (const b of boidsRef.current) { b.flock(boidsRef.current); b.update(canvas.width, canvas.height); b.draw(ctx); }
frameId = requestAnimationFrame(animate);
};
animate();
return () => { removeEventListener("resize", resize); cancelAnimationFrame(frameId); };
}, []);
return (
<div className="container">
<canvas ref={canvasRef} className="canvas" />
<div className="info">
<h1>Boids Flocking Simulation</h1>
<p>Watch as 100 birds demonstrate emergent flocking behavior through three simple rules:</p>
<ul>
<li><strong>Separation:</strong> Avoid crowding neighbors</li>
<li><strong>Alignment:</strong> Steer toward average heading</li>
<li><strong>Cohesion:</strong> Steer toward average position</li>
</ul>
</div>
</div>
);
}
createRoot(document.getElementById("root")!).render(<App />);
```
**styles.css**
```css
html[data-theme="light"] {
color-scheme: light;
--bg-color: #f0f4f8;
--bird-color: #2d3748;
--text-color: #1a202c;
--info-bg: rgba(255, 255, 255, 0.7);
--info-border: rgba(45, 55, 72, 0.1);
}
html[data-theme="dark"] {
color-scheme: dark;
--bg-color: #1a202c;
--bird-color: #63b3ed;
--text-color: #e2e8f0;
--info-bg: rgba(26, 32, 44, 0.7);
--info-border: rgba(226, 232, 240, 0.1);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
overflow: hidden;
background-color: var(--bg-color);
}
.container {
position: relative;
width: 100vw;
height: 100vh;
}
.canvas {
display: block;
width: 100%;
height: 100%;
}
.info {
position: absolute;
top: 20px;
left: 20px;
max-width: 400px;
padding: 5px;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
color: var(--text-color);
}
.info h1 {
font-size: 1.5rem;
margin-bottom: 12px;
font-weight: 600;
}
.info p {
margin-bottom: 12px;
line-height: 1.5;
}
.info ul {
margin-left: 20px;
line-height: 1.6;
}
```
**index.html**
```html
<div id="root"></div>
```
**config.json**
```json
{
"dependencies": {
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"description": "Boids Flocking Simulation using the **Boids algorithm** created by Craig Reynolds in 1986, simulating the flocking behavior of birds through emergent complexity from simple rules."
}
```