Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Game of Life source code #453

Merged
merged 13 commits into from
Nov 16, 2023
36 changes: 36 additions & 0 deletions game-of-life-python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# `rplife`

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/).

## Installation

1. Create and activate a Python virtual environment:

```sh
$ python -m venv ./venv
$ source venv/bin/activate
(venv) $
```

2. Install `rplife` in editable mode:

```sh
(venv) $ cd rplife
(venv) $ pip install -e .
```

## Execution

To execute `rplife`, go ahead and run the following command:

```sh
(venv) $ rplife -a
```

## Author

Real Python - Email: [email protected]

## License

Distributed under the MIT license. See `LICENSE` for more information.
19 changes: 19 additions & 0 deletions game-of-life-python/source_code_final/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[build-system]
requires = ["setuptools>=64.0.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "rplife"
dynamic = ["version"]
description = "Conway's Game of Life in your terminal"
readme = "README.md"
authors = [{ name = "Real Python", email = "[email protected]" }]
dependencies = [
'tomli; python_version < "3.11"',
]

[project.scripts]
rplife = "rplife.__main__:main"

[tool.setuptools.dynamic]
version = {attr = "rplife.__version__"}
1 change: 1 addition & 0 deletions game-of-life-python/source_code_final/rplife/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "1.0.0"
25 changes: 25 additions & 0 deletions game-of-life-python/source_code_final/rplife/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import sys

from rplife import patterns, views
from rplife.cli import get_command_line_args


def main():
args = get_command_line_args()
View = getattr(views, args.view)
if args.all:
for pattern in patterns.get_all_patterns():
_show_pattern(View, pattern, args)
else:
_show_pattern(View, patterns.get_pattern(name=args.pattern), args)


def _show_pattern(View, pattern, args):
try:
View(pattern=pattern, gen=args.gen, frame_rate=args.fps).show()
except Exception as error:
print(error, file=sys.stderr)


if __name__ == "__main__":
main()
50 changes: 50 additions & 0 deletions game-of-life-python/source_code_final/rplife/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import argparse

from rplife import __version__, patterns, views


def get_command_line_args():
parser = argparse.ArgumentParser(
prog="rplife",
description="Conway's Game of Life in your terminal",
)
parser.add_argument(
"--version", action="version", version=f"%(prog)s v{__version__}"
)
parser.add_argument(
"-p",
"--pattern",
choices=[pat.name for pat in patterns.get_all_patterns()],
default="Blinker",
help="take a pattern for the Game of Life (default: %(default)s)",
)
parser.add_argument(
"-a",
"--all",
action="store_true",
help="show all available patterns in a sequence",
)
parser.add_argument(
"-v",
"--view",
choices=views.__all__,
default="CursesView",
help="display the life grid in a specific view (default: %(default)s)",
)
parser.add_argument(
"-g",
"--gen",
metavar="NUM_GENERATIONS",
type=int,
default=10,
help="number of generations (default: %(default)s)",
)
parser.add_argument(
"-f",
"--fps",
metavar="FRAMES_PER_SECOND",
type=int,
default=7,
help="frames per second (default: %(default)s)",
)
return parser.parse_args()
51 changes: 51 additions & 0 deletions game-of-life-python/source_code_final/rplife/grid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import collections

ALIVE = "♥"
DEAD = "‧"


class LifeGrid:
def __init__(self, pattern):
self.pattern = pattern

def evolve(self):
neighbors = (
(-1, -1), # Above left
(-1, 0), # Above
(-1, 1), # Above right
(0, -1), # Left
(0, 1), # Right
(1, -1), # Below left
(1, 0), # Below
(1, 1), # Below right
)
num_neighbors = collections.defaultdict(int)
for row, col in self.pattern.alive_cells:
for drow, dcol in neighbors:
num_neighbors[(row + drow, col + dcol)] += 1

stay_alive = {
cell for cell, num in num_neighbors.items() if num in {2, 3}
} & self.pattern.alive_cells
come_alive = {
cell for cell, num in num_neighbors.items() if num == 3
} - self.pattern.alive_cells

self.pattern.alive_cells = stay_alive | come_alive

def as_string(self, bbox):
start_col, start_row, end_col, end_row = bbox
display = [self.pattern.name.center(2 * (end_col - start_col))]
for row in range(start_row, end_row):
display_row = [
ALIVE if (row, col) in self.pattern.alive_cells else DEAD
for col in range(start_col, end_col)
]
display.append(" ".join(display_row))
return "\n ".join(display)

def __str__(self):
return (
f"{self.pattern.name}:\n"
f"Alive cells -> {sorted(self.pattern.alive_cells)}"
)
34 changes: 34 additions & 0 deletions game-of-life-python/source_code_final/rplife/patterns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from dataclasses import dataclass
from pathlib import Path

try:
import tomllib
except ImportError:
import tomli as tomllib

PATTERNS_FILE = Path(__file__).parent / "patterns.toml"


@dataclass
class Pattern:
name: str
alive_cells: set[tuple[int, int]]

@classmethod
def from_toml(cls, name, toml_data):
return cls(
name,
alive_cells={tuple(cell) for cell in toml_data["alive_cells"]},
)


def get_pattern(name, filename=PATTERNS_FILE):
data = tomllib.loads(filename.read_text(encoding="utf-8"))
return Pattern.from_toml(name, toml_data=data[name])


def get_all_patterns(filename=PATTERNS_FILE):
data = tomllib.loads(filename.read_text(encoding="utf-8"))
return [
Pattern.from_toml(name, toml_data) for name, toml_data in data.items()
]
142 changes: 142 additions & 0 deletions game-of-life-python/source_code_final/rplife/patterns.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
["Blinker"]
alive_cells = [[2, 1], [2, 2], [2, 3]]

["Toad"]
alive_cells = [[2, 2], [2, 3], [2, 4], [3, 1], [3, 2], [3, 3]]

["Beacon"]
alive_cells = [[1, 1], [1, 2], [2, 1], [4, 3], [4, 4], [3, 4]]

["Pulsar"]
alive_cells = [
[2, 4],
[2, 5],
[2, 6],
[2, 10],
[2, 11],
[2, 12],
[4, 2],
[5, 2],
[6, 2],
[4, 7],
[5, 7],
[6, 7],
[4, 9],
[5, 9],
[6, 9],
[4, 14],
[5, 14],
[6, 14],
[7, 4],
[7, 5],
[7, 6],
[7, 10],
[7, 11],
[7, 12],
[9, 4],
[9, 5],
[9, 6],
[9, 10],
[9, 11],
[9, 12],
[10, 2],
[11, 2],
[12, 2],
[10, 7],
[11, 7],
[12, 7],
[10, 9],
[11, 9],
[12, 9],
[10, 14],
[11, 14],
[12, 14],
[14, 4],
[14, 5],
[14, 6],
[14, 10],
[14, 11],
[14, 12]
]

["Penta Decathlon"]
alive_cells = [
[5, 4],
[6, 4],
[7, 4],
[8, 4],
[9, 4],
[10, 4],
[11, 4],
[12, 4],
[5, 5],
[7, 5],
[8, 5],
[9, 5],
[10, 5],
[12, 5],
[5, 6],
[6, 6],
[7, 6],
[8, 6],
[9, 6],
[10, 6],
[11, 6],
[12, 6]
]

["Glider"]
alive_cells = [[0, 2], [1, 0], [1, 2], [2, 1], [2, 2]]

["Glider Gun"]
alive_cells = [
[0, 24],
[1, 22],
[1, 24],
[2, 12],
[2, 13],
[2, 20],
[2, 21],
[2, 34],
[2, 35],
[3, 11],
[3, 15],
[3, 20],
[3, 21],
[3, 34],
[3, 35],
[4, 0],
[4, 1],
[4, 10],
[4, 16],
[4, 20],
[4, 21],
[5, 0],
[5, 1],
[5, 10],
[5, 14],
[5, 16],
[5, 17],
[5, 22],
[5, 24],
[6, 10],
[6, 16],
[6, 24],
[7, 11],
[7, 15],
[8, 12],
[8, 13]
]

["Bunnies"]
alive_cells = [
[10, 10],
[10, 16],
[11, 12],
[11, 16],
[12, 12],
[12, 15],
[12, 17],
[13, 11],
[13, 13]
]
Loading
Loading