Skip to content

Instantly share code, notes, and snippets.

@al-the-x
Created November 16, 2014 23:13
Show Gist options
  • Save al-the-x/c9eb047ab1d0a361899e to your computer and use it in GitHub Desktop.
Save al-the-x/c9eb047ab1d0a361899e to your computer and use it in GitHub Desktop.
Implementation of Conway's Game of Life in Python for a potentially infinite board using the `doctest` module: `python -m doctest conway.py`
def coalesce(*args):
return next((a for a in args if a is not None), None)
class Game(dict):
_xmin = _xmax = _ymin = _ymax = None
@property
def bounds(self):
"""
>>> Game((0,0)).bounds
{'y': (0, 0), 'x': (0, 0)}
>>> Game((999,999)).bounds
{'y': (999, 999), 'x': (999, 999)}
>>> Game((0,0), (999,999)).bounds
{'y': (0, 999), 'x': (0, 999)}
>>> Game((-999,-999), (999,999)).bounds
{'y': (-999, 999), 'x': (-999, 999)}
"""
return {
'x': (self._xmin, self._xmax),
'y': (self._ymin, self._ymax)
}
def _set_bounds(self, x, y):
self._xmax, self._xmin, self._ymax, self._ymin = (
max(coalesce(self._xmax, x), x),
min(coalesce(self._xmin, x), x),
max(coalesce(self._ymax, y), y),
min(coalesce(self._ymin, y), y),
)
def __str__(self):
"""
>>> print Game()
+---+
| |
+---+
>>> print Game((0,0))
+---+
| X |
+---+
>>> print Game((0,0),(0,1),(0,-1),(1,0),(-1,0))
+---+---+---+
| | X | |
+---+---+---+
| X | X | X |
+---+---+---+
| | X | |
+---+---+---+
>>> print Game((1,1), (-1,-1))
+---+---+---+
| X | | |
+---+---+---+
| | | |
+---+---+---+
| | | X |
+---+---+---+
>>> print Game((2,2), (-2,-2))
+---+---+---+---+---+
| X | | | | |
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
| | | | | X |
+---+---+---+---+---+
"""
xmin, xmax = (coalesce(v,0) for v in self.bounds['x'])
ymin, ymax = (coalesce(v,0) for v in self.bounds['y'])
S, spacer = '', '+' + '---+' * (ymax + 1 - ymin)
for x in xrange(xmin, xmax + 1):
S += spacer + "\n|"
for y in xrange(ymin, ymax + 1):
S += ' ' + (
'X' if self.is_alive(x,y) else ' '
) + ' |'
S += "\n"
S += spacer
return S
def __repr__(self):
"""
>>> Game(
... (0,0), (0,1), (0,-1)
... )
[(0, 1), (0, -1), (0, 0)]
"""
return repr(self.values())
def tick(self):
"""
>>> game = Game()
>>> game.tick()
[]
>>> game.live(0,0).live(0,1).live(0,-1).tick()
[(0, 0), (-1, 0), (1, 0)]
"""
return [ coord for coord in self.consider()
if self.rules(self.is_alive(*coord),
self.live_neighbors(*coord))
]
def live_neighbors(self, x, y):
"""
>>> game = Game()
>>> game.live_neighbors(0,0)
0
>>> game.live(0,0).is_alive(0,0)
True
>>> game.live(0,1).is_alive(0,1)
True
>>> game.live(0,-1).is_alive(0,-1)
True
>>> game.live_neighbors(0,0)
2
>>> game.live_neighbors(0,1)
1
>>> game.live_neighbors(0,-1)
1
"""
return reduce(
lambda total, coord: total + (
1 if self.is_alive(*coord) else 0
), self.neighbors(x,y), 0
)
def consider(self):
"""
>>> game = Game()
>>> game.consider()
()
>>> game.live(0,0).consider()
((0, 1), (-1, 1), (0, 0), (-1, 0), (-1, -1), (0, -1), (1, 0), (1, -1), (1, 1))
>>> game.live(1,1).consider()
((0, 1), (1, 2), (-1, 1), (0, 0), (-1, 0), (2, 2), (2, 1), (0, 2), (2, 0), (-1, -1), (0, -1), (1, 0), (1, -1), (1, 1))
"""
coords = [ ]
for coord in self.values():
coords += (coord,) + self.neighbors(*coord)
return tuple(set(coords))
def neighbors(self, x, y):
"""
>>> game = Game()
>>> game.neighbors(0,0)
((0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, 0), (1, -1), (1, 1))
>>> game.neighbors(1,1)
((0, 1), (1, 2), (0, 0), (2, 1), (2, 0), (2, 2), (1, 0), (0, 2))
"""
offsets = (-1, 0, 1)
return tuple(set( (x + dX, y + dY)
for dX in offsets
for dY in offsets
if not (dX == dY == 0)
))
def __init__(self, *coords):
"""
>>> game = Game(
... (0,0), (0,-1), (0, +1)
... )
>>> game.is_alive(0,0)
True
>>> game.is_alive(0,-1)
True
>>> game.is_alive(0,+1)
True
"""
[ self.live(*coord) for coord in coords ]
def __missing__(self, coord):
return False
def __setitem__(self, index, value):
self._set_bounds(*index)
return super(Game, self).__setitem__(index, value)
def live(self, x, y):
"""
>>> game = Game()
>>> game.is_alive(0,0)
False
>>> game.live(0,0).is_alive(0,0)
True
"""
self[(x,y)] = self[(x,y)] or (x,y)
return self
def is_alive(self, x, y):
"""
>>> game = Game()
>>> game.is_alive(0,0)
False
>>> game.is_alive(0,1)
False
>>> game.is_alive(1,0)
False
"""
return self[(x,y)] and True
def rules(self, cell, neighbors):
"""
>>> game = Game()
>>> game.rules(True, 1)
False
>>> game.rules(True, 2)
True
>>> game.rules(True, 3)
True
>>> game.rules(True, 4)
False
>>> game.rules(True, 5)
False
>>> game.rules(False, 1)
False
>>> game.rules(False, 2)
False
>>> game.rules(False, 3)
True
>>> game.rules(False, 4)
False
>>> game.rules(False, 5)
False
"""
if cell and 2 <= neighbors <= 3:
return True
if not cell and neighbors is 3:
return True
return False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment