From 38335c375f286f9b869cf43a9b9b5d5953487402 Mon Sep 17 00:00:00 2001 From: Georgios Atheridis Date: Thu, 7 Jul 2022 09:29:25 +0300 Subject: [PATCH] game of life --- .flake8 | 7 + .gitignore | 129 ++++ LICENSE | 21 + README.md | 50 ++ game_of_life/__init__.py | 0 game_of_life/board.py | 77 +++ game_of_life/cell.py | 55 ++ game_of_life/data/save.json | 1078 +++++++++++++++++++++++++++++++ game_of_life/data/settings.json | 22 + game_of_life/game_of_life.py | 236 +++++++ game_of_life/main.py | 50 ++ requirements.txt | 11 + setup.py | 20 + 13 files changed, 1756 insertions(+) create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 game_of_life/__init__.py create mode 100755 game_of_life/board.py create mode 100755 game_of_life/cell.py create mode 100644 game_of_life/data/save.json create mode 100644 game_of_life/data/settings.json create mode 100644 game_of_life/game_of_life.py create mode 100644 game_of_life/main.py create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..ae11a13 --- /dev/null +++ b/.flake8 @@ -0,0 +1,7 @@ +[flake8] +ignore = E203, E266, E501, W503 +# line length is intentionally set to 80 here because black uses Bugbear +# See https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#line-length for more details +max-line-length = 80 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8aa2645 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1aed34d --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +

Conway's Game of Life - In Python's Pygame

+ +

+ Conway's Game of Life - In Python's Pygame +

+ +## Installation + +To install the latest version just type `$ pip install git+https://github.com/atheridis/game-of-life.git` +in your terminal. To run the program type `$ gameoflife`. + +You may also clone this repository and install it from there. +``` +$ git clone https://github.com/atheridis/game-of-life.git +$ cd game-of-life +$ pip install . +``` + +## Settings + +You will find settings in `~/.config/game-of-life`. Currently Settings are changed through +editing a json file. You can choose the game resolution, the width of the cells in pixels, +the colour of dead and alive cells, and the maximum amount of frames that get computed +each second. + +You will need to run the game at least once before the directory becomes available.n your terminal. +To run the program type `$ gameoflife`. + +## How to use + +### Loading and Saving + +Your numrow allow you to load saved states. Some saved states already exist for you to try. +You may press the Function Keys, F1, F2, ..., F10 to save to the corresponding slot (where F10 is 0 on the numrow). +If a state is already saved in one of those keys it will be overwritten **without warning**. + +### Keybindings + +* Use the Up and Down arrow keys to speed up or slow down the game respectively. +* Use the Spacebar to pause and resume the game +* Press [c] to clear the board +* Press [r] to randomize the board +* Press [ESC] to quit. + +## OLD PROJECT + +This is one of many of my older projects which I never used git with. +I have decided to turn it into a package and upload it to github. diff --git a/game_of_life/__init__.py b/game_of_life/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game_of_life/board.py b/game_of_life/board.py new file mode 100755 index 0000000..27b66e8 --- /dev/null +++ b/game_of_life/board.py @@ -0,0 +1,77 @@ +from itertools import product + +from .cell import Cell + + +class Board: + def __init__(self, size): + """ + :param size: + """ + self.size = size + self._cells = [] + self.living_cells = [] + self.grid = [] + self._altered = set() + + def create(self, state=None): + """ + :param start_state: + """ + state = [] if state is None else state + + self.grid = [] + self._cells = [] + self.living_cells = [] + self._altered = set() + + for i in range(self.size[0]): + self.grid.append([]) + for j in range(self.size[1]): + if [i, j] in state: + c = Cell((i, j), 1) + self.living_cells.append(c) + else: + c = Cell((i, j), 0) + self._cells.append(c) + self.grid[i].append(c) + + self._assign_neighbours() + + def update_grid(self): + self._altered = set() + for living_cell in self.living_cells: + altered_cells = living_cell.update_neighbours() + self._altered.update(altered_cells) + + self.living_cells = [] + + for altered_cell in self._altered: + altered_cell.update_status() + if altered_cell: + self.living_cells.append(altered_cell) + + def change_cell_status(self, pos): + self.grid[pos[0]][pos[1]].change_status() + + if self.grid[pos[0]][pos[1]]: + self.living_cells.append(self.grid[pos[0]][pos[1]]) + else: + self.living_cells.remove(self.grid[pos[0]][pos[1]]) + + def randomise_grid(self, alive_chance): + self.living_cells = [] + for c in self._cells: + c.randomise_status(alive_chance) + if c: + self.living_cells.append(c) + + def _assign_neighbours(self): + for (n, m) in product(range(self.size[0]), range(self.size[1])): + for (i, j) in product(range(3), range(3)): + if ( + 0 <= n - i + 1 < self.size[0] + and 0 <= m - j + 1 < self.size[1] + and (i, j) != (1, 1) + ): + self.grid[n][m].neighbours.append(self.grid[n - i + 1][m - j + 1]) diff --git a/game_of_life/cell.py b/game_of_life/cell.py new file mode 100755 index 0000000..fe66e70 --- /dev/null +++ b/game_of_life/cell.py @@ -0,0 +1,55 @@ +import random + + +class Cell: + def __init__(self, pos, status=0): + """ + :param pos: + :param status: + """ + self.pos = pos + self.neighbours = [] + self.status = status + self._living_neighbours = 0 + + def change_status(self): + if self.status == 0: + self.status = 1 + else: + self.status = 0 + + def update_status(self): + if self: + if self._living_neighbours > 3: + self.status = 0 + elif self._living_neighbours < 2: + self.status = 0 + elif self._living_neighbours == 3: + self.status = 1 + + self._living_neighbours = 0 + + def neighbour_count(self): + self._living_neighbours += 1 + + def update_neighbours(self): + updated = [self] + if self: + for neighbour in self.neighbours: + neighbour.neighbour_count() + updated.append(neighbour) + + return updated + + def randomise_status(self, alive_chance): + rand = random.random() + if rand <= alive_chance: + self.status = 1 + else: + self.status = 0 + + def __bool__(self): + if self.status == 0: + return False + + return True diff --git a/game_of_life/data/save.json b/game_of_life/data/save.json new file mode 100644 index 0000000..68404d0 --- /dev/null +++ b/game_of_life/data/save.json @@ -0,0 +1,1078 @@ +{ + "starting_position": [ + [ + 5, + 2 + ], + [ + 5, + 3 + ], + [ + 6, + 3 + ], + [ + 6, + 2 + ], + [ + 5, + 12 + ], + [ + 6, + 12 + ], + [ + 7, + 12 + ], + [ + 4, + 13 + ], + [ + 3, + 14 + ], + [ + 8, + 13 + ], + [ + 9, + 14 + ], + [ + 9, + 15 + ], + [ + 3, + 15 + ], + [ + 8, + 17 + ], + [ + 7, + 18 + ], + [ + 6, + 18 + ], + [ + 5, + 18 + ], + [ + 4, + 17 + ], + [ + 6, + 19 + ], + [ + 6, + 16 + ], + [ + 5, + 22 + ], + [ + 4, + 22 + ], + [ + 3, + 22 + ], + [ + 3, + 23 + ], + [ + 4, + 23 + ], + [ + 5, + 23 + ], + [ + 2, + 24 + ], + [ + 6, + 24 + ], + [ + 1, + 26 + ], + [ + 2, + 26 + ], + [ + 6, + 26 + ], + [ + 7, + 26 + ], + [ + 3, + 36 + ], + [ + 4, + 36 + ], + [ + 4, + 37 + ], + [ + 3, + 37 + ] + ], + "save_1": [ + [ + 11, + 31 + ], + [ + 11, + 32 + ], + [ + 11, + 36 + ], + [ + 11, + 37 + ], + [ + 11, + 41 + ], + [ + 11, + 42 + ], + [ + 11, + 46 + ], + [ + 11, + 47 + ], + [ + 11, + 51 + ], + [ + 11, + 52 + ], + [ + 16, + 51 + ], + [ + 16, + 52 + ], + [ + 8, + 37 + ], + [ + 8, + 36 + ], + [ + 18, + 20 + ], + [ + 8, + 32 + ], + [ + 22, + 45 + ], + [ + 22, + 46 + ], + [ + 8, + 31 + ], + [ + 8, + 52 + ], + [ + 21, + 45 + ], + [ + 8, + 51 + ], + [ + 21, + 47 + ], + [ + 18, + 50 + ], + [ + 8, + 27 + ], + [ + 18, + 18 + ], + [ + 8, + 26 + ], + [ + 8, + 47 + ], + [ + 15, + 53 + ], + [ + 8, + 46 + ], + [ + 15, + 51 + ], + [ + 8, + 22 + ], + [ + 8, + 21 + ], + [ + 8, + 42 + ], + [ + 18, + 48 + ], + [ + 8, + 41 + ], + [ + 10, + 28 + ], + [ + 20, + 22 + ], + [ + 10, + 30 + ], + [ + 20, + 21 + ], + [ + 10, + 33 + ], + [ + 10, + 35 + ], + [ + 10, + 38 + ], + [ + 10, + 40 + ], + [ + 14, + 15 + ], + [ + 14, + 16 + ], + [ + 10, + 43 + ], + [ + 10, + 45 + ], + [ + 10, + 48 + ], + [ + 10, + 50 + ], + [ + 10, + 53 + ], + [ + 21, + 21 + ], + [ + 21, + 23 + ], + [ + 9, + 23 + ], + [ + 9, + 25 + ], + [ + 9, + 28 + ], + [ + 9, + 30 + ], + [ + 9, + 33 + ], + [ + 9, + 35 + ], + [ + 9, + 38 + ], + [ + 9, + 40 + ], + [ + 9, + 43 + ], + [ + 9, + 45 + ], + [ + 9, + 48 + ], + [ + 9, + 50 + ], + [ + 9, + 53 + ], + [ + 22, + 22 + ], + [ + 22, + 23 + ], + [ + 19, + 20 + ], + [ + 8, + 17 + ], + [ + 8, + 16 + ], + [ + 17, + 18 + ], + [ + 17, + 19 + ], + [ + 19, + 48 + ], + [ + 11, + 16 + ], + [ + 19, + 49 + ], + [ + 11, + 17 + ], + [ + 11, + 21 + ], + [ + 11, + 22 + ], + [ + 17, + 49 + ], + [ + 17, + 50 + ], + [ + 11, + 26 + ], + [ + 11, + 27 + ], + [ + 15, + 17 + ], + [ + 15, + 15 + ], + [ + 9, + 15 + ], + [ + 20, + 47 + ], + [ + 9, + 18 + ], + [ + 20, + 46 + ], + [ + 9, + 20 + ], + [ + 19, + 19 + ], + [ + 16, + 16 + ], + [ + 16, + 17 + ], + [ + 14, + 52 + ], + [ + 14, + 53 + ], + [ + 10, + 15 + ], + [ + 10, + 18 + ], + [ + 10, + 20 + ], + [ + 10, + 23 + ], + [ + 10, + 25 + ], + [ + 20, + 34 + ], + [ + 20, + 35 + ], + [ + 21, + 35 + ], + [ + 22, + 35 + ], + [ + 22, + 34 + ], + [ + 22, + 33 + ], + [ + 21, + 33 + ], + [ + 20, + 33 + ], + [ + 19, + 34 + ], + [ + 23, + 34 + ] + ], + "save_3": [ + [ + 3, + 5 + ], + [ + 3, + 8 + ], + [ + 5, + 5 + ], + [ + 6, + 6 + ], + [ + 6, + 7 + ], + [ + 6, + 8 + ], + [ + 6, + 9 + ], + [ + 5, + 9 + ], + [ + 4, + 9 + ], + [ + 9, + 5 + ], + [ + 11, + 5 + ], + [ + 9, + 8 + ], + [ + 10, + 9 + ], + [ + 11, + 9 + ], + [ + 12, + 9 + ], + [ + 12, + 8 + ], + [ + 12, + 7 + ], + [ + 12, + 6 + ], + [ + 15, + 5 + ], + [ + 17, + 5 + ], + [ + 18, + 6 + ], + [ + 18, + 7 + ], + [ + 18, + 8 + ], + [ + 15, + 8 + ], + [ + 18, + 9 + ], + [ + 17, + 9 + ], + [ + 16, + 9 + ], + [ + 21, + 5 + ], + [ + 23, + 5 + ], + [ + 24, + 6 + ], + [ + 24, + 7 + ], + [ + 24, + 8 + ], + [ + 24, + 9 + ], + [ + 23, + 9 + ], + [ + 22, + 9 + ], + [ + 21, + 8 + ], + [ + 27, + 5 + ], + [ + 29, + 5 + ], + [ + 30, + 6 + ], + [ + 30, + 7 + ], + [ + 30, + 8 + ], + [ + 30, + 9 + ], + [ + 29, + 9 + ], + [ + 28, + 9 + ], + [ + 27, + 8 + ] + ], + "save_4": [ + [ + 3, + 6 + ], + [ + 4, + 6 + ], + [ + 5, + 7 + ], + [ + 6, + 9 + ], + [ + 6, + 10 + ], + [ + 7, + 11 + ], + [ + 8, + 12 + ], + [ + 9, + 13 + ], + [ + 8, + 14 + ], + [ + 10, + 13 + ], + [ + 11, + 12 + ], + [ + 12, + 11 + ], + [ + 13, + 10 + ], + [ + 13, + 9 + ], + [ + 14, + 7 + ], + [ + 15, + 6 + ], + [ + 16, + 6 + ], + [ + 17, + 6 + ], + [ + 17, + 7 + ], + [ + 17, + 8 + ], + [ + 17, + 9 + ], + [ + 17, + 10 + ], + [ + 16, + 11 + ], + [ + 3, + 11 + ], + [ + 2, + 10 + ], + [ + 2, + 9 + ], + [ + 2, + 8 + ], + [ + 2, + 7 + ], + [ + 2, + 6 + ], + [ + 11, + 14 + ], + [ + 6, + 14 + ], + [ + 6, + 15 + ], + [ + 7, + 16 + ], + [ + 8, + 16 + ], + [ + 13, + 14 + ], + [ + 13, + 15 + ], + [ + 12, + 16 + ], + [ + 11, + 16 + ], + [ + 6, + 17 + ], + [ + 6, + 18 + ], + [ + 5, + 18 + ], + [ + 5, + 17 + ], + [ + 4, + 18 + ], + [ + 4, + 19 + ], + [ + 3, + 19 + ], + [ + 3, + 20 + ], + [ + 5, + 20 + ], + [ + 5, + 21 + ], + [ + 4, + 21 + ], + [ + 6, + 21 + ], + [ + 6, + 22 + ], + [ + 5, + 22 + ], + [ + 4, + 22 + ], + [ + 4, + 23 + ], + [ + 5, + 23 + ], + [ + 7, + 19 + ], + [ + 8, + 18 + ], + [ + 11, + 18 + ], + [ + 12, + 19 + ], + [ + 13, + 18 + ], + [ + 13, + 17 + ], + [ + 14, + 17 + ], + [ + 14, + 18 + ], + [ + 15, + 18 + ], + [ + 15, + 19 + ], + [ + 16, + 19 + ], + [ + 16, + 20 + ], + [ + 14, + 20 + ], + [ + 14, + 21 + ], + [ + 13, + 21 + ], + [ + 15, + 21 + ], + [ + 15, + 22 + ], + [ + 15, + 23 + ], + [ + 14, + 23 + ], + [ + 14, + 22 + ], + [ + 13, + 22 + ] + ] +} diff --git a/game_of_life/data/settings.json b/game_of_life/data/settings.json new file mode 100644 index 0000000..015f3dd --- /dev/null +++ b/game_of_life/data/settings.json @@ -0,0 +1,22 @@ +{ + "settings": { + "resolution": [ + 1600, + 900 + ], + "cell_width": 15, + "colour": { + "dead": [ + 0, + 0, + 0 + ], + "alive": [ + 0, + 255, + 255 + ] + }, + "frame_limit": 75 + } +} \ No newline at end of file diff --git a/game_of_life/game_of_life.py b/game_of_life/game_of_life.py new file mode 100644 index 0000000..d7f209d --- /dev/null +++ b/game_of_life/game_of_life.py @@ -0,0 +1,236 @@ +import json +import sys +from math import floor + +import pygame + +from .board import Board + +pygame.init() + + +class GameOfLife: + def __init__( + self, resolution, cell_width, dead_colour, alive_colour, frame_limit, save_path + ): + self._resolution = resolution + self._cell_width = cell_width + self._dead_colour = dead_colour + self._alive_colour = alive_colour + self._frame_limit = frame_limit + self._save_path = save_path + + # Required due to MOUSEBUTTONDOWN sometimes activating after get_pressed + self._changing = False + + self.rows = int(round(self._resolution[1] / self._cell_width)) + self.cols = int(round(self._resolution[0] / self._cell_width)) + self.cell_width = self._resolution[0] / self.cols + + self._save_game_keys = ( + pygame.K_F1, + pygame.K_F2, + pygame.K_F3, + pygame.K_F4, + pygame.K_F5, + pygame.K_F6, + pygame.K_F7, + pygame.K_F8, + pygame.K_F9, + ) + + self._load_game_keys = ( + pygame.K_1, + pygame.K_2, + pygame.K_3, + pygame.K_4, + pygame.K_5, + pygame.K_6, + pygame.K_7, + pygame.K_8, + pygame.K_9, + pygame.K_0, + ) + + self._screen = pygame.display.set_mode(self._resolution) + pygame.display.set_caption("Game of Life") + self.fpsClock = pygame.time.Clock() + + self.board = Board((self.rows, self.cols)) + self.board.create() + self.load() + + self.paused = False + self.changing = False + self.lock_x = False + self.lock_y = False + + @classmethod + def fromjsonfile(cls, settings_path, save_path): + with open(settings_path) as f: + file = json.loads(f.read())["settings"] + return cls.fromdict(file, save_path) + + @classmethod + def fromdict(cls, dict, save_path): + resolution = dict["resolution"] + cell_width = dict["cell_width"] + dead_colour = dict["colour"]["dead"] + alive_colour = dict["colour"]["alive"] + frame_limit = dict["frame_limit"] + return cls( + resolution, cell_width, dead_colour, alive_colour, frame_limit, save_path + ) + + def save(self, code): + save = [] + for cell in self.board.living_cells: + save.append(cell.pos) + + with open(self._save_path) as rf: + try: + save_data = json.loads(rf.read()) + except ValueError: + save_data = {} + print("Save File Empty") + + print(self._save_game_keys.index(code) + 1) + save_data[f"save_{self._save_game_keys.index(code) + 1}"] = save + with open(self._save_path, "w") as wf: + wf.write(json.dumps(save_data)) + + def load(self, code="0"): + try: + if code == "0": + with open(self._save_path) as f: + save = json.loads(f.read())["starting_position"] + else: + with open(self._save_path) as f: + save = json.loads(f.read())[f"save_{code}"] + except KeyError: + print("save not found") + else: + self.board.create(save) + + def _quit(self): + pygame.quit() + sys.exit() + + def _handle_key(self, event): + if event.key == pygame.K_ESCAPE: + self._quit() + elif event.key == pygame.K_SPACE: + self.paused = not self.paused + elif event.key in self._save_game_keys: + self.save(event.key) + elif event.key in self._load_game_keys: + self.load(event.unicode) + elif event.key == pygame.K_r: + self.board.randomise_grid(0.1) + elif event.key == pygame.K_c: + self.board.create() + elif self.paused and event.key == pygame.K_RIGHT: + self.board.update_grid() + elif event.key == pygame.K_UP and self._frame_limit < 1000: + self._frame_limit += 10 + elif event.key == pygame.K_DOWN: + if self._frame_limit > 10: + self._frame_limit -= 10 + else: + self._frame_limit = 1 + elif pygame.KMOD_SHIFT: + self.original_posx = int(floor(pygame.mouse.get_pos()[1] / self.cell_width)) + self.original_posy = int(floor(pygame.mouse.get_pos()[0] / self.cell_width)) + + def _handle_events(self): + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self._quit() + elif event.type == pygame.KEYDOWN: + self._handle_key(event) + elif event.type == pygame.MOUSEBUTTONDOWN: + self._changing = True + self.original_posx = int( + floor(pygame.mouse.get_pos()[1] / self.cell_width) + ) + self.original_posy = int( + floor(pygame.mouse.get_pos()[0] / self.cell_width) + ) + self.original_cell_status = self.board.grid[self.original_posx][ + self.original_posy + ].status + self.board.change_cell_status((self.original_posx, self.original_posy)) + elif pygame.mouse.get_pressed()[0] & self._changing: + # if event.type == pygame.MOUSEBUTTONDOWN: + # self._changing = True + # self.original_posx = int( + # floor(pygame.mouse.get_pos()[1] / self.cell_width) + # ) + # self.original_posy = int( + # floor(pygame.mouse.get_pos()[0] / self.cell_width) + # ) + # self.original_cell_status = self.board.grid[self.original_posx][ + # self.original_posy + # ].status + # self.board.change_cell_status( + # (self.original_posx, self.original_posy) + # ) + self.posx = int(floor(pygame.mouse.get_pos()[1] / self.cell_width)) + self.posy = int(floor(pygame.mouse.get_pos()[0] / self.cell_width)) + if pygame.key.get_mods() & pygame.KMOD_SHIFT: + if self.posx != self.original_posx and not self.lock_x: + self.lock_y = True + if ( + self.board.grid[self.posx][self.original_posy].status + == self.original_cell_status + ): + self.board.change_cell_status( + (self.posx, self.original_posy) + ) + if self.posy != self.original_posy and not self.lock_y: + self.lock_x = True + if ( + self.board.grid[self.original_posx][self.posy].status + == self.original_cell_status + ): + self.board.change_cell_status( + (self.original_posx, self.posy) + ) + elif ( + self.board.grid[self.posx][self.posy].status + == self.original_cell_status + ): + self.board.change_cell_status((self.posx, self.posy)) + elif event.type == pygame.MOUSEBUTTONUP: + self._changing = False + self.lock_x = False + self.lock_y = False + + def _draw_screen(self): + self._empty_space() + + for cell in self.board.living_cells: + if cell: + pygame.draw.rect( + self._screen, + self._alive_colour, + ( + cell.pos[1] * self.cell_width, + cell.pos[0] * self.cell_width, + self.cell_width, + self.cell_width, + ), + ) + + pygame.display.flip() + + def _empty_space(self): + self._screen.fill(self._dead_colour) + + def run(self): + while True: + self._draw_screen() + self._handle_events() + if not self.paused: + self.board.update_grid() + self.fpsClock.tick(self._frame_limit) diff --git a/game_of_life/main.py b/game_of_life/main.py new file mode 100644 index 0000000..150fded --- /dev/null +++ b/game_of_life/main.py @@ -0,0 +1,50 @@ +from .game_of_life import GameOfLife +import os +import shutil +import sys + + +def configs(): + if os.name == "posix": + if "XDG_CONFIG_HOME" in os.environ: + CONFIG_PATH = os.path.join(os.environ["XDG_CONFIG_HOME"], "game-of-life") + else: + CONFIG_PATH = os.path.join(os.environ["HOME"], ".config/game-of-life") + elif os.name == "nt": + if "APPDATA" in os.environ: + CONFIG_PATH = os.path.join(os.environ["APPDATA"], "game-of-life") + else: + print( + "APPDATA is not set, something must be very wrong.", + file=sys.stderr, + ) + sys.exit(1) + else: + print("Your OS is not supported", file=sys.stderr) + + os.makedirs(CONFIG_PATH, exist_ok=True) + SAVE_PATH = os.path.join(CONFIG_PATH, "save.json") + SETTINGS_PATH = os.path.join(CONFIG_PATH, "settings.json") + + if not os.path.exists(os.path.join(CONFIG_PATH, "save.json")): + shutil.copyfile( + os.path.join(os.path.dirname(os.path.realpath(__file__)), "data/save.json"), + SAVE_PATH, + ) + if not os.path.exists(os.path.join(CONFIG_PATH, "settings.json")): + shutil.copyfile( + os.path.join( + os.path.dirname(os.path.realpath(__file__)), "data/settings.json" + ), + SETTINGS_PATH, + ) + return SAVE_PATH, SETTINGS_PATH + + +def main(): + save_path, settings_path = configs() + GameOfLife.fromjsonfile(settings_path, save_path).run() + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7906c60 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +black==22.6.0 +click==8.1.3 +flake8==4.0.1 +mccabe==0.6.1 +mypy-extensions==0.4.3 +pathspec==0.9.0 +platformdirs==2.5.2 +pycodestyle==2.8.0 +pyflakes==2.4.0 +pygame==2.1.2 +tomli==2.0.1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ed9ecaf --- /dev/null +++ b/setup.py @@ -0,0 +1,20 @@ +from setuptools import setup, find_packages + +setup( + name="game_of_life", + version="0.0.1", + author="Georgios Atheridis", + author_email="atheridis@tutamail.com", + packages=find_packages(), + entry_points={ + "console_scripts": [ + "gameoflife=game_of_life.main:main", + ], + }, + package_data={ + "game_of_life": ["data/*.json"], + }, + install_requires=[ + "pygame", + ], +) -- 2.30.2