Functional Game of Life
parent
774cb8243d
commit
932ca4409a
36
src/cam.py
36
src/cam.py
|
@ -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))
|
||||
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue