Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save shricodev/299ec59c9b33ebfbb753bd4246ed7df6 to your computer and use it in GitHub Desktop.
Save shricodev/299ec59c9b33ebfbb753bd4246ed7df6 to your computer and use it in GitHub Desktop.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Galton Board – Matter.js Demo</title>
<style>
/* --- Layout / colours ---------------------------------------------- */
body {
margin: 0;
background: #111;
font-family: Arial, Helvetica, sans-serif;
color: #fff;
user-select: none;
}
h2 {
text-align: center;
margin: 10px 0 2px 0;
font-weight: normal;
}
#ctrl {
text-align: center;
margin-bottom: 4px;
}
#ctrl input {
width: 240px;
}
#world {
display: block;
margin: 0 auto 20px auto;
background: #222;
border: 1px solid #666;
}
</style>
</head>
<body>
<h2>Galton Board – adjust gravity with the slider</h2>
<div id="ctrl">
Gravity:
<input
id="gravitySlider"
type="range"
min="0"
max="2"
step="0.01"
value="1"
/>
<span id="gVal">1.00</span> g
</div>
<canvas id="world" width="500" height="700"></canvas>
<!-- Matter.js from CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
/**********************************************************************
* Convenience aliases
*********************************************************************/
const { Engine, Render, Runner, World, Bodies, Body, Events, Common } =
Matter;
/**********************************************************************
* Basic engine / renderer setup
*********************************************************************/
const W = 500,
H = 700,
canvas = document.getElementById("world");
const engine = Engine.create();
engine.world.gravity.y = 1; // default = 1 g
const render = Render.create({
canvas: canvas,
engine: engine,
options: {
width: W,
height: H,
wireframes: false,
background: "#fff",
},
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);
/**********************************************************************
* Static structural elements : walls, ground, bin dividers
*********************************************************************/
const wallStyle = { fillStyle: "yellow" },
pegStyle = { fillStyle: "red" },
paddleStyle = { fillStyle: "orange" };
const walls = [
// side walls
Bodies.rectangle(-10, H / 2, 20, H, {
isStatic: true,
render: wallStyle,
}),
Bodies.rectangle(W + 10, H / 2, 20, H, {
isStatic: true,
render: wallStyle,
}),
// floor
Bodies.rectangle(W / 2, H + 10, W, 20, {
isStatic: true,
render: wallStyle,
}),
];
World.add(engine.world, walls);
/**********************************************************************
* Peg field : evenly spaced rows, not triangular
*********************************************************************/
const pegRows = 10,
pegCols = 10,
pegSpacingX = W / (pegCols + 1), // leave margin
pegSpacingY = 45,
pegRadius = 6,
offsetY = 100; // first row y
for (let row = 0; row < pegRows; row++) {
const y = offsetY + row * pegSpacingY;
for (let col = 1; col <= pegCols; col++) {
const x = col * pegSpacingX;
World.add(
engine.world,
Bodies.circle(x, y, pegRadius, {
isStatic: true,
friction: 0,
restitution: 0.4,
render: pegStyle,
}),
);
}
}
/**********************************************************************
* Spinning paddles (rotating obstacles)
*********************************************************************/
const paddles = [];
const paddleYStart = offsetY + pegSpacingY / 2; // between peg rows
for (let i = 0; i < 5; i++) {
const paddle = Bodies.rectangle(
i % 2 ? W * 0.25 : W * 0.75, // alternate sides
paddleYStart + i * pegSpacingY * 2,
60,
10,
{ isStatic: true, render: paddleStyle },
);
paddles.push(paddle);
World.add(engine.world, paddle);
}
/* give them continuous rotation */
Events.on(engine, "beforeUpdate", () => {
paddles.forEach((p, idx) => {
Body.rotate(p, (idx % 2 ? 1 : -1) * 0.04); // CW / CCW
});
});
/**********************************************************************
* Bins and dividers
*********************************************************************/
const binCount = 10,
binWidth = W / binCount,
binTop = H - 140;
for (let i = 0; i <= binCount; i++) {
const x = i * binWidth;
World.add(
engine.world,
Bodies.rectangle(
x,
binTop, // divider top point
8,
160, // divider size
{ isStatic: true, render: wallStyle },
),
);
}
/**********************************************************************
* Ball factory + drop timer
*********************************************************************/
const ballRadius = 7;
function dropBall() {
const ball = Bodies.circle(
W / 2 + Common.random(-10, 10), // narrow funnel
10,
ballRadius,
{
density: Common.random(0.001, 0.003), // mass
restitution: Common.random(0.3, 0.9), // bounciness
friction: Common.random(0.01, 0.05),
render: { fillStyle: "#333" },
},
);
World.add(engine.world, ball);
}
/* drop a ball every 300 ms */
setInterval(dropBall, 300);
/**********************************************************************
* Keep world tidy – remove off‑screen balls
*********************************************************************/
Events.on(engine, "afterUpdate", () => {
const bodies = Composite.allBodies(engine.world);
bodies.forEach((b) => {
if (!b.isStatic && b.position.y > H + 100) {
World.remove(engine.world, b);
}
});
});
/**********************************************************************
* Gravity slider UI
*********************************************************************/
const gSlider = document.getElementById("gravitySlider");
const gVal = document.getElementById("gVal");
gSlider.addEventListener("input", (e) => {
const val = parseFloat(e.target.value);
engine.world.gravity.y = val;
gVal.textContent = val.toFixed(2);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment