Write the Grid
00:00 In the previous lesson, I showed you how I’ll be storing the game of life’s data and how the next frame calculation is performed. In this lesson, I’ll be turning those concepts into Python.
00:12 Let’s implement the basics and run some tests before getting all fancy with our presentation. To get going, you need a class to store the starting patterns and a class to store the grid.
00:23 The grid class needs at least two methods. One to show the state of the grid for debugging and one to calculate the next frame. Let me show you the code for both of these.
00:35 I’m starting here with a class to store the live cells, it doubles as both the starting pattern and as the data inside the grid class, which I’ll get to shortly.
00:45
I’m using a data class, which if you haven’t seen these before, they’re a shortcut for creating a class with specific attributes. In this case, those attributes are name
for the pattern and a set of tuples where the tuples are pairs of int
s. Data classes use Python type hinting to specify what is to be stored in an attribute.
01:05
The ability to use the built-in types like int
and tuple
as type hints was added in Python 3.9. If you’re using something earlier, you’ll have to import the corresponding type hint classes from the typing
module instead.
01:21 The next class here is the one that abstracts the grid. It stores the current frame as data and provides a method for calculating the next frame. At the top of the class, I have a couple of constants that get used when displaying the grid, showing a heart for alive and dot for dead.
01:39 The constructor for the grid class takes a pattern, storing it. This object starts as the initial pattern and then gets modified as the frames progress.
01:50
The evolve()
method is responsible for calculating the next state. Inside of it, I’ve got those eight neighbor delta tuples used to find the nearest neighbors of a cell.
02:00
You’ll recall these values from the previous lesson. When I introduced the algorithm for counting neighbors, I mentioned that I’d use a defaultdict
By creating a defaultdict
with an int
, any key that doesn’t exist will get created with an integer value.
02:15 The default integer value is zero. Let me scroll down a little bit.
02:26 This loop is what calculates the neighbor counts. Remember, this is being done the backwards way by looping through the alive cells. Then for each cell, I loop through the neighbor deltas and increment the value in the neighbor counting dict.
02:40 When the outer loop finishes, I’ll have a dictionary where the keys are the coordinates of cells with neighbors and the value is the number of neighbors that cell has.
02:50 Here is where the set map begins. Comprehensions tend to be faster than their loop equivalents, but at the cost of readability, this is a set comprehension denoted by the brace brackets.
03:02 I’ve always found this a bit problematic personally, the fact that both set and dictionaries use the same symbols, but hey, no language is perfect. To read a comprehension like this, I tend to start in the middle and work my way outwards.
03:16
The num_neighbors
dict is being iterated over. That’s the middle part, and each iteration is returning a cell coordinate and a number. If that number is in the set containing two and three, then the corresponding cell coordinate is put in the outer set.
03:32
This corresponds to that first grid with blue cells I covered in the last lesson. I then take the newly calculated set containing cells with two or three neighbors and perform an and
operation on that and the currently alive cells.
03:49 This gives me a set with the coordinates of cells that are staying alive, staying alive. Really? Did you not expect me to make that joke at some point? The next comprehension is for the reproduction sequence.
04:05 This is similar to the previous one, but looking for those cells with exactly three neighbors, that comprehension should only apply to dead cells. So I subtract the live cells pattern.
04:17
This leaves me with dead cells that have three neighbors. Those are the ones that come alive. And finally, I or
these two new sets together, storing it in the pattern object, and giving me the next frame.
04:31
Let me scroll down a bit more here. I’m implementing a __str__
method to help with debugging and make sure that everything is working. This method simply returns the name of the starter pattern and the current list of alive cells.
04:45 Let’s go to the REPL and try this out
04:50 importing the grid class.
05:09
It’s my blinker and constructing a new grid, passing that blinker pattern. If I print the grid, it shows the results of the __str__
method.
05:21
As expected, it says it used the blinker pattern and has a list of the alive cells. You can kind of visualize the vertical bar calling. evolve()
calculates the next frame,
05:36 and you get the same name, but the alive cells have oscillated. I’ll do it again, and you’re back to the original. Because the blinker is an oscillator with a period of two,
05:52 it’s a little hard to visualize the grid based on a list of cells, so let’s go back and add some code.
06:03
The as_string()
method of the grid class is going to convert the alive cells list into a visual representation of the grid. It takes one argument, which is a tuple of the bounding area of the grid to display.
06:16 Remember back at the beginning when I said you could think about the grid as infinite? Well, the boundaries can be thought of as just which part to display.
06:23 Depending on what is sent in here, our live cells might not even be visible. First, I expand the tuple out into start and end values for columns and rows.
06:34 Then I build a title string based on the pattern name, but centered. Then I loop through the rows, constructing each, printing out a dot if the cell is not in the alive list and a heart if it is. Remember the alive and dead constants at the top of the file, which were heart and dot respectively.
06:53 All those cells get joined into a row and are just joined with a carriage return. Let’s go back to the REPL and visualize our grids.
07:06 I’ll show our blinker using a five by five, the same way I’ve shown it in the slides,
07:16 and there you go. I’ll visualize the—let me get the next frame, and
07:28 it blinked. You’ve got the basics of the game and now have a way to visualize it all. Hard-coding a starter pattern kind of sucks. Next up, let’s try to suck a little less.
Become a Member to join the conversation.