Introduction
Life is an implementation of the Game of Life that automatically generates a large field of cells and lets it develop. This could easily be the beginnings of a screensaver or quickly adapted into a more traditional game of life (much smaller grid, place cells with your mouse, hit play and see if the colony lives or dies).
If you're not familiar with the Game of Life the concept is simple. Each green cell represents a living thing. Depending on the number of neighbors each cell has, it is either happy (and reproduces), average( it just sits there ), or depressed (it dies).
There is no traditional goal in this game. You don't really "win" or "lose". It's usually just interesting to see what sort of patterns come out of this simple algorithm.
The performance of this problem on this size grid is not nearly as good as I'd like but I haven't had sufficient time to optimize it. Perhaps optimization can be left to you as an exercise?
The Code
Note: I have only the briefest descriptions here because I just wanted to get these pages up quickly. I'll be adding more soon hopefully. Until then I hoped that the code would still be interesting to look at and experiment with. Enjoy!
life.py
Life.py contains all the code for drawing the state of the game that is provided by engine.py.
"""
Game of Life Engine
Mike Steder
Randomly generates Game of Life boards and displays them.
"""
# My code:
from engine import *
#
import time
import string
import random
# PyGame Constants
import pygame
from pygame.locals import *
# for optimizing drawing:
from pygame import surfarray
def drawfield( screen, scale_surface, pixels ):
surfarray.blit_array( scale_surface, pixels )
temp = pygame.transform.scale(scale_surface, screen.get_size())
screen.blit(temp, (0,0))
return
def writeParametersToLog( xsize, ysize, starting_pop, static, avgpop ):
pass
def main():
print 'The game of life!'
WINSIZE = 640,480
SCALE = 4
ARRAYSIZE = WINSIZE[0]/SCALE, WINSIZE[1]/SCALE
# Initialize the Pygame Engine!
pygame.init()
screen = pygame.display.set_mode(WINSIZE,0,8)
scale_screen = pygame.surface.Surface( ARRAYSIZE,0,8 )
pygame.display.set_caption('Life')
white = 255,240,200
black = 20,20, 40
red = 255, 20, 40
green = 20,255,40
blue = 20,20,255
screen.fill(black)
scale_screen.fill(black)
screen.set_palette( [black, red, green, blue, white] )
scale_screen.set_palette( [black, red, green, blue, white] )
# Initialize the model:
# 2,3,1,0 correspond to palette colors set above (0=black, 1=red, etc)
count = 1000
static = 2
model = Simulation(ARRAYSIZE[0],ARRAYSIZE[1],
2,3,1,0,
count,
static,
)
i = 0
pop = 0
popsum = 0
popave = 0.0
timesteps = 365
elapsedtime = 0.0
drawtime = 0.0
updatetime = 0.0
totaltime=0.0
fps = 0.0
sumfps = 0.0
avgfps = 0.0
done = False
while not done:
print i,'-',avgfps,'-',pop,'-',popave
starttime = time.time()
drawfield( screen, scale_screen, model.pixel_data )
endtime = time.time()
drawtime = endtime - starttime
starttime = time.time()
pygame.display.update()
endtime = time.time()
updatetime = endtime - starttime
starttime = time.time()
model.timestep()
endtime = time.time()
elapsedtime = endtime-starttime
totaltime = (drawtime + updatetime + elapsedtime)
sumfps += ( 1.0/totaltime )
i += 1
avgfps = sumfps / i
pop = model.numcreatures
popsum += pop
prev_popave = popave
popave = popsum / (i*1.0)
# Restart if either all creatures die or the screen is filled:
if( pop <= 0 or
pop >= (ARRAYSIZE[0] * ARRAYSIZE[1]) ):
print "Restarting @ pop = %s"%pop
i = 0
pop = 0
popsum = 0
popave = 0.0
sumfps = 0.0
model = Simulation(ARRAYSIZE[0],ARRAYSIZE[1],
2,3,1,0,
random.randint(100,3000),
random.randint(1,7),
)
# Handle someone closing the window or pressing escape
events = pygame.event.get( )
for e in events:
if( e.type == QUIT ):
done = True
break
elif (e.type == KEYDOWN):
if( e.key == K_ESCAPE ):
done = True
break
if( e.key == K_f ):
pygame.display.toggle_fullscreen()
print "Exiting!"
return
if __name__=="__main__":
main()
syntax highlighted by Code2HTML, v. 0.9.1
This example uses Surfarray to convert the Numeric Array data returned from "model.timestep()" into graphics. (Essentially, each cell represents a pixel of the image).
engine.py
engine.py generates the array that we display in life.py. Engine.py defines a Simulation class that handles the bookkeeping of what cells are occupied. When it's "timestep" method is called it loops over all occupied spaces and determines if they die, do nothing, or reproduce.
#################################################################
## Game of Life
## Mike Steder
##
## Simulation Engine Object
## engine.py
##
## "this object generates a field/universe for our creatures
## and distributes them. A call to a step method timesteps
## the entire 'civilization' of creatures once.
##
## Currently the model follows the following rules:
##
## 1). The field is randomly populated with creatures to a certain percentage of
## the available space
## 2). Creatures with 3 or more neighbors(diagonals count):
## ( This creature has no neighbors:
## O O O
## O X O
## O O O
## This creature has 3 neighbors
## X X O
## O X X
## O O O )
## will mate with a nearby neighbor and reproduce
## into one of their open adjacent spots (diagonals count)
## 3). A creature with 2 neighbors will just sit still and wait for something exciting to happen.
## 4). A creature with fewer then 2 neighbors will get lonely and die.
#################################################################
# Import my module first
import _engine
import sys
import random
import time
import Numeric
# Coords = [UP, UP-RIGHT, RIGHT, etc] clockwise
NEIGHBORS = [
(0,1), # up
(0,-1), # down
(1,0), # left
(-1,0), # right
(1,1), # upper left
(-1,1), # upper right
(-1,-1), # lower right
(1,-1), # lower left
]
class Simulation:
def __init__(self,xdim=10, ydim=10,
# colors will all be real palette values
plantcolor=0, herbcolor=0,
carncolor=0, emptycolor=0,
numcreatures=10,
static=2, # number of neighbors necessary to be in balance
):
self.xdim = xdim
self.ydim = ydim
self.xrange = range(0,self.xdim)
self.yrange = range(0, self.ydim )
self.range = range(0, self.xdim * self.ydim)
self.plantcolor=plantcolor
self.herbcolor=herbcolor
self.carncolor=carncolor
self.emptycolor=emptycolor
self.numcreatures = numcreatures
self.static = static
self.environment = None
self.lut = None
self.pixel_data = Numeric.zeros( (xdim,ydim), 'i' )
self.init_environment()
def init_environment(self):
self.environment = Numeric.zeros((self.xdim,self.ydim),'i')
print "Setting up pixel_data array:"
for i in xrange(self.xdim):
for j in xrange(self.ydim):
try:
self.pixel_data[i][j] = self.emptycolor
except IndexError:
print "Tried to set pixel_data[%s][%s]! (xdim=%s,ydim=%s)"%(i,j,self.xdim,self.ydim)
except TypeError:
print "i=%s,j=%s,self.emptycolor=%s"%(i,j,self.emptycolor)
print "Setting up Lookup Table:"
self.lut = {}
print 'Setting up creatures'
i = 0 # self.numplants
iterations = 0
while i < self.numcreatures:
x = self.randomx()
y = self.randomy()
if self.environment[x][y] == 0:
self.environment[x][y] = 1
self.pixel_data[x][y] = self.plantcolor # GREEN!
if( i % 100 == 0 ):
print i,"- (",x,",",y,")","- Created!"
i+=1
iterations += 1
self.lut[(x,y)] = (x,y)
else:
# Duplicate location, something's already living here.
# Try again!
iterations += 1
continue
print "%s Creatures setup in %s iterations."%(len(self.lut),iterations)
# End of initenvironment
return
### Determine random starting locations
def randomx(self):
x = random.randrange(0,self.xdim-1)
return x
def randomy(self):
y = random.randrange(0,self.ydim-1)
return y
def timestep(self):
# Set move options and call move
values = self.lut.values()
moves = len(values)
avgmove = 0.0
startstep = time.time()
for coords in values:
startmove = time.time()
self.move( coords )
endmove = time.time()
avgmove += (endmove - startmove)
endstep = time.time()
return
def countNeighbors( self, coords ):
myNeighbors = 0
freelist = []
for n in NEIGHBORS:
nx,ny = (coords[0] + n[0])%self.xdim, (coords[1] + n[1])%self.ydim
if( self.environment[nx][ny] ):
myNeighbors += 1
else:
freelist.append( n )
return myNeighbors, freelist
def move( self, coords ):
x,y = coords[0],coords[1]
# numNeighbors,freeList = self.countNeighbors(coords)
numNeighbors,freeList = _engine.count_neighbors( x, y, self.environment )
# print "neighbors, freelist = %s, %s"%(numNeighbors,freeList)
if( numNeighbors > self.static and numNeighbors < 8 ):
random.shuffle(freeList)
item = freeList[0]
nx = (x + item[0])%self.xdim
ny = (y + item[1])%self.ydim
self.environment[nx][ny] = 1
self.pixel_data[nx][ny] = self.plantcolor
self.lut[(nx,ny)] = (nx,ny)
self.numcreatures+=1
elif( numNeighbors < self.static ):
# Die
self.environment[x][y] = 0
self.pixel_data[x][y] = self.emptycolor
del self.lut[(x,y)]
self.numcreatures -=1
else:
# Do nothing, wait for your neighbors to do something
pass
return
syntax highlighted by Code2HTML, v. 0.9.1
Download
Download this Game of Life example.