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:
|
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))
|
||||||
|
|
||||||
|
|
|
@ -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
|
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
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue