[Tutor] Implementation Help Request on Game of Life

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Thu Jan 2 11:57:01 2003


On Thu, 2 Jan 2003, Max Romantschuk wrote:

> I'm a programming professional, but a Python novice. Thus I've decided
> to implement Conway's Game of Life in Python in order to learn more.
>
> I've want to encapsulate the actual logic in a Python class called
> PlayField.  It is the two-dimensional matrix in which the game's cells
> live.
>
> I'would also like the PlayField to work much like a list, so that I
> could fetch a cell like this:
>
> spam = PlayField(10,10)  # rows, colums
> print spam[1][2].state # cell on row 1 column 2, 'alive' for example
> print spam[1][2].liveNeighbors # uses getattr to calculate the number


It might be good to abstract the PlayField so that it's not necessarily
defined by a list of lists.  That is, if you allow people to access it
like this:

###
spam = PlayField(10,10)
print spam.state(1,2)
print spam.liveNeighbors(1,2)
###

where state() and liveNeighbors() are now methods of the class, then it
might be easier to return a list of liveNeighbors to point 1,2 this way.



The way we have it now,

    print spam[1][2].liveNeighbors

is slightly complicated because Python will split this up into a few
subexpressions, where the subexpression will need to remember something
about the previous subexpression.  Internally, this looks something like:

    t = spam[1]
    t2 = t[2]
    t3 = t2.liveNeighbors


't' will be some object that keeps track of which 'Row', we want, and 't2'
keeps track of both 'Row' and 'Column'. The first two subexpressions might
involve writing a __getitem__() to support indexing, and the third might
involve the __getattr__().


The function approach is less complicated, since a function call like:

    spam.liveNeighbors(1,2)

has all the information it'll need, all in the same place.  But let's see
how far we can go by trying it both ways.  *grin*



> I would have no problem doing this with a one-dimensional list, but how
> do I do this with a two-dimensional one? I guess it can be done, but I
> can't get my head around how to go about it.


Here's one way to approach this: the following classes should allow you to
do spam[1][2].state and spam[1][2].liveNeighbors... just as long as
Playfield.state() and Playfield.liveNeighbors() are defined.  For
convenience, I've omitted their definitions.  *grin*

###
class Playfield:
    def __init__(self, rows, cols):
        self.rows, self.cols = rows, cols


    def __getitem__(self, i):
        """Returns a 'Row' object that allows us to do later
           queries where the Row is fixed."""
        return Row(self, i)


    def state(self, i, j):
        """Returns the current state of the playfield at
           coordinate (i,j)."""
        return "fixme!  I am state()."   ## fixme



    def liveNeighbors(self, i, j):
        """Returns a list of all live neighbors to
           coordinate (i,j)."""
        return "fixme!  I am liveNeighbors()."   ## fixme



class Row:
    def __init__(self, play_field, row_index):
        self.play_field, self.index = play_field, row_index

    def __getitem__(self, column_index):
        """Returns an 'Element' that represents a single element of
           the playfield."""
        return Element(self.play_field, self.index, column_index)



class Element:
    def __init__(self, play_field, row_index, column_index):
        self.play_field = play_field
        self.r, self.c = row_index, column_index


    def __getattr__(self, attr):
        ## Todo: make this "data-directed" so that we don't have to
        ## keep writing a bunch of if/elif statements
        if attr == 'state':
            return self.play_field.state(self.r, self.c)
        elif attr == 'liveNeighbors':
            return self.play_field.liveNeighbors(self.r, self.c)
        else:
            raise AttributeError, \
                ("Element instance has no such attribute %s" % attr)
###


The code is incomplete, but I hope it gives some ideas on how to approach
this.  I hope this helps!