CSCI 241 Labs: Lab 9
The Game of Life


There are 6 checkpoints , including the clean-up checkpoint, in this lab. You and your partner should work together using just one of your accounts. CHANGE WHO IS CONTROLLING THE COMPUTER AFTER EACH CHECKPOINT! If you need help with any exercise, raise your hand.

Copy the lab materials to your account from /home/student/Classes/Cs241/Labs/Lab09

In this lab you will complete an implementation of the Game of Life. To keep the game manageable, we will use 40 rows and 60 columns on the board.

 

Reading the Initial Board State

We will read an initial cell pattern from a data file. Let's start by looking at one possible input file. We included several input data files inside the directory you copied for this lab exercise.

Go to a terminal window and type cd to go into the Lab09 directory. Once there, type the command:

 more life1.dat
This command lists the contents of the life1.dat data file to the screen. The format of this data file lists just the row and column values of the live cells. Each line contains an (row, column) ordered pair.

What makes this game interesting is that different input files will exhibit very different behavior. That behavior depends on the file's initial pattern of live cells:

Start running BlueJ and open the Lab09 project. There are two classes listed, GameOfLife and GraphicBoard. Most of your work will be done inside GameofLife. GraphicBoard will help us get a more visual display of the data when we reach the last part of the lab exercise.

Edit the GameOfLife class. Look over the code. What kind of actions does it perform? What kind of variables are declared inside main()? Which other methods are included?

In Chapter 8 we learn about class variables and constants. The two integer and two character constants at the beginning of the class are declared at the "class level". This means they are available to all methods in the class without passing their values through parameters.

Just after these declarations you'll see a partially-completed method named makeBoard(). The job of makeBoard() is to create an empty board of characters and return it. The method already contains code that declares and instantiates a 2-dimensional board of characters of the appropriate size.

Add these parts to the method to complete it:

  1. Replace the void return type in the method prototype so that it will be a 2-dimensional array of characters.
  2. Write a nested for loop to initialize each position in this array to hold the EMPTY character constant.
  3. Add a return statement to return the newBoard.

Find the readBoardFromFile() method. It is also stubbed in (partially written). Make these changes so it will work with the data files:

  1. Change the prefix variable to make it point to your Lab09 directory.
  2. Add a while loop to the method to read from the file until the end of the file is reached.
    Hint: Review the "Going Around in Circles" lab to see how to use the hasNext() method in the Scanner class when reading files.
  3. Inside the while loop, read first a row then a column value. Note that integer variables with these names have been already declared inside this method to hold the values.
  4. Set newBoard[row][column] = ALIVE;

To test this code:

  1. Compile the class.
  2. Note: Because your main() needs to get a file name as input, you cannot use Run Main Method within BlueJ to run it. Choose void main(String[]), and then type any of the life_.dat file names, in double-quotes, inside the braces, when the Method Call window appears. (For example, you might type: {"life3.dat"}).
  3. The displayASCIIBoard() method will display the initial board.
    Note: You may want to expand BlueJ's output window slightly to make the board show up better. Use the mouse to grab a lower corner of the window and drag it down.

1 Show us your code. Be ready to answer the following questions:

  1. When calling the main() method, what do the { and } symbols hold?
  2. What characters are used for ALIVE and EMPTY? Why do you think we chose those characters?
  3. How big is life1.dat (in bytes)? Hint: in a terminal window, cd to the Lab09 directory and type
    ls -l to see the contents of this directory.
  4. How big would the data file be if we chose to use a format where each cell was in the file as an individual character, '+' or ' '?

 

Counting the Neighbors

The trickiest part of this program is accurately counting the number of neighbors that are alive. Return to the editor and find the countNeighbors() method. The method is partially written; checking only the three neighboring cells above the current cell. Complete the part of the method that checks the two neighbors on each side of the current cell and the three neighbors below the current cell. Be certain to check for wrapping in both the vertical and horizontal directions.

To test this method,

  1. Go to the main() method and uncomment the lines designated for checkpoint #2.
  2. Run main() with "life5.dat" as its input file.
  3. countNeighbors() is tested 5 times. This is the output you should see to know if your code is correct:
    10,16 neighbors: 2
    0,0 neighbors: 0
    39,59 neighbors: 0
    10,17 neighbors: 3
    8,17 neighbors: 1
    Try other row and column values of your own choosing. Look at what displayASCIIBoard() gives you to identify locations of other interesting cells to test.
2 Show us your completed code.

Be ready to answer the following questions:

  1. Why do we use the oldBoard when counting the neighbors?
  2. Could the % operator be used instead of the range checks on r and c? Why or why not?

 

Evolving

Before you continue, COMMENT OUT the main() method lines you needed only for checkpoint #2 (copyBoard() and countNeighbors() calls).

Find the evolveToNext() method. This method is also stubbed in for you. The prelab describes four different rules for when a cell is ALIVE or EMPTY. Our approach simplifies the code a bit. We first set the entire board to EMPTY, and then set appropriate cells to ALIVE, when needed. Since only the first two rules set a cell to be ALIVE, only the first two rules need to be implemented.

The first rule is implemented in the stubbed method.

  • Add another if to the method to implement the second rule.
  • Here is how to run the main() method:

    1. Make the loop run 50 times.
    2. Uncomment the line inside the loop that runs the evolveToNext() method.
    3. Enter the filename "life3.dat" when the Method Call window appears.
      The ASCII board should be displayed and then redisplayed, 50 times. Within the grid, you will see an "arrowhead"-shaped object move down and to the left across your screen.

    3 Show us your corrected code.

    Be ready to answer the following questions:

    1. What would be the result if we did implement the last two rules?
    2. evolveToNext() contains two if statements, used to follow the rules for the next generation. Would an else if work for the second one? Why or why not?

     

    Displaying the Board Graphically

    Let's move from an ASCII display of the board to a graphical display. The other class in our project, GraphicBoard, extends the ACM Graphics Library GraphicsProgram, so it has these capabilities.

    Back in GameOfLife, uncomment the lines in main() that are designated for checkpoint #4. Run the main() method and you will see that the graphics window appears with a box drawn in it.

    Open the editor window for GraphicBoard. Find the method named drawBoundingBox(). You will see that the method adds 4 GLines to the window. A GLine is simply a line that extends from (x,y) point to another. This is the code which ran and drew the 4 lines in the window.

    The displayBoard() method draws circles using GOvals. A GOval is an oval, with location, height and width. An oval with equal height and width is a circle. As seen in other labs, we had to translate our program's coordinates to pixels. To review:

    1. Pixels work in an (X, Y) coordinate system. Our program works with [row][column]. Note, however, that X is equivalent to column and Y is equivalent to row. Therefore, when we supply pixel coordinates, we write [columnPixel][rowPixel]!
    2. Your instructors placed the origin (0,0) at pixel (50, 50), so we had to add 50 to each value before drawing. Similarly, we assumed each cell is 10 pixels by 10 pixels. Therefore we will multiply by 10 before adding the 50.
    The equations used to translate are:
    x = 50 + 10 * col
    y = 50 + 10 * row
    Look for those lines inside of the already-written code. The two 5's in the same line make the circle have both height and width of 5 pixels.

    You and your partner should now complete the displayBoard() method. Your job is to enclose the lines which draw the circles inside a nested-for loop. Here is your algorithm:

    For each row and column, if the board at that position is ALIVE, draw a circle there. Otherwise, do nothing.

    Now go back to the GameOfLife class and revise the main() method one more time. Look for the notes to replace the call to displayASCIIBoard() with a call to displayBoard().

    Test displayBoard() by running the main() method. Once it is working, modify the loop in the main() method so that displays 500 generations of the board. Run it on different life__.dat input files and see how they look!

    4 Show us your running code. Be prepared to answer the following question:
    Translating from program coordinates to pixel coordinates is a real pain. How could you revise your program to make this easier?

     

    Designing Your Own Data Set

    Run the main method with life5.dat. This is a combination of life1.dat and life3.dat. The arrow gizmo from life3.dat runs into the pinwheel from life1.dat.

    For this checkpoint you will develop your own data files. Go to a terminal window and cd to the Lab09 directory. Enter the command:

    cp life5.dat mylife.dat
    This copies life5.dat to a new file named mylife.dat.

    BlueJ does not allow you to edit non-Java files, so we will have to use a different editor. You can reach a simple text editor named gedit from your main Gnome Applications menu. From the upper left corner of your monitor screen, choose Applications-Accessories-Text Editor to begin running gedit. Navigate to your Lab09 directory and open the mylife.dat file. The editing commands work very similarly to those of the BlueJ editor, so take a moment to try some simple edits. You can move around the file using either the arrow keys or the mouse. You save the file by choosing File-Save (or Ctrl-S).

    Edit mylife.dat to add some extra filled positions. Try to draw a particular shape, such as a pinwheel or arrow, somewhere else within the grid. Test your changes by running the main() method.

    Now try these other files and be ready to describe how their appearances change as the simulation runs:

    5 Show us your modified datafile and describe the running animations.

     

    After the Lab

    Don't forget to exit Firefox before you log out.

    6 Show us that you have logged out, cleaned up, turned off your monitor and pushed in your chairs for this last checkpoint.