Build an Interactive Galton Board in Matter.js
This article explains how to build an interactive Galton board (quincunx) simulation using the Matter.js 2D physics engine to demonstrate the Central Limit Theorem and normal distributions. You will learn how to configure the physics engine, construct static funnels and peg lattices, partition collection bins, and emit dynamic particles whose random deflections naturally assemble into a classic bell curve.
1. Initialize the Matter.js Engine
Start by importing the required Matter.js modules and initializing
the core components: the engine, world, renderer, and runner. Set up an
HTML <canvas> element or let Matter.js generate one
automatically within a container element.
const { Engine, Render, Runner, Bodies, Composite } = Matter;
const engine = Engine.create();
const world = engine.world;
const width = 600;
const height = 800;
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: width,
height: height,
wireframes: false,
background: '#1a1a1a'
}
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);2. Construct the Peg Grid (Triangular Lattice)
The core mechanism of a Galton board is a staggered grid of static circular pegs. Each peg introduces an independent Bernoulli trial, deflecting falling particles left or right with roughly equal probability.
Arrange the pegs in an inverted triangle or diamond pattern using nested loops:
const pegRadius = 4;
const rows = 10;
const startY = 180;
const spacing = 35;
for (let r = 0; r < rows; r++) {
const rowY = startY + r * spacing;
const count = r + 1;
const startX = width / 2 - ((count - 1) * spacing) / 2;
for (let c = 0; c < count; c++) {
const pegX = startX + c * spacing;
const peg = Bodies.circle(pegX, rowY, pegRadius, {
isStatic: true,
render: { fillStyle: '#ffffff' }
});
Composite.add(world, peg);
}
}3. Build the Funnel and Boundary Walls
To ensure balls enter the peg matrix consistently from a central origin, add angled static barriers at the top to form a funnel. Add vertical walls on the canvas edges to contain the particles.
const wallOptions = { isStatic: true, render: { fillStyle: '#444444' } };
// Funnel guides
const leftFunnel = Bodies.rectangle(width / 2 - 50, 80, 120, 10, {
...wallOptions,
angle: Math.PI / 4
});
const rightFunnel = Bodies.rectangle(width / 2 + 50, 80, 120, 10, {
...wallOptions,
angle: -Math.PI / 4
});
// Outer boundary walls
const leftWall = Bodies.rectangle(0, height / 2, 20, height, wallOptions);
const rightWall = Bodies.rectangle(width, height / 2, 20, height, wallOptions);
const floor = Bodies.rectangle(width / 2, height, width, 20, wallOptions);
Composite.add(world, [leftFunnel, rightFunnel, leftWall, rightWall, floor]);4. Create Vertical Collection Bins
Beneath the lowest row of pegs, install evenly spaced vertical dividers. As particles finish their descent, these dividers sort them into discrete slots, translating physical trajectories into a visible histogram of outcomes.
const binCount = rows + 1;
const binWidth = width / binCount;
const binHeight = 200;
const binY = height - binHeight / 2;
for (let i = 0; i <= binCount; i++) {
const dividerX = i * binWidth;
const divider = Bodies.rectangle(dividerX, binY, 4, binHeight, {
isStatic: true,
render: { fillStyle: '#888888' }
});
Composite.add(world, divider);
}5. Spawn Particles and Tune Physics Parameters
Emit small circular bodies continuously above the funnel. Particle
properties such as restitution (bounciness) and friction dictate how
purely random the deflections are. Set restitution between
0.4 and 0.6 and reduce friction to keep the
particles mobile and prevent clumping.
function dropBall() {
// Minor random offset prevents deterministic stacking at the funnel point
const jitter = (Math.random() - 0.5) * 4;
const ball = Bodies.circle(width / 2 + jitter, 20, 5, {
restitution: 0.5,
friction: 0.05,
render: { fillStyle: '#ff4757' }
});
Composite.add(world, ball);
}
// Spawn a ball every 100 milliseconds
setInterval(dropBall, 100);6. Mathematical Convergence
Each time a ball strikes a peg, its horizontal movement represents an approximately independent binary decision (\(X_i \in \{-1, 1\}\)). By the time a ball reaches the collection bins after traversing \(n\) rows, its horizontal position corresponds to the sum \(\sum_{i=1}^{n} X_i\).
According to the Central Limit Theorem, the distribution of this sum approximates a Gaussian distribution as the number of rows increases:
\[f(x) \approx \frac{1}{\sigma \sqrt{2\pi}} e^{-\frac{(x - \mu)^2}{2\sigma^2}}\]
As hundreds of particles settle into the bins, the center bins receive the highest volume of particles while the outer bins receive exponentially fewer, producing a visible normal probability curve.