Now on ScienceBlogs: Dr. Rolando Arafiles: Antivaccine rhetoric, colloidal silver for the flu, and Morgellons disease

Enter to Win

Science After Sunclipse

A blag for math, physics and the New Enlightenment

Search

Profile

Blake Stacey is a physics boffin and science-fiction writer who wandered the Earth and eventually settled in the nation-state of Denial.

Recent Posts

Reader Favourites

Comments are temporarily disabled while I work on other things and slowly recharge my enthusiasm for blogging. In the meantime, why not read one of these?

front-cover-sidebar.jpg Support independent publishing: buy this book on Lulu.

Spiffy Icons

Recent Comments

Archives

Categories

Blagnet

« "Finding Your Inner Fish" | Main | There Is Nothing in This World More Helpless and Depraved than a Man in the Depths of a Luminiferous Aether Binge »

Giving the GIF of Life

Category: Cellular automataPicturesSoftware
Posted on: April 15, 2009 11:38 AM, by Blake Stacey

The following is an edited repost of an item I originally wrote last year.

Sometimes, my brain needs to take a break from work, from arguing on the Internet and all the rest. On these occasions, I like to take a classic problem and see what I can do with it in a short time: Given an hour and a computer, what can I accomplish? Can I use a new feature of some tool which I haven't employed before? These sorts of largely inconsequential, fun little problems can be a more effective mental vacation than getting blitzed and watching WALL-E; not to disparage the latter in general, but exercising the intellect has its own way of clearing out the mind-cruft. Doing anything quickly can be a great relief from the slog of research.

Also, when you can get a program like that running in half an hour, the spectacle of creationists failing to implement a WEASEL program after days and days becomes truly enjoyable. You know, sometimes it's the little joys which make life so beautiful.

First, the punchline:

This is a fifty-frame animation of Conway's Game of Life, a classic example of cellular automata, some of whose history I covered in this book review. This particular implementation begins with a 32×32 grid of random values and successively advances by applying the transition rules of that famous automaton. The purpose of the exercise is twofold: first, to demonstrate some basic programming techniques and show how much you can do in Python with half an hour of free time; and second, to see how persistent and cyclic features arise from random configurations.

I chose to establish periodic boundary conditions, so that patterns leaving one side wrap around to the other (readers of my generation will recall the game Asteroids). This means we're playing Life on a torus — is there anything donuts can't do? We'll be using the Python language with a few extra modules (SciPy) for stuff like visual output.

Now, the code!

#!/usr/bin/python
# conway.py
# Blake Stacey
# (bstacey at alum.removethis.mit.andthis.edu)
import math, scipy, random, pylab

def iTest(x, p):
	if(x <= p):
		return 0
	else:
		return 1

fP = 0.5            # probability of dead square
iGridEdge = 32      # size of grid
iTrials = 50        # number of times to loop

random.seed()

imThis = scipy.array(scipy.rand(iGridEdge, iGridEdge))
imThat = scipy.array(scipy.zeros((iGridEdge, iGridEdge)))

for i in range(iGridEdge):
	for j in range(iGridEdge):
		imThis[i][j] = iTest(imThis[i][j], fP)

pylab.ion()
pylab.hold(False)

for t in range(iTrials):
	for i in range(iGridEdge):
		for j in range(iGridEdge):
			# bounds checking
			iPlus = (i + 1) % iGridEdge
			iMinus = (i - 1) % iGridEdge
			jPlus = (j + 1) % iGridEdge
			jMinus = (j - 1) % iGridEdge

			# count number of neighbors
			iNext = (imThis[i][jPlus]
					 + imThis[iPlus][j]
					 + imThis[iMinus][j]
					 + imThis[i][jMinus]
					 + imThis[iPlus][jPlus]
					 + imThis[iMinus][jMinus]
					 + imThis[iPlus][jMinus]
					 + imThis[iMinus][jPlus])
			iCurrent = imThis[i][j]

			# update rule
			if(iCurrent == 0):
				if(iNext == 3):
					imThat[i][j] = 1
				else:
					imThat[i][j] = 0
			else:
				if(iNext <= 1):
					imThat[i][j] = 0
				elif(iNext > 3):
					imThat[i][j] = 0
				elif((iNext == 2) or (iNext == 3)):
					imThat[i][j] = 1
	# end loop over elements

	# swap buffers
	imOther = imThis
	imThis = imThat
	imThat = imOther

	# print summary statistic and display image
	print sum(sum(imThis)) / float((iGridEdge * iGridEdge))
	pylab.imshow(imThis, cmap = pylab.cm.prism)
	pylab.draw()
By sticking a pylab.savefig command after the pylab.imshow call, the images can be written to files:
	pylab.imshow(imThis, cmap = pylab.cm.prism)
	pylab.savefig("frame_" + str(t) + ".png")
	pylab.draw()
