In this assignment, you’ll gain practice with arrays by implementing Conway’s Game of Life, a simplified model of the rise and fall of organisms or societies that leads to pretty patterns. (Check out the Wikipedia page!)
In the Game of Life, you work with a grid of “cells,” each of which is “alive” or “dead.” You will represent this grid as an array of Booleans (alive is true, dead is false). You may choose whether you want to use a 1D array or a 2D array. If you use a 1D array, that is, a simple array of Booleans, you will think of index i
as representing the i
th cell, counting from the top left, all the way across a row, then across the second row, then the third, and so on. If you use a 2D array, then index [i][j]
can represent the i
th row and j
th column.
The grid is filled with an initial pattern of on-and-off cells. After this, the “game” procedes in generations, or time steps. Each generation, every cell in the grid updates itself simultaneously, according to the following rules (taken from Wikipedia):
Any live cell with fewer than two live neighbors dies, as if caused by underpopulation.
Any live cell with two or three live neighbors lives on to the next generation.
Any live cell with more than three live neighbors dies, as if by overpopulation.
Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
The word “neighbors” here refers to the eight cells that surround the current cell. You may realize that some cells, namely the ones at the borders of the grid, do not have eight neighbors. You should think of the grid as “wrapping around” horizontally and vertically, so that for example if I am in the furthest left column, and walk one step left, I wrap around to the furthest right column.
You will implement a loop that clears the screen, prints the current generation’s grid to the Terminal, using symbols of your choice to represent live and dead cells (e.g. *
and .
), then uses the current generation to compute the next generation, and sleeps for a bit before looping back and continuing the animation.
Declare a grid
type with whatever underlying type you’d like to use to represent the grid (likely an array of Booleans or an array of arrays of Booleans). The grid should be 50x50 in size, so altogether you will be storing 2,500 Booleans. (Note: if you’d like to use a different size, that’s all right! Anything much taller than 50 will be hard, because your Terminal window doesn’t get that tall; you might want to make it shorter or wider, though.) Implement the following useful methods on the type:
AliveAt(row, column int) bool
, which returns true if the cell at the given row and column is alive, false otherwise.
SetValueAt(row int, column int, newValue bool)
, which has a pointer receiver (that is, declare it as func (g *grid) SetValueAt(...)
instead of func (g grid) SetValueAt(...)
), so that you can actually change the grid inside this method. This method just changes the current value of the cell at (row, column)
to newValue
.
Draw()
, which uses Print
and Println
statements to visualize the current grid, using characters of your choice for alive and dead cells.
NeighborsOf(row, column int) [8]bool
, which returns an array of eight booleans representing the eight neighbors of the given cell. Remember that we wrap around when we hit the edge. (That is, (0, 0)
has as its neighbors (49, 49), (0, 49), (1, 49), (49, 0), (1, 0), (49, 1), (0, 1), (1, 1)
.)
CountAliveNeighbors(row, column int) int
, which returns the number of alive neighbors of a given cell. This shouldn’t be too hard if you can use NeighborsOf
.
AliveInNextGeneration(row, column int) bool
, which returns true if the given cell will be alive in the next generation. This should be easy if you already have CountAliveNeighbors
.
NextGeneration() grid
, which returns a brand new grid in which every cell has been updated for the next generation.
In your main function, create a new grid and set some of its entries to true. You may want to look at Wikipedia’s Game of Life page for some good ideas on initial configurations!
Now, loop infinitely (using for {...}
), and in each iteration, do the following:
Clear the screen, using clear.WipeScreen()
from the package you used in your animations assignment.
Use the current grid’s Draw
method to print it to the screen.
Compute the new grid: currentGrid = currentGrid.NextGeneration()
.
Sleep for a small amount of time: time.Sleep(300 * time.Millisecond)
is reasonable.
Enjoy your animation! You can now experiment with different starting configurations if you’d like.