Initializing 3D Canvas...

Cellular Automata

2 min read1 page

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:

1. A Cellular Automaton (CA) happens on a strict Grid. 2. Every single square (or Voxel) in the grid is called a Cell. 3. A Cell can only be in one of two states: 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.
python
1# Defining the World
2def create_ca_grid(width, height):
3 """The game board."""
4
5 # 1. Create a 2D Array (a list of lists)
6 grid = []
7
8 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)
13
14 grid.append(row)
15
16 # 3. We manually turn on a few cells to start the simulation
17 grid[5][5] = 1 # 'Alive' (1)
18
19 return grid
Cell State (Empty / Alive / Dead)
0.00
2 min read1 page

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. If we are looking at a 2D Grid, a single cell is surrounded by exactly 8 other cells (Top, Bottom, Left, Right, and the 4 Diagonals). 2. This 8-cell ring is called the Moore Neighborhood. 3. (If we ignored the diagonals and only looked at Top/Bottom/Left/Right, it would be called the von Neumann Neighborhood). 4. The entire simulation relies exclusively on checking the states (Dead/Alive) of these 8 specific neighbors.
python
1# Finding the Neighbors
2def get_moore_neighborhood(grid, cell_x, cell_y):
3 """The 8 cells surrounding our target."""
4
5 neighbors = []
6
7 # We loop from -1 to +1 on both X and Y axis
8 for x_offset in [-1, 0, 1]:
9 for y_offset in [-1, 0, 1]:
10
11 # Skip the center cell (that's us!)
12 if x_offset == 0 and y_offset == 0:
13 continue
14
15 nx = cell_x + x_offset
16 ny = cell_y + y_offset
17
18 # 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])
21
22 return neighbors
Neighborhood (Target / von Neumann / Moore)
0.00
1 min read1 page

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. Every cell acts as a tiny accountant. 2. It looks at all 8 cells around it. 3. It completely ignores the "Dead" ones (state 0). 4. It counts the "Alive" ones (state 1). 5. This results in a single integer between 0 and 8. This number will determine the cell's ultimate fate in the next step.
python
1# Counting the Living
2def count_living_neighbors(grid, cell_x, cell_y):
3 """How crowded is it?"""
4
5 # Get the 8 neighbors
6 neighbors = get_moore_neighborhood(grid, cell_x, cell_y)
7
8 alive_count = 0
9
10 # Loop through them and count
11 for neighbor in neighbors:
12 if neighbor.state == 1: # 1 means Alive!
13 alive_count += 1
14
15 return alive_count
Alive Neighbors Count
0.00
2 min read1 page

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. Underpopulation: An Alive cell with less than 2 neighbors dies of loneliness. 2. Survival: An Alive cell with 2 or 3 neighbors is perfectly happy and stays alive. 3. Overpopulation: An Alive cell with 4 or more neighbors dies from overcrowding. 4. Reproduction: A Dead cell with exactly 3 neighbors magically springs to life (a new baby is born).
python
1# Conway's Game of Life
2def apply_rules(current_state, alive_neighbors):
3 """The 4 rules of existence."""
4
5 # Rule 1: Underpopulation (Death by loneliness)
6 if current_state == 1 and alive_neighbors < 2:
7 return 0
8
9 # Rule 2: Survival
10 if current_state == 1 and (alive_neighbors == 2 or alive_neighbors == 3):
11 return 1
12
13 # Rule 3: Overpopulation (Death by overcrowding)
14 if current_state == 1 and alive_neighbors > 3:
15 return 0
16
17 # Rule 4: Reproduction!
18 if current_state == 0 and alive_neighbors == 3:
19 return 1
20
21 # Otherwise, stay exactly the same
22 return current_state
Alive Neighbors Count
2.00
2 min read1 page

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. To fix this, we must use two separate grids: a Read Buffer (Current Gen) and a Write Buffer (Next Gen). 2. We look at the Read Buffer to count neighbors. 3. We write the result to the Write Buffer. 4. Once the entire loop is finished, we swap the buffers! The Write Buffer becomes the new Read Buffer for the next generation. 5. This ensures that every single cell updates simultaneously, like a synchronized clock.
python
1# Double Buffering
2def compute_next_generation(current_grid):
3 """Why we need two grids."""
4
5 # 1. Create a completely blank new grid
6 next_grid = create_blank_grid()
7
8 # 2. Read from the OLD grid, write to the NEW grid
9 for cell in current_grid:
10
11 # Check neighbors in the OLD grid
12 alive = count_neighbors(current_grid, cell.x, cell.y)
13
14 # Calculate fate
15 new_state = apply_rules(cell.state, alive)
16
17 # Save the fate to the NEW grid!
18 next_grid[cell.y][cell.x].state = new_state
19
20 return next_grid
Buffer (Read / Write)
0.00
2 min read1 page

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. Still Lifes: Patterns that are perfectly stable and never change (e.g., a 2x2 square). 2. Oscillators: Patterns that blink back and forth between two or more shapes (like a beating heart). 3. Spaceships (Gliders): Patterns that constantly reconstruct themselves slightly offset from their previous position, appearing to "fly" across the grid. 4. Guns: Massive patterns that act as factories, infinitely spawning Gliders and shooting them across the screen.
python
1# Creating Gliders
2def spawn_glider(grid, start_x, start_y):
3 """A pattern that moves across the grid forever."""
4
5 # We turn on 5 specific cells in a 3x3 grid
6 # This specific arrangement forces the cells to constantly
7 # die and reproduce in a way that shifts exactly 1 pixel diagonally!
8
9 grid[start_y][start_x + 1] = 1
10 grid[start_y + 1][start_x + 2] = 1
11 grid[start_y + 2][start_x] = 1
12 grid[start_y + 2][start_x + 1] = 1
13 grid[start_y + 2][start_x + 2] = 1
Archetype (Still Life / Oscillator / Glider)
0.00
7 min read2 pages

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. Double Buffering: Read cell states from the read target texture and write the next generation to the write target texture to avoid frame desynchronization. 2. Direct Pixel Shader: Calculate Conway Game of Life states in parallel inside a WebGL pixel shader, completely bypassing CPU matrix updates.
python
1# High-performance 2D Cellular Automata using double buffering
2def 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 + x
7 neighbors = count_alive_neighbors(current_grid, x, y, width, height)
8
9 # Conway's Game of Life Rules
10 if current_grid[idx] == 1:
11 next_grid[idx] = 1 if neighbors in (2, 3) else 0
12 else:
13 next_grid[idx] = 1 if neighbors == 3 else 0
14 return next_grid
Run Simulation
Simulation Delay (Frames)
5.00