Created
April 21, 2025 13:11
-
-
Save shricodev/299ec59c9b33ebfbb753bd4246ed7df6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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