import io
import time
_NEIGHBORS = (-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)
class life:
"""A game of Conway's Life. The board is finite and toroidal (that is, the cells
on the right edge are immediately to the left of the cells on the left, and the
cells of the first row are immediately below those of the last.)"""
def __init__(self, nrows, ncols):
"""Default initialization of an NROWS x NCOLS board."""
self._nrows = nrows
self._ncols = ncols
def __str__(self):
"""A (printable) string that displays the current contents of the board."""
out = io.StringIO()
for r in range(self.rows):
for c in range(self.cols):
print('*' if self.is_alive(r, c) else '-', end='', file=out)
print(file=out)
return out.getvalue()
def set_board(self, board):
"""Sets the values of is_alive(r, c) from the values extracted from BOARD,
which should be a sequence (iterable) of sequences (iterables)
returning values representing occupied or unoccupied Life cells.
Each element of BOARD (up to SELF.rows elements) is used to fill
consecutive rows of SELF with up to SELF.cols values each. The
values iterated by the rows of BOARD are interpreted according to
the live_value method. Any leftover rows or columns from BOARD are
discarded. Any unfilled cells of SELF are set unoccupied."""
for r in range(self.rows):
for c in range(self.cols):
self.set_alive(r, c, False)
for r, row in enumerate(board):
if r >= self.rows:
break
for c, val in enumerate(row):
if c >= self.cols:
break
self.set_alive(r, c, life.live_value(val))
def is_alive(self, r, c):
"""True iff there is an organism alive at column C of row R. R and C
wrap around, as on a toroidal board."""
return self._is_alive(r % self.rows, c % self.cols)
def set_alive(self, r, c, livep):
"""Set is_alive(R, C) to LIVEP."""
self._set_alive(r % self.rows, c % self.cols, livep)
def neighbors(self, r, c):
"""The number of living neighbors of the cell at column C of row R."""
sum = 0
for i, j in _NEIGHBORS:
sum += bool(self.is_alive(r+i, c+j))
return sum
def survives(self, r, c):
"""True iff the next generation will have an occupant at R, C."""
return life.will_live(self.is_alive(r, c), self.neighbors(r, c))
@property
def rows(self):
return self._nrows
@property
def cols(self):
return self._ncols
@staticmethod
def will_live(now_alive, neighbors):
"""True iff a cell with NEIGHBORS live neighboring cells, and that
currently contains a live organism iff NOW_ALIVE is true, will
contain a living organism in the next generation."""
return (now_alive and 2 <= neighbors <= 3) or \
(not now_alive and neighbors == 3)
@staticmethod
def live_value(v):
"""True iff V is an external representation of a live organism in
a life board. Live organisms are either 1-character strings
other than " ", "_", or ".", or else true, non-string values."""
if type(v) is str and len(v) >= 1:
return v[0] not in ' ._'
else:
return bool(v)
# Methods that must be overridden in each subtype.
def _is_alive(self, r, c):
"""True iff there is an organism alive at column C of row R. R and C
must be within the bounds of the board."""
raise NotImplemented
def _set_alive(self, r, c, livep):
"""Set is_alive(R, C) to True iff LIVEP is a true value. R and C must be
within the bounds of the board."""
raise NotImplemented
def tick(self):
"""Update the board to the next generation."""
raise NotImplemented