Skip to content

Commit

Permalink
Game of Life source code
Browse files Browse the repository at this point in the history
  • Loading branch information
lpozo committed Oct 20, 2023
1 parent 8511114 commit 8902295
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 0 deletions.
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.

## Installation

1. Create and activate a Python virtual environment

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

2. Install the `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, framerate=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(
"-g",
"--gen",
metavar="NUM_GENERATIONS",
type=int,
default=10,
help="number of generations (default: %(default)s)",
)
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(
"-f",
"--fps",
metavar="FRAMES_PER_SECOND",
type=int,
default=7,
help="frames per second (default: %(default)s)",
)
return parser.parse_args()
50 changes: 50 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,50 @@
import collections

ALIVE = "♥"
DEAD = "‧"


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

def evolve(self):
neighbors = (
(-1, -1),
(-1, 0),
(-1, 1),
(0, -1),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
)
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}:\nAlive cells -> {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 as toml
except ImportError:
import tomli as toml

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, file_name=PATTERNS_FILE):
data = toml.loads(file_name.read_text(encoding="utf-8"))
return Pattern.from_toml(name, toml_data=data[name])


def get_all_patterns(file_name=PATTERNS_FILE):
data = toml.loads(file_name.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

0 comments on commit 8902295

Please sign in to comment.