Skip to content

Instantly share code, notes, and snippets.

@winny-
Last active August 29, 2015 14:24
Show Gist options
  • Save winny-/b5e15ac0c338c7513212 to your computer and use it in GitHub Desktop.
Save winny-/b5e15ac0c338c7513212 to your computer and use it in GitHub Desktop.
First [messy] attempt at Conway's Game of Life
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()
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