Skip to content

Commit 4652400

Browse files
authored
Implemented Conway's Game of Life with classic "glider" example (#62)
Implemented Conway's Game of Life with classic "glider" example
2 parents e1caff4 + ae241cd commit 4652400

File tree

1 file changed

+185
-0
lines changed

1 file changed

+185
-0
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
"""
2+
Conway's Game of Life
3+
"""
4+
import itertools
5+
6+
class GameOfLife:
7+
@classmethod
8+
def dead_grid(cls, *, height=None, width=None):
9+
return [
10+
[Dead() for _cols in range(width)]
11+
for _rows in range(height)
12+
]
13+
14+
@classmethod
15+
def from_str(cls, string):
16+
non_empty_lines = (
17+
line for line in string.splitlines()
18+
if len(line) > 0
19+
)
20+
parsed_grid = [
21+
[Cell.from_str(char) for char in line]
22+
for line in non_empty_lines
23+
]
24+
return cls(grid=parsed_grid)
25+
26+
def __init__(self, grid=None):
27+
self.grid = grid
28+
self.height = len(grid)
29+
self.width = len(grid[0])
30+
31+
32+
def __str__(self):
33+
return '\n'.join(
34+
''.join(str(cell) for cell in row)
35+
for row in self.grid
36+
)
37+
38+
def next_generation(self):
39+
next_grid = [
40+
[
41+
cell.next_state(neighbor_count)
42+
for cell, neighbor_count in row
43+
]
44+
for row in self.grid_with_live_neighbor_counts()
45+
]
46+
return GameOfLife(grid=next_grid)
47+
48+
def grid_with_live_neighbor_counts(self):
49+
'''
50+
Returns an iterator of grid rows in which each element
51+
is a tuple containing the cell and the count of living neighbors
52+
adjacent to that cell.
53+
E.g. [[(Live, 0), (Dead, 3), ...], ...]
54+
'''
55+
return (
56+
(
57+
(cell, self.count_live_neighbors(row, col))
58+
for (row, col), cell in coordinated_row
59+
)
60+
for coordinated_row in self.coordinate()
61+
)
62+
63+
def coordinate(self):
64+
'''
65+
Returns an iterator of grid rows in which each element
66+
is a tuple containg the coordinates and the content of the grid
67+
at those coordinates.
68+
E.g. [[((0, 0), Live), ((0, 1), Dead), ...], ...]
69+
'''
70+
return (
71+
(
72+
((row_index, col_index), cell)
73+
for col_index, cell in enumerate(row)
74+
)
75+
for row_index, row in enumerate(self.grid)
76+
)
77+
78+
def count_live_neighbors(self, row, col):
79+
directions_1D = (-1, 0, 1)
80+
directions_2D = itertools.product(directions_1D, directions_1D)
81+
neighbor_coords = (
82+
(row + d_row, col + d_col)
83+
for (d_row, d_col) in directions_2D
84+
if (d_row, d_col) != (0, 0)
85+
)
86+
87+
def is_coord_alive(coord):
88+
cell = self.get(*coord, default=Dead())
89+
return int(cell.is_alive)
90+
91+
return sum(map(is_coord_alive, neighbor_coords))
92+
93+
def get(self, row, col, default=None):
94+
is_within_rows = (0 <= row < self.height)
95+
is_within_cols = (0 <= col < self.width)
96+
if is_within_rows and is_within_cols:
97+
return self.grid[row][col]
98+
return default
99+
100+
101+
class Cell:
102+
@classmethod
103+
def from_str(cls, string):
104+
if string == Live.string_form:
105+
return Live()
106+
return Dead()
107+
108+
def __str__(self):
109+
return self.string_form
110+
111+
class Dead(Cell):
112+
string_form = '·'
113+
is_alive = False
114+
115+
def next_state(self, neighbor_count):
116+
if neighbor_count == 3:
117+
return Live()
118+
return Dead()
119+
120+
class Live(Cell):
121+
string_form = '0'
122+
is_alive = True
123+
124+
def next_state(self, neighbor_count):
125+
if neighbor_count in [2, 3]:
126+
return Live()
127+
return Dead()
128+
129+
130+
from textwrap import dedent
131+
132+
def run_string_example(
133+
*,
134+
seed_string=None,
135+
seed_name=None,
136+
num_gens=10
137+
):
138+
seed_game = GameOfLife.from_str(seed_string)
139+
if seed_name is None:
140+
seed_name = f'A {seed_game.height}x{seed_game.width} grid'
141+
print(dedent(f'''
142+
=========================
143+
| Conway's Game of Life |
144+
{'':=^50}
145+
| {f'Starting with seed: "{seed_name:.10}"': <46.46} |
146+
| {f'Running for {str(num_gens):1.3} generations.': <46.46} |
147+
{'':=^50}
148+
'''))
149+
latest_generation = seed_game
150+
for gen_num in range(1, num_gens + 1):
151+
print(f'Generation {gen_num}:')
152+
print(str(latest_generation))
153+
latest_generation = latest_generation.next_generation()
154+
print('Done')
155+
156+
def glider_example():
157+
glider_string = dedent('''
158+
··0····
159+
0·0····
160+
·00····
161+
·······
162+
·······
163+
·······
164+
''')
165+
run_string_example(
166+
seed_string=glider_string,
167+
seed_name='Glider',
168+
num_gens=15
169+
)
170+
171+
def question_example():
172+
from textwrap import dedent
173+
174+
game_string = dedent('''
175+
·0·
176+
0·0
177+
''')
178+
run_string_example(
179+
seed_string=game_string,
180+
num_gens=4
181+
)
182+
183+
if __name__ == '__main__':
184+
glider_example()
185+
question_example()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy