Skip to content

Commit b838b47

Browse files
lpozoKateFinegan
andauthored
Game of Life source code (#453)
* Game of Life source code * TR updates and step folders * Fix format issues * Format issue * DR updates, first round * framerate -> frame_rate * Removed duplicate frame_rate and bbox * frame_rate in __main__.py * README LE * Link to tutorial --------- Co-authored-by: KateFinegan <[email protected]>
1 parent 735dd06 commit b838b47

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1949
-0
lines changed

game-of-life-python/README.md

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# `rplife`
2+
3+
Conway's Game of Life in your terminal, to accompany the Real Python tutorial [Build Conway's Game of Life With Python](https://realpython.com/conway-game-of-life-python/).
4+
5+
## Installation
6+
7+
1. Create and activate a Python virtual environment:
8+
9+
```sh
10+
$ python -m venv ./venv
11+
$ source venv/bin/activate
12+
(venv) $
13+
```
14+
15+
2. Install `rplife` in editable mode:
16+
17+
```sh
18+
(venv) $ cd rplife
19+
(venv) $ pip install -e .
20+
```
21+
22+
## Execution
23+
24+
To execute `rplife`, go ahead and run the following command:
25+
26+
```sh
27+
(venv) $ rplife -a
28+
```
29+
30+
## Author
31+
32+
Real Python - Email: [email protected]
33+
34+
## License
35+
36+
Distributed under the MIT license. See `LICENSE` for more information.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[build-system]
2+
requires = ["setuptools>=64.0.0", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "rplife"
7+
dynamic = ["version"]
8+
description = "Conway's Game of Life in your terminal"
9+
readme = "README.md"
10+
authors = [{ name = "Real Python", email = "[email protected]" }]
11+
dependencies = [
12+
'tomli; python_version < "3.11"',
13+
]
14+
15+
[project.scripts]
16+
rplife = "rplife.__main__:main"
17+
18+
[tool.setuptools.dynamic]
19+
version = {attr = "rplife.__version__"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "1.0.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import sys
2+
3+
from rplife import patterns, views
4+
from rplife.cli import get_command_line_args
5+
6+
7+
def main():
8+
args = get_command_line_args()
9+
View = getattr(views, args.view)
10+
if args.all:
11+
for pattern in patterns.get_all_patterns():
12+
_show_pattern(View, pattern, args)
13+
else:
14+
_show_pattern(View, patterns.get_pattern(name=args.pattern), args)
15+
16+
17+
def _show_pattern(View, pattern, args):
18+
try:
19+
View(pattern=pattern, gen=args.gen, frame_rate=args.fps).show()
20+
except Exception as error:
21+
print(error, file=sys.stderr)
22+
23+
24+
if __name__ == "__main__":
25+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import argparse
2+
3+
from rplife import __version__, patterns, views
4+
5+
6+
def get_command_line_args():
7+
parser = argparse.ArgumentParser(
8+
prog="rplife",
9+
description="Conway's Game of Life in your terminal",
10+
)
11+
parser.add_argument(
12+
"--version", action="version", version=f"%(prog)s v{__version__}"
13+
)
14+
parser.add_argument(
15+
"-p",
16+
"--pattern",
17+
choices=[pat.name for pat in patterns.get_all_patterns()],
18+
default="Blinker",
19+
help="take a pattern for the Game of Life (default: %(default)s)",
20+
)
21+
parser.add_argument(
22+
"-a",
23+
"--all",
24+
action="store_true",
25+
help="show all available patterns in a sequence",
26+
)
27+
parser.add_argument(
28+
"-v",
29+
"--view",
30+
choices=views.__all__,
31+
default="CursesView",
32+
help="display the life grid in a specific view (default: %(default)s)",
33+
)
34+
parser.add_argument(
35+
"-g",
36+
"--gen",
37+
metavar="NUM_GENERATIONS",
38+
type=int,
39+
default=10,
40+
help="number of generations (default: %(default)s)",
41+
)
42+
parser.add_argument(
43+
"-f",
44+
"--fps",
45+
metavar="FRAMES_PER_SECOND",
46+
type=int,
47+
default=7,
48+
help="frames per second (default: %(default)s)",
49+
)
50+
return parser.parse_args()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import collections
2+
3+
ALIVE = "♥"
4+
DEAD = "‧"
5+
6+
7+
class LifeGrid:
8+
def __init__(self, pattern):
9+
self.pattern = pattern
10+
11+
def evolve(self):
12+
neighbors = (
13+
(-1, -1), # Above left
14+
(-1, 0), # Above
15+
(-1, 1), # Above right
16+
(0, -1), # Left
17+
(0, 1), # Right
18+
(1, -1), # Below left
19+
(1, 0), # Below
20+
(1, 1), # Below right
21+
)
22+
num_neighbors = collections.defaultdict(int)
23+
for row, col in self.pattern.alive_cells:
24+
for drow, dcol in neighbors:
25+
num_neighbors[(row + drow, col + dcol)] += 1
26+
27+
stay_alive = {
28+
cell for cell, num in num_neighbors.items() if num in {2, 3}
29+
} & self.pattern.alive_cells
30+
come_alive = {
31+
cell for cell, num in num_neighbors.items() if num == 3
32+
} - self.pattern.alive_cells
33+
34+
self.pattern.alive_cells = stay_alive | come_alive
35+
36+
def as_string(self, bbox):
37+
start_col, start_row, end_col, end_row = bbox
38+
display = [self.pattern.name.center(2 * (end_col - start_col))]
39+
for row in range(start_row, end_row):
40+
display_row = [
41+
ALIVE if (row, col) in self.pattern.alive_cells else DEAD
42+
for col in range(start_col, end_col)
43+
]
44+
display.append(" ".join(display_row))
45+
return "\n ".join(display)
46+
47+
def __str__(self):
48+
return (
49+
f"{self.pattern.name}:\n"
50+
f"Alive cells -> {sorted(self.pattern.alive_cells)}"
51+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from dataclasses import dataclass
2+
from pathlib import Path
3+
4+
try:
5+
import tomllib
6+
except ImportError:
7+
import tomli as tomllib
8+
9+
PATTERNS_FILE = Path(__file__).parent / "patterns.toml"
10+
11+
12+
@dataclass
13+
class Pattern:
14+
name: str
15+
alive_cells: set[tuple[int, int]]
16+
17+
@classmethod
18+
def from_toml(cls, name, toml_data):
19+
return cls(
20+
name,
21+
alive_cells={tuple(cell) for cell in toml_data["alive_cells"]},
22+
)
23+
24+
25+
def get_pattern(name, filename=PATTERNS_FILE):
26+
data = tomllib.loads(filename.read_text(encoding="utf-8"))
27+
return Pattern.from_toml(name, toml_data=data[name])
28+
29+
30+
def get_all_patterns(filename=PATTERNS_FILE):
31+
data = tomllib.loads(filename.read_text(encoding="utf-8"))
32+
return [
33+
Pattern.from_toml(name, toml_data) for name, toml_data in data.items()
34+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
["Blinker"]
2+
alive_cells = [[2, 1], [2, 2], [2, 3]]
3+
4+
["Toad"]
5+
alive_cells = [[2, 2], [2, 3], [2, 4], [3, 1], [3, 2], [3, 3]]
6+
7+
["Beacon"]
8+
alive_cells = [[1, 1], [1, 2], [2, 1], [4, 3], [4, 4], [3, 4]]
9+
10+
["Pulsar"]
11+
alive_cells = [
12+
[2, 4],
13+
[2, 5],
14+
[2, 6],
15+
[2, 10],
16+
[2, 11],
17+
[2, 12],
18+
[4, 2],
19+
[5, 2],
20+
[6, 2],
21+
[4, 7],
22+
[5, 7],
23+
[6, 7],
24+
[4, 9],
25+
[5, 9],
26+
[6, 9],
27+
[4, 14],
28+
[5, 14],
29+
[6, 14],
30+
[7, 4],
31+
[7, 5],
32+
[7, 6],
33+
[7, 10],
34+
[7, 11],
35+
[7, 12],
36+
[9, 4],
37+
[9, 5],
38+
[9, 6],
39+
[9, 10],
40+
[9, 11],
41+
[9, 12],
42+
[10, 2],
43+
[11, 2],
44+
[12, 2],
45+
[10, 7],
46+
[11, 7],
47+
[12, 7],
48+
[10, 9],
49+
[11, 9],
50+
[12, 9],
51+
[10, 14],
52+
[11, 14],
53+
[12, 14],
54+
[14, 4],
55+
[14, 5],
56+
[14, 6],
57+
[14, 10],
58+
[14, 11],
59+
[14, 12]
60+
]
61+
62+
["Penta Decathlon"]
63+
alive_cells = [
64+
[5, 4],
65+
[6, 4],
66+
[7, 4],
67+
[8, 4],
68+
[9, 4],
69+
[10, 4],
70+
[11, 4],
71+
[12, 4],
72+
[5, 5],
73+
[7, 5],
74+
[8, 5],
75+
[9, 5],
76+
[10, 5],
77+
[12, 5],
78+
[5, 6],
79+
[6, 6],
80+
[7, 6],
81+
[8, 6],
82+
[9, 6],
83+
[10, 6],
84+
[11, 6],
85+
[12, 6]
86+
]
87+
88+
["Glider"]
89+
alive_cells = [[0, 2], [1, 0], [1, 2], [2, 1], [2, 2]]
90+
91+
["Glider Gun"]
92+
alive_cells = [
93+
[0, 24],
94+
[1, 22],
95+
[1, 24],
96+
[2, 12],
97+
[2, 13],
98+
[2, 20],
99+
[2, 21],
100+
[2, 34],
101+
[2, 35],
102+
[3, 11],
103+
[3, 15],
104+
[3, 20],
105+
[3, 21],
106+
[3, 34],
107+
[3, 35],
108+
[4, 0],
109+
[4, 1],
110+
[4, 10],
111+
[4, 16],
112+
[4, 20],
113+
[4, 21],
114+
[5, 0],
115+
[5, 1],
116+
[5, 10],
117+
[5, 14],
118+
[5, 16],
119+
[5, 17],
120+
[5, 22],
121+
[5, 24],
122+
[6, 10],
123+
[6, 16],
124+
[6, 24],
125+
[7, 11],
126+
[7, 15],
127+
[8, 12],
128+
[8, 13]
129+
]
130+
131+
["Bunnies"]
132+
alive_cells = [
133+
[10, 10],
134+
[10, 16],
135+
[11, 12],
136+
[11, 16],
137+
[12, 12],
138+
[12, 15],
139+
[12, 17],
140+
[13, 11],
141+
[13, 13]
142+
]

0 commit comments

Comments
 (0)