danynash
  • Home
  • About me
  • My Research
  • Selected Publications
  • Tutorials
  • Lessons

The Galton Board

Mapping Electric Potential Field.
Published

January 3, 2025

Click on the inside of the simulation to interact.

import {p5} from "@tmcw/p5";
math = require("mathjs");
Matter = require("matter-js");
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<=30; 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<=30; 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