The Game of Life is a cellular automaton developed by mathematician John Conway. It takes place on an infinite grid, where each box can either be “alive” or “dead”. There are only four rules to the Game of Life:
- Any livhing cell that has one or less living neighbors dies of loneliness.
- Any living cell that has more than three living neighbors dies of overpopulation.
- Any dead cell with exactly three lving neighbors comes alive through reproduction.
- All other cells remain the way they were.
Task to Complete
Develop a program that simulates the Game of Life, using the four rules. In addition, the program must:
- be interactive with the user
- be presented using graphics
- take care of “boundary” situations to represent an infinite plane
Step 1: Program Structure
Because this is a relatively simple task, I only used one GameOfLife class to run the simulation. This class would possess a boolean 2D matrix, to represent the grid of cells, with living as true and dead as false. (It was also possible to create a matrix of Cell classes, but this is not necessary as a boolean variable can sufficiently represent a cell.)
Apart from the 2D matrix, this class would also possess the final instance variables NUMROWS and NUMCOLS. These two variables will store the dimensions of the matrix, for convenient use later on.
In the main method of this program, a GameOfLife object will be constructed. This will call the constructor of this class, which will:
- initialize the matrix with NUMROWS and NUMCOLS
- include an infinite loop to run an update() method
The update method will be used to process the rules and move on to the next generation.
Step 2: Display Function
Now that the structure for the program has been created, the graphics will have to be set up to display the contents of the boolean matrix. First, I let the GameOfLife class extend the JFrame class, for purposes of convenience later on. I also created a setUpGraphics() method, called in the constructor, to set up the GUI of the simulation. This method involves creating a JPanel called board on which the grid will be displayed. Again, for convenience purposes, I created the final instance variables WIDTH and HEIGHT to store the dimensions of the JFrame and JPanel.
Having created a JFrame and JPanel, I added a new display() method to display the contents of the matrix. This method uses nested for loops to loop through the matrix, and depending on the boolean variable, draws a black or white square on a BufferedImage with the fillRect method. At the end of the loop, this BufferedImage is drawn onto board.
To make the display() method easier, some changes and additions were made to the designation of the instance variables:
- To simulate an infinite plane, the display() method should only show display a part of the matrix and leave the boundaries off the window. Therefore, the final instance variable EXTRA was created to represent how much of the matrix to leave off on each side. (Ex. EXTRA = 10 means that 10 rows/columns will be left off of each side.)
- I also added the final instance variable SIZE to represent the size of the blocks. Because of this, I changed NUMROWS and NUMCOLS into non-final instance variables, to be calculated in the constructor by dividing WIDTH and HEIGHT by SIZE (and adding 2*EXTRA to account for the border issue).
In order to test the display() method, I created a randomize() method to generate a random boolean 2D matrix (with the dimensions numRows by numCols). In the constructor, I called this method and also added display() to the infinite loop.
Step 3: Game of Life Rules
Having developed a display() method, I now added the rules of the Game of Life into the update() method. To do this, an auxiliary matrix, with the same dimensions, must first be created so that changes that are going to happen during a turn do not affect other changes supposedly happening simultaneously.
Now, with nested for loops, the program loops through all the cells in the matrix and computes the number of neighbors for each. (This process uses another set of nested for loops in looping through each neighbor, checking if it is alive.) Now, there is a set of if-else if-else statements that uses the four given rules to determine what changes will occur to this cell; any changes are made in the auxiliary matrix. At the end, the reference of the original matrix is set to the auxiliary array so that it becomes the “real” matrix that is used in displaying and further updating processes.
*Note: The display() method was moved into the update() method, rather than being left in the constructor, because there is no reason in updating the display if there are no new changes being made.
This was the result after compiling and running the code:
*Note: There are patterns halfway on and halfway off the window, showing that the “boundary” situation has been successfully taken care of. (The display() method is not showing the entire grid.)
Step 4: User Interaction
Having developed a program capable of simulating the Game of Life, interactive features must be added. In particular, the user must be able to:
- pause the simulation
- add or remove cells from the grid
To do this, I first let my GameOfLife class implement MouseMotionListener and MouseListener, and used the method addMouseListener() and addMouseMotionListener() to allow my JPanel to listen for mouse clicks, movements, etc.
With these interfaces implemented, I used the mouseMoved method to update the position of the mouse in newly-created instance variables mouseX and mouseY, which store the current position of the mouse.
Next, in mouseClicked, I added an if statement to determine which button (left or right) was was pressed. If the left was pressed, the grid corresponding to the position of the mouse would be changed to the opposite of what it originally was. So if the user clicks an living cell, it dies, or if it clicks an empty space, a living cell appears there.
*Note: To enhance this feature, I modified the display() method to accept two integer parameters; at the grid location corresponding to these two integers, the cell will be highlighted. Therefore, I am able to highlight the newly-added cells.
Next, to create a pausing feature, I created another boolean instance variable running, set to true in the constructor, to determine whether or not the program will run with an if statement nested in the infinite loop.
With this in place, I allowed the mouseClicked method to change running to the opposite of what it was if the right mouse button was pressed. Therefore, if the programming is running, right-clicking will pause it; if it is paused, right-clicking will resume it.
With these features in place, the task has been completed. The program is able to simulate the Game Of Life with graphics, and is interactive with the user through adding/removing cells and pausing.
Step 5: Additional Features
With the basics in place, I added some extra features to improve the Game Of Life simulation. Here is a list of the additional features:
- I added the instance variables generation and population to keep track of the generation and population in the simulation. These are displayed with JLabels on an additional JPanel at the top of the window.
- Another JPanel was added to the bottom of the window to house additional controls.
- First, in addition to right-clicking, a pause/resume JButton was added. The text displayed on the button changes from “Pause” to “Resume” depending on the current state of hte program.
- Another JButton was added to restart the simulation. When pressed, a JOptionPane confirmDialog message pops up to confirm whether or not to user wants to restart. If not, the program returns to its original state. If yes, the randomize() method is called again.
- A JSlider was added to allow the user to adjust the speed of the simulation. Using Java.util.concurrent.TimeUnit, the program can be “slept” for a amount of time every time it passes through the infinite loop. This JSlider controls this sleep-time to change the simulation speed. A JLabel is also placed beside the JSlider that categorizes the speeds into “Max”, “Very fast”, “Fast”, “Medium”, etc.
Here is the final result from running the simulation: