Introduction

game of life image

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.

life.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.

engine.py
#################################################################
##  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.