Cellular Automata
In 1970, mathematician John Conway invented a "Zero-Player Game" called the Game of Life. It proved that you don't need complex math formulas to generate organic shapes; you just need a grid and a few simple rules.
The Cell State:
0 (Dead/Empty) or 1 (Alive/Filled). 4. By updating the states of these cells over time, complex shapes (like coral, mold, or crystals) can organically grow across the grid.1# Defining the World2def create_ca_grid(width, height):3 """The game board."""45 # 1. Create a 2D Array (a list of lists)6 grid = []78 for y in range(height):9 row = []10 for x in range(width):11 # 2. Every cell starts as 'Dead' (0)12 row.append(0)1314 grid.append(row)1516 # 3. We manually turn on a few cells to start the simulation17 grid[5][5] = 1 # 'Alive' (1)1819 return grid
In a Cellular Automaton, a cell cannot see the whole grid. It has no idea what is happening 10 cells away. It can only see its immediate, direct neighbors.
Local Awareness:
1# Finding the Neighbors2def get_moore_neighborhood(grid, cell_x, cell_y):3 """The 8 cells surrounding our target."""45 neighbors = []67 # We loop from -1 to +1 on both X and Y axis8 for x_offset in [-1, 0, 1]:9 for y_offset in [-1, 0, 1]:1011 # Skip the center cell (that's us!)12 if x_offset == 0 and y_offset == 0:13 continue1415 nx = cell_x + x_offset16 ny = cell_y + y_offset1718 # Make sure the neighbor is actually inside the grid!19 if nx >= 0 and nx < grid.width and ny >= 0 and ny < grid.height:20 neighbors.append(grid[ny][nx])2122 return neighbors
Once a cell looks at its Moore Neighborhood, it only cares about one specific metric:How many of my 8 neighbors are currently Alive?
The Census:
1# Counting the Living2def count_living_neighbors(grid, cell_x, cell_y):3 """How crowded is it?"""45 # Get the 8 neighbors6 neighbors = get_moore_neighborhood(grid, cell_x, cell_y)78 alive_count = 0910 # Loop through them and count11 for neighbor in neighbors:12 if neighbor.state == 1: # 1 means Alive!13 alive_count += 11415 return alive_count
John Conway discovered a specific set of 4 rules that magically generate complex, moving patterns. These rules evaluate the Neighbor Count we just calculated, and decide if the Cell lives or dies.
The Game of Life Rules:
1# Conway's Game of Life2def apply_rules(current_state, alive_neighbors):3 """The 4 rules of existence."""45 # Rule 1: Underpopulation (Death by loneliness)6 if current_state == 1 and alive_neighbors < 2:7 return 089 # Rule 2: Survival10 if current_state == 1 and (alive_neighbors == 2 or alive_neighbors == 3):11 return 11213 # Rule 3: Overpopulation (Death by overcrowding)14 if current_state == 1 and alive_neighbors > 3:15 return 01617 # Rule 4: Reproduction!18 if current_state == 0 and alive_neighbors == 3:19 return 12021 # Otherwise, stay exactly the same22 return current_state
If we update the cells directly on the grid one by one, we will ruin the simulation. The cells at the bottom of the grid would be looking at the "Future" state of the cells at the top!
Double Buffering:
1# Double Buffering2def compute_next_generation(current_grid):3 """Why we need two grids."""45 # 1. Create a completely blank new grid6 next_grid = create_blank_grid()78 # 2. Read from the OLD grid, write to the NEW grid9 for cell in current_grid:1011 # Check neighbors in the OLD grid12 alive = count_neighbors(current_grid, cell.x, cell.y)1314 # Calculate fate15 new_state = apply_rules(cell.state, alive)1617 # Save the fate to the NEW grid!18 next_grid[cell.y][cell.x].state = new_state1920 return next_grid
Because the rules of the Game of Life are perfectly balanced between Death and Reproduction, certain starting patterns create fascinating long-term behaviors.
Archetypes:
1# Creating Gliders2def spawn_glider(grid, start_x, start_y):3 """A pattern that moves across the grid forever."""45 # We turn on 5 specific cells in a 3x3 grid6 # This specific arrangement forces the cells to constantly7 # die and reproduce in a way that shifts exactly 1 pixel diagonally!89 grid[start_y][start_x + 1] = 110 grid[start_y + 1][start_x + 2] = 111 grid[start_y + 2][start_x] = 112 grid[start_y + 2][start_x + 1] = 113 grid[start_y + 2][start_x + 2] = 1
Simulating tens of thousands of cells in real-time requires bypassing 3D scene overhead. Using a native GPU fragment shader with ping-pong textures, we can simulate millions of cells at a locked 60 frames per second.
Simulation Rules:
1# High-performance 2D Cellular Automata using double buffering2def step_simulation(current_grid, width, height):3 next_grid = bytearray(width * height)4 for y in range(height):5 for x in range(width):6 idx = y * width + x7 neighbors = count_alive_neighbors(current_grid, x, y, width, height)89 # Conway's Game of Life Rules10 if current_grid[idx] == 1:11 next_grid[idx] = 1 if neighbors in (2, 3) else 012 else:13 next_grid[idx] = 1 if neighbors == 3 else 014 return next_grid