 Example of a Galton Box at the end of animation.


A   Galton device   Sir Francis Galton's device   is also known as a   bean machine,   a   Galton Board,   or a   quincunx.



Description of operation

In a Galton box, there are a set of pins arranged in a triangular pattern.   A number of balls are dropped so that they fall in line with the top pin, deflecting to the left or the right of the pin.   The ball continues to fall to the left or right of lower pins before arriving at one of the collection points between and to the sides of the bottom row of pins.

Eventually the balls are collected into bins at the bottom   (as shown in the image),   the ball column heights in the bins approximate a   bell curve.   Overlaying   Pascal's triangle   onto the pins shows the number of different paths that can be taken to get to each bin.



Task

Generate an animated simulation of a Galton device.



Task requirements

   The box should have at least 5 pins on the bottom row. 
   A solution can use graphics or ASCII animation. 
   Provide a sample of the output/display such as a screenshot.
   There can be one or more balls in flight at the same time. 
   If multiple balls are in flight, ensure they don't interfere with each other.
   A solution should allow users to specify the number of balls, or it should run until full or a preset limit. 
   Optionally,   display the number of balls.


Global pegRadius, pegSize, pegSize2, height, width, delay, histogramSize, ball
 
Procedure eventLoop()
  Protected event
  Repeat
    event = WindowEvent()
    If event = #PB_Event_CloseWindow
      End
    EndIf
  Until event = 0 
EndProcedure
 
Procedure animate_actual(x1, y1, x2, y2, steps)
  Protected x.f, y.f, xstep.f, ystep.f, i, lastX.f, lastY.f
  x = x1
  y = y1
  xstep = (x2 - x1)/steps
  ystep = (y2 - y1)/steps
  For i = 1 To steps
    lastX = x
    lastY = y
    StartDrawing(CanvasOutput(0))
      DrawingMode(#PB_2DDrawing_XOr)
      Circle(x, y, pegRadius, RGB(0, 255, 255))
    StopDrawing()
    eventLoop()
    Delay(delay)      ; wait in ms
    StartDrawing(CanvasOutput(0))
      DrawingMode(#PB_2DDrawing_XOr)
      Circle(x, y, pegRadius, RGB(0, 255, 255))
    StopDrawing()
    eventLoop()
    x + xstep
    y + ystep
  Next
EndProcedure
 
Procedure draw_ball(xpos, ypos)                      
  Static Dim ballcounts(0) ;tally drop positions
  If xpos > ArraySize(ballcounts())
    Redim ballcounts(xpos)
  EndIf 
  ballcounts(xpos) + 1
  animate_actual(xpos, ypos, xpos, height - ballcounts(xpos) * pegSize, 20)
  StartDrawing(CanvasOutput(0))
    Circle(xpos, height - ballcounts(xpos) * pegSize, pegRadius, RGB(255, 0, 0))
  StopDrawing()
  eventLoop()
  If ballcounts(xpos) <= histogramSize
    ProcedureReturn 1 
  EndIf
  SetWindowTitle(0, "Ended after " + Str(ball) + " balls") ;histogramSize exceeded
EndProcedure 
 
Procedure animate(x1, y1, x2, y2)
  animate_actual(x1, y1, x2, y1, 4)
  animate_actual(x2, y1, x2, y2, 10)
EndProcedure 
 
Procedure galton(pegRows)
  ;drop a ball into the galton box
  Protected xpos, ypos, i, oldX, oldY
 
  oldX = width / 2 - pegSize / 2
  xpos = oldX
  oldY = pegSize
  ypos = oldY
  animate_actual(oldX, 0, xpos, ypos, 10)
  For i = 1 To pegRows 
    If Random(1)
      xpos + pegSize 
    Else
      xpos - pegSize
    EndIf 
    ypos + pegSize2
    animate(oldX, oldY, xpos, ypos)
    oldX = xpos
    oldY = ypos
  Next
 
  ProcedureReturn draw_ball(xpos, ypos)
EndProcedure
 
Procedure setup_window(numRows, ballCount)  
  ;Draw numRows levels of pegs
  Protected xpos, ypos, i, j
 
  width = (2 * numRows + 2) * pegSize
  histogramSize = (ballCount + 2) / 3
  If histogramSize > 500 / pegSize: histogramSize = 500 / pegSize: EndIf
  height = width + histogramSize * pegSize
  OpenWindow(0, 0, 0, width, height, "Galton box animation", #PB_Window_SystemMenu)
  CanvasGadget(0, 0, 0, width, height) 
 
  StartDrawing(CanvasOutput(0))
    Box(0, 0, width, height, RGB($EB, $EB, $EB))
    For i = 1 To numRows
      ypos = i * pegSize2
      xpos = width / 2 - (i - 1) * pegSize - pegSize / 2
      For j = 1 To i
        Circle(xpos, ypos, pegRadius, RGB(0, 0, 255))
        xpos + pegSize2
      Next
    Next
    For i = 1 To numRows
      Line((numRows - i + 1) * pegSize2 - pegSize / 2, width - pegSize, 1, histogramSize * pegSize, 0)
    Next 
  StopDrawing()
EndProcedure
 
;based on the galton box simulation from Unicon book
Define pegRows = 10, ballCount
pegRadius = 4
pegSize = pegRadius * 2 + 1
pegSize2 = pegSize * 2 
delay = 2                      ; ms delay
 
Repeat
  ballCount = Val(InputRequester("Galton box simulator","How many balls to drop?", "100"))
Until ballCount > 0
 
setup_window(pegRows, ballCount)
eventLoop()
For ball = 1 To ballCount
  If Not galton(pegRows): Break: EndIf
Next
Repeat: eventLoop(): ForEver