diff --git a/.gitignore b/.gitignore index ed98108..0c4d392 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Exports *.csv +# VS Code +.vscode/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/README.md b/README.md index f8f8036..48ef4fd 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,101 @@ -# CGOL · [![GitHub License](https://img.shields.io/github/license/INeido/CGOL?style=for-the-badge)](https://github.com/INeido/CGOL/blob/main/LICENSE) [![PyPI](https://img.shields.io/pypi/v/CGOL?style=for-the-badge)](https://pypi.org/project/CGOL/) [![GitHub release](https://img.shields.io/github/v/release/INeido/CGOL?label=GitHub&style=for-the-badge)](https://github.com/INeido/CGOL/releases) +# CGOL · [![PyPI](https://img.shields.io/pypi/v/CGOL?style=for-the-badge&logo=PyPi)](https://pypi.org/project/CGOL/) [![GitHub release](https://img.shields.io/github/v/release/INeido/CGOL?label=GitHub&style=for-the-badge&logo=GitHub)](https://github.com/INeido/CGOL/releases) ![GitHub repo size](https://img.shields.io/github/repo-size/INeido/CGOL?style=for-the-badge) ![GitHub License](https://img.shields.io/github/license/INeido/CGOL?style=for-the-badge) +A Conway's Game of Life implementation using numpy and pygame. -A whack Conway's Game of Life implementation. - -![](https://github.com/INeido/CGOL/blob/main/img/demo0.png?raw=true) +![](https://github.com/INeido/CGOL/blob/main/samples/logo.png?raw=true) ## Description -Play around with cellular automata. +This project has no particular aim. It is a purely a personal project and barely maintained. + +It is a CLI based [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life): implementation using numpy for fast calculations and pygame for an interactive simulation. + +--- + +Rules of Conway's Game of Life +1. Any live cell with two or three live neighbors survives. +2. Any dead cell with three live neighbors becomes a live cell. +3. All other live cells die in the next generation. Similarly, all other dead cells stay dead. + +## Installing + +Install using pip +```bash +pip install cgol +``` + +Manually install using CLI +```bash +git clone https://github.com/INeido/CGOL +pip install -e CGOL/. +``` + +## Usage -![](https://github.com/INeido/CGOL/blob/main/img/demo1.png?raw=true) +Here are some examples. + +Start a simulation with the default setting but with a custom seed. +```bash +cgol -s 42 +``` +![](https://github.com/INeido/CGOL/blob/main/samples/demo1.gif?raw=true) + +Change grid size, cell size and fade color. +```bash +cgol -cf 130 20 0 -cs 8 -sx 90 -sy 160 +``` +![](https://github.com/INeido/CGOL/blob/main/samples/demo2.gif?raw=true) + +Change the color to white on black without fade. +```bash +cgol -fd 0.0 -ca 255 255 255 +``` +![](https://github.com/INeido/CGOL/blob/main/samples/demo3.gif?raw=true) Draw with the mouse to birth or kill cells. -![](https://github.com/INeido/CGOL/blob/main/img/demo0.gif?raw=true) +![](https://github.com/INeido/CGOL/blob/main/samples/demo0.gif?raw=true) + ## Arguments +``` +usage: CGOL [-h] [--res-h RH] [--res-w RW] [--colour-alive CA [CA ...]] [--colour-dead CD [CD ...]] [--colour-fade CF [CF ...]] + [--colour-background CB [CB ...]] [--cell_size CS] [--size-x SX] [--size-y SY] [--tickrate T] [--seed S] [--save-file F] [--load L] + [--pause-stalemate PS] [--pause-oscillators PO] [--fade-rate FR] [--fade-death-value FD] + +Conway's Game of Life + +options: + -h, --help show this help message and exit + --res-h RH, -rh RH Height of the Game. + --res-w RW, -rw RW Width of the Game. + --colour-alive CA [CA ...], -ca CA [CA ...] + Colour for alive cells. 'R G B' + --colour-dead CD [CD ...], -cd CD [CD ...] + Colour for dead cells. 'R G B' + --colour-fade CF [CF ...], -cf CF [CF ...] + Colour to fade dead cells to. 'R G B' + --colour-background CB [CB ...], -cb CB [CB ...] + Colour for dead cells. 'R G B' + --cell_size CS, -cs CS + Size of a cell in pixel. + --size-x SX, -sx SX Height of the World. + --size-y SY, -sy SY Width of the World. + --tickrate T, -t T Number of times the game shall update in a second (FPS). + --seed S, -s S Seed value used to create World. + --save-file F, -f F Path of the in-/output file. (Should be .csv) + --load L, -l L Load revious save. + --pause-stalemate PS, -ps PS + Game pauses on a stalemate. + --pause-oscillators PO, -po PO + Game pauses when only oscillators remain. + --fade-rate FR, -fr FR + Value by which a cell should decrease every generation. + --fade-death-value FD, -fd FD + Value a cell should have after death. +``` + | Argument | Description | Default Value | | ------ | ------ | ------ | | --res-h (-rh) | Height of the Game. | 720 | @@ -60,18 +139,3 @@ Draw with the mouse to birth or kill cells. | Right Arrow | Forward one generation. | | + | Extend grid by one cell in every direction. | | - | Reduce grid by one cell in every direction. | - -## Installing - -You can install the code and download the requirements with the following commands. -```bash -git clone https://github.com/INeido/CGOL -cd CGOL -pip install -r requirements.txt -python ./src/cgol/__main__.py -``` - -Alternatively, install using pip -```bash -pip install cgol -``` \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d14658e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,38 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "CGOL" +version = "0.8.0" +description = "A whack Conway's Game of Life implementation." +readme = "README.md" +requires-python = ">=3" +license = {text = "GPL-3"} +classifiers = [ + "Programming Language :: Python :: 3", + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Topic :: Games/Entertainment :: Simulation", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", + "Natural Language :: English", +] +authors = [ + {name = "Neido", email = "reg@neido.de"} +] +maintainers = [ + {name = "Neido", email = "reg@neido.de"} +] +dependencies = [ + "pygame", + "numpy", +] + +[project.urls] +Homepage = "https://github.com/INeido/CGOL/" +Repository = "https://github.com/INeido/CGOL/" +Issues = "https://github.com/INeido/CGOL/issues" + +[project.scripts] +cgol = "cgol.__main__:main" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d65fad8..d74d269 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ argparse pygame numpy --e . diff --git a/img/demo0.gif b/samples/demo0.gif similarity index 100% rename from img/demo0.gif rename to samples/demo0.gif diff --git a/img/demo1.png b/samples/demo0.png similarity index 100% rename from img/demo1.png rename to samples/demo0.png diff --git a/samples/demo1.gif b/samples/demo1.gif new file mode 100644 index 0000000..f02b31a Binary files /dev/null and b/samples/demo1.gif differ diff --git a/samples/demo2.gif b/samples/demo2.gif new file mode 100644 index 0000000..03a7deb Binary files /dev/null and b/samples/demo2.gif differ diff --git a/samples/demo3.gif b/samples/demo3.gif new file mode 100644 index 0000000..3256daa Binary files /dev/null and b/samples/demo3.gif differ diff --git a/img/demo0.png b/samples/logo.png similarity index 100% rename from img/demo0.png rename to samples/logo.png diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index bd60c2a..0000000 --- a/setup.cfg +++ /dev/null @@ -1,42 +0,0 @@ -[metadata] -name = CGOL -version = 2.0.2 -license = GNU v3 -description = A whack Conways Game of Life implementation. -long_description = file: README.md -long_description_content_type= text/markdown -author = Neido -author_email = reg@neido.de -url = https://github.com/INeido/CGOL/ -project_urls = - Source=https://github.com/INeido/CGOL/ - Issues=https://github.com/INeido/CGOL//issues -classifiers= - Programming Language :: Python :: 3 - Development Status :: 4 - Beta - Intended Audience :: Science/Research - Topic :: Games/Entertainment :: Simulation - License :: OSI Approved :: GNU General Public License v3 (GPLv3) - Operating System :: OS Independent - Natural Language :: English - -[options] -python_requires = >=3.6 -include_package_data = True -packages = find: -package_dir = - =src -install_requires = - argparse - pygame - numpy - -[options.packages.find] -where=src - -[options.extras_require] -test = - pytest>=6.2.0 -docs = - sphinx >= 1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0 - sphinx_rtd_theme \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 6068493..0000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup() diff --git a/src/cgol/__init__.py b/src/cgol/__init__.py new file mode 100644 index 0000000..076a9a8 --- /dev/null +++ b/src/cgol/__init__.py @@ -0,0 +1,40 @@ +from .game import Game +import argparse + + +def main(): + # Gather arguments + parser = argparse.ArgumentParser(prog="CGOL", description="Conway's Game of Life") + parser.add_argument("--res-h", "-rh", dest="rh", default=720, type=int, required=False, help="Height of the Game.") + parser.add_argument("--res-w", "-rw", dest="rw", default=1280, type=int, required=False, help="Width of the Game.") + parser.add_argument("--color-alive", "-ca", dest="ca", default=(255, 144, 0), type=int, required=False, help="Colour for alive cells. 'R G B'", nargs='+') + parser.add_argument("--color-dead", "-cd", dest="cd", default=(0, 0, 0), type=int, required=False, help="Colour for dead cells. 'R G B'", nargs='+') + parser.add_argument("--color-fade", "-cf", dest="cf", default=(0, 0, 0), type=int, required=False, help="Colour to fade dead cells to. 'R G B'", nargs='+') + parser.add_argument("--color-background", "-cb", dest="cb", default=(16, 16, 16), type=int, required=False, help="Colour for dead cells. 'R G B'", nargs='+') + parser.add_argument("--cell_size", "-cs", dest="cs", default=16, type=int, required=False, help="Size of a cell in pixel.") + parser.add_argument("--size-x", "-sx", dest="sx", default=45, type=int, required=False, help="Height of the World.") + parser.add_argument("--size-y", "-sy", dest="sy", default=80, type=int, required=False, help="Width of the World.") + parser.add_argument("--tickrate", "-t", dest="t", default=30, type=float, required=False, help="Number of times the game shall update in a second (FPS).") + parser.add_argument("--seed", "-s", dest="s", default=-1, type=int, required=False, help="Seed value used to create World.") + parser.add_argument("--save-file", "-f", dest="f", default="./cgol.csv", type=str, required=False, help="Path of the in-/output file. (Should be .csv)") + parser.add_argument("--load", "-l", dest="l", default=False, type=bool, required=False, help="Load revious save.") + parser.add_argument("--pause-stalemate", "-ps", dest="ps", default=False, type=bool, required=False, help="Game pauses on a stalemate.") + parser.add_argument("--pause-oscillators", "-po", dest="po", default=False, type=bool, required=False, help="Game pauses when only oscillators remain.") + parser.add_argument("--fade-rate", "-fr", dest="fr", default=0.01, type=float, required=False, help="Value by which a cell should decrease every generation.") + parser.add_argument("--fade-death-value", "-fd", dest="fd", default=0.5, type=float, required=False, help="Value a cell should have after death.") + args = parser.parse_args() + + # Create new Game + game = Game(args.rh, args.rw, tuple(args.ca), tuple(args.cd), tuple(args.cf), tuple(args.cb), args.cs, args.t, args.f, args.ps, args.po) + + game.setup_pygame() + + # Let Game create the World + game.create_world(args.sx, args.sy, args.s, args.l, args.fr, args.fd) + + # Start Game + game.game_loop() + + +if __name__ == "__main__": + main() diff --git a/src/cgol/__main__.py b/src/cgol/__main__.py index 9b5ffd7..29c2e0a 100644 --- a/src/cgol/__main__.py +++ b/src/cgol/__main__.py @@ -1,52 +1,4 @@ -""" -CGOL -==== -A whack Conway's Game of Life implementation. - -Rules of Conway's Game of Life: -1. Any live cell with two or three live neighbours survives. -2. Any dead cell with three live neighbours becomes a live cell. -3. All other live cells die in the next generation. Similarly, all other dead cells stay dead. - -Github Repo: -https://github.com/INeido/CGOL -""" -from game import Game -import argparse - - -def main(): - # Gather arguments - parser = argparse.ArgumentParser(prog="CGOL", description="Conway's Game of Life") - parser.add_argument("--res-h", "-rh", dest="rh", default=720, type=int, required=False, help="Height of the Game.") - parser.add_argument("--res-w", "-rw", dest="rw", default=1280, type=int, required=False, help="Width of the Game.") - parser.add_argument("--colour-alive", "-ca", dest="ca", default=(255, 144, 0), type=int, required=False, help="Colour for alive cells. 'R G B'", nargs='+') - parser.add_argument("--colour-dead", "-cd", dest="cd", default=(0, 0, 0), type=int, required=False, help="Colour for dead cells. 'R G B'", nargs='+') - parser.add_argument("--colour-fade", "-cf", dest="cf", default=(0, 0, 0), type=int, required=False, help="Colour to fade dead cells to. 'R G B'", nargs='+') - parser.add_argument("--colour-background", "-cb", dest="cb", default=(16, 16, 16), type=int, required=False, help="Colour for dead cells. 'R G B'", nargs='+') - parser.add_argument("--cell_size", "-cs", dest="cs", default=16, type=int, required=False, help="Size of a cell in pixel.") - parser.add_argument("--size-x", "-sx", dest="sx", default=45, type=int, required=False, help="Height of the World.") - parser.add_argument("--size-y", "-sy", dest="sy", default=80, type=int, required=False, help="Width of the World.") - parser.add_argument("--tickrate", "-t", dest="t", default=30, type=float, required=False, help="Number of times the game shall update in a second (FPS).") - parser.add_argument("--seed", "-s", dest="s", default=-1, type=int, required=False, help="Seed value used to create World.") - parser.add_argument("--save-file", "-f", dest="f", default="./cgol.csv", type=str, required=False, help="Path of the in-/output file. (Should be .csv)") - parser.add_argument("--load", "-l", dest="l", default=False, type=bool, required=False, help="Load revious save.") - parser.add_argument("--pause-stalemate", "-ps", dest="ps", default=False, type=bool, required=False, help="Game pauses on a stalemate.") - parser.add_argument("--pause-oscillators", "-po", dest="po", default=False, type=bool, required=False, help="Game pauses when only oscillators remain.") - parser.add_argument("--fade-rate", "-fr", dest="fr", default=0.01, type=float, required=False, help="Value by which a cell should decrease every generation.") - parser.add_argument("--fade-death-value", "-fd", dest="fd", default=0.5, type=float, required=False, help="Value a cell should have after death.") - args = parser.parse_args() - - # Create new Game - game = Game(args.rh, args.rw, tuple(args.ca), tuple(args.cd), tuple(args.cf), tuple(args.cb), args.cs, args.t, args.f, args.ps, args.po) - - game.setup_pygame() - - # Let Game create the World - game.create_world(args.sx, args.sy, args.s, args.l, args.fr, args.fd) - - # Start Game - game.game_loop() +from . import main if __name__ == "__main__": diff --git a/src/cgol/game.py b/src/cgol/game.py index 27bf0f3..b0d0531 100644 --- a/src/cgol/game.py +++ b/src/cgol/game.py @@ -1,8 +1,17 @@ """ -Game +CGOL ==== +A Conway's Game of Life implementation using numpy and pygame. + +Rules of Conway's Game of Life: +1. Any live cell with two or three live neighbors survives. +2. Any dead cell with three live neighbors becomes a live cell. +3. All other live cells die in the next generation. Similarly, all other dead cells stay dead. + +Github Repo: +https://github.com/INeido/CGOL """ -from world import World +from .world import World from pygame.locals import * import pygame import numpy @@ -12,22 +21,21 @@ class Game: - """ - ### Game + """Game + ==== Contains the functions needed to run the game. - Parameters: - :param res_h: Height of the Game. - :param res_w: Width of the Game. - :param c_a: Colour for alive cells. - :param c_d: Colour for dead cells. - :param c_b: Colour for background. - :param cell_size: Size of a cell in pixel. - :param tickrate: Number of times the game shall update in a second (FPS). - :param save_file: Path of the in-/output file. - :param pause_stalemate: Game pauses on a stalemate. - :param pause_oscillators: Game pauses when only oscillators remain. + :param int res_h: Height of the Game. + :param int res_w: Width of the Game. + :param tuple c_a: Colour for alive cells. + :param tuple c_d: Colour for dead cells. + :param tuple c_b: Colour for background. + :param int cell_size: Size of a cell in pixel. + :param int tickrate: Number of times the game shall update in a second (FPS). + :param str save_file: Path of the in-/output file. + :param bool pause_stalemate: Game pauses on a stalemate. + :param bool pause_oscillators: Game pauses when only oscillators remain. """ def __init__(self, res_h: int, res_w: int, c_a: tuple, c_d: tuple, c_f: tuple, c_b: tuple, cell_size: int, tickrate: int, save_file: str, pause_stalemate: bool, pause_oscillators: bool): @@ -44,8 +52,8 @@ def __init__(self, res_h: int, res_w: int, c_a: tuple, c_d: tuple, c_f: tuple, c self.pause_oscillators = pause_oscillators def setup_pygame(self): - """ - ### Setup Pygame + """Setup Pygame + ==== Creates and configures pygame instance. """ @@ -54,9 +62,23 @@ def setup_pygame(self): self.dis = pygame.display.set_mode((self.res_w, self.res_h), 0, 8) self.clock = pygame.time.Clock() - def get_borders(self): + def get_save_path(self): + """Get Save Path + ==== + + Makes sure the 'cgol/images' folder exists and return its path. + + :return: The home directory of the user appended with '/cgol/images'.capitalize() + :rtype: string """ - ### Get Borders + path = os.path.expanduser("~") + "/cgol/images/" + if not os.path.exists(path): + os.makedirs(path) + return path + + def get_borders(self): + """Get Borders + ==== Gets the visible edges of the grid. """ @@ -66,8 +88,8 @@ def get_borders(self): self.vis_south = min(self.world.size_x, self.world.size_x - int(self.offset_y / self.cell_size) - self.world.size_x - int(-self.res_h / self.cell_size) + 1) def draw(self): - """ - ### Draw Game + """Draw Game + ==== Draws the current World. """ @@ -96,8 +118,8 @@ def update_surface(self): self.sur = pygame.Surface((self.world.size_y*self.cell_size, self.world.size_x*self.cell_size)) def center(self): - """ - ### Center + """Center + ==== Updates offsets so that grid is centered. """ @@ -105,16 +127,16 @@ def center(self): self.offset_y = (self.res_h / 2) - (self.world.size_x / 2 * self.cell_size) def create_world(self, size_x: int, size_y: int, seed: int, load: bool, fr, fd): - """ - ### Create World + """Create World + ==== Creates a new World Object. Parameters: - :param size_x: Height of the Grid. - :param size_y: Width of the Grid. - :param seed: Seed for the array generation. Default is random (-1). - :param load: Boolean indicating whether the last game should be loaded. + :param int size_x: Height of the Grid. + :param int size_y: Width of the Grid. + :param int seed: Seed for the array generation. Default is random (-1). + :param bool load: Boolean indicating whether the last game should be loaded. """ if load: try: @@ -132,8 +154,8 @@ def create_world(self, size_x: int, size_y: int, seed: int, load: bool, fr, fd): self.update_surface() def save_grid(self): - """ - ### Save Grid + """Save Grid + ==== Saves the Grid into a CSV file. """ @@ -154,21 +176,21 @@ def save_grid(self): print("Couldn't save file.", e) def load_grid(self): - """ - ### Load Grid + """Load Grid + ==== Loads the Grid from a CSV file. - Returns: - array: Rows of the CSV + :return: Rows of the CSV. + :rtype: array """ with open(self.save_file, "r") as csvfile: reader = csv.reader(csvfile) return [row for row in reader] def calc_generation(self): - """ - ### Calc Generation + """Calc Generation + ==== Calculates and renders the cells. """ @@ -192,13 +214,13 @@ def calc_generation(self): self.game_loop(pause=True) def interpolate(self, point0, point1): - """ - ### Interpolate + """Interpolate + ==== Interpolates between two points. - Returns: - tuple or int: Coordinates of interpolated cells + :return: Coordinates of interpolated cells. + :rtype: tuple or int """ # Calculate distance between previous and current position distance = numpy.linalg.norm(numpy.array(point1) - numpy.array(point0)) @@ -221,8 +243,8 @@ def interpolate(self, point0, point1): return int(x), int(y) def game_loop(self, pause=False): - """ - ### Game Loop + """Game Loop + ==== The main loop that runs the game. """ @@ -287,7 +309,7 @@ def game_loop(self, pause=False): self.update_surface() # P pressed: Save screenshot if event.key == pygame.K_p: - pygame.image.save(self.sur, f"img/cgol{self.world.generations}.png") + pygame.image.save(self.sur, f"{self.get_save_path() + str(self.world.generations)}.png") # + pressed: Extend grid if event.key == pygame.K_PLUS: self.world.extend() diff --git a/src/cgol/world.py b/src/cgol/world.py index 325a132..2bffafc 100644 --- a/src/cgol/world.py +++ b/src/cgol/world.py @@ -6,16 +6,15 @@ class World: - """ - ### World + """World + ==== Contains various functions to store and update a grid with Cells. - Parameters: - :param size_x: Height of the World. - :param size_y: Width of the World. - :param seed: Seed for the array generation. Default is random. - :param rows: The 2D Array filled with random 0s and 1s with the last being settings. + :param int size_x: Height of the World. + :param int size_y: Width of the World. + :param int seed: Seed for the array generation. Default is random. + :param array rows: The 2D Array filled with random 0s and 1s with the last being settings. """ def __init__(self, size_x: int, size_y: int, seed: int, fade_rate: float, fade_dead: float, rows=[]): @@ -32,13 +31,12 @@ def __init__(self, size_x: int, size_y: int, seed: int, fade_rate: float, fade_d self.grid_backup_1 = numpy.zeros_like(self.grid) def populate(self, mode: str, ): - """ - ### Populate + """Populate + ==== Fill 'grid' with different values. - Parameters: - :param mode: The mode based on which the array should be filled. + :param bool mode: The mode based on which the array should be filled. """ if mode == "seed": self.grid = numpy.random.default_rng(self.seed).choice([0.0, 1.0], size=(self.size_x, self.size_y), p=[0.75, 0.25]) @@ -54,21 +52,20 @@ def populate(self, mode: str, ): return False def load_from_csv(self, grid): - """ - ### Load from CSV + """Load from CSV + ==== Loads data from CSV file. - Parameters: - :param grid: The 2D Array filled with random 0s and 1s with the last being settings. + :param array grid: The 2D Array filled with random 0s and 1s with the last being settings. """ self.grid = numpy.array(grid[:-2], dtype=float) self.seed = int(grid[-2][0]) self.generations = int(grid[-1][0]) def backup(self): - """ - ### Backup + """Backup + ==== Creates a shallow copy of the grid. """ @@ -77,30 +74,30 @@ def backup(self): self.grid_backup_1 = numpy.copy(temp) def check_stalemate(self): - """ - ### Check Stalemate + """Check Stalemate + ==== Compares the last backup with the current grid to see of it changed. - Returns: - Boolean: Is the backup the same as the current grid? + :return: Is the backup the same as the current grid? + :rtype: bool """ return numpy.array_equal(self.grid, self.grid_backup_0) def check_oscillators(self): - """ - ### Compare Backup + """Compare Backup + ==== Compares the second last backup with the current grid to see of it changed. - Returns: - Boolean: Is the second last backup the same as the current grid? + :return: Is the second last backup the same as the current grid? + :rtype: bool """ return numpy.array_equal(self.grid, self.grid_backup_1) def extend(self): - """ - ### Extend + """Extend + ==== Extends grid in every direction by one row/column. """ @@ -109,8 +106,8 @@ def extend(self): self.size_y += 2 def reduce(self): - """ - ### Reduce + """Reduce + ==== Reduces grid in every direction by one row/column. """ @@ -120,41 +117,40 @@ def reduce(self): self.size_x -= 2 self.size_y -= 2 - def get_neighbours(self): - """ - ### Get Neighbours in Toroidal Space + def get_neighbors(self): + """Get Neighbours in Toroidal Space + ==== - Counts the number of alive neighbours of a cell inside a toroidal space. Neighbours off the edge will wrap around. + Counts the number of alive neighbors of a cell inside a toroidal space. Neighbours off the edge will wrap around. - Returns: - 2d Array: The number of neighbours of a cell. + :return: The number of neighbors of a cell. + :rtype: np.array """ # Create a new array the same size as 'grid' - neighbours = numpy.zeros_like(self.grid) + neighbors = numpy.zeros_like(self.grid) # Convert faded values to zeros clipped_grid = numpy.where(self.grid < 1, 0, 1) - # Roll over every axis to get a new array with the number of neighbours + # Roll over every axis to get a new array with the number of neighbors for dx in [-1, 0, 1]: for dy in [-1, 0, 1]: if not dx and not dy: continue - neighbours += numpy.roll(numpy.roll(clipped_grid, dx, axis=0), dy, axis=1) + neighbors += numpy.roll(numpy.roll(clipped_grid, dx, axis=0), dy, axis=1) - return neighbours + return neighbors - def apply_rules(self, neighbours): - """ - ### Apply Rules + def apply_rules(self, neighbors): + """Apply Rules + ==== Determines the new state of each cell for the current tick. - Parameters: - :param neighbours: The number of neighbours for each cell. + :param np.array neighbors: The number of neighbors for each cell. - Returns: - 2d Array: New state of cells. + :return: New state of cells. + :rtype: np.array """ # Create a copy of the grid to store the next generation next_generation = numpy.copy(self.grid) @@ -166,21 +162,21 @@ def apply_rules(self, neighbours): dead = numpy.where(self.grid < 1) # Apply the rules to cells that are currently alive - next_generation[alive] = numpy.where((neighbours[alive] == 2) | (neighbours[alive] == 3), 1.0, self.fade_dead) + next_generation[alive] = numpy.where((neighbors[alive] == 2) | (neighbors[alive] == 3), 1.0, self.fade_dead) # Apply the rule to cells that are currently dead - next_generation[dead] = numpy.where(neighbours[dead] == 3, 1.0, self.grid[dead] - self.fade_rate) + next_generation[dead] = numpy.where(neighbors[dead] == 3, 1.0, self.grid[dead] - self.fade_rate) return numpy.where(next_generation < 0.00001, 0.0, next_generation) def update(self): - """ - ### Update World + """Update World + ==== Updates the state of the cells in the world according to the rules of the Game of Life. """ - # Get neighbours - neighbours = self.get_neighbours() + # Get neighbors + neighbors = self.get_neighbors() # Apply rules - self.grid = self.apply_rules(neighbours) + self.grid = self.apply_rules(neighbors)