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:
@ -18,9 +22,10 @@ class CAM:
"""
"""
cps = max(cps, 1)
self._dimen = dimen
self._planes = [CellPlane(dimen) for i in cps]
self._master = self._planes[0].grid if cps > 0 else None
self._planes = [cp.CellPlane(dimen) for i in range(cps)]
self.master = self._planes[0].grid
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
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
def __init__(self):
def __init__(self, grid, wrap_around=True):
"""
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,
the current state the given cell at the offset is checked to be.
"""
self.grid = grid
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.
@ -83,12 +85,12 @@ class Neighborhood:
for k in sorted(self.offsets.keys()):
position = [sum(x) for x in zip(cell.index, k)]
for i in range(len(position)):
if wrap_around:
position[i] = position[i] % grid.shape[i]
elif i < 0 or i >= grid.shape[i]:
if self.wrap_around:
position[i] = position[i] % self.grid.shape[i]
elif i < 0 or i >= self.grid.shape[i]:
break
else:
cells.append((grid[tuple(position)], self.offsets[k]))
cells.append((self.grid[tuple(position)], self.offsets[k]))
return cells
@ -111,7 +113,7 @@ class Neighborhood:
@classmethod
def moore(cls, dimen, value=True):
def moore(cls, grid, wrap_around=True, value=True):
"""
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.
"""
offsets = {}
variants = ([-1, 0, 1],) * dimen
variants = ([-1, 0, 1],) * len(grid.shape)
for current in itertools.product(*variants):
if any(current):
offsets[current] = value
m_neighborhood = cls()
m_neighborhood = cls(grid, wrap_around)
m_neighborhood.extend(offsets)
return m_neighborhood
@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.
@ -145,14 +147,15 @@ class Neighborhood:
Note the center cell is excluded, so the total number of offsets are 2N.
"""
offsets = {}
variant = [0] * dimen
variant = [0] * len(grid.shape)
for i in range(len(variant)):
for j in [-1, 1]:
variant[i] = j
offsets[tuple(variant)] = value
variant[i] = 0
n_neighborhood = cls()
n_neighborhood = cls(grid, wrap_around)
n_neighborhood.extend(offsets)
return n_neighborhood

View File

@ -5,9 +5,12 @@
@author: jrpotter
@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
TOLERATE = 1
SATISFY = 2
@ -26,22 +29,41 @@ class Ruleset:
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.wrap_around = wrap_around
def _matches(self, cell, grid, neighborhood):
def matches(self, cell, neighborhood):
"""
Determines that neighborhood matches expectation exactly.
Note this is just like the tolerate method with a tolerance of 1, but
recoding allows for short circuiting.
"""
residents = neighborhood.neighbors(cell, grid, self.wrap_around)
residents = neighborhood.neighbors(cell)
for resident in residents:
if resident[0].value != resident[1]:
return False
@ -49,7 +71,7 @@ class Ruleset:
return True
def _tolerate(self, cell, grid, neighborhood, tolerance):
def tolerate(self, cell, neighborhood, 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.
"""
matches = 0
residents = neighborhood.neighbors(cell, grid, self.wrap_around)
residents = neighborhood.neighbors(cell)
for resident in residents:
if resident[0].value == resident[1]:
matches += 1
@ -65,32 +87,15 @@ class Ruleset:
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.
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.
"""
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