├── .gitignore ├── requirements.txt ├── media └── pygame-of-life.gif ├── .pylintrc ├── grid_defs.py ├── .github └── workflows │ └── pylint.yml ├── example_grids.py ├── README.md └── pygame_life.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2021.5.30 2 | pygame==2.1.3.dev8 -------------------------------------------------------------------------------- /media/pygame-of-life.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matheusgomes28/pygame-life/HEAD/media/pygame-of-life.gif -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [BASIC] 2 | 3 | # Good variable names which should always be accepted, separated by a comma. 4 | good-names= x, y -------------------------------------------------------------------------------- /grid_defs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Grid definitions for the game of life engine. 3 | """ 4 | from collections import namedtuple 5 | 6 | Dim = namedtuple("Dimension", ["width", "height"]) 7 | Grid = namedtuple("Grid", ["dim", "cells"]) 8 | Neighbours = namedtuple("Neighbours", ["alive", "dead"]) 9 | -------------------------------------------------------------------------------- /.github/workflows/pylint.yml: -------------------------------------------------------------------------------- 1 | name: Pylint 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.8", "3.9", "3.10"] 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Set up Python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v3 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install pylint 21 | pip install -r requirements.txt 22 | - name: Analysing the code with pylint 23 | run: | 24 | pylint --unsafe-load-any-extension=y -- $(git ls-files '*.py') 25 | -------------------------------------------------------------------------------- /example_grids.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example grids for the (short) game of life engine 3 | """ 4 | 5 | from grid_defs import Dim, Grid 6 | 7 | GOSPER_GLIDER = Grid( 8 | Dim(50, 50), 9 | { 10 | (22, 8), 11 | (12, 7), 12 | (36, 7), 13 | (17, 9), 14 | (11, 8), 15 | (1, 9), 16 | (25, 4), 17 | (2, 8), 18 | (16, 7), 19 | (25, 10), 20 | (21, 6), 21 | (23, 9), 22 | (14, 6), 23 | (36, 6), 24 | (22, 7), 25 | (14, 12), 26 | (17, 8), 27 | (11, 10), 28 | (25, 9), 29 | (35, 7), 30 | (1, 8), 31 | (18, 9), 32 | (22, 6), 33 | (21, 8), 34 | (23, 5), 35 | (12, 11), 36 | (17, 10), 37 | (11, 9), 38 | (35, 6), 39 | (25, 5), 40 | (2, 9), 41 | (13, 6), 42 | (13, 12), 43 | (15, 9), 44 | (16, 11), 45 | (21, 7), 46 | }, 47 | ) 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pygame Of Life 2 | 3 | Implementation of Conway's "Game Of Life" in less than 100 4 | lines of modern Python. 5 | 6 | ![](https://raw.githubusercontent.com/matheusgomes28/pygame-life/main/media/pygame-of-life.gif) 7 | 8 | The engine is entirely and purely implemented using vanilla 9 | Python 3.9, and [Pygame](https://www.pygame.org/) is used for 10 | the graphics front. 11 | 12 | ## Running The Game 13 | 14 | I recommend using the latest version of Python (3.9) on a 15 | separate environment to your normal development one. Two 16 | good options are Anaconda or Virtualenv, just create a new 17 | environment for this game and install the dependencies with: 18 | 19 | ```shell 20 | pip install -r requirements.txt 21 | ``` 22 | 23 | Given that all the dependencies were installed, running the 24 | game is as simple as 25 | 26 | ```shell 27 | python pygame_life.py 28 | ``` 29 | 30 | ## Understand The Code 31 | 32 | This game was created for a post in my personal blog. 33 | The intention was to show that, by keeping it simple, [Conway's Game Of Life 34 | can be implemented in a few lines of Python](https://matgomes.com/conways-game-of-life-python). 35 | -------------------------------------------------------------------------------- /pygame_life.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pygame of life module. Contains the short engine 3 | to simluate the grid of life. 4 | """ 5 | 6 | import sys 7 | import time 8 | from collections import defaultdict 9 | from copy import deepcopy 10 | 11 | import pygame 12 | 13 | from example_grids import GOSPER_GLIDER 14 | from grid_defs import Grid, Neighbours 15 | 16 | 17 | def get_neighbours(grid: Grid, x: int, y: int) -> Neighbours: 18 | """ 19 | Gets the neighbour states for a particular cell in 20 | (x, y) on the grid. 21 | """ 22 | offsets = [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)] 23 | possible_neighbours = {(x + x_add, y + y_add) for x_add, y_add in offsets} 24 | alive = {(pos[0], pos[1]) for pos in possible_neighbours if pos in grid.cells} 25 | return Neighbours(alive, possible_neighbours - alive) 26 | 27 | 28 | def update_grid(grid: Grid) -> Grid: 29 | """ 30 | Given a grid, this function returns the next iteration 31 | of the game of life. 32 | """ 33 | new_cells = deepcopy(grid.cells) 34 | undead = defaultdict(int) 35 | 36 | for x, y in grid.cells: 37 | alive_neighbours, dead_neighbours = get_neighbours(grid, x, y) 38 | if len(alive_neighbours) not in [2, 3]: 39 | new_cells.remove((x, y)) 40 | 41 | for pos in dead_neighbours: 42 | undead[pos] += 1 43 | 44 | for pos, _ in filter(lambda elem: elem[1] == 3, undead.items()): 45 | new_cells.add((pos[0], pos[1])) 46 | 47 | return Grid(grid.dim, new_cells) 48 | 49 | 50 | def draw_grid(screen: pygame.Surface, grid: Grid) -> None: 51 | """ 52 | This function draws the game of life on the given 53 | pygame.Surface object. 54 | """ 55 | cell_width = screen.get_width() / grid.dim.width 56 | cell_height = screen.get_height() / grid.dim.height 57 | border_size = 2 58 | 59 | for x, y in grid.cells: 60 | pygame.draw.rect( 61 | screen, 62 | (255, 0, 0), 63 | ( 64 | x * cell_width + border_size, 65 | y * cell_height + border_size, 66 | cell_width - border_size, 67 | cell_height - border_size, 68 | ), 69 | ) 70 | 71 | 72 | def main(): 73 | """ 74 | Main entry point 75 | """ 76 | grid = GOSPER_GLIDER 77 | 78 | pygame.init() 79 | screen = pygame.display.set_mode((600, 400)) 80 | 81 | while True: 82 | if pygame.QUIT in [e.type for e in pygame.event.get()]: 83 | sys.exit(0) 84 | 85 | screen.fill((0, 0, 0)) 86 | draw_grid(screen, grid) 87 | grid = update_grid(grid) 88 | pygame.display.flip() 89 | time.sleep(0.1) 90 | 91 | 92 | if __name__ == "__main__": 93 | main() 94 | --------------------------------------------------------------------------------