Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save shricodev/d4a3e1d9e02cfd83a80ae11ecba3d38e to your computer and use it in GitHub Desktop.
Save shricodev/d4a3e1d9e02cfd83a80ae11ecba3d38e to your computer and use it in GitHub Desktop.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Galton Board</title>
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #fff;
}
#controls {
position: absolute;
top: 10px;
left: 10px;
color: #000;
font-family: sans-serif;
z-index: 10;
}
#controls input {
vertical-align: middle;
}
</style>
</head>
<body>
<div id="controls">
Gravity:
<input
type="range"
id="gravitySlider"
min="0"
max="2"
step="0.01"
value="1"
/>
</div>
<canvas id="world"></canvas>
<!-- Matter.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
(function () {
const {
Engine,
Render,
Runner,
World,
Bodies,
Body,
Events,
Constraint,
} = Matter;
const WIDTH = 500,
HEIGHT = 700;
const engine = Engine.create();
engine.gravity.y = 1;
const world = engine.world;
// renderer
const canvas = document.getElementById("world");
canvas.width = WIDTH;
canvas.height = HEIGHT;
const render = Render.create({
canvas: canvas,
engine: engine,
options: {
width: WIDTH,
height: HEIGHT,
wireframes: false,
background: "#fff",
},
});
Render.run(render);
Runner.run(Runner.create(), engine);
// colors & thickness
const wallColor = "yellow",
pegColor = "red",
paddleColor = "orange",
ballColor = "#000";
const T = 20;
// walls & ground
World.add(world, [
Bodies.rectangle(WIDTH / 2, HEIGHT + T / 2, WIDTH, T, {
isStatic: true,
render: { fillStyle: wallColor },
}),
Bodies.rectangle(-T / 2, HEIGHT / 2, T, HEIGHT, {
isStatic: true,
render: { fillStyle: wallColor },
}),
Bodies.rectangle(WIDTH + T / 2, HEIGHT / 2, T, HEIGHT, {
isStatic: true,
render: { fillStyle: wallColor },
}),
]);
// bins (thick dividers)
const BIN_COUNT = 10,
binW = WIDTH / BIN_COUNT;
for (let i = 0; i <= BIN_COUNT; i++) {
World.add(
world,
Bodies.rectangle(i * binW, HEIGHT - 100, 4, 200, {
isStatic: true,
render: { fillStyle: wallColor },
}),
);
}
// pegs – fewer & farther apart
const pegR = 6,
pegRows = 4,
pegSpacingX = 80,
pegSpacingY = 100,
pegStartY = 180,
pegCols = Math.floor(WIDTH / pegSpacingX);
for (let row = 0; row < pegRows; row++) {
for (let col = 0; col < pegCols; col++) {
const x =
col * pegSpacingX +
(row % 2 ? pegSpacingX / 2 : 0) +
pegSpacingX / 2;
const y = pegStartY + row * pegSpacingY;
World.add(
world,
Bodies.circle(x, y, pegR, {
isStatic: true,
render: { fillStyle: pegColor },
}),
);
}
}
// spinning paddles
const paddles = [];
const paddleYs = [250, 390, 530];
paddleYs.forEach((py) => {
const paddle = Bodies.rectangle(WIDTH / 2, py, 200, 10, {
render: { fillStyle: paddleColor },
});
const pivot = Constraint.create({
pointA: { x: WIDTH / 2, y: py },
bodyB: paddle,
pointB: { x: 0, y: 0 },
stiffness: 1,
length: 0,
});
paddle.spinSpeed =
(0.02 + Math.random() * 0.03) * (Math.random() < 0.5 ? -1 : 1);
World.add(world, [paddle, pivot]);
paddles.push(paddle);
});
Events.on(engine, "beforeUpdate", () => {
paddles.forEach((p) => Body.rotate(p, p.spinSpeed));
});
// visible funnel walls
const funnelTopY = 40,
funnelBotY = 100,
funnelHalfW = 50,
ft = 10;
// left wall
World.add(
world,
Bodies.rectangle(
WIDTH / 2 - funnelHalfW - ft / 2,
(funnelTopY + funnelBotY) / 2,
ft,
funnelBotY - funnelTopY,
{ isStatic: true, render: { fillStyle: wallColor } },
),
);
// right wall
World.add(
world,
Bodies.rectangle(
WIDTH / 2 + funnelHalfW + ft / 2,
(funnelTopY + funnelBotY) / 2,
ft,
funnelBotY - funnelTopY,
{ isStatic: true, render: { fillStyle: wallColor } },
),
);
// spawn balls in wide tube
function spawnBall() {
const tubeW = funnelHalfW * 2 - 10;
const x0 = WIDTH / 2 + (Math.random() * tubeW - tubeW / 2);
const y0 = funnelTopY + 5;
const radius = 8 + Math.random() * 4;
const ball = Bodies.circle(x0, y0, radius, {
restitution: 0.3 + Math.random() * 0.7,
friction: 0.001 + Math.random() * 0.1,
density: 0.0005 + Math.random() * 0.002,
render: { fillStyle: ballColor },
});
World.add(world, ball);
}
setInterval(spawnBall, 300);
// gravity slider
document
.getElementById("gravitySlider")
.addEventListener("input", (e) => {
engine.gravity.y = parseFloat(e.target.value);
});
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment