CSCI 150: Lab 7
The Game of Life!
Due: 11:59 PM on Tuesday, April 6 13
The purpose of this lab is to:
- Explore the Game of Life
- Animate a simulation
- Practice working with classes and Object-Oriented Programming
Optional Prelab
We have put together a set of optional prelab questions (with an answer key) that will help you work through some of the ideas related to this lab before you start doing any programming. You can find these questions here in Prelab 7. It is highly recommended that you read through the prelab before working on the lab.
Part 1 - The Game of Life
life.py: 40 points, partner encouraged.
The Game of Life, created by mathematician John Conway, is what is referred to as a cellular automaton. It is a set of rules which are used to generate patterns that evolve over time. Despite the name, the Game of Life is not a game; it is really a simulation. In some ways, it can be thought of as an extremely simplified biological simulator which produces unexpectedly complex behavior.
The Game of Life is played on an infinite board made up of square cells. It takes way too long to draw an infinite board, so we'll make do with a small finite piece. Each cell can be either live or empty. We'll indicate a live cell as a red square, and a empty cell as a white one. The board begins in some initial configuration, which just mean a setting of each cell to be either live or empty (generally we'll start mostly empty cells, and only a few live cells).

The board is repeatedly updated according to a set of rules, thereby generating a new configuration based on the previous configuration. Some empty cells will become live, some live cells will disappear, and some cells will be unchanged. This configuration is then updated according to those same rules, producing yet another configuration. This continues in a series of rounds indefinitely (or until you get bored of running your simulation).
Rules of Life
The rules are pretty simple: to figure out whether a cell (x,y) will be live or empty in the following round, you just look at the 8 neighbors of that cell (those that share a corner or an edge, so N, S, W, E, NW, NE, SW and SE). What happens to (x,y) next round depends on the number of its neighbors who are live and whether it is currently live or not. In particular:
- If a cell has 4 or more live neighbors, it disappears (overcrowding).
- If a cell has 0 or 1 live neighbors, it disappears (loneliness... *sniff*).
- If a cell has exactly 2 live neighbors, nothing changes (if it was live, it stays live, if it was empty, it stays empty).
- If a cell has exactly 3 live neighbors, it comes to life (I don't have a good explanation for this one, but it does seem to make for cool patterns).
For example, consider the following 3 initial configurations, and the two configurations that follow each.

In the first example, both live cells have only one live neighbor, so they both disappear. Any empty cell has at most two live neighbors, so no new live cells spawn. Thus in one step, there are no live cells. Clearly, at this point, the configuration is stable.
In the second example, two live cells have only one neighbor, so both disappear. But the third cell lives, and the cell to its immediate left has exactly 3 live neighbors, so it spawns. On the next iteration, we find ourselves in a case similar to the previous example, and all cells disappear.
Note that we can't set a cell to be live or empty the instant we determine its status for the subsequent round; we will likely need to know whether it is alive or empty on this round to determine the future status of other nearby cells. To see this, consider the second example. We can immediately tell that the top-most live cell will disappear. But had we set it to empty immediately, then when we got to the second live cell, it would have only had 1 live neighbor and we would have (erroneously) determined that it too must disappear. Thus it is critical that we first determine for every cell whether or not it will be live, and only after doing so update the status of each.
In the last example, all currently living cells disappear; the middle cell has too many neighbors, and the other have too few. However, four empty cells have exactly 3 live neighbors, and so those cells spawn. In the following round, there are neither cells that disappear nor cells that spawn, so we have a stable configuration. In general, a pattern that doesn't change is called a still-life.
While all of these patterns stabilized quickly, some patterns take a long time to stabilize, and some never do. Of those that never stabilize, some at least have a regularity to them; they eventually eventually repeat states. These are said to be oscillators. Others never repeat the same state again, and produce an infinite number of configurations.
Describe the Problem: |
Write a program life.py that simulates the game of life for a given number of iterations, displaying the state of the board graphically at each step. |
Understand the Problem: |
Make sure your answers from your prelab were correct. If not, go back and figure out what went wrong. |
Design an Algorithm: |
We will be creating the game of life using two different classes: one for the game board, and one for each cell. Cell ClassThe Cell class represents square cells in the Game of Life. Each turn of the game, each cell will be updated based on the rules of the game. Because each cell has a different location and a different status (i.e., alive or empty), the Cell class should have four instance variables:
The Cell class should also have the following methods:
Dealing with Edges (Donuts!)
The edges of the board tend to be problematic. For example, if you are considering cell (WIDTH-1,HEIGHT-1) and you attempt to check the liveness of its neighbors as you would for most cells, you'll end up attempting to access positions whose indices are too large. You may have run into this problem in Lab 4 doing operations such as scroll and blur. One natural fix is to simply create a board that is 2 units taller and 2 units wider than you actually want, and only do updates on non-border cells. This approach would work fine, but on this lab you're going to do something a bit different; we're going to use a torus rather than a plane for our game board. A torus is just the technical name for the shape of a donut. OK, so what does that mean here? It means that if you were to walk off the right side of the board, you'd appear on the left side at the corresponding position, and vice-versa. Likewise if you walked off the top of the board, you'd appear on the bottom. This means every cell has 8 neighbors now. Consider cell (0,0). It has the 3 obvious neighbors in the directions E, SE and S. If we want to find the N neighbor, we have to wrap around the top of the board, bringing us to (0, h-1). Similarly, the NE neighbor would be (1, h-1). The NW neighbor would be (w-1, h-1), and so on. One reason this is so handy is that if you use the mod operator appropriately, you don't need any special cases to handle edges or corners. In particular, say we are working with a Cell at location self.x and self.y. Then some of the neighbors of this Cell are at locations:
Alright, so what did this have to do with donuts? Well, since the top of the board is now effectively connected to the bottom of the board, you could imagine the board as a flexible square of rubber, and this connection could be represented by gluing the top edge to the bottom edge. Now we've created a rubber tube. But the left and right edges are also connected. If we bend our tube and glue these edges together, we've created a torus. Mmmm... donuts. Board ClassThe Board class will represent the collection of all of the Cells that make up the entire game. Thus, it should have one instance variable:
The Board class should have the following methods:
|
Implement a Design |
Testing your Cell Class FirstTo help you debug your Cell class before working on the Board class and later complete the Game of Life, we have provided a test program test_cell.py that will use the Cell class that you implemented in the life.py file. If your Cell class is implemented correctly, you should see the following output when you run the test_cell.py program: Cell 5,6 has 2 alive neighbors .......... .......... .......... .......... .......... .....xxx.. .......... .......... .......... .......... .......... .......... .......... .......... ......x... ......x... ......x... .......... .......... .......... .......... .......... .......... .......... .......... .....xxx.. .......... .......... .......... .......... Final Program NarrativeAfter you implement the Cell and Board classes above (and know that your Cell class is working), you should create a main function that does the following:
Aging Cells (Optional)Keep track of how many rounds each live cell has been alive, and color the cell based on that age. For example, you might have newborn cells begin red but slowly turn blue as long as they stay alive. |
Maintain: |
Be sure to comment your code once you're done if you haven't been doing so along the way. |
Part 2 - Wrap Up
Congratulations! You have finished the seventh lab. As with every lab, your last job prior to submission is to complete a brief write-up by filling out a Google Form, which is also how you submit your Honor Code statement (so please do not forget to do this).
Handin
Finally, all you need to do is submit your solution to the assignment. To do so, please click on the "Submit" button in the top right of your programming environment. This will save all of your programs for myself and the graders to look at. You are welcome and encouraged to submit partial solutions to your assignment -- you can always resubmit by pushing the "Resubmit" button in the top right, which will save the latest version of your programs. We will only grade the latest version that you submitted. By submitting multiple times, you avoid accidentially forgetting to turn in any completed part of your assignment, especially in case you become busy around the lab due date.
Grading Rubric
Cell Class (14 points)
Points | Rubric Item |
---|---|
+2 | Started a Cell class |
+4 | Correct Constructor |
+4 | Correct countNeighbors method |
+4 | Correct update method |
Partial Credit
Points | Rubric Item |
---|---|
+2 | Constructor was close, but had some errors |
+2 | countNeighbors method was close, but had some errors |
+2 | update method was close, but had some errors |
+1 | Constructor was started |
+1 | countNeighbors method was started |
+1 | update method was started |
Board Class (18 points)
Points | Rubric Item |
---|---|
+2 | Started a Board class |
+4 | Correct Constructor |
+4 | Correct initialize method |
+4 | Correct update method |
+4 | Correct draw method |
Partial Credit
Points | Rubric Item |
---|---|
+2 | Constructor was close, but had some errors |
+2 | initialize method was close, but had some errors |
+2 | update method was close, but had some errors |
+2 | draw method was close, but had some errors |
+1 | Constructor was started |
+1 | initialize method was started |
+1 | update method was started |
+1 | draw method was started |
Main Function (8 points)
Points | Rubric Item |
---|---|
+1 | Asks the user for a setup number |
+1 | Creates an instance of the Board class |
+1 | Initializes the Board |
+1 | Creates a Picture object to draw the board |
+1 | Draws the initial Board configuration |
+1 | Runs for multiple steps (should be 100) |
+1 | Updates the Board each step |
+1 | Draws the Board each step |