Before writing any code, read Gardner’s article on Conway’s Game of Life and this entire document
Work on your own (or, if you have been assigned a partner, with them and only them)
Unless otherwise indicated, use only the Python operations and commands we have been using thus far in class and lab.
Download and unzip the starter archive (see section on the starter archive)
Write all your code in the supplied hw8.py
and keep
it named that way. In that file, replace
<YOUR NAME(s)>
with your name (or names).
Work in areas indicated by the comments marked with ...
and
once you have completed work on that area, remove those ...
comments.
Replace ...status...
with a concise, proofread and
edited comment at the top of your file that indicates how much of the
assignment you were able to complete and any known bugs or limitations
of your code.
Submit your work by uploading your hw8.py
file via
MySLC
Submit only your hw8.py
file - and make sure it is
named hw8.py
. Do not submit an entire folder. (If you are
working with an assigned partner, only one member of your team should
upload.)
You are strongly encouraged to experiment with the runnable
solution module (hw8x.py
) which is included in the starter
archive.
Test your work as you go!
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).
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.
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:
hw8.py
: the only file which you need to modifygrid_graphics.py
: uses Python’s graphics to create a
window for painting colored squareshw8x.py
: the runnable solution version of
hw8.py
.The text files are:
glider.txt
- a grid representation of a gliderblinker.txt
- a grid representation of a horizontal
blinker (“traffic light”).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)
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”.
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 .
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.
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.
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
).
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
.
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.
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.
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).
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.
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.
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.
Consider these problems only after completing all of the numbered problems above.
Modify evolve_once
so that in addition to being side
effecting (modifying the board and painting squares in the graphics
window) it also returns the number of cells that have changed.
Modify evolve_loop
so that it stops if it hits a
stable configuration meaning that no changes occur between two
successive generations.
Add a menu option (and all necessary code) to allow current game grid to be saved into a text file in a way that can be read back into the game at a future state.
Print messages in the shell to indicate how much time elapses while evolving one generation.
Challenging: modify evolve_loop
so that it
stops if a pattern emerges: if one generation that it evolves is
identical to some previous generation (but not necessarily the
immediately previous one). If it stops it should indicate (by printing a
message) how long (how many generations) form the “cycle”.
And much much more…