There's an awful lot of whitespace in the resulting images, and the quickest hack for, well, hacking it off is to use ImageMagick:
 convert -crop 682x682+272+83 frame_0.png trimmed_0.png
I actually did all fifty frames with a bash loop:
for i in `seq 0 49`; do convert -crop 682x682+272+83
  frame_$i.png $i.png; done
Noting that the images had more resolution than was really necessary, I sized them down:
for i in `seq 0 49`; do convert -resize 100x100 $i.png
  resize_$i.png; done
Then I made the frames into an animated GIF, realized that they were out of order because the single-digit names were not prefixed with 0, fixed that problem, and made a new animation:
convert -delay 20 -loop 0 resize*.png animation.gif
The result was the image seen above. Here, I used a random initial configuration, because I had allotted myself half an hour to do the whole thing and I didn’t feel like specifying anything more detailed. However, you can put in a feature like a glider with code like this:
imThis = scipy.array(scipy.zeros((iGridEdge, iGridEdge)))
imThat = scipy.array(scipy.zeros((iGridEdge, iGridEdge)))
imThis[5][5] = 1
imThis[5][6] = 1
imThis[5][7] = 1
imThis[6][5] = 1
imThis[7][6] = 1

An old comrade of mine ported this code to JavaScript so that one can play with it on the Web.

Share this: Stumbleupon Reddit Email + More

TrackBacks

TrackBack URL for this entry: http://scienceblogs.com/mt/pings/106554

Comments

1

Ah, Life. I wasted many hours in my twenties writing and rewriting my 16-bit Windows Life program, trying all sorts of tricks to speed it up. (The last version I wrote was actually written in 32-bit assembly, which required jumping through all sorts of hoops to make work on 16-bit Windows. Plus you could change the colors while it was running!)

Good times.

Posted by: Brian Author Profile Page | April 15, 2009 12:23 PM

2

My own attempt at a Python Life implementation. I use the PIL instead, which means I had to scale up end result to get a resolution where the results were easily visible.

Hopefully, Sciblogs won't mangle the code too much.

from PIL import Image
import random, itertools

max_x = 32
max_y = 32

def neighbours(x,y,state):
    px = (x + 1) % max_x
    py = (y + 1) % max_y
    # Negative indices wrap around, so no need to correct.
    mx = x - 1
    my = y - 1
    return sum([state[i][j] for i in (mx, x, px) for j in (my, y, py)])

def next_state(state):
    # States under which a cell is alive: If its dead but has 3 living neighbours,
    # or is alive and has 2 or 3 neighbours (not including itself)
    return [[ (state[x][y], neighbours(x,y,state)) in ((0,3), (1,3), (1,4)) for y in xrange(max_y)] for x in xrange(max_x)]

green = (0,255,0)
red = (255,0,0)
colors = (red, green)
def to_image(index, state):
    im = Image.new('RGB', (max_x, max_y))
    # Technically gets x and y mixed around, but it doesn't really matter. If we cared, we'd toss in a zip(*)
    colormap = [colors[c] for c in itertools.chain(*state)]
    im.putdata(colormap)
    im.resize((max_x * 2, max_y * 2)).save('%02i.png' % index)

# Set up our initial state
state = [[random.randint(0,1) for y in xrange(max_y)] for x in xrange(max_x)]

for i in xrange(50):
    to_image(i, state)
    state = next_state(state)

Posted by: mds | April 15, 2009 1:06 PM

3

Heh, first thing I did when I got my first PC (A 16MHz 386SX -- what awesome compute power! Almost as much as my current cellphone!!) was to install MASM and write a Game of Life that ran in 320x200(?) 16-colur mode. Very pretty. Acceptably fast and memory efficient, too.

For an encore, I taught myself C by writing a Mandelbrot generator (then I discovered the InterToobz and downloaded Fractint. It was about then I realized that everything either fun or useful had already been written by someone else).

Posted by: Eamon Knight | April 15, 2009 1:12 PM

5

I bet nobody's done life on a klein bottle.

Posted by: zpmorgan | April 18, 2009 11:23 PM

6

I should have been less loath to take comp sci.

What little I learnt I've forgotten. I did once want to do something, but I'd lost the stuff I'd written for homework, and I couldn't arsed to find out how to jump through hoops to import data from a file again. If he cared, Edmund (pdf, Danish) would slap me silly now.

(Yes, I'm working through my backlog of blogs. Remarkable how many hours there are in the day once one's alienated all one's msn contacts.)

Posted by: Sili Author Profile Page | May 17, 2009 3:25 PM

ScienceBlogs

Search ScienceBlogs:

Go to:

Advertisement
Collective Imagination
Enter to win the daily giveaway
Advertisement
Collective Imagination

© 2006-2009 ScienceBlogs LLC. ScienceBlogs is a registered trademark of ScienceBlogs LLC. All rights reserved.