// Game of Life simulator by Jonas Lindstrøm Jensen (mail@jonaslindstrom.dk).

import java.applet.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;

public class Game extends Applet implements Runnable, MouseListener {
	int frame; // Number of frame in this game
	int delay; // Delay and animator controlling the fps
	Thread animator;

	// Buttons and stuff
	Button buttonNew;
	TextField textProp;
	Button buttonPat1;
	Button buttonPat2;
	Button buttonPat3;


	int H = 100; // Number of rows
	int W = 100; // Number of columns
	int w = 5; // Width of squares
	int h = 5; // Height of squares
	int[][] alive = new int[W][H]; // Which squares are alive?

	// Dimension and Graphics-class of the offscreen
	Dimension offDimension;
	Image offImage;
	Graphics offGraphics;

	private void paintField(Graphics g) {
		int i,j;

		g.setColor(Color.black);

		for (i=1; i < W; i++) {
			for(j=1; j < H; j++) {
				g.drawRect(i*w, 40 + j*h, w, h); // Draw squares

				if(alive[i][j] == 1)
					g.fillRect(i*w, 40 + j*h, w, h); // ...black if they are alive
			}
		}
	}

	private void writeStuff(Graphics g) {
		int i,j,c = 0;
		
		for (i=1; i < W; i++) {
			for(j=1; j < H; j++) {
				c += alive[i][j]; // How many alive squares are there?
			}
		}
		
		g.setColor(Color.red);
		g.drawString("N=" + c, 20, 10);
		g.drawString("Frame " + frame, 20, 30);
	}

	// GO GO - intialize the thread
	public void start() {
		animator = new Thread(this);
		animator.start();
	}


	public void run() {


		// Remember the starting time
		long tm = System.currentTimeMillis();
		while (Thread.currentThread() == animator) {
			// Display the next frame of animation.
			repaint();

			// Delay depending on how far we are behind.
			try {
				tm += delay;
				Thread.sleep(Math.max(0, tm - System.currentTimeMillis()));
			} catch (InterruptedException e) {
				break;
			}

			// Advance the frame
			frame++;
		}
	}

	/**
	* This method is called when the applet is no longer
	* visible. Set the animator variable to null so that the
	* thread will exit before displaying the next frame.
	*/
	public void stop() {
		animator = null;
		offImage = null;
		offGraphics = null;
	}

	/**
	* Update a frame of animation.
	*/
	public void update(Graphics g) {
		Dimension d = size();

		// Create the offscreen graphics context
		if ((offGraphics == null)
		 || (d.width != offDimension.width)
		 || (d.height != offDimension.height)) {
		    offDimension = d;
		    offImage = createImage(d.width, d.height);
		    offGraphics = offImage.getGraphics();
		}

		// Erase the previous image
		offGraphics.setColor(getBackground());
		offGraphics.fillRect(0, 0, d.width, d.height);
		offGraphics.setColor(Color.black);


		int[][] N = new int[W][H];

			int i,j;

			// Count the number of neighbours of the square not on the border
			for(i=1; i<W-1; i++) {
				for(j=1; j<H-1; j++) {
					N[i][j] = alive[i-1][j-1] + alive[i-1][j] + alive[i-1][j+1] + 
						alive[i][j-1] + alive[i][j+1] +
						alive[i+1][j-1] + alive[i+1][j] + alive[i+1][j+1];			
	
				}
			}


			// The board is cyclic like torus - so counting neighbours on the border is a bit messy...
			for(i=1; i<W-1; i++) {
				N[i][0] = alive[i-1][1] + alive[i][1] + alive[i+1][1] + 
					alive[i-1][0] + alive[i+1][0] + 
					alive[i-1][H-1] + alive[i][H-1] + alive[i+1][H-1];

				N[i][H-1] = alive[i-1][H-2] + alive[i][H-2] + alive[i+1][H-2] + 
					alive[i-1][H-1] + alive[i+1][H-1] + 
					alive[i-1][0] + alive[i][0] + alive[i+1][0];
			}

			for(j=1; j<H-1; j++) {
				N[0][j] = alive[1][j-1] + alive[1][j] + alive[1][j+1] + 
					alive[0][j-1] + alive[0][j+1] + 
					alive[W-1][j-1] + alive[W-1][j] + alive[W-1][j+1];

				N[W-1][j] = alive[W-2][j-1] + alive[W-2][j] + alive[W-2][j+1] +
					alive[W-1][j-1] + alive[W-1][j+1] + 
					alive[0][j-1] + alive[0][j] + alive[0][j+1];
			}


			N[0][0] = alive[1][0] + alive[1][1] + alive[0][1] +
				alive[0][H-1] + alive[1][H-1] +
				alive[W-1][0] + alive[W-1][1] +
				alive[W-1][H-1];

			N[0][H-1] = alive[0][H-2] + alive[1][H-2] + alive[1][H-1] + 
				alive[0][0] + alive[1][0] +
				alive[W-1][H-1] + alive[W-1][H-2] +
				alive[W-1][0];

			N[W-1][0] = alive[W-2][0] + alive[W-2][1] + alive[W-1][1] +
				alive[W-2][H-1] + alive[W-1][H-1] +
				alive[0][0] + alive[0][1] +
				alive[0][H-1];

			N[W-1][H-1] = alive[W-2][H-1] + alive[W-2][H-2] + alive[W-1][H-2] +
				alive[W-2][0] + alive[W-1][0] +
				alive[0][H-2] + alive[0][H-1] +
				alive[0][0];

			// The rules - If N < 2 og N > 3, dead, if N=3 ressurect!
			for(i=0; i<W; i++) {
				for(j=0; j<H; j++) {
					if(alive[i][j] == 1 && (N[i][j] < 2 || N[i][j] > 3)) alive[i][j] = 0;
					if(alive[i][j] == 0 && N[i][j] == 3) alive[i][j] = 1;
				}
			}



		// Paint the frame into the image
		paintFrame(offGraphics);

		// Paint the image onto the screen
		g.drawImage(offImage, 0, 0, null);
	}

	/**
	* Paint the previous frame (if any).
	*/
	public void paint(Graphics g) {
		if (offImage != null) {
		    g.drawImage(offImage, 0, 0, null);
		}
	}
	
	// New random game, where the propability a cell is alive is p	
	private void newSeed(double p) {
		int i,j;
		Random generator = new Random();
		
		for(i=0; i<W; i++) {
			for(j=0; j<H; j++) {
				double r = generator.nextDouble();

				if(r < p) {
					alive[i][j] = 1;
				} else {
					alive[i][j] = 0;
				}
			}
		}	
	}

	// Initialize applet
	public void init() {
		String str = getParameter("fps");
		int fps = 10; //(str != null) ? Integer.parseInt(str) : 10;
		delay = (fps > 0) ? (1000 / fps) : 100;

		float[] hsb = new float[3];
		Color.RGBtoHSB(255,255,255,hsb);
		setBackground(Color.getHSBColor(hsb[0], hsb[1], hsb[2]));

		buttonNew = new Button("New game");
		add(buttonNew);

		textProp = new TextField("0.1");
		add(textProp);
		
	

		buttonPat1 = new Button("Infinite");
		add(buttonPat1);

		buttonPat2 = new Button("Die hard");
		add(buttonPat2);

		buttonPat3 = new Button("Gun");
		add(buttonPat3);

		addMouseListener(this);

		newSeed(0.1);
	}

	public boolean action (Event e, Object args) {
		frame = 0;

		// New random game
		if (e.target == buttonNew) {
			double p = Double.parseDouble(textProp.getText());
			newSeed(p);
		}


		// A pattern that should expand infinitely... it doesn't though
		if (e.target == buttonPat1) {
			alive = new int[W][H];

			alive[57][52] = 1;
			alive[57][54] = 1;
			alive[56][54] = 1;
			alive[53][56] = 1;
			alive[54][56] = 1;
			alive[55][56] = 1;
			alive[52][58] = 1;
			alive[53][58] = 1;
			alive[54][58] = 1;
			alive[53][59] = 1;
		}


		// Die hard - dies out after 130 frames
		if (e.target == buttonPat2) {
			alive = new int[W][H];

			alive[50][55] = 1;
			alive[51][55] = 1;
			alive[51][56] = 1;
			alive[55][56] = 1;
			alive[56][56] = 1;
			alive[57][56] = 1;
			alive[56][54] = 1;
		}


		// A gun firing sliders
		if (e.target == buttonPat3) {
			alive = new int[W][H];

			alive[30][36] = 1;
			alive[30][37] = 1;
			alive[31][36] = 1;
			alive[31][37] = 1;
			alive[40][36] = 1;
			alive[40][37] = 1;
			alive[40][38] = 1;
			alive[41][35] = 1;
			alive[41][39] = 1;
			alive[42][34] = 1;
			alive[42][40] = 1;
			alive[43][34] = 1;
			alive[43][40] = 1;
			alive[44][37] = 1;
			alive[45][35] = 1;
			alive[45][39] = 1;
			alive[46][36] = 1;
			alive[46][37] = 1;
			alive[46][38] = 1;
			alive[47][37] = 1;
			alive[50][36] = 1;
			alive[50][35] = 1;
			alive[50][34] = 1;
			alive[51][34] = 1;
			alive[51][35] = 1;
			alive[51][36] = 1;
			alive[52][33] = 1;
			alive[52][37] = 1;
			alive[54][32] = 1;
			alive[54][33] = 1;
			alive[54][37] = 1;
			alive[54][38] = 1;
			alive[64][34] = 1;
			alive[64][35] = 1;
			alive[65][34] = 1;
			alive[65][35] = 1;
		}


		return true;
	}

	public void paintFrame(Graphics g) {
		paintField(g);
		writeStuff(g);
	}



	// If the mouse is pressed on a square, kill or ressurect it
	public void mousePressed (MouseEvent me) {
		int x = me.getX() / 5 + 1;
		int y = (me.getY() - 40) / 5;

		if (x >= 0 && x < H && y >= 0 && y < W)
			alive[x][y] = (alive[x][y] + 1) % 2;
	}

	public void mouseReleased (MouseEvent me) {} 
	public void mouseEntered (MouseEvent me) {}
	public void mouseExited (MouseEvent me) {}  
	public void mouseClicked (MouseEvent me) {}
}

