r
/
fifth
1
Fork 0

Functional Game of Life

master
jrpotter 2015-05-31 20:58:09 -04:00
parent 774cb8243d
commit 932ca4409a
4 changed files with 110 additions and 44 deletions

View File

@ -2,7 +2,11 @@
""" """
from cell_plane import CellPlane import copy
import numpy as np
import ruleset as rs
import cell_plane as cp
import neighborhood as nh
class CAM: class CAM:
@ -18,9 +22,10 @@ class CAM:
""" """
""" """
cps = max(cps, 1)
self._dimen = dimen self._dimen = dimen
self._planes = [CellPlane(dimen) for i in cps] self._planes = [cp.CellPlane(dimen) for i in range(cps)]
self._master = self._planes[0].grid if cps > 0 else None self.master = self._planes[0].grid
def tick(self, ruleset, neighborhood, *args): def tick(self, ruleset, neighborhood, *args):
@ -31,5 +36,28 @@ class CAM:
is placed into the master grid. Depending on the timing specifications set by the user, this is placed into the master grid. Depending on the timing specifications set by the user, this
may also change secondary cell planes (the master is always updated on each tick). may also change secondary cell planes (the master is always updated on each tick).
""" """
self._master = ruleset.update(self._master, self.master, neighborhood, *args) self.master[:] = rs.Ruleset.update(self.master, ruleset, neighborhood, *args)
def randomize(self):
"""
Set the master grid to a random configuration.
"""
@np.vectorize
def v_random(cell):
cell.value = np.random.random_integers(0, 1)
return cell
v_random(self.master)
def console_display(self):
"""
A convenience method used to display the grid onto the console.
This should not be called frequently; I haven't benchmarked it but I do not anticipate this
running very well for larger grids.
"""
vfunc = np.vectorize(lambda x: int(x.value))
print(vfunc(self.master))

30
src/life.py Normal file
View File

@ -0,0 +1,30 @@
import cam
import ruleset as rs
import neighborhood as nh
def game_of_life(cell, neighbors):
"""
Rules of the Game of Life.
Note we ignore the second component of the neighbors tuples since
life depends on all neighbors
"""
total = sum(map(lambda x: int(x[0].value), neighbors))
if cell.value:
if total < 2 or total > 3:
return False
else:
return True
elif total == 3:
return True
else:
return False
if __name__ == '__main__':
c = cam.CAM(1, (10, 10))
c.randomize()
c.console_display()
r = rs.Ruleset(rs.Rule.SATISFY)
n = nh.Neighborhood.moore(c.master, True)
c.tick(r, n, game_of_life)
c.console_display()

View File

@ -61,7 +61,7 @@ class Neighborhood:
return 0 return 0
def __init__(self): def __init__(self, grid, wrap_around=True):
""" """
Sets up an empty neighborhood. Sets up an empty neighborhood.
@ -69,10 +69,12 @@ class Neighborhood:
Note the offsets have a tuple as a key representing the position being offsetted by, and as a value, Note the offsets have a tuple as a key representing the position being offsetted by, and as a value,
the current state the given cell at the offset is checked to be. the current state the given cell at the offset is checked to be.
""" """
self.grid = grid
self.offsets = {} self.offsets = {}
self.wrap_around = wrap_around
def neighbors(self, cell, grid, wrap_around=True): def neighbors(self, cell):
""" """
Returns all cells in the given neighborhood. Returns all cells in the given neighborhood.
@ -83,12 +85,12 @@ class Neighborhood:
for k in sorted(self.offsets.keys()): for k in sorted(self.offsets.keys()):
position = [sum(x) for x in zip(cell.index, k)] position = [sum(x) for x in zip(cell.index, k)]
for i in range(len(position)): for i in range(len(position)):
if wrap_around: if self.wrap_around:
position[i] = position[i] % grid.shape[i] position[i] = position[i] % self.grid.shape[i]
elif i < 0 or i >= grid.shape[i]: elif i < 0 or i >= self.grid.shape[i]:
break break
else: else:
cells.append((grid[tuple(position)], self.offsets[k])) cells.append((self.grid[tuple(position)], self.offsets[k]))
return cells return cells
@ -111,7 +113,7 @@ class Neighborhood:
@classmethod @classmethod
def moore(cls, dimen, value=True): def moore(cls, grid, wrap_around=True, value=True):
""" """
Returns a neighborhood corresponding to the Moore neighborhood. Returns a neighborhood corresponding to the Moore neighborhood.
@ -122,19 +124,19 @@ class Neighborhood:
Note the center cell is excluded, so the total number of offsets are 3^N - 1. Note the center cell is excluded, so the total number of offsets are 3^N - 1.
""" """
offsets = {} offsets = {}
variants = ([-1, 0, 1],) * dimen variants = ([-1, 0, 1],) * len(grid.shape)
for current in itertools.product(*variants): for current in itertools.product(*variants):
if any(current): if any(current):
offsets[current] = value offsets[current] = value
m_neighborhood = cls() m_neighborhood = cls(grid, wrap_around)
m_neighborhood.extend(offsets) m_neighborhood.extend(offsets)
return m_neighborhood return m_neighborhood
@classmethod @classmethod
def neumann(cls, dimen, value=True): def neumann(cls, grid, wrap_around=True, value=True):
""" """
Returns a neighborhood corresponding to the Von Neumann neighborhood. Returns a neighborhood corresponding to the Von Neumann neighborhood.
@ -145,14 +147,15 @@ class Neighborhood:
Note the center cell is excluded, so the total number of offsets are 2N. Note the center cell is excluded, so the total number of offsets are 2N.
""" """
offsets = {} offsets = {}
variant = [0] * dimen variant = [0] * len(grid.shape)
for i in range(len(variant)): for i in range(len(variant)):
for j in [-1, 1]: for j in [-1, 1]:
variant[i] = j variant[i] = j
offsets[tuple(variant)] = value offsets[tuple(variant)] = value
variant[i] = 0 variant[i] = 0
n_neighborhood = cls() n_neighborhood = cls(grid, wrap_around)
n_neighborhood.extend(offsets) n_neighborhood.extend(offsets)
return n_neighborhood return n_neighborhood

View File

@ -5,9 +5,12 @@
@author: jrpotter @author: jrpotter
@date: May 31st, 2015 @date: May 31st, 2015
""" """
from enum import Enum import copy
import enum
import numpy as np
class Rule(Enum):
class Rule(enum.Enum):
MATCH = 0 MATCH = 0
TOLERATE = 1 TOLERATE = 1
SATISFY = 2 SATISFY = 2
@ -26,22 +29,41 @@ class Ruleset:
a neighborhood instance's offsets member. a neighborhood instance's offsets member.
""" """
def __init__(self, method, wrap_around=True): @staticmethod
@np.vectorize
def update(cell, rules, neighborhood, *args):
"""
Allow for batch processing of rules.
We choose our processing function based on the specified rule and update every cell in the grid simultaneously
via a vectorization.
"""
tmp = copy.deepcopy(cell)
if rules.method == Rule.MATCH:
tmp.value = rules.matches(cell, neighborhood, *args)
elif rules.method == Rule.TOLERATE:
tmp.value = rules.tolerate(cell, neighborhood, *args)
elif rules.method == Rule.SATISFY:
tmp.value = rules.satisfies(cell, neighborhood, *args)
return tmp
def __init__(self, method):
""" """
""" """
self.method = method self.method = method
self.wrap_around = wrap_around
def _matches(self, cell, grid, neighborhood): def matches(self, cell, neighborhood):
""" """
Determines that neighborhood matches expectation exactly. Determines that neighborhood matches expectation exactly.
Note this is just like the tolerate method with a tolerance of 1, but Note this is just like the tolerate method with a tolerance of 1, but
recoding allows for short circuiting. recoding allows for short circuiting.
""" """
residents = neighborhood.neighbors(cell, grid, self.wrap_around) residents = neighborhood.neighbors(cell)
for resident in residents: for resident in residents:
if resident[0].value != resident[1]: if resident[0].value != resident[1]:
return False return False
@ -49,7 +71,7 @@ class Ruleset:
return True return True
def _tolerate(self, cell, grid, neighborhood, tolerance): def tolerate(self, cell, neighborhood, tolerance):
""" """
Determines that neighborhood matches expectation within tolerance. Determines that neighborhood matches expectation within tolerance.
@ -57,7 +79,7 @@ class Ruleset:
consider this cell to be alive. Note tolerance must be a value 0 <= t <= 1. consider this cell to be alive. Note tolerance must be a value 0 <= t <= 1.
""" """
matches = 0 matches = 0
residents = neighborhood.neighbors(cell, grid, self.wrap_around) residents = neighborhood.neighbors(cell)
for resident in residents: for resident in residents:
if resident[0].value == resident[1]: if resident[0].value == resident[1]:
matches += 1 matches += 1
@ -65,32 +87,15 @@ class Ruleset:
return (matches / len(residents)) >= tolerance return (matches / len(residents)) >= tolerance
def _satisfies(self, cell, grid, neighborhood, valid_func): def satisfies(self, cell, neighborhood, valid_func):
""" """
Allows custom function to relay next state of given cell. Allows custom function to relay next state of given cell.
The passed function is supplied the list of 2-tuple elements, of which the first is a Cell and the second is The passed function is supplied the list of 2-tuple elements, of which the first is a Cell and the second is
the expected state as declared in the Neighborhood, as well as the grid and cell in question. the expected state as declared in the Neighborhood, as well as the grid and cell in question.
""" """
residents = neighborhood.neighbors(cell, grid, self.wrap_around) residents = neighborhood.neighbors(cell)
return valid_func(cell, grid, residents) return valid_func(cell, residents)
@np.vectorize
def update(self, cell, *args):
"""
Allow for batch processing of rules.
We choose our processing function based on the specified rule and update every cell in the grid simultaneously
via a vectorization.
"""
if self.method == Rule.MATCH:
cell.value = self._matches(cell, *args)
elif self.method == Rule.TOLERATE:
cell.value = self._tolerate(cell, *args)
elif self.method == Rule.SATISFY:
cell.value = self._satisfy(cell, *args)
return cell