package Final_Project_JMedernach; /** * Joshua Medernach * 4-19-13 * CSCI 2120-601 * Conway's Game of Life */ /** * This class is the GUI aspect of Conway's Game of Life. This class also * acts as an Observer to the LifeEngine. * * This is where all the sparkly stuff happens! */ import java.util.Observer; import java.util.Observable; import java.awt.*; import javax.swing.*; import javax.swing.JFrame; import java.awt.Color; import java.awt.event.MouseListener; import java.awt.event.MouseEvent; import java.awt.event.MouseAdapter; import java.awt.event.KeyListener; import java.awt.event.KeyEvent; import java.awt.event.KeyAdapter; public class LifeGUI implements Observer{ private LifeEngine target; private boolean[][] grid; private JFrame frame; private Container contentPane; // The content pane of the JFrame private int pressCount; // variable for KeyboardListener class private boolean isRunning; // variable for KeyboardListener class /** * The Constructor registers the object of this class to be the "observer" * of the given model, namely an object of the LifeEngine class. The * constructor, then, sets up the JFrame and adds two Listeners to it. * * The frame, in the end, gets drawn on a grid and set to visible. */ public LifeGUI(LifeEngine model) { target = model; target.addObserver(this); grid = target.getGrid(); pressCount = 0; isRunning = false; frame = new JFrame("Conway's Game of Life by Joshua Medernach"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(832,854); //Grid size is 832,854 frame.setLocationRelativeTo(null); frame.setResizable(false); frame.addMouseListener(new CoordinateListener()); frame.addKeyListener(new KeyboardListener()); // Draw the grid onto the JFrame contentPane = new DrawGrid(); frame.add(contentPane); frame.setVisible(true); } /** * update() allows the object of LifeGUI to be notified. Once notified, * the GUI will redraw the updated grid that is recieved from the model the * GUI is observering. */ public void update(Observable o, Object arg) { grid = ((LifeEngine)o).getGrid(); // Redraw the grid // In attempt to reduce lag of the game, we remove the content pane and // replace with a new content pane with new redrawn grid instead of repaint. // Repainting new cells over old cells can produce quite a bit of lag so // we do not want to do it that way. frame.remove(contentPane); contentPane = new DrawGrid(); frame.add(contentPane); frame.validate(); } /** * This is a drawing class. It draws the grid and cells onto a JPanel. */ private class DrawGrid extends JPanel { @Override public void paintComponent(Graphics g) { super.paintComponent(g); /** * This will be a fairly complex problem trying to draw all three kinds of * shapes: * -The black lined grid * -The dead cells * -The live cells * * But we see we can break it down to three problems for simplicity */ // Draw the black lined grid. Note: each grid box is 11x11 pixels g.setColor(Color.black); // Int i represents the number of rows in a grid int x; // x-coordinate for the Drawer int y; // y-coordinate for the Drawer for (int i = 0; i < 75; i++) { // We set the y-coordinate here y = i*11; // Int j represents the number of cells in a row for (int j = 0; j < 75; j++) { x = j*11; g.drawRect(x,y,11,11); } } // Draw all the dead/live cells. Note: each cell is 10x10 pixels // Reset the Drawer's coordinates x = 0; y = 0; for (int i = 0; i < 75; i++) { y = (i*11)+1; for (int j = 0; j < 75; j++) { x = (j*11)+1; // Finally, check the status of cell at the grid[i][j] if (grid[i][j] == LifeEngine.ALIVE) g.setColor(Color.yellow); // Yellow means alive else if (grid[i][j] == LifeEngine.DEAD) g.setColor(Color.gray); // Gray means dead g.fillRect(x,y,10,10); } } } } /** * This class is a subless of the MouseListener. It only detects a mouse click, * analyzes the location of the mouse click and sends a command to the * object of LifeEngine the GUI is observing. */ private class CoordinateListener extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { int x = e.getX(); int y = e.getY(); int processedX = getRealCoordinateValue(x-3); int processedY = getRealCoordinateValue(y-25); // Let us check to see if the mouse click is not on a border // Also, we check to see if the coordinate is not out of bounds of the grid if ( !(processedX == -1) && !(processedY == -1) && (processedX > -1) && (processedX < 75) && (processedY > -1) && (processedY < 75)) { // Not on a border nor out of bounds, mouse click is inside a cell! // After obtaining the real coordinates for the grid, send the command // to change the state of the cell (to dead or to alive). target.changeStatus(new Coordinate(processedY,processedX)); } // Else, mouse click was on a border or out of bounds of the grid. // Do nothing as it did "not register". } /** * This method processes a raw value from the mouse click coordinate into * a real coordinate value for the grid. * * Returns -1 if the raw value lies on a border */ private int getRealCoordinateValue(int rawValue) { int result = 0; if ((rawValue%11)==0) result = -1; // Click was on the border, hence does "not" register else { // Click was in a box, so it registers int moldable = rawValue; while((moldable - 11) > 0) { moldable = moldable - 11; result++; } } return result; } } /** * This class is a subclass of the KeyListener. It listens specificially for * the 's' and 'e' button presses. It sends a command to the LifeEngine * object the GUI is observing. */ private class KeyboardListener extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { // The Start Button if (e.getKeyChar() == 's') { // The key pressed was the 's' button pressCount++; isRunning = true; // Game is running } // The Stop Button if (e.getKeyChar() == 'e') { // The key pressed was the 'e' button pressCount = 0; isRunning = false; // Game is not running } // Run the game when the Start Button is pressed /** * Note: I added pressCount because I discovered a funny glitch where if * you press 's' button again after the stimulation started, the * stimulation runs faster, and faster if pressed again. Eventually, * the stimulation runs so fast it trips itself up and starts * miscalucating the status of cells. Obviously, we want to limit the * number of times the 's' button is pressed. * * The reason why the stimulation runs faster when 's' button is * pressed again is because multiple threads are being created and * ran at the same time. */ if ((pressCount == 1) && (e.getKeyChar() == 's')) { Thread thread = new Thread(new Runnable() { public void run() { while (isRunning) { try { Thread.sleep(100); // Slow down the motion of cells on grid } catch (Exception e) {} // Do nothing // Send command to the target model to advance target.advance(); } } }); thread.start(); } } } }