Introduction to Computer Programming

Program 8

Conway’s Game of Life

Due: Monday, December 5 at 5pm


Instructions


Details

Conway’s Game of Life

Your goal for this assignment is to implement Conway’s Game of Life. You are encouraged to familiarize yourself with the game and its history. In particular, you should read “The fantastic combinations of John Conway’s new solitaire game ‘life’” by Martin Gardner from the October 1970 issue of Scientific American. (The final set of diagrams is not included in this version of the article, but that should not prevent you from understanding how the “game” works.) You are strongly encouraged to experiment with an existing implementation of the game such as this or this or Golly (a very powerful downloadable version of the game).

columns v. rows

It is important to remember that when specifying the total number of columns and rows in a board, each is just an integer, but the order matters. Likewise, when specifying the coordinate - the specific column and row of a cell in the board - both are integers and order matters. (3, 4) is not the same as (4, 3).

When using the paint method from the grid_graphics module, the coordinates are specified as an ordered pair (a tuple of length two) where the first item in the pair is the horizontal (x) coordinate (the column number) and the second item is the vertical (y) coordinate (the row number).

However, the standard convention for representing a two-dimensional “array” is as a list of lists and thinking of that as a list of rows. This means we specify the row number first when accessing the elements directly in a list of lists. So if we have a grid g, and wanted to refer to what is at position (3, 4) - i.e., column 3 (counting from 0) and row 4 (counting from 0), we would write:

 g[4][3]

This can lead to hard to find bugs where a value intended to specify a column number is instead used as a row index and vice versa. To combat that problem we will use two functions get_cell and set_cell that will abstract the problem of accessing and updating the contents of our lists of lists.

starter archive

The starter archive is a zip file that represents a folder (hw8) which contains a collection of Python files and text files. The Python files are:

The text files are:

runnable solution

You are welcome (and encouraged) to experiment with the runnable solution module (hw8x.py) that is included in the starter archive. If you import that file in your working file (hw8.py) as in:

import hw8x
    

then you can use it to (temporarily!) use my solutions if you get stuck on a problem so that you can proceed. For example, to make toggle_cell work you could use:

def toggle_cell(grid, p):
    hw8x.toggle_cell(grid, p)

The assignment itself

  1. Download and unzip the starter archive. It contains hw8.py (the starter file, where you will write all your code); grid_graphics.py, and hw8x.py (the runnable solution), and hw8_helper.py which has functions provided for your convenience, but kept in a separate file to keep hw8.py from becoming too sprawling. hw8.py as provided automatically runs the main gol function when the module is run in IDLE. Experiment with what it does out of the box. In particular, try using the h (for help) and q (for quitting) keys. Remember to have the GUI respond to key presses, the graphics window must have the “focus”.


  2. Complete set_cell(grid, p, active=True) so that it sets the cell at column-row pair p in grid to be the value of active. This is meant as a warm-up function and it should be similar to get_cell which has been completed for you. However, set_cell is side-effecting: it mutates grid and should not return anything. Once completed correctly, you should never need to use the [y][x] style notation for accessing the contents of a list of lists anywhere else in the assignment - you can always use some combination of get_cell and set_cell instead. You can test the function by using the f and c keys in the GUI which should now have the effect of filling and clearing the grid with the active or inactive cells .

  3. Complete toggle_cell(grid, p) so that it inverts the contents of cell at column-row pair p in grid. It should both change the contents of the cell and effect that change at the corresponding location in the graphics window. Call get_cell, set_cell, and update_cell. Test using the t key which should toggle (invert) the entire graphics board.

  4. Add a click_handler(p) function that assumes p is a column-row pair and calls toggle_cell on the global _board at the specified point p. Use set_click_handler in the main gol function before the event-loop begins. Test by clicking on the on the graphics grid - that should have the effect of toggling whether clicked upon cells become active or inactive.


    These next several problems combine together to play Conway’s Game of Life’s for one generation. Namely, for every cell in _board: if a cell is currently active and has 2 or 3 active neighbors than it remains active; otherwise it becomes inactive. If a cell is currently inactive and has exactly 3 active neighbors it becomes active.

  5. Complete count_neighbors(grid, p) so that it returns the number of active cells that “touch” the specified point p in grid. (This problem is closely related to lab A, problem 6. If you want, you can write a version of that function first, to assist.) The most it can return is 8. Take care not to consider coordinates that are out of bounds (negative or beyond the number of rows and columns of grid).

  6. Complete compute_changes(grid) so that it returns a pair of lists: the first is a list of the coordinates of newly active cells (those that are “born”), the second is a list of coordinates of newly inactive cells (those they have just “died”). Call count_neighbors.

  7. Complete evolve_once(grid) so that it plays Conway’s Game of Life for one generation. (Hint: use compute_changes, set_cells, and update_cells.) Test using the space-bar to evolve the game one “generation” at a time.

  8. Complete evolve_loop() so the game evolves continually. This is meant to be almost trivial (to get this started) once the previous problems are complete: call evolve_once, use set_timer. Test using the e key to “start evolving”. (You may wish to create a new constant, global variable or both to indicate how fast evolution occurs.)


    As it stands, you can play Conway’s Game of Life. However, this leaves several important features out and has the annoying problem that once continual evolution begins we cannot stop it without quitting the game.

  9. Use a global variable (_is_evolving) to modify start_evolution and evolve_loop so that evolution, once started, can be stopped with any key press or mouse click. There are numerous ways to achieve this effect, my recommendation is to write a function stop_evolution that pairs with start_evolution and then to modify start_evolution so that it changes the key and click handlers so that they call other functions that call stop_evolution (or they could call stop_evolution directly).

  10. Complete read_gridfile(filename) so that it reads filename (which can be assumed to be the valid name of readable text file) and returns a grid corresponding to the contents of the text file such that each line of text is a row, each position on each line corresponds to the contents of the cells in that row where a LIVE_SYMBOL leads to an active cell and all other symbols lead to inactive cells. The dimensions of the returned grid should be the number of lines for the number of rows, and the number of columns corresponding to the length of the longest line in the file. You can test this feature by using the l key (for “loading” a file) and then trying it on the supplied glider.txt or blinker.txt file.

  11. Complete census and print_stats so that the former returns the number of alive cells in a grid and the latter uses the former to display that information to the user. Test using the s key.

  12. Complete randomize_grid(grid, density) so that it sets every cell of grid to be True with the probability indicated by density (which should be an integer between 0 and 100 - indicating the percent chance that any cell is to be filled). randomize_grid(_board, 0) is equivalent to clearing the board; randomize_grid(_board, 100) is equivalent to filling the board. randomize_grid(_board, 50) sets each cell as active or inactive with equal likelihood. (Import any necessary functions.) Add a separate function which prompts the user to enter a density. Make that function robust (invalid or too large numbers should be rejected). Add a key option (I suggest r) to main_key_handler that allows the user to randomize the board.


Challenge problems

Consider these problems only after completing all of the numbered problems above.