Last active
August 29, 2015 14:24
-
-
Save winny-/b5e15ac0c338c7513212 to your computer and use it in GitHub Desktop.
First [messy] attempt at Conway's Game of Life
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import print_function | |
from copy import deepcopy | |
import curses | |
from time import sleep | |
import random | |
import os | |
from collections import namedtuple | |
from line_profiler import LineProfiler | |
try: | |
range = xrange | |
except NameError: | |
pass | |
Area = namedtuple('Area', ['x', 'y']) | |
A = Area | |
Point = namedtuple('Point', ['x', 'y']) | |
P = Point | |
profiler = LineProfiler() | |
class Board(object): | |
def __init__(self, seed_or_area, fill=False): | |
if isinstance(seed_or_area, Area): | |
self = self.make(seed_or_area, fill) | |
return | |
self.seed = seed_or_area | |
self.grid = deepcopy(self.seed) | |
@classmethod | |
def make(cls, area, fill=False): | |
return cls([[fill for _ in range(area.x)] | |
for _ in range(area.y)]) | |
@classmethod | |
def make_random(cls, area): | |
return cls([[random.choice([True, False]) for _ in range(area.x)] | |
for _ in range(area.y)]) | |
@classmethod | |
def parse(cls, s, alive='o'): | |
return cls([[cell == alive for cell in row] for row in s.split('\n') if row]) | |
@property | |
def width(self): | |
return len(self.grid[0]) | |
@property | |
def height(self): | |
return len(self.grid) | |
@property | |
def area(self): | |
return Area(x=self.width, | |
y=self.height) | |
def __repr__(self): | |
return '<{}.{} {}x{} at 0x{:x}>'.format( | |
__name__, | |
self.__class__.__name__, | |
self.width, | |
self.height, | |
hash(self), | |
) | |
def __str__(self): | |
return '\n'.join(''.join('o' if cell else '-' for cell in row) for row in self.grid) | |
def __getitem__(self, key): | |
return self.grid[key] | |
# @profile | |
# def get_neighbor_points(self, point): | |
# for other in (Point(x=point.x+x_offset, y=point.y+y_offset) | |
# for x_offset in range(-1, 2) | |
# for y_offset in range(-1, 2)): | |
# if other == point: | |
# continue | |
# if not -1 < other.x < self.width: | |
# continue | |
# if not -1 < other.y < self.height: | |
# continue | |
# yield other | |
@profile | |
def get_all_neighbor_points(self, point): | |
yield Point(x=point.x-1, y=point.y-1) | |
yield Point(x=point.x, y=point.y-1) | |
yield Point(x=point.x+1, y=point.y-1) | |
yield Point(x=point.x-1, y=point.y) | |
yield Point(x=point.x+1, y=point.y) | |
yield Point(x=point.x-1, y=point.y+1) | |
yield Point(x=point.x, y=point.y+1) | |
yield Point(x=point.x+1, y=point.y+1) | |
@profile | |
def get_neighbor_points(self, point): | |
for pt in self.get_all_neighbor_points(point): | |
if not -1 < pt.x < self.width: | |
continue | |
if not -1 < pt.y < self.height: | |
continue | |
yield pt | |
@profile | |
def get_neighbors(self, point): | |
return (self.grid[pt.y][pt.x] for pt in self.get_neighbor_points(point)) | |
@property | |
def cell_count(self): | |
return self.width * self.height | |
@property | |
def alive(self): | |
return sum(cell for row in self.grid for cell in row) | |
@property | |
def dead(self): | |
return self.cell_count - self.alive | |
class ConwaysGameOfLife(object): | |
def __init__(self, seed_or_board): | |
self.board = seed_or_board if isinstance(seed_or_board, Board) else Board(seed_or_board) | |
self.generation = 0 | |
def __getitem__(self, key): | |
return self.board[key] | |
def __getattr__(self, attr): | |
return getattr(self.board, attr) | |
@profile | |
def age(self, generations=1): | |
new_grid = deepcopy(self.board.grid) | |
for x in range(self.width): | |
for y in range(self.height): | |
pt = Point(x=x, y=y) | |
neighbors = self.get_neighbors(pt) | |
alive = sum(neighbors) | |
if alive < 2: | |
living = False | |
elif alive > 3: | |
living = False | |
elif alive == 3: | |
living = True | |
else: | |
living = None | |
if living is not None: | |
new_grid[pt.y][pt.x] = living | |
self.board.grid = new_grid | |
self.generation += 1 | |
def prompt(window, options): | |
window.clear() | |
s = '\n'.join('({}) {}'.format(opt[0], opt[1]) for opt in enumerate(options)) | |
window.addstr(0, 0, s) | |
y, x = window.getmaxyx() | |
window.move(y-1, x-1) | |
choice = int(window.getkey()) | |
return choice | |
def inner_main(window): | |
y, x = window.getmaxyx() | |
area = A(x=x-1, y=y-1) | |
idx = prompt(window, ['All alive', 'Random', 'Common seed']) | |
if idx == 0: | |
seed = Board.make(area) | |
elif idx == 1: | |
seed = Board.make_random(area) | |
else: | |
seed = COMMON | |
curses.noecho() | |
curses.cbreak() | |
window.nodelay(True) | |
window.clear() | |
bottom_row = y - 1 | |
game = ConwaysGameOfLife(seed) | |
fmt = 'Generation: {} | A/D/T: {}/{}/{} | {:2.2%} alive' | |
while True: | |
window.clear() | |
window.addstr(0, 0, str(game.board))#, curses.A_NORMAL) | |
# for x in range(game.width): | |
# for y in range(game.height): | |
# alive = game[y][x] | |
# ch = ord('o' if alive else '-') | |
# window.addch(y, x, ch) | |
window.addstr(bottom_row, 0, fmt.format( | |
game.generation, | |
game.alive, | |
game.dead, | |
game.cell_count, | |
float(game.alive) / game.cell_count, | |
))#, curses.A_REVERSE | curses.A_DIM) | |
y, x = window.getmaxyx() | |
window.move(y-1, x-1) | |
game.age() | |
window.refresh() | |
sleep(.01) | |
c = window.getch() | |
if c > 0 and chr(c) == 'q': | |
return | |
def main(): | |
curses.wrapper(inner_main) | |
COMMON = Board.parse(""" | |
-------------------------------------- | |
-oo---oo----oo---oo----o---------oo--- | |
-oo--o--o--o--o--o-o---o----ooo--o---- | |
------oo----o-o---o----o---ooo------o- | |
-------------o---------------------oo- | |
-------------------------------------- | |
-------------------------------------- | |
-----o-----o-------------------------- | |
-----o-----o-------------------------- | |
-----oo---oo-------------------------- | |
-------------------------------------- | |
-ooo--oo-oo--ooo---------------------- | |
---o-o-o-o-o-o------------------------ | |
-----oo---oo-------------------------- | |
-------------------------------------- | |
-----oo---oo-------------------------- | |
---o-o-o-o-o-o------------------------ | |
-ooo--oo-oo--ooo---------------------- | |
-------------------------------------- | |
-----oo---oo-------------------------- | |
-----o-----o-------------------------- | |
-----o-----o-------------------------- | |
-------------------------------------- | |
""") | |
if __name__ == '__main__': | |
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Timer unit: 1e-06 s | |
Total time: 6.86093 s | |
File: conway.py | |
Function: get_all_neighbor_points at line 89 | |
Line # Hits Time Per Hit % Time Line Contents | |
============================================================== | |
89 @profile | |
90 def get_all_neighbor_points(self, point): | |
91 97319 903298 9.3 13.2 yield Point(x=point.x-1, y=point.y-1) | |
92 97319 846498 8.7 12.3 yield Point(x=point.x, y=point.y-1) | |
93 97319 862461 8.9 12.6 yield Point(x=point.x+1, y=point.y-1) | |
94 97319 844364 8.7 12.3 yield Point(x=point.x-1, y=point.y) | |
95 97319 845817 8.7 12.3 yield Point(x=point.x+1, y=point.y) | |
96 97319 859206 8.8 12.5 yield Point(x=point.x-1, y=point.y+1) | |
97 97319 845160 8.7 12.3 yield Point(x=point.x, y=point.y+1) | |
98 97319 854128 8.8 12.4 yield Point(x=point.x+1, y=point.y+1) | |
Total time: 26.4493 s | |
File: conway.py | |
Function: get_neighbor_points at line 100 | |
Line # Hits Time Per Hit % Time Line Contents | |
============================================================== | |
100 @profile | |
101 def get_neighbor_points(self, point): | |
102 875871 16329197 18.6 61.7 for pt in self.get_all_neighbor_points(point): | |
103 778552 4849418 6.2 18.3 if not -1 < pt.x < self.width: | |
104 2745 3892 1.4 0.0 continue | |
105 775807 4196029 5.4 15.9 if not -1 < pt.y < self.height: | |
106 9543 12842 1.3 0.0 continue | |
107 766264 1057965 1.4 4.0 yield pt | |
Total time: 0.452985 s | |
File: conway.py | |
Function: get_neighbors at line 109 | |
Line # Hits Time Per Hit % Time Line Contents | |
============================================================== | |
109 @profile | |
110 def get_neighbors(self, point): | |
111 97319 452985 4.7 100.0 return (self.grid[pt.y][pt.x] for pt in self.get_neighbor_points(point)) | |
Total time: 45.9379 s | |
File: conway.py | |
Function: age at line 139 | |
Line # Hits Time Per Hit % Time Line Contents | |
============================================================== | |
139 @profile | |
140 def age(self, generations=1): | |
141 8 740809 92601.1 1.6 new_grid = deepcopy(self.board.grid) | |
142 1603 2756 1.7 0.0 for x in range(self.width): | |
143 98914 193801 2.0 0.4 for y in range(self.height): | |
144 97319 774904 8.0 1.7 pt = Point(x=x, y=y) | |
145 97319 2486737 25.6 5.4 neighbors = self.get_neighbors(pt) | |
146 97319 40745176 418.7 88.7 alive = sum(neighbors) | |
147 | |
148 97318 216698 2.2 0.5 if alive < 2: | |
149 36657 63340 1.7 0.1 living = False | |
150 60661 98026 1.6 0.2 elif alive > 3: | |
151 22777 37540 1.6 0.1 living = False | |
152 37884 58555 1.5 0.1 elif alive == 3: | |
153 17420 28635 1.6 0.1 living = True | |
154 else: | |
155 20464 30218 1.5 0.1 living = None | |
156 | |
157 97318 150255 1.5 0.3 if living is not None: | |
158 76854 309985 4.0 0.7 new_grid[pt.y][pt.x] = living | |
159 | |
160 7 454 64.9 0.0 self.board.grid = new_grid | |
161 | |
162 7 22 3.1 0.0 self.generation += 1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment