|
from collections import defaultdict |
|
from time import sleep |
|
from itertools import product |
|
import curses |
|
import random |
|
|
|
|
|
class Cell(object): |
|
def __init__(self, x, y): |
|
self._x, self._y = x, y |
|
|
|
@property |
|
def position(self): |
|
return (self._x, self._y) |
|
|
|
def __gt__(self, other): |
|
x1, y1 = self.position |
|
x2, y2 = other.position |
|
return (x1 > x2) and (y1 > y2) |
|
|
|
def __lt__(self, other): |
|
x1, y1 = self.position |
|
x2, y2 = other.position |
|
return x1 < x2 or y1 < y2 |
|
|
|
def __hash__(self): |
|
return hash(self.position) |
|
|
|
def __eq__(self, other): |
|
return self.position == other.position |
|
|
|
def __repr__(self): |
|
return "Cell" + str(self.position) |
|
|
|
|
|
class GameBoard(object): |
|
def __init__(self, *cells): |
|
self._cells = set(cells) |
|
|
|
def __str__(self): |
|
cells = self.cells |
|
pos_map = dict([[c.position, c] for c in cells]) |
|
out = "" |
|
for y in range(self.size, 0, -1): |
|
out += "\n" |
|
for x in range(self.size): |
|
out += '*' if (x, y) in pos_map else ' ' |
|
return out |
|
|
|
@property |
|
def cells(self): |
|
return self._cells |
|
|
|
@cells.setter |
|
def cells(self, items): |
|
self._cells = set(items) |
|
|
|
def _play(self, size=None): |
|
self.size = size |
|
while True: |
|
yield self.get_next_tick() |
|
|
|
def _get_neighbours_all(self, cell): |
|
x, y = cell.position |
|
positions = set(product(range(x - 1, x + 2), range(y - 1, y + 2))) - \ |
|
set([(x, y)]) |
|
return [Cell(x, y) for x, y in positions] |
|
|
|
def get_next_tick(self): |
|
living_cell_neighbors = defaultdict(list) |
|
dead_cell_neighbors = defaultdict(list) |
|
for living_cell in self.cells: |
|
for cell in self._get_neighbours_all(living_cell): |
|
if cell in self.cells: |
|
living_cell_neighbors[living_cell].append(cell) |
|
else: |
|
dead_cell_neighbors[cell].append(living_cell) |
|
next_tick_cells = [lcell for lcell in living_cell_neighbors if |
|
len(living_cell_neighbors[lcell]) in (2, 3)] |
|
next_tick_cells.extend([dcell for dcell in dead_cell_neighbors if |
|
len(dead_cell_neighbors[dcell]) == 3]) |
|
self.cells = next_tick_cells |
|
return self |
|
|
|
def __eq__(self, other): |
|
return self.cells == other.cells |
|
|
|
def __repr__(self): |
|
return "GameBoard" + str(tuple(self.cells)) |
|
|
|
def __enter__(self): |
|
return self |
|
|
|
def __exit__(self, type, value, traceback): |
|
return type is None |
|
|
|
|
|
class GameBoardNG(GameBoard): |
|
def __init__(self, cells=[], width=None, height=None, n_cells=None): |
|
self.width = width |
|
self.height = height |
|
self.n_cells = n_cells |
|
super(GameBoardNG, self).__init__(*cells) |
|
|
|
@property |
|
def cells(self): |
|
return self._cells |
|
|
|
@cells.setter |
|
def cells(self, items): |
|
if not items and self.n_cells: |
|
opt = xrange(1, self.n_cells + 1) |
|
items = [Cell(x, y) for x, y in zip( |
|
[x % self.width for x in random.sample(opt, self.n_cells)], |
|
[y % self.height for y in random.sample(opt, self.n_cells)])] |
|
self._cells = set(items) |
|
|
|
|
|
class GameBoardCurses(GameBoardNG): |
|
def __init__(self, cells=[], cell_symbol=None, frame_rate=None, |
|
n_cells=None): |
|
self.cell_symbol = cell_symbol |
|
self.frame_rate = frame_rate |
|
self.screen = curses.initscr() |
|
width, height = self.screen.getmaxyx() |
|
width, height = width - 1, height - 1 # include borders |
|
super(GameBoardCurses, self).__init__(*cells, |
|
width=width, |
|
height=height, |
|
n_cells=n_cells) |
|
|
|
def __enter__(self): |
|
return self |
|
|
|
def __exit__(self, type, value, traceback): |
|
if not type: |
|
return True |
|
if type is KeyboardInterrupt: |
|
curses.endwin() |
|
return True |
|
return False |
|
|
|
def play(self): |
|
min_cell = Cell(0, 0) |
|
max_cell = Cell(self.width, self.height) |
|
|
|
for gb in self._play(): |
|
sleep(self.frame_rate) |
|
self.screen.clear() |
|
self.screen.border(0) |
|
for cell in gb.cells: |
|
if cell > min_cell and max_cell > cell: |
|
x, y = cell.position |
|
self.screen.addstr(x, y, self.cell_symbol) |
|
self.screen.refresh() |
|
|
|
if __name__ == "__main__": |
|
from optparse import OptionParser |
|
|
|
parser = OptionParser(usage="usage: %prog [options]") |
|
parser.add_option("-s", "--symbol", dest="cell_symbol", |
|
default="X", type="string", |
|
help="ASCI symbol for cell representation") |
|
parser.add_option("-r", "--rate", dest="frame_rate", |
|
default=0.2, type="float", |
|
help="Interval between frames") |
|
parser.add_option("-n", "--init-cells-number", dest="n_cells", |
|
default=700, type="int", |
|
help="Number of cells to start the game") |
|
(options, args) = parser.parse_args() |
|
|
|
with GameBoardCurses(cell_symbol=options.cell_symbol, |
|
n_cells=options.n_cells, |
|
frame_rate=options.frame_rate) as game: |
|
game.play() |