Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 26 additions & 67 deletions cellular_automata/conways_game_of_life.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"""
Conway's Game of Life implemented in Python.
https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
"""
"""Conway's Game of Life implemented in Python.
https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life"""

# Import block
from __future__ import annotations

from PIL import Image

# Define glider example
# Constants
GLIDER = [
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
Expand All @@ -19,80 +18,40 @@
[0, 0, 0, 0, 0, 0, 0, 0],
]

# Define blinker example
BLINKER = [[0, 1, 0], [0, 1, 0], [0, 1, 0]]

BLINKER = [
[0, 1, 0],
[0, 1, 0],
[0, 1, 0]
]

# Functions
def new_generation(cells: list[list[int]]) -> list[list[int]]:
"""
Generates the next generation for a given state of Conway's Game of Life.
>>> new_generation(BLINKER)
[[0, 0, 0], [1, 1, 1], [0, 0, 0]]
"""
next_generation = []
for i in range(len(cells)):
next_generation_row = []
for j in range(len(cells[i])):
# Get the number of live neighbours
neighbour_count = 0
if i > 0 and j > 0:
neighbour_count += cells[i - 1][j - 1]
if i > 0:
neighbour_count += cells[i - 1][j]
if i > 0 and j < len(cells[i]) - 1:
neighbour_count += cells[i - 1][j + 1]
if j > 0:
neighbour_count += cells[i][j - 1]
if j < len(cells[i]) - 1:
neighbour_count += cells[i][j + 1]
if i < len(cells) - 1 and j > 0:
neighbour_count += cells[i + 1][j - 1]
if i < len(cells) - 1:
neighbour_count += cells[i + 1][j]
if i < len(cells) - 1 and j < len(cells[i]) - 1:
neighbour_count += cells[i + 1][j + 1]

# Rules of the game of life (excerpt from Wikipedia):
# 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.
alive = cells[i][j] == 1
if (
(alive and 2 <= neighbour_count <= 3)
or not alive
and neighbour_count == 3
):
next_generation_row.append(1)
else:
next_generation_row.append(0)

next_generation.append(next_generation_row)
return next_generation

"""Generates the next generation for a given state of Conway's Game of Life."""
next_gen = [[0] * len(row) for row in cells]
for i, row in enumerate(cells):
for j, cell in enumerate(row):
neighbours = sum(
cells[x][y] for x in range(i - 1, i + 2) for y in range(j - 1, j + 2)
if 0 <= x < len(cells) and 0 <= y < len(row) and (x != i or y != j)
)
next_gen[i][j] = 1 if cell == 1 and 2 <= neighbours <= 3 or cell == 0 \
and neighbours == 3 else 0
return next_gen

def generate_images(cells: list[list[int]], frames: int) -> list[Image.Image]:
"""
Generates a list of images of subsequent Game of Life states.
"""
"""Generates a list of images of subsequent Game of Life states."""
images = []
for _ in range(frames):
# Create output image
img = Image.new("RGB", (len(cells[0]), len(cells)))
pixels = img.load()

# Save cells to image
for x in range(len(cells)):
for y in range(len(cells[0])):
colour = 255 - cells[y][x] * 255
pixels[x, y] = (colour, colour, colour)

# Save image
for x, row in enumerate(cells):
for y, cell in enumerate(row):
colour = 255 - cell * 255
pixels[y, x] = (colour, colour, colour)
images.append(img)
cells = new_generation(cells)
return images


if __name__ == "__main__":
images = generate_images(GLIDER, 16)
images[0].save("out.gif", save_all=True, append_images=images[1:])