// A revision of the GaltonBoard applet

// Written by Julian Devlin, 8/97, for the text book
// "Introduction to Probability," by Charles M. Grinstead & J. Laurie Snell


// A few necessary packages
import java.awt.*;
import java.applet.Applet;
import java.util.Random;


// The GaltonBoard applet class
public class GaltonBoard extends Applet {
	Board myboard;							// Pins, slots, etc.				
	
	// UI elements
	Panel contpanel;
	Scrollbar pausebar, probbar, rowsbar;
	Label pauselabel, problabel, rowslabel;
	//Checkbox soundon;
	
	// State variables
	int screenwidth = 400;
	int screenheight = 500;
	
	// Initialization
	public void init() {
		
		//resize(screenwidth, screenheight);				// Make applet correct size
		setBackground(new Color(255, 255, 255));		// Set color
		myboard= new Board(this);							// Instantiate Board class
		// Set up UI controls
		contpanel = new Panel();
		contpanel.setLayout(new GridLayout(6, 1, 5, 5));
		contpanel.setBackground(new Color(255, 255, 255));
		
		pausebar = new Scrollbar(Scrollbar.HORIZONTAL, 100, 20, 1, 2000);
		probbar = new Scrollbar(Scrollbar.HORIZONTAL, 50, 20, 0, 100);
		rowsbar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 20, 1, 30);
		pauselabel = new Label("Delay - " + String.valueOf(pausebar.getValue()) + " microseconds");
		problabel = new Label("Chance of falling right - " + String.valueOf(probbar.getValue()) + "/100");
		rowslabel = new Label("Rows of pins - " + String.valueOf(rowsbar.getValue()));
		//soundon = new Checkbox("Sound", null, false);
		
		setLayout( new BorderLayout(5,5));
		add("South", contpanel);
		add("Center", myboard);
		
		contpanel.add(pausebar);
		contpanel.add(pauselabel);
		
		contpanel.add(probbar);
		contpanel.add(problabel);
		contpanel.add(rowsbar);
		contpanel.add(rowslabel);
		//contpanel.add(soundon);
		
		myboard.pinrows = rowsbar.getValue();
		
		validate();
		
		//myboard.init();
		myboard.start();		// Run board....
	} 
	
	
	// If it is run as an application, make a window with an applet in it
	public static void main(String arg[]) {
		Frame f = new Frame("My Window");
		GaltonBoard myGalt = new GaltonBoard();
		myGalt.init();
		//myGalt.start();
		f.setLayout(new GridLayout(1, 1));
//		f.resize(myGalt.screenwidth, myGalt.screenheight);
		f.add(myGalt);
		f.show();
	}

	 public boolean handleEvent(Event evt)
        {
        		// If user is changing options
            if(evt.target instanceof Scrollbar)
            {
                if (evt.target==pausebar) {
                	myboard.pauselength = ((Scrollbar)evt.target).getValue();
                	pauselabel.setText("Delay - " 
                		+ String.valueOf(pausebar.getValue()) + " microseconds");
                }
           		if (evt.target==probbar) {
           			myboard.leftprob = (float) ((Scrollbar)evt.target).getValue()/100;
						problabel.setText("Chance of falling right - " 
							+ String.valueOf(probbar.getValue()) + "/100");
           		}
           		if (evt.target==rowsbar) {
           			myboard.pinrows = ((Scrollbar)evt.target).getValue();
           			rowslabel.setText("Rows of pins - " 
           				+ String.valueOf(rowsbar.getValue()));
           		}
                return true;
            }
				// Otherwise, handle as usual
            return super.handleEvent(evt);
         }

}


// Board class - does all the work
class Board extends Canvas implements Runnable
{
	// Constants
	final int NUMBALLS = 100;
	final int PINSIZE = 4;
	final int BALLSIZE = 8;
	final int SIDE = 20;
	final int HEIGHT = (int) Math.round((Math.sqrt(3)/2)*SIDE);
	final int DROPCOORD = 20;
	final int CUTCOORD = 20;
	
	// Variables
	int pinrows;
	
	int maxy;
	int barwidth;
	int firstcol;
	
	int curball = 0;
	int xpos;
	int ypos;
	int oldx, oldy;
	int slotno;
	int pauselength = 100;
	
	float leftprob = (float) .5;
	
	int flatball;
	
	//jd- 30 is actuall max pin num	
	int slotballs[] = new int[31];
	
	int oldclipx, oldclipy;
	Rectangle cliparea;
	
	int screenwidth = size().width;				// DUPLICATE
	int screenheight = size().height;
	
	Thread runner;
	Random randGen = new Random();
	//AudioClip myclip;
	boolean isrunning;
	boolean painted;
	GaltonBoard mydad;
	
	
	public Board(GaltonBoard parent) {
		mydad = parent;
	}
	
	public void doinit()
	{
		//for (int k=0; k<slotballs.length; k++) {
		//	slotballs[k]=0;
		//	}
		//System.out.println(String.valueOf(size().width) + " " + String.valueOf(size().height));
		//System.out.println(String.valueOf(mydad.soundon.getState()));
		screenwidth = size().width;				// DUPLICATE
		screenheight = size().height;
		
		
		maxy=DROPCOORD+(HEIGHT*pinrows)+1;
		firstcol=screenwidth/2-((pinrows)*(SIDE/2))-(BALLSIZE/2);
		barwidth=SIDE-BALLSIZE-4;
		//pauselength = 100;
		//leftprob=(float) .5;
		//myclip = getAudioClip(getCodeBase(), "collide.au");
		//cliparea=new Rectangle(0, 0, screenwidth, screenheight);
		//this.start();
	
	}
	
	public Dimension preferredSize() {
		return minimumSize();
	}
	
	 public synchronized Dimension minimumSize() {
	 	return new Dimension(screenwidth, screenheight);
	}
	
	// Start running a thread
	public void start() {
		if (runner == null) {
			runner = new Thread(this);
			runner.start();
			isrunning=true;
		}
	}
	
	// Stop the thread
	public void stop() {
		if (runner != null) {
			runner.stop();
			runner = null;
			isrunning=false;
		}
	}
	
	// Continually initialize, and drop balls
	public void run() {
		//System.out.println("H");
		while (true) {
			dropAll();
		}
	}					

	// Stop/start the simulation
	public boolean mouseDown(Event evt, int x, int y) {
		if (runner != null) {
			if (isrunning==true) {
				runner.suspend();
				isrunning=false;
			}
			else {
				runner.resume();
				isrunning=true;
			}
		}
		return true;	
	}
	
	// Return coordinates of a certain pin
	public Point pinCoord(int row, int col) {
		Point coord = new Point(screenwidth/2 - ((int) (((float) row/2 - col) * SIDE) + (PINSIZE/2)), 
			DROPCOORD + (row * HEIGHT) - (PINSIZE/2));
		return coord;
	}
	
	public void paint(Graphics g) {
		//System.out.println("paint1");
		g.setColor(new Color(255, 255, 255));
		
		g.fillRect(0, 0, screenwidth, screenheight);
		
		g.setColor(new Color(0, 100, 0));
		
		// Draw each pin
		for (int r = 0; r < pinrows; r++) {
			for (int c = 0; c <= r; c++) {
				g.drawOval(pinCoord(r, c).x, pinCoord(r, c).y, PINSIZE, PINSIZE);
			}
		}
		
		g.setColor(new Color(100, 0, 0));
		
		for (int m = 0; m < pinrows+2; m++) {
			//System.out.println("bars");
			g.fillRect((screenwidth/2-((pinrows+1)*(SIDE/2))+(m*SIDE)-(barwidth/2)), maxy, barwidth, (screenheight-CUTCOORD)-maxy);
			//System.out.println(String.valueOf(screenwidth/2-(pinrows*(SIDE/2))+(m*SIDE)-(barwidth/2)) + " " + String.valueOf(maxy) + " " + String.valueOf(barwidth) + " " + String.valueOf(CUTCOORD-maxy));
		}
		
		g.setColor(new Color(0, 0, 100));
		
		g.drawOval(xpos, ypos, BALLSIZE, BALLSIZE );
		
		g.setColor(new Color(0, 0, 0));
		
		for (int j = 0; j<=pinrows; j++) {
			for (int k = 1; k<=slotballs[j]; k++) {
			g.drawOval(firstcol+(j*SIDE), screenheight-CUTCOORD-(flatball*k), BALLSIZE, flatball);
		}
		//System.out.println("painting");
		}
	}
	
	public void update(Graphics g) {
		painted=true;
		//System.out.println("update1");
		//g.drawRect(cliparea.x-1, cliparea.y-1, cliparea.width+2, cliparea.height+2);
//		g.clearRect(cliparea.x, cliparea.y, cliparea.width, cliparea.height);
		g.clipRect(cliparea.x, cliparea.y, cliparea.width, cliparea.height);
		paint(g);
		//System.out.println("updating");
	}
	
	// Return random bounded integer (unused at the moment)
	public int myRand(int range) {
		float randFloat;
		int randInt;
		randFloat = randGen.nextFloat();
		randInt = (int) Math.floor(randFloat*range)+1;
		return randInt;
	}
	
	// Drop all balls
	public void dropAll() {
		doinit();
		flatball = BALLSIZE;
		// Initialize number of balls in each slot to 0
		for (int k = 0; k < slotballs.length; k++) {
			slotballs[k] = 0;
			}
		//xpos = screenwidth/2 - (BALLSIZE/2);
		//ypos = (DROPCOORD - (PINSIZE/2) - BALLSIZE + 1);
		cliparea = new Rectangle(0, 0, screenwidth, screenheight);
		painted = false;
		
		for (curball = 0; curball<= NUMBALLS-1; curball++) {
			xpos = screenwidth/2 - (BALLSIZE/2);
			ypos = (DROPCOORD - (PINSIZE/2) - BALLSIZE + 1);
			dropBall();
		}
		isrunning = false;
		runner.suspend();
	}
	
	// Drop one ball all the way down
	public void dropBall() {
		//xpos = screenwidth/2-(BALLSIZE/2);
		//ypos = (DROPCOORD-(PINSIZE/2)-BALLSIZE+1);
		slotno = 0;
		for (int i = 1; i<= pinrows; i++) {
			nextPin();
		}
		dropSlot();	
	}	
	
	// drop down one pin
	public void nextPin() {
		pause(pauselength);
		oldx = xpos;
		oldy = ypos;
		
		if (randGen.nextFloat() > leftprob)
			xpos -= (SIDE/2);
		else {
			xpos += (SIDE/2);
			slotno++;
		}
		ypos += HEIGHT;
		
		if (painted==true) {
			//System.out.println("smallset");
			cliparea.x=Math.min(xpos, oldx);
			cliparea.y=oldy;
			cliparea.width=Math.max(xpos, oldx) - cliparea.x + BALLSIZE + 1;
			cliparea.height=ypos - oldy + BALLSIZE + 1;
		}
		else {
			//System.out.println("bigset");
		oldclipx = cliparea.x;
		oldclipy = cliparea.y;
		cliparea.x = Math.min(Math.min(xpos, oldx), oldclipx);
		cliparea.y = Math.min(Math.min(oldy, oldclipy), ypos);
		cliparea.width = (Math.max(Math.max(xpos, oldx), (oldclipx+cliparea.width-(BALLSIZE+1)))-cliparea.x)+BALLSIZE+1;
		cliparea.height = (Math.max(Math.max(ypos, (oldclipy+cliparea.height-(BALLSIZE+1))), oldy)-cliparea.y)+BALLSIZE+1;
	}
		//myclip.play();
		painted = false;
		repaint();
		
	}
	
	// Drop the ball into its final slot
	public void dropSlot() {
		pause(pauselength);
		oldy=ypos;
		ypos = (screenheight - CUTCOORD) - ((slotballs[slotno]+1) * flatball);
		slotballs[slotno] = slotballs[slotno]+1;
		if (ypos <= maxy) {

			flatball=flatball/2;
			cliparea=new Rectangle(0, 0, screenwidth, screenheight);
			repaint();
		}
		else {
			if (painted == true) {
				//System.out.println("smallset");
				cliparea.x = xpos;
				cliparea.y = oldy;
				cliparea.width = BALLSIZE+1;
				cliparea.height = ypos-oldy+BALLSIZE+1;
			}
			else {
				//System.out.println("bigset");
			oldclipy = cliparea.y;
			cliparea.y = Math.min(Math.min(oldy, oldclipy), ypos);
			cliparea.height = (Math.max(Math.max(ypos, (oldclipy+cliparea.height-(BALLSIZE+1))), oldy)-cliparea.y)+BALLSIZE+1;
			}
		}
		//xpos = screenwidth/2-(BALLSIZE/2);
		//ypos = (DROPCOORD-(PINSIZE/2)-BALLSIZE+1);

		painted=false;
		repaint();
	}
	
	// Return basic info
	public String getAppletInfo() {
		return "This applet was written by Julian Devlin. It simulates a Galton board.";	
	}
	
	// Wait a certain amount of time
	void pause(int time) {
		try { Thread.sleep(time); }
		catch (InterruptedException e) { }
	}			
	
}