Last active
September 11, 2023 21:52
-
-
Save Sonictherocketman/4551dcf001932639a03e8849be92ff71 to your computer and use it in GitHub Desktop.
The game of life in Python.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python3 | |
# | |
# The Game of Life in Python with no dependencies. | |
# by Brian Schrader | |
# | |
# The board is randomly generated each time so each play-through is unique! | |
# | |
# Usage: | |
# ./life.py | |
from collections import Counter | |
from tkinter import * | |
from tkinter.ttk import * | |
from dataclasses import dataclass | |
import math | |
import random | |
max_size = 1600, 1000 | |
state = [] | |
particle_width = 15 | |
particle_kwargs = { | |
'outline': 'black', | |
'fill': 'white', | |
'width': 1, | |
} | |
old_particle_kwargs = { | |
'outline': 'black', | |
'fill': 'green', | |
'width': 1, | |
} | |
@dataclass | |
class CellState: | |
current_state: int | |
previous_state: int = None | |
age: int = 1 | |
id: int = None | |
OLD_AGE = 5 | |
def set_state(self, value): | |
self.previous_state = self.current_state | |
self.current_state = value | |
if self.current_state and self.previous_state: | |
self.age += 1 | |
else: | |
self.age = 0 | |
@property | |
def changed(self): | |
return self.previous_state != self.current_state | |
class App(Frame): | |
def __init__(self, master=None): | |
super().__init__(master) | |
self.pack() | |
class Space: | |
def __init__(self, master=None, initial_state: [[CellState]]=[]): | |
self.master = master | |
self.state = initial_state | |
self.canvas = Canvas(self.master, bg='black') | |
self.canvas.pack(fill=BOTH, expand=1) | |
def render(self): | |
width = particle_width | |
for x, y, state in self.cells(): | |
if state.changed: | |
if state.current_state: | |
# Newly created cell. Add it. | |
state.id = self.canvas.create_rectangle( | |
(x * width), | |
(y * width), | |
(x * width) + (width), | |
(y * width) + (width), | |
**particle_kwargs, | |
) | |
else: | |
# Newly dead cell. Delete it. | |
self.canvas.delete(state.id) | |
state.id = None | |
elif state.age == state.OLD_AGE: | |
# Newly old cell. Recolor it. | |
self.canvas.delete(state.id) | |
state.id = self.canvas.create_rectangle( | |
(x * width), | |
(y * width), | |
(x * width) + (width), | |
(y * width) + (width), | |
**old_particle_kwargs, | |
) | |
def cells(self): | |
for y, row in enumerate(self.state): | |
for x, value in enumerate(row): | |
yield (x, y, value) | |
def get_next_state(self, x, y, state): | |
""" | |
| a | b | c | | |
| d | - | e | | |
| f | g | h | | |
""" | |
y_lim = len(self.state) - 1 | |
x_lim = len(self.state[0]) - 1 | |
a = 0 if x == 0 or y == 0 else self.state[y-1][x-1].current_state | |
b = 0 if y == 0 else self.state[y-1][x ].current_state | |
c = 0 if y == 0 or x == x_lim else self.state[y-1][x+1].current_state | |
d = 0 if x == 0 else self.state[y ][x-1].current_state | |
e = 0 if x == x_lim else self.state[y ][x+1].current_state | |
f = 0 if x == 0 or y == y_lim else self.state[y+1][x-1].current_state | |
g = 0 if y == y_lim else self.state[y+1][x ].current_state | |
h = 0 if x == x_lim or y == y_lim else self.state[y+1][x+1].current_state | |
if not any({a, b, c, d, e, f, g, h}): | |
# All dead. Don't bother. | |
return 0 | |
counter = Counter([a, b, c, d, e, f, g, h]) | |
alive_neighbors = counter[1] | |
if state.current_state and (alive_neighbors == 2 or alive_neighbors == 3): | |
return 1 | |
elif state.current_state: | |
return 0 | |
elif alive_neighbors == 3: | |
return 1 | |
else: | |
return 0 | |
def tick(self): | |
for x, y, value in self.cells(): | |
self.state[y][x].set_state(self.get_next_state(x, y, value)) | |
def main(wait=120): | |
# initialize board | |
for y in range(max_size[1] // particle_width): | |
state.append([]) | |
for _ in range(max_size[0] // particle_width): | |
state[y].append(CellState(random.choices([1, 0], [0.04, 0.96])[0])) | |
# startup app | |
application = App() | |
application.master.title('The Board') | |
application.master.maxsize(*max_size) | |
application.master.geometry(f'{max_size[0]}x{max_size[1]}') | |
space = Space(application.master, initial_state=state) | |
space.render() | |
def _render(): | |
try: | |
space.tick() | |
except StopIteration: | |
print('Done') | |
return | |
else: | |
space.render() | |
application.master.after(wait, _render) | |
# kickoff | |
application.master.after(wait, _render) | |
application.mainloop() | |
if __name__ == '__main__': | |
main() |
Author
Sonictherocketman
commented
Sep 11, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment