
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;


/**
 * This program is a very simple implementation of John H. Conway's famous "Game of Life".
 * In this game, the user sets up a board that contains a grid of cells, where each cell can be 
 * either "living" or "dead".  Once the board is set up and the game is started, it runs itself.
 * The board goes through a sequence of "generations."  In each generation, every cell can
 * change its state from living to dead or vice versa, depending on the number of neighbors
 * that it has.  The rules are:
 * 
 *     1.  If a cell is dead, and if it has exactly 3 living neighbors, then the cell comes
 *         to life; if the number of neighbors is less than or greater than 3, then the dead 
 *         cell remains dead.  (That is, three living neighbors give birth to a new cell.)
 *         
 *     2.  If a cell is alive, and if it has exactly 2 or 3 living neighbors, then the cell
 *         remains alive; otherwise, it dies.  (If a cell has 0 or 1 neighbors, it dies of
 *         loneliness; if it has 4 or more neighbors, it dies of overcrowding.)
 *         
 * It is important that all these changes happen simultaneously in each generation.  When
 * counting neighbors, the 8 cells that are next to a given cell horizontally, veritcally,
 * and diagonally are considered.  Ideally, the board would be infinite.  On a finite board,
 * special consideration must be given to cells that lie along the boundary.  One nice 
 * approach is to consider the left edge to be next to the right edge and the top edge
 * to be next to the bottom edge.  This effectively turns the board into a "torus" (the shape
 * of the surface of a doughnut), which is finite but has no boundary.
 * 
 * This class has a main() routine, so it can be run as a stand-alone application.  The main
 * program simply opens a window that shows a Life board with some control buttons along the
 * bottom.  The user creates the initial board by clicking and dragging on the board to create
 * living cells.  Clicking and dragging while holding down the right mouse button will change
 * living cells back to dead.
 */
public class Life extends JPanel implements ActionListener, MouseListener, MouseMotionListener {
	
	/**
	 * Main program opens a window whose content pane is a JPanel belonging to class Life.
	 */
	public static void main(String[] args) {
		JFrame f = new JFrame("Life");
		JPanel p = new Life();
		f.setContentPane(p);
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.pack();
		f.setLocation(100,50);
		f.setVisible(true);
	}
	
	private final int GRID_SIZE;  // Number of squares along each side of the board; the value is set in the constructors.
	
	private boolean[][] alive;   // Represents the board.  alive[r][c] is true if the cell in row r, column c is alive.
	
	private MosaicPanel display;  // Displays the game to the user..  White squares are alive; black squares are dead.
	
	private Timer timer;  // Drives the game when the user presses the "Start" button.
	
	private JButton  stopGoButton;  // Button for starting and stopping the running of the game.
	private JButton  nextButton;    // Button for computing just the next generation.
	private JButton  randomButton;  // Button for filling the board randomly with each cell having a 25% chance of  being alive.
	private JButton clearButton;    // Button for clearing the board, that is setting all the cells to "dead".
	private JButton  quitButton;    // Button for ending the program.
	
	/**
	 * Create a Life game that has a 100-by-100 grid and shows a Quit button.
	 */
	public Life() {
		this(100, true);  // Simply call the other constructor, with default parameters.
	}
	
	/**
	 * Create a life game that has a grid of a specified size and might or might not have a Quit button.
	 * @param gridSize The number of squares along each side of the grid.  If this is not a positive number, an error will occur
	 * @param showQuitButton A Quit button is shown if this is true.  (This would not be appropriate for an applet.)
	 */
	public Life(int gridSize, boolean showQuitButton) {
		GRID_SIZE = gridSize;
		alive = new boolean[GRID_SIZE][GRID_SIZE];
		setLayout(new BorderLayout(3,3));
		setBackground(Color.GRAY);
		setBorder(BorderFactory.createLineBorder(Color.GRAY,3));
		int cellSize = 600/gridSize; // Aim for abotu a 600-by-600 pixel board.
		if (cellSize < 1)
			cellSize = 1;
		display = new MosaicPanel(GRID_SIZE,GRID_SIZE,cellSize,cellSize);
		display.setGroutingColor(null);
		add(display,BorderLayout.CENTER);
		JPanel bottom = new JPanel();
		add(bottom,BorderLayout.SOUTH);
		clearButton = new JButton("Clear");
		stopGoButton = new JButton("Start");
		quitButton = new JButton("Quit");
		nextButton = new JButton("One Frame");
		randomButton = new JButton("Random Fill");
		bottom.add(stopGoButton);
		bottom.add(nextButton);
		bottom.add(randomButton);
		bottom.add(clearButton);
		if (showQuitButton)
			bottom.add(quitButton);
		stopGoButton.addActionListener(this);
		clearButton.addActionListener(this);
		quitButton.addActionListener(this);
		randomButton.addActionListener(this);
		nextButton.addActionListener(this);
		display.addMouseListener(this);
		display.addMouseMotionListener(this);
		timer = new Timer(50,this);
	}
	
	/**
	 * Compute the next generation of cells.  The "alive" array is modified to reflect the
	 * state of each cell in the new generation.  (Note that this method does not actually
	 * draw the new board; it only sets the values in the "alive" array.  The board is
	 * redrawn in the showBoard() method.)
	 */
	private void doFrame() {
	}
	
	
	/**
	 * Clears the board by setting every cell in the grid to be dead.
	 * (This method only sets the values in the array "alive".  It does not
	 * actually draw the board; that is done in the showBoard() method.)
	 */
	private void doClear() {
	}
	
	
	/**
	 * 
	 ]	 */
	private void showBoard() {
		display.setAutopaint(false);  // For efficiency, prevent redrawing of individual squares.
		for (int r = 0; r < GRID_SIZE; r++)
			for (int c = 0; c < GRID_SIZE; c++) {
				if (alive[r][c])
					display.setColor(r,c,Color.WHITE);
				else
					display.setColor(r,c,null);
			}
		display.setAutopaint(true);  // Redraw the whole borad, and turn on drawing of individual squares.
	}
	
	
	/**
	 * Respond to an ActionEvent from one of the control buttons or from the timer.
	 */
	public void actionPerformed(ActionEvent e) {
		Object src = e.getSource();  // The object that caused the event.
		if (src == quitButton) { // End the program.
			System.exit(0);
		}
		else if (src == clearButton) {  // Clear the board.
			doClear();
			showBoard();
		}
		else if (src == nextButton) {  // Compute and display the next generation.
			doFrame();
			showBoard();
		}
		else if (src == stopGoButton) {  // Start or stop the game, depending on whether or not it is currenty running.
			if (timer.isRunning()) {  // If the game is currently running, stop it.
				timer.stop();  // This stops the game by turning off the timer that drives the game.
				clearButton.setEnabled(true);  // Some buttons are disabled while the game is running.
				randomButton.setEnabled(true);
				nextButton.setEnabled(true);
				stopGoButton.setText("Start");  // Change text of button to "Start", since it can be used to start the game again.
			}
			else {  // If the game is not currently running, start it.
				timer.start();  // This starts the game by turning the timee that will drive the game.
				clearButton.setEnabled(false);  // Buttons that modify the board are disabled while the game is running.
				randomButton.setEnabled(false);
				nextButton.setEnabled(false);
				stopGoButton.setText("Stop"); // Change text of button to "Stop", since it can be used to stop the game.
			}
		}
		else if (src == randomButton) { // Fill the board randomly.
			for (int r = 0; r < GRID_SIZE; r++) {
				for (int c = 0; c < GRID_SIZE; c++)
					alive[r][c] = (Math.random() < 0.25);  // 25% probability that the cell is alive.
			}
			showBoard();
		}
		else if (src == timer) {  // Each time the timer fires, a new frame is computed and displayed.
			doFrame();
			showBoard();
		}
	}
	
	
	/**
	 * The square containg the mouse comes to life or, if the right-mouse button is down, dies.
	 */
	public void mousePressed(MouseEvent e) {
		if (timer.isRunning())
			return;
		int row = display.yCoordToRowNumber(e.getY());
		int col = display.yCoordToRowNumber(e.getX());
		if (row >= 0 && row < display.getRowCount() && col >= 0 && col < display.getColumnCount()) {
			if (e.isMetaDown() || e.isControlDown()) {
				display.setColor(row,col,null);
				alive[row][col] = false;
			}
			else {
				display.setColor(row,col,Color.WHITE);
				alive[row][col] = true;
			}
		}
	}
	
	
	/**
	 * The square containg the mouse comes to life or, if the right-mouse button is down, dies.
	 */
	public void mouseDragged(MouseEvent e) {
		mousePressed(e);  // Dragging the mouse into a square has the same effect as clicking in that square.
	}
	
	public void mouseClicked(MouseEvent e) { }  // Other methods required by the MouseListener and MouseMotionListener interfaces.
	public void mouseEntered(MouseEvent e) { }
	public void mouseExited(MouseEvent e) { }
	public void mouseReleased(MouseEvent e) { }
	public void mouseMoved(MouseEvent e) { }
	
	
}
