import pygame
import random
import math
import numpy as np
from config import *
Initialize Pygame
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
import math
Create a new grid with random alive/dead cells, and optionally some zombies.
def create_grid():
Initialize an empty grid
grid = [[0 for _ in range(COLS)] for _ in range(ROWS)]
if START_PATTERN == "square":
Set all cells inside a square to 1
side_length = min(ROWS, COLS) // 2
left = COLS // 2 - side_length // 2
top = ROWS // 2 - side_length // 2
for row in range(top, top + side_length):
for col in range(left, left + side_length):
grid[row][col] = 1
elif START_PATTERN == "star":
Set all cells inside a star to 1
radius = min(ROWS, COLS) // 4
center_row, center_col = ROWS // 2, COLS // 2
for row in range(ROWS):
for col in range(COLS):
if (
abs(row - center_row) <= radius / 2
or abs(col - center_col) <= radius / 2
or abs(row - center_row) + abs(col - center_col) <= radius
):
grid[row][col] = 1
elif START_PATTERN == "hex":
radius = min(ROWS, COLS) // 2
center_row, center_col = ROWS // 2, COLS // 2
for row in range(ROWS):
for col in range(COLS):
if (
abs(row - center_row) + abs(col - center_col / 2) <= radius / 2
or abs(col - center_col) <= radius / 2
):
grid[row][col] = 1
elif START_PATTERN == "star_thick":
Set all cells inside a star with thicker arms to 1
radius = min(ROWS, COLS) // 4
center_row, center_col = ROWS // 2, COLS // 2
for row in range(ROWS):
for col in range(COLS):
if (
abs(row - center_row) <= radius
or abs(col - center_col) <= radius
or abs(row - center_row) + abs(col - center_col) <= radius + 1
):
grid[row][col] = 1
else:
Generate a grid of random values between 0 and 1
random_grid = np.random.rand(ROWS, COLS)
Threshold the random values with the spawn rate to get a grid of 0s and 1s
grid = np.where(random_grid < SPAWN_RATE, 1, 0)
if ZOMBIE:
Count the number of existing zombies
num_zombies = np.sum(grid == 2)
Spawn new zombies until the desired total is reached
while num_zombies < MAX_ZOMBIES:
row, col = np.random.randint(0, ROWS), np.random.randint(0, COLS)
if grid[row][col] != 2:
grid[row][col] = 2
num_zombies += 1
return grid
Count the number of alive neighbors around a cell.
def count_neighbors(grid, row, col):
count = 0
for i in range(-1, 2):
for j in range(-1, 2):
if i == 0 and j == 0:
continue
if row + i < 0 or row + i >= ROWS or col + j < 0 or col + j >= COLS:
continue
count += grid[row + i][col + j]
return count
Get the color for a cell based on the row number.
def get_cell_color(row, zombie=False):
top_color = (60, 173, 100) # Color for top of the wave
bottom_color = (52, 152, 219) # Color for bottom of the wave
alpha = min(255, max(0, int(255 * (row / ROWS)))) # Opacity based on the row number
if zombie:
top_color = (255, 0, 0)
bottom_color = (255, 255, 0)
color = (
int((top_color[0] * alpha + bottom_color[0] * (255 - alpha)) / 255),
int((top_color[1] * alpha + bottom_color[1] * (255 - alpha)) / 255),
int((top_color[2] * alpha + bottom_color[2] * (255 - alpha)) / 255),
)
return color
def conway_rules(grid):
new_grid = [[0 for _ in range(COLS)] for _ in range(ROWS)]
for row in range(ROWS):
for col in range(COLS):
neighbors = count_neighbors(grid, row, col)
if grid[row][col] == 1 and (neighbors < 2 or neighbors > 3):
new_grid[row][col] = 0
elif grid[row][col] == 0 and neighbors == 3:
new_grid[row][col] = 1
else:
new_grid[row][col] = grid[row][col]
if RANDOM_FLIP:
randomly_flip_cells(new_grid)
return new_grid
Update the grid based on the rules of Conway’s Game of Life.
def update_grid(grid):
if ZOMBIE:
return zombie(grid)
else:
return conway_rules(grid)
Return a darker version of the given color.
def darker(color, depth=0.2):
r, g, b = color
return int(r * (1 - depth)), int(g * (1 - depth)), int(b * (1 - depth))
Return a lighter version of the given color.
def lighter(color, depth=0.2):
r, g, b = color
return (
int(r + (240 - r) * depth // 2),
int(g + (240 - g) * depth // 2),
int(b + (240 - b) * depth // 2),
)
def draw_grid():
for x in range(0, WIDTH, CELL_SIZE):
pygame.draw.line(screen, (50, 50, 50), (x, 0), (x, HEIGHT))
for y in range(0, HEIGHT, CELL_SIZE):
pygame.draw.line(screen, (50, 50, 50), (0, y), (WIDTH, y))
Draw the grid on the screen as spheres with a color gradient and a pulsating effect.
def draw_color_scheme(grid, scale_factor, last_grid):
Draw the background color gradient
for y in range(HEIGHT):
color = interpolate_colors(BACKGROUND_TOP, BACKGROUND_BOTTOM, y / HEIGHT)
pygame.draw.line(screen, color, (0, y), (WIDTH, y))
Draw the cells as 3D circles with the wave pattern and pulsating effect
for row in range(ROWS):
for col in range(COLS):
if grid[row][col] != last_grid[row][col]:
if grid[row][col] >= 1:
if grid[row][col] == 2:
color = get_cell_color(row, True)
if grid[row][col] == 1:
color = get_cell_color(row)
size = int(CELL_SIZE + CELL_SIZE * scale_factor)
x = col * CELL_SIZE + CELL_SIZE // 2
y = row * CELL_SIZE + CELL_SIZE // 2
Draw the circle
pygame.draw.circle(screen, color, (x, y), size // 2)
Draw the circle highlight
highlight_size = size // 4
highlight_color = lighter(color, 0.2)
highlight_pos = (x - highlight_size, y - highlight_size)
highlight_rect = pygame.Rect(
highlight_pos, (highlight_size * 2, highlight_size * 2)
)
pygame.draw.ellipse(screen, highlight_color, highlight_rect)
Draw the circle shadow
shadow_size = size // 4
shadow_color = darker(color, 0.2)
shadow_pos = (x + shadow_size, y + shadow_size)
shadow_rect = pygame.Rect(
shadow_pos, (size - shadow_size * 4, size - shadow_size * 4)
)
pygame.draw.ellipse(screen, shadow_color, shadow_rect)
Draw the specular highlight
specular_size = size // 10
specular_color = (255, 255, 255)
pygame.draw.circle(
screen,
specular_color,
(x - specular_size, y + specular_size),
specular_size,
)
else:
Draw a circle for dead cells
x = col * CELL_SIZE + CELL_SIZE // 2
y = row * CELL_SIZE + CELL_SIZE // 2
size = int(CELL_SIZE + CELL_SIZE * scale_factor)
color = interpolate_colors(CELL_COLORS[1], (0, 0, 0), 0.5)
pygame.draw.circle(screen, color, (x, y), size // 2)
if GRID:
draw_grid()
pygame.display.update()
Interpolate between two colors.
def interpolate_colors(color1, color2, t):
r = int(color1[0] + (color2[0] - color1[0]) * t)
g = int(color1[1] + (color2[1] - color1[1]) * t)
b = int(color1[2] + (color2[2] - color1[2]) * t)
return (r, g, b)
Create a glider pattern at the specified row and column.
def create_glider(grid, row, col):
glider = [[0, 0, 1], [1, 0, 1], [0, 1, 1]]
for i in range(3):
for j in range(3):
grid[row + i][col + j] = glider[i][j]
return grid
Randomly flip the states of a few cells on the grid.
def randomly_flip_cells(grid):
if FLIP <= 1:
return
num_flips = random.randint(1, FLIP)
for i in range(num_flips):
row = random.randint(0, ROWS - 1)
col = random.randint(0, COLS - 1)
grid[row][col] = 1 - grid[row][col]
def check_if_neighbor(grid, row, col, cell_type=1):
return any(
grid[(row + i) % ROWS][(col + j) % COLS] == cell_type
for i in range(-1, 2)
for j in range(-1, 2)
)
Apply the zombie infection rule to the given grid.
def zombie(grid):
new_grid = [[0 for _ in range(COLS)] for _ in range(ROWS)]
for row in range(ROWS):
for col in range(COLS):
neighbors = count_neighbors(grid, row, col)
if grid[row][col] == 0:
if neighbors >= 3 and check_if_neighbor(grid, row, col, 2):
if random.random() < INFECT_PROBABILITY:
new_grid[row][col] = 2 # Cell turns into a zombie
elif neighbors == 3 and not check_if_neighbor(grid, row, col, 2):
new_grid[row][col] = 1
else:
new_grid[row][col] = 0 # Cell stays dead
elif grid[row][col] == 1:
if neighbors < 1 or neighbors > 3:
new_grid[row][col] = 0 # Live cell dies
else:
new_grid[row][col] = 1 # Live cell stays alive
elif grid[row][col] == 2:
if neighbors == 3 and not check_if_neighbor(grid, row, col, 2):
new_grid[row][col] = 0
else:
new_grid[row][col] = 2
return new_grid
Run the game.
def main():
grid = create_grid()
last_grid = [[0 for _ in range(COLS)] for _ in range(ROWS)]
running = True
scale_factor = 1
scale_direction = 1
zoom_factor = 0.0
drawing = False
paused = False
paused_before_draw = False
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
zoom_factor += 0.1
elif event.key == pygame.K_DOWN:
if zoom_factor > 0:
zoom_factor -= 0.1
elif event.key == pygame.K_0:
zoom_factor = 1.0
elif event.key == pygame.K_SPACE:
grid = create_grid()
elif event.key == pygame.K_RETURN:
if not paused_before_draw:
paused = not paused
elif event.key == pygame.K_g:
row = random.randint(0, ROWS - 3)
col = random.randint(0, COLS - 3)
grid = create_glider(grid, row, col)
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
Start drawing
paused_before_draw = paused
paused = True
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
Stop drawing
paused = paused_before_draw
paused_before_draw = False
elif event.type == pygame.MOUSEMOTION:
if paused and pygame.mouse.get_pressed():
Draw the pattern
x, y = event.pos
row = y // CELL_SIZE
col = x // CELL_SIZE
grid[row][col] = 1
if ZOMBIE:
grid[row][col] = 2
scaled_width = int(WIDTH * zoom_factor)
scaled_height = int(HEIGHT * zoom_factor)
scaled_screen = pygame.transform.scale(screen, (scaled_width, scaled_height))
screen.blit(
scaled_screen, ((WIDTH - scaled_width) // 2, (HEIGHT - scaled_height) // 2)
)
pygame.display.update()
if not paused:
last_grid = grid
grid = update_grid(grid)
draw_color_scheme(grid, scale_factor, last_grid)
Update the scale factor for the pulsating effect
scale_factor += 0.025 * scale_direction
if scale_factor > SCALE_FACTOR or scale_factor < 0:
scale_direction *= -1
clock.tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()