I’ve been dabbling in Python programming for a while now, but recently decided it was time to put some actual effort into learning the language and its libraries. I’ve also been looking for things to do with the Raspberry Pi, so I decided to kill two birds with one stone. The Raspberry Pi User Guide has an example of writing a simple game using a library called pygame, so last night I sat down to read that example and write a new program from scratch.
I decided to implement Conway’s Game of Life, a simulation that’s notorious for wasting the time of generations of hackers. Myself included – after I got my Project:65 computer to run Life in BASIC, I rediscovered how much fun it is to watch Life’s patterns develop over time.
Life is a simulation where a simple set of rules determine how a grid of “cells” grow and die over time. Despite the simple rules, complex patterns and behaviors can be created with the right starting conditions. Since Life runs on a 2D grid, it’s easy to represent graphically.
I used a simple model for my grid – a list of lists, with 0 representing empty space and 1 representing a live cell. This isn’t terribly efficient, especially in terms of memory usage, but the grid I’m using is only 80×60. There are apparently some pretty good libraries for numeric processing in Python, and I actually need to look into those, but let’s do this one thing at a time.
The program has two modes – simulation and free draw. Simulation mode just runs the Life simulation and shows the results. Free draw mode lets you mess with things, scribbling on the grid with the mouse to create an initial pattern of cells (or change the simulation mid-run). I let the user switch between modes at any time by pressing the space bar, and in free draw mode they can reset the simulation by pressing ‘c’ (for ‘clear screen’).
Input handling with the pygame library was delightfully simple, although pygame does seem to want to busy-wait on its input queue. To give you some of the flavor of it, here’s the input handler for the free draw loop, which does a lot of the actual work of the program:
for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() if event.type == KEYDOWN: if event.key == K_ESCAPE: # exit program pygame.event.post(pygame.event.Event(QUIT)) elif event.key == K_SPACE: # exit free draw mode return elif event.key == ord('c') or event.key == ord('C'): for x in range (width): # clear screen for y in range (height): grid1[x][y] = 0 need_redraw = True generation = 0 elif event.type == MOUSEBUTTONDOWN: if event.button == 1: # left button = draw draw_mode = True elif event.button == 3: # right button = erase erase_mode = True x = event.pos//10 # screen coords to grid coords y = event.pos//10 if x > 0 and x < width-1 and y > 0 and y < height-1: if draw_mode: grid1[x][y] = 1 if erase_mode: grid1[x][y] = 0 need_redraw = True elif event.type == MOUSEBUTTONUP: if event.button == 1: draw_mode = False elif event.button == 3: erase_mode = False elif event.type == MOUSEMOTION: x = event.pos//10 y = event.pos//10 if x > 0 and x < width-1 and y > 0 and y < height-1: if draw_mode: grid1[x][y] = 1 if erase_mode: grid1[x][y] = 0 need_redraw = True
I was curious about how fast this simulation would perform. The Project:65 version, in interpreted BASIC, uses something like an 80×24 grid, and at 4 MHz it can take upwards of 10 seconds to generate a new frame. The Raspberry Pi, at 700 MHz, generates almost 8 fps, despite not doing any of the optimizations that the BASIC version had.
All in all, I was very happy with how much I accomplished with just a couple hours work, using a language I’m still not very familiar with. I’m going to have to do an assembly version of Life on some system or other for comparison, but I’ll bet that’ll take a heck of a lot longer.
Despite finding Python relatively painless, I have to admit I’ve always had a love-hate relationship with scripting languages (and if you don’t care about the theory of computer languages, you might want to just skip down a couple paragraphs while I rant). For one, dynamic typing has always offended my sensibilities. I like nice orderly code, with nice orderly parameters, and static type checking so that the compiler can warn me about as many of the stupid things I’ve written as possible. Of course, I also understand that all it takes is one errant static_cast<>() to throw all that work into chaos. I admit that dynamically typed languages are great for prototyping, but I continue to consider them unfit for large-scale projects.
The Python scoping rules also threw me a curve on this project. It has to do with the fact that variables (usually) aren’t declared. Now, if you use a global variable in an rvalue inside a function, the Python interpreter will happily find that variable and use it. However, any variable name you use as an lvalue is implicitly declared as local to that function. The weird side effect of this is that, if you try to assign a value to a global variable inside a function, you instead create a local variable with the same name that masks the global variable. The worst part is that when it happened to me, the resulting error didn’t point to the assignment. Instead, I got an error a couple lines before that, where I was supposedly referencing an uninitialized variable.
The solution, it turns out, is just to add a declaration in the function saying that such-and-such variable is global. But I was left scratching my head for a few minutes, wondering why I couldn’t read that variable in one function, while others (that didn’t assign to it!) could access it just fine.
The moral here is that scoping rules are one of the most important things to learn when you’re trying to get the hang of a new language. It’s weird and unfortunate that, so much of the time, tutorials tend to gloss over those rules.
The better moral, though (and this is where you non-theory people can start reading again), is that Life is a great way to waste time on a computer. I did this project on the Pi just for the sake of doing something with the Pi, but there’s no reason you couldn’t install Python and Pygame on another Linux box, or Windows, or whatever. I’ve included a link to my (pretty simple) code below, and if you go to the Wikipedia page on Conway’s Game of Life, you can find a lot of information about the interesting properties and patterns that hackers have created with the game.