Created
January 29, 2025 05:33
-
-
Save thinkyhead/7065c70f0e5920a3e915a1ad35b081bd to your computer and use it in GitHub Desktop.
This file contains hidden or 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 python | |
# | |
# Conway's Game of Life (Three Color Variant) | |
# Author: @thinkyhead | |
# Date: 11 Oct 2024 | |
# | |
# P = PAUSE | |
# Q = QUIT | |
# K = CLEAR | |
# B = SPAWN BLINKERS | |
# G = SPAWN GLIDERS | |
# H = SPAWN GLIDER GUNS | |
# L = SPAWN LINES | |
# X = SPAWN EXPLODERS | |
# | |
import pygame | |
import random | |
import time | |
# Milliseconds to delay between each update | |
DELAY = 50 | |
# Set screen dimensions | |
WIDTH = 1024 | |
HEIGHT = 768 | |
screen : pygame.Surface = None | |
# Cell size | |
CELL_SIZE = 4 | |
# Colors | |
BLACK = (0, 0, 0) | |
WHITE = (255, 255, 255) | |
RED = (255, 0, 0) | |
GREEN = (0, 255, 0) | |
YELLOW = (255, 224, 0) | |
color_by_type = (BLACK, GREEN, YELLOW, RED) | |
colors = len(color_by_type) - 1 | |
# Create a grid to store the cells | |
WW = WIDTH // CELL_SIZE | |
HH = HEIGHT // CELL_SIZE | |
grid = [[0 for _ in range(WW)] for _ in range(HH)] | |
population = 0 | |
prevpop = [ 0, 0 ] | |
stable_count = 0 | |
grow_count = 0 | |
# Function to draw the grid | |
def draw_grid(): | |
screen.fill(BLACK) | |
for r in range(HH): | |
for c in range(WW): | |
pygame.draw.rect(screen, color_by_type[grid[r][c]], (c * CELL_SIZE, r * CELL_SIZE, CELL_SIZE, CELL_SIZE)) | |
# Update the display | |
pygame.display.flip() | |
# Function to count the number of living neighbors | |
def count_neighbors(row, col): | |
around = [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)] | |
neighbors = [0] * colors | |
for dx, dy in around: | |
r, c = (row + dx), (col + dy) | |
if (0 <= r < HH) and (0 <= c < WW) and (grid[r][c] > 0): | |
neighbors[grid[r][c] - 1] += 1 | |
return neighbors | |
# Function to update the grid based on Conway's rules | |
def update_grid(): | |
global grid, population, prevpop, stable_count, grow_count | |
# Create a new nested array 'new_grid' as a copy of 'grid' | |
new_grid = [[0 for _ in range(WW)] for _ in range(HH)] | |
population = 0 | |
for r in range(HH): | |
for c in range(WW): | |
n = count_neighbors(r, c) | |
total = n[0] + n[1] + (n[2] if colors == 3 else 0) | |
# The original cell's value | |
v = grid[r][c] | |
# Rule 1: Any live cell with fewer than two live neighbors dies (underpopulation). | |
# Rule 2: Any live cell with two or three live neighbors lives on to the next generation. | |
# Rule 3: Any live cell with more than three live neighbors dies (overpopulation). | |
# Rule 4: Any empty cell with exactly three live neighbors becomes a live cell (reproduction). | |
if v == 0: | |
if total == 3: | |
if colors == 3: | |
v = 3 if n[2] > 1 else 2 if n[1] > 1 else 1 if n[0] > 1 else random.randint(1, 3) | |
else: | |
v = 2 if n[1] > 1 else 1 | |
elif not (2 <= total <= 3): | |
v = 0 | |
new_grid[r][c] = v | |
population += (v > 0) | |
grid = new_grid | |
# If the population is the same as two generations ago, we might have reached a stable state | |
if prevpop[0] == population: | |
stable_count += 1 | |
grow_count = 0 | |
else: | |
stable_count = 0 | |
grow_count += (1 if prevpop[0] < population else -1) | |
# Remember this population, retain the previous one | |
prevpop[0] = prevpop[1] ; prevpop[1] = population | |
# Function to handle mouse clicks to set cells | |
def handle_click(pos, color): | |
row = pos[1] // CELL_SIZE | |
col = pos[0] // CELL_SIZE | |
if 0 <= row < HH and 0 <= col < WW: | |
grid[row][col] = color | |
# Function to initialize the grid with random cells | |
def init_grid(): | |
for r in range(HH): | |
for c in range(WW): | |
# Randomly set cell state to alive (1,2,...) or dead (0) | |
if (random.random() < 0.2): | |
grid[r][c] = random.randint(1, colors) | |
def spawn_group(row, col, rot, group): | |
if rot < 0: | |
rot = random.randint(0, len(group) - 1) | |
gy = len(group[rot]) | |
gx = len(group[rot][0]) | |
if row < 0: | |
row = random.randint(0, HH - gy - 1) | |
if col < 0: | |
col = random.randint(0, WW - gx - 1) | |
for r in range(gy): | |
for c in range(gx): | |
if group[rot][r][c]: | |
grid[row + r][col + c] = random.randint(1, colors) | |
def spawn_mutated_group(row, col, rot, group): | |
hbase = [list(reversed(row)) for row in group] | |
vbase = [row for row in reversed(group)] | |
hvbase = [list(reversed(row)) for row in vbase] | |
rotated = [list(row) for row in zip(*group)] | |
hrot = [list(reversed(row)) for row in rotated] | |
vrot = [row for row in reversed(rotated)] | |
hvrot = [list(reversed(row)) for row in vrot] | |
spawn_group(row, col, rot, [group, hbase, vbase, hvbase, rotated, hrot, vrot, hvrot]) | |
def spawn_xploder(row=-1, col=-1, rot=-1): | |
xploder = ( | |
[0, 1, 0, 0], | |
[0, 1, 1, 0], | |
[1, 0, 0, 1], | |
[0, 1, 1, 0] | |
) | |
spawn_mutated_group(row, col, rot, xploder) | |
def spawn_blinker(row=-1, col=-1, rot=-1): | |
blinker = ( | |
[0, 0, 0], | |
[1, 1, 1], | |
[0, 0, 0] | |
) | |
spawn_mutated_group(row, col, rot, blinker) | |
def spawn_glider(row=-1, col=-1, rot=-1): | |
glider = ( | |
[0, 1, 0], | |
[0, 0, 1], | |
[1, 1, 1] | |
) | |
spawn_mutated_group(row, col, rot, glider) | |
def spawn_glider_gun(row=-1, col=-1, rot=-1): | |
glider_gun = ( | |
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), | |
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), | |
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1), | |
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1), | |
(1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), | |
(1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), | |
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), | |
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), | |
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | |
) | |
spawn_mutated_group(row, col, rot, glider_gun) | |
def spawn_line(): | |
if random.random() < 0.5: | |
# Vertical line in a single column | |
len = random.randint(6, HH // 2) | |
row = random.randint(2, HH - 2 - len) | |
col = random.randint(2, WW - 2) | |
for r in range(row, row + len - 1): | |
grid[r][col] = random.randint(1, colors) | |
else: | |
# Horizontal line in a single row | |
len = random.randint(6, WW // 2) | |
col = random.randint(2, WW - 2 - len) | |
row = random.randint(2, HH - 2) | |
for c in range(col, col + len - 1): | |
grid[row][c] = random.randint(1, colors) | |
def main(): | |
global screen, running, paused, drawing, color, pos | |
global population, prevpop, stable_count, grow_count | |
global grid | |
# Initialize PyGame | |
pygame.init() | |
screen = pygame.display.set_mode((WIDTH, HEIGHT)) | |
pygame.display.set_caption("Conway's Game of Life (Color Variant)") | |
# Initialize the grid with random cells at the start of the game | |
init_grid() | |
update_grid() | |
update_grid() | |
# Main game loop | |
running = True | |
paused = False | |
drawing = False | |
color = 2 | |
pos = None | |
while running: | |
for event in pygame.event.get(): | |
# QUIT | |
if event.type == pygame.QUIT: | |
running = False | |
# MOUSE BUTTON CLICK | |
elif event.type == pygame.MOUSEBUTTONDOWN: | |
drawing = True | |
color = color + 1 if color < colors else 1 | |
handle_click(event.pos, color) | |
# MOUSE MOVE | |
elif event.type == pygame.MOUSEMOTION: | |
if drawing and (not (pygame.key.get_mods() & pygame.KMOD_META)): | |
handle_click(event.pos, color) | |
# MOUSE BUTTON RELEASE | |
elif event.type == pygame.MOUSEBUTTONUP: | |
# Check for SHIFT key | |
if not (pygame.key.get_mods() & pygame.KMOD_META): | |
drawing = False | |
# KEY PRESS | |
elif event.type == pygame.KEYDOWN: | |
# P = PAUSE | |
# Q = QUIT | |
# K = CLEAR | |
# B = SPAWN BLINKERS | |
# G = SPAWN GLIDERS | |
# H = SPAWN GLIDER GUNS | |
# L = SPAWN LINES | |
# X = SPAWN EXPLODERS | |
if event.key == pygame.K_p: | |
paused = not paused | |
elif event.key == pygame.K_q: | |
running = False | |
elif event.key == pygame.K_k: | |
grid = [[0 for _ in range(WW)] for _ in range(HH)] | |
population = 0 | |
elif event.key == pygame.K_b: | |
for r in range(10): spawn_blinker() | |
elif event.key == pygame.K_g: | |
for r in range(10): spawn_glider() | |
elif event.key == pygame.K_h: | |
for r in range(1): spawn_glider_gun() | |
#paused = True | |
elif event.key == pygame.K_l: | |
for r in range(4): spawn_line() | |
elif event.key == pygame.K_x: | |
for r in range(10): spawn_xploder() | |
if drawing: draw_grid() ; continue | |
# Get the current time in milliseconds | |
t = pygame.time.get_ticks() | |
# Update the grid, using up some time | |
if paused: | |
time.sleep(0.1) | |
continue | |
update_grid() | |
draw_grid() | |
# Print the population at the top of the screen | |
if stable_count > 1000: xtra = "Superstable" | |
elif stable_count > 10: xtra = "Stable" | |
elif grow_count > 10: xtra = "Growing" | |
elif grow_count < -10: xtra = "Reducing" | |
else: xtra = 'Dynamic' | |
pygame.display.set_caption(f"Conway's Game of Life (Color Variant) - Population: {population} - {xtra}") | |
#pygame.time.delay(DELAY - (pygame.time.get_ticks() - t)) | |
# Quit PyGame | |
pygame.quit() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment