[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!