The Galton Board
Galton’s board study using LEGO bricks to understand the concept of normal distribution.
Published
November 28, 2024
Click on the inside of the simulation to interact.
p5(sketch => {
let alto = 700;
let ancho = 1000;
let dx = 40;
let dy = 40;
let sep = 1.5;
let posGroundX=25;
let posGroundY=600;
let anchoBarra = 2000;
let altoBarra = 50;
let system;
let circulos;
var Engine = Matter.Engine,
// Render = Matter.Render, // Uso la lib p5j
World = Matter.World,
Bodies = Matter.Bodies,
Composite = Matter.Composite,
Common = Matter.Common,
Svg = Matter.Svg,
Body = Matter.Body,
Vertices = Matter.Vertices;
// ENGINE
var engine;
var world;
var ground;
var circle;
var objects;
let pos_ini_i = 10;
let pos_ini_j = 3;
let filas = 16;
let columnas = 19 ;
var galton_board_1;
var galton_board_2;
var galton_board_3;
var galton_board=[];
var par;
var particulas=[];
var palo_1;
var palo_2;
var palo_3;
var palo_4;
var palo_5;
var palo_6;
var palo_7;
var palo_8;
var palo_9;
var palo_10;
let par_temp_temp;
let w_palo =30;
let l_palo = 200;
var par_x;
var par_y;
var par_temp=[];
let agrega_par = false;
sketch.mouseClicked = function() {
par_x = sketch.mouseX;
par_y = sketch.mouseY;
console.log("fgdgdfgfd",par_x);
agrega_par = true;
}
sketch.setup = function() {
sketch.createCanvas(ancho, alto);
//system = new ParticleSystem(sketch,sketch.createVector(sketch.width/2, 50));
for(let n=1; n<=300; n++){
par = new addParticleWorld(sketch, Matter, sketch.createVector(480, 50),math.random()*0.001,math.random()*0.001)
particulas.push(par);
}
circulos = new Grid_circles_v2(sketch,filas,columnas, ancho, alto);
circulos.add_circles();
// Engine
engine = Engine.create();
// engine.gravity.x=0;
engine.gravity.y = 0.7;
world = engine.world;
Matter.Runner.run(engine); // Engine.run(engine)
ground = Matter.Bodies.rectangle(posGroundX, posGroundY+100, anchoBarra, altoBarra, { isStatic: true, angle: 0 });
// palo_1 = Matter.Bodies.rectangle(-100, 500, w_palo, l_palo, { isStatic: true, angle: 0 });
palo_2 = Matter.Bodies.rectangle(0+95, 575, w_palo, l_palo, { isStatic: true, angle: 0 });
palo_3 = Matter.Bodies.rectangle(0+95*2, 575, w_palo, l_palo, { isStatic: true, angle: 0 });
palo_4 = Matter.Bodies.rectangle(0+95*3, 575, w_palo, l_palo, { isStatic: true, angle: 0 });
palo_5 = Matter.Bodies.rectangle(0+95*4, 575, w_palo, l_palo, { isStatic: true, angle: 0 });
palo_6 = Matter.Bodies.rectangle(0+95*5, 575, w_palo, l_palo, { isStatic: true, angle: 0 });
palo_7 = Matter.Bodies.rectangle(0+95*6, 575, w_palo, l_palo, { isStatic: true, angle: 0 });
palo_8 = Matter.Bodies.rectangle(0+95*7, 575, w_palo, l_palo, { isStatic: true, angle: 0 });
palo_9 = Matter.Bodies.rectangle(0+95*8, 575, w_palo, l_palo, { isStatic: true, angle: 0 });
palo_10 = Matter.Bodies.rectangle(0+95*9, 575, w_palo, l_palo, { isStatic: true, angle: 0 });
circle = Matter.Bodies.circle(480, 30, circulos.deltaX*0.55/4, { isStatic: false, inertia: Infinity,
friction: 0.00, restitution: 0.4,
frictionAir: 0, frictionStatic: 0 });
Body.setVelocity(circle, { x: 0.1, y: 0.0 });
// GALTON BOARD
for(let lineas_num=0; lineas_num< 9; lineas_num++){
let y = 0;
// let galton_board_temp=[];
for(let dots_x = pos_ini_i-lineas_num; dots_x <= (pos_ini_i+lineas_num); dots_x++){
let dots_y= pos_ini_j + lineas_num;
galton_board.push(Matter.Bodies.circle(circulos.nm2xy(dots_x,dots_y)[0],circulos.nm2xy(dots_x,dots_y)[1],circulos.deltaX*0.55/2,{isStatic: true }));
y=y+1;
}
}
// AGREGANDO OBJETOS AL MUNDO
objects = galton_board.concat(ground);
objects = objects.concat(circle);
for(let n=1; n<=300; n++){
objects = objects.concat(particulas[n-1].get_body());
}
objects = objects.concat([palo_2,palo_3,palo_4, palo_5,palo_6,palo_7,palo_8,palo_9,palo_10]);
Engine.run(engine);
//World.add(world,[ground, circle,galton_board_1,galton_board_2,galton_board_3]);
World.add(world,objects);
};
sketch.draw = function() {
// Use degrees.
// sketch.angleMode(DEGREES);
sketch.background('#F9F9F9');
circulos.run();
circulos.mouseMoved(sketch,math);
sketch.drawingContext.shadowOffsetX = 0;
sketch.drawingContext.shadowOffsetY = 0;
sketch.drawingContext.shadowBlur = 0;
sketch.drawingContext.shadowColor = '#DBDBDB';
// Ground
sketch.push();
sketch.translate(ground.position.x,ground.position.y);
sketch.rotate(0);
sketch.fill(0,80,250,200);
sketch.stroke(0,80,250,200);
sketch.rectMode(sketch.CENTER);
sketch.rect(0,0,anchoBarra,altoBarra);
sketch.pop();
// Palo_2
sketch.push();
sketch.translate(palo_2.position.x,palo_2.position.y);
sketch.rotate(0);
sketch.fill('black');
sketch.stroke(0,80,250,200);
sketch.rectMode(sketch.CENTER);
sketch.rect(0,0,w_palo,l_palo);
sketch.pop();
// Palo_3
sketch.push();
sketch.translate(palo_3.position.x,palo_3.position.y);
sketch.rotate(0);
sketch.fill('black');
sketch.stroke(0,80,250,200);
sketch.rectMode(sketch.CENTER);
sketch.rect(0,0,w_palo,l_palo);
sketch.pop();
// Palo_4
sketch.push();
sketch.translate(palo_4.position.x,palo_4.position.y);
sketch.rotate(0);
sketch.fill('black');
sketch.stroke(0,80,250,200);
sketch.rectMode(sketch.CENTER);
sketch.rect(0,0,w_palo,l_palo);
sketch.pop();
// Palo_5
sketch.push();
sketch.translate(palo_5.position.x,palo_5.position.y);
sketch.rotate(0);
sketch.fill('black');
sketch.stroke(0,80,250,200);
sketch.rectMode(sketch.CENTER);
sketch.rect(0,0,w_palo,l_palo);
sketch.pop();
// Palo_6
sketch.push();
sketch.translate(palo_6.position.x,palo_6.position.y);
sketch.rotate(0);
sketch.fill('black');
sketch.stroke(0,80,250,200);
sketch.rectMode(sketch.CENTER);
sketch.rect(0,0,w_palo,l_palo);
sketch.pop();
// Palo_7
sketch.push();
sketch.translate(palo_7.position.x,palo_7.position.y);
sketch.rotate(0);
sketch.fill('black');
sketch.stroke(0,80,250,200);
sketch.rectMode(sketch.CENTER);
sketch.rect(0,0,w_palo,l_palo);
sketch.pop();
// Palo_8
sketch.push();
sketch.translate(palo_8.position.x,palo_8.position.y);
sketch.rotate(0);
sketch.fill('black');
sketch.stroke(0,80,250,200);
sketch.rectMode(sketch.CENTER);
sketch.rect(0,0,w_palo,l_palo);
sketch.pop();
// Palo_9
sketch.push();
sketch.translate(palo_9.position.x,palo_9.position.y);
sketch.rotate(0);
sketch.fill('black');
sketch.stroke(0,80,250,200);
sketch.rectMode(sketch.CENTER);
sketch.rect(0,0,w_palo,l_palo);
sketch.pop();
// Palo_8
sketch.push();
sketch.translate(palo_10.position.x,palo_10.position.y);
sketch.rotate(0);
sketch.fill('black');
sketch.stroke(0,80,250,200);
sketch.rectMode(sketch.CENTER);
sketch.rect(0,0,w_palo,l_palo);
sketch.pop();
//circle
sketch.push();
sketch.translate(circle.position.x,circle.position.y );
sketch.rectMode(sketch.CENTER);
sketch.fill(0,80,250,200);
sketch.stroke(0,80,250,200);
sketch.circle(0,0,circulos.deltaX*0.55/2);
sketch.pop();
//GALTON draw
for(let lineas_num=0; lineas_num<9; lineas_num++){
let y=0;
for(let dots_x = pos_ini_i-lineas_num; dots_x <= (pos_ini_i+lineas_num); dots_x++){
let dots_y=pos_ini_j+lineas_num;
sketch.push();
sketch.translate(circulos.nm2xy(dots_x,dots_y)[0],circulos.nm2xy(dots_x,dots_y)[1] );
sketch.rectMode(sketch.CENTER);
sketch.fill('orange');
sketch.stroke(0,20,250,200);
sketch.circle(0,0,circulos.deltaX*0.55);
sketch.pop();
y=y+1;
}
}
for(let n=1; n<=particulas.length; n++){
// sketch.push();
particulas[n-1].display();
}
if (agrega_par) {
console.log('GGGGGG');
par_temp_temp =new addParticleWorld(sketch, Matter, sketch.createVector(par_x +math.random()*0.8, par_y+math.random()*0.8),math.random()*0.001,math.random()*0.001);
par_temp.push(par_temp_temp);
objects = objects.concat(par_temp_temp.get_body());
World.add(world,par_temp_temp.get_body());
//agrega_par=false;
}
for(let n=0; n<par_temp.length; n++){
// sketch.push();
par_temp[n].display();
}
agrega_par = false;
Engine.update(engine);
}
})
class Stud_v2 {
constructor(p5, posX, posY,i,j, dx, dy, value) {
this.posX = posX ;
this.posY = posY;
this.i = i;
this.j = j;
this.dx = dx;
this.dy = dy;
this.value = value;
this.p5 = p5;
}
run() {
this.display();
}
display() {
this.p5.drawingContext.shadowOffsetX = 0;
this.p5.drawingContext.shadowOffsetY = 4;
this.p5.drawingContext.shadowBlur = 7;
this.p5.drawingContext.shadowColor = '#DBDBDB';
this.p5.stroke('#E8E8E8');
this.p5.strokeWeight(this.value);
this.p5.fill('DBDBDB');
this.p5.circle(this.posX , this.posY, this.dx);
}
brilla(nivel){
this.value = nivel;
}
isDead() {
return 0;
}
}
class Grid_circles_v2 {
constructor(p5,filas,columnas,ancho, alto) {
this.p5 = p5;
this.filas = filas,
this.columnas = columnas;
this.sepX=ancho*0.05;
this.sepY=alto*0.06;
this.ancho = ancho- this.sepX*2;
this.alto = alto - this.sepY*2;
this.deltaX = this.p5.round(this.ancho/this.columnas);
this.deltaY = this.p5.round(this.alto/this.filas);
this.circles = [];
}
add_circles() {
for (let i = 0; i < this.columnas ; i= i+1) {
let CircleRow = [];
for ( let j = 0; j < this.filas ; j= j+1) {
let posX = this.sepX + this.deltaX*i;
let posY = this.sepY + this.deltaY*j;
// console.log('DeltaX', posX);
// console.log('DeltaY', posY);
CircleRow.push(new Stud_v2(this.p5,posX, posY,i+1,j+1, this.deltaX*0.55, this.deltaY*0.55, 1));
}
this.circles.push(CircleRow);
}
//return circles;
}
run() {
for (let i = 0; i < this.circles.length; i++) {
for (let j = 0; j < this.circles[i].length; j++){
this.circles[i][j].run();
}
}
return 1;
}
nm2xy(i,j){
i=i-1;
j=j-1;
return [this.circles[i][j].posX,this.circles[i][j].posY];
}
mouseMoved(p5, math){
this.math = math;
this.locX = this.p5.mouseX;
this.locY = this.p5.mouseY;
for (let i = 0; i < this.circles.length; i++) {
// console.log(i);
// console.log(this.locX);
for (let j = 0; j < this.circles[i].length; j++){
// m = this.circles[i][j];
if(this.math.abs(this.locX - this.circles[i][j].posX) < this.circles[i][j].dx/2 &
this.math.abs(this.locY - this.circles[i][j].posY) < this.circles[i][j].dy/2
){
this.circles[i][j].brilla(2);
}
if(this.math.abs(this.locX - this.circles[i][j].posX) > this.circles[i][j].dx/2 ||
this.math.abs(this.locY - this.circles[i][j].posY) > this.circles[i][j].dy/2
){
this.circles[i][j].brilla(0);
}
}
}
return false;
}
}
class Particle {
constructor(p5, position) {
this.p5 = p5;
this.acceleration = this.p5.createVector(0, 0.05);
this.velocity = this.p5.createVector(this.p5.random(-1, 1), this.p5.random(-1, 0));
this.position = position.copy();
this.lifespan = 255;
}
run() {
this.update();
this.display();
}
// Method to update position
update() {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
this.lifespan -= 2;
}
// Method to display
display() {
this.p5.stroke(200, this.lifespan);
this.p5.strokeWeight(2);
this.p5.fill(127, this.lifespan);
this.p5.ellipse(this.position.x, this.position.y, 12, 12);
}
// Is the particle still useful?
isDead() {
return this.lifespan < 0;
}
}
class ParticleSystem {
constructor(p5, position) {
this.p5 = p5;
this.origin = position.copy();
this.particles = [];
}
addParticle() {
this.particles.push(new Particle(this.p5, this.origin));
}
run() {
this.particles = this.particles.filter(particle => {
particle.run();
return !particle.isDead();
});
}
// return(this.particles);
}
class addParticleWorld{
constructor(p5, matter, position, x_vel, y_vel){
this.p5 = p5;
this.matter = matter;
this.position = position.copy();
this.xvel = x_vel;
this.yvel = y_vel;
this.circle = this.matter.Bodies.circle(this.position.x, this.position.y, 5, { isStatic: false, inertia: Infinity, friction: 0.00, restitution: 0.4, frictionAir: 0, frictionStatic: 0 });
this.matter.Body.setVelocity(this.circle, { x: this.xvel, y: this.yvel });
//this.acceleration = this.p5.createVector(0, 0.05);
//this.velocity = this.p5.createVector(this.p5.random(-1, 1), this.p5.random(-1, 0));
}
display() {
this.p5.push();
this.p5.translate(this.circle.position.x,this.circle.position.y );
this.p5.rectMode(this.p5.CENTER);
this.p5.fill(0,80,250,200);
this.p5.stroke(0,80,250,200);
this.p5.circle(0,0,10);
this.p5.pop();
}
get_body(){
return(this.circle);
}
}
Simulation created with P5js and Matter-js.
To empirically model Galton’s board, here I used a LEGO grid:
You can compare the simulation with the empirical data presented below.
Under construction …
Back to top