CPSC 225, Spring 2002
Programming Assignment 3
Conway's "Game of Life"
CONWAY'S "GAME OF LIFE" is not really a game, and it doesn't have anything obvious to do with life. It is, however, a traditional programming assignment. For your third programming assignment in this course, you will write a version of the "Game of Life" that uses the ncurses library to display the board and to allow the user to control the game.
This is a two-week assignment. It is due in class on Wednesday, February 13. Please turn in a printout (made using a2ps), and put copies of the source code and compiled program in your homework directory.
Work with a friend? You can do this assignment with a partner, if you want. (No more than two people in a group.) In that case, you should at least implement "torus mode" as described under the "Enhancements" section below. Don't forget to tell me whose homework directory to look in.
The "Game of Life" is an example of a cellular automaton. You have to imagine an infinite, two-dimensional grid of squares or "cells". Each square is occupied by a not-very-intelligent little automaton (a fancy word for a computing device). Each automaton can be in one of two states, which are called "alive" and "dead". There is a universal clock that goes "tick tock tick tock...". On each "tick", each automaton counts the number of "alive" automata among its eight neighbors (horizontal, vertical, and diagonal). On each "tock", each automaton can change its state according to the following rules:
- If the current state is "dead" and if the automaton has exactly three "alive" neighbors, then the automaton changes to the "alive" state. (Exactly three living cells give birth.)
- If the current state is "alive" and if the automaton has fewer than two neighbors or more than three "alive" neighbors, then the automaton changes to the "dead" state. (A live cell dies of loneliness if it has zero or one living neighbors, and it dies of overcrowding if is has more than three living neighbors.)
To play the Game of Life, you bring some of the cells to life, start the clock, and see what happens. Even though the behavior of each individual automaton is very simple, the pattern of living and dead cells can have interesting behavior over time.
For the assignment, you will simulate the Game of Life. Of course, you won't be able to use an infinite board. Instead, you will use a finite, two-dimensional array as the playing board. The state of the game will be displayed as a two-dimensional grid of characters arranged in rows and columns. A blank character will represent a "dead" cell, and an asterisk (*) will represent an "alive" cell. The board will be displayed using the ncurses library, along with a menu that will allow the user to run the game and to set up the initial state. In fact, of course, you will use the menu from Programming Assignment 2, and you will show the board in the large blank area that occupied most of the screen in that assignment. For this assignment, you will implement all the menu commands except for the "Load from File" and "Save to File" commands, which will be part of a future assignment. Here is what the commands should do:
- Manual Input --- Lets the user change the board by hand. The cursor moves to the center of the board. The user can move it around with the up, down, left, and right arrow keys. When the user hits the space key, the current cell is set to be "dead", if it isn't already, and the cursor moves to the next cell. When the user types any non-space character, the current cell is set to be "alive", if it isn't already, and the cursor moves to the next cell. When the user presses the return key, manual input ends. (Be careful not to move the cursor off the board!)
- Run --- Set the clock to running, and show the changing pattern of dead and alive cells. Continue until the user presses the return key.
- Next Generation --- Compute and display the next "generation" of the game. That is, do one "tick,tock" of the clock.
- Clear --- Set all the cells to "dead".
- Random Fill, 25% --- Fill the board with random values, with each cell having a 25% probability of being alive.
- Random Fill, 50% --- Fill the board with random values, with each cell having a 50% probability of being alive.
- Random Fill, 75% --- Fill the board with random values, with each cell having a 75% probability of being alive.
- EXIT --- Terminate the program.
You will find my compiled version of the game of life in the directory /home/cs225/life. The name of the program is also life. This version does everything that you need to do for this assignment. It also handles files, and you will find a few sample files that you can load into the program if you are curious. Remember that you do not have to handle the Save and Load commands for this assignment.
Here are a few points of advice for your program:
- My program assumes a window size of 24 rows and 80 columns. If the window is smaller, it prints an error message and quits. If the window is bigger, it only uses a 24-by-80 section of the window. You will almost certainly want to do the same thing, since working with two-dimensional C-style arrays is very difficult (that is to say, it is impossible) if you don't know the size in advance. See the "Enhancements" section, below, for a solution to the size problem.
- You will need a two-dimensional array to store the state of each of the cells on the playing board. I strongly suggest that you use an array that is two rows and two columns bigger than the board. Use the extra columns to create an invisible border of dead cells around the actual data for the board. I will discuss this further in class. Since this array is so important, you can make it a global variable if you want. (My board size is 22 by 58, and my array is 24 by 60.)
- In order to compute one "generation" of cells, you will need a second array. Construct the new generation in the second array, then copy the contents of the second array into the first array. Don't forget to update the screen as well, to reflect the changes in the data.
- To implement the Run command, use a while loop that continually computes new generations until the user presses return. Each time through the loop, call getch(). If the user presses the return key, this returns the character '\n'. Use the timeout() function to set a time out, so that getch() won't wait forever for the user to press a key. The parameter of timeout determines how fast the generations will go by. In my program, I use timeout(100).
- You will need to use random numbers to implement the random fill commands. Random numbers in C++ are discussed on pages 99 to 102 of the textbook. Unfortunately, it doesn't mention that you should initialize the random number generator. For information on this, see the sample program random_numbers in the directory /home/cs225/life.
Enhancements
There are several ways that the Life program can be enhanced. You are not required to do any of these, unless you are working with a partner. (If you are working with a partner, you should at least implement Torus Mode.)
Torus Mode: Cells along an edge of the board don't have as many neighbors as cells in the center, and this tends to distort what happens along an edge. A solution to this is to imagine that the cells along the left edge of the board are actually next to the cells along the right edge of the board, and the cells along the bottom are next to the cells along the top. This only affects the way that you count neighbors, making it substantially more complicated. If you implement torus mode, it's nice to have a menu item that can be used to turn it on and off.
Variable run speed: As the Run command is executed, you might use the number keys 1, 2, 3, 4 and 5 to control the speed at which the Game is run. Just set the value of timeout() when one of these keys is pressed.
Variable-sized board: Although variable-sized C-style two-dimensional arrays are impossible, something similar is possible in C++. It uses the vector template from the Standard Template Library. (This is discussed in Section 7.3 of the text.) You don't have to understand vectors in order to use them. Here is how you can make the size of your board depend on the size of the window in which your program is running. I assume that you want to use a two-dimensional array of bools :
Include the vector header file: #include <vector> Outside of any function, define the type BoolArray2D as follows: class BoolArray2D : public vector< vector<bool> > { public: void setSize(int rows, int cols) { resize(rows); for (int r = 0; r < rows; r++) (*this)[r].resize(cols); } }; (There must be a space between the two >'s). Declare a variable of type BoolArray2D. For example BoolArray2D alive; This gives an array that has size zero. When you know the number of rows and columns that you want, use the setSize() method to set the size of the array. For example: alive.setSize(rows+2,columns+2); Now, you can use alive in exactly the same way that you would use a normal 2-dimensional array of bools.A version of the program that implements all these enhancements is (or will soon be) in the /home/cs225/life directory. You can probably come up with other enhancements. For example, you might try to enhance manual input mode with some hot keys.
David Eck, January 2002