Concept
I’ve always liked Conway’s Game of Life as a way to represent real cellular life using simple rules. But whenever I looked at the end result, I couldn’t fully see the resemblance to living systems. There was something about it that felt off, even if I couldn’t explain it at first.
After thinking about it more, I realized what bothered me about the classic aesthetic:
- The pixelated style felt too rigid and angular to resemble the organic curves we see in real life.
- Motion was hard to perceive; it felt more like watching stop-motion frames.
- The black-and-white palette didn’t offer enough visual variety.
At the same time, I didn’t want to overcomplicate my version. Like the original, I wanted to keep the code simple while addressing these issues.
Code Highlight
push();
noStroke();
colorMode(HSL);
let saturation = map(count2, 0, 24, 0, 150);
let lightness = map(count2, 0, 24, 50, 100);
// Map the (x, y) position to a hue gradient from top-left (0,0) to bottom-right (cols-1, rows-1)
// Project (x, y) onto the main diagonal from (0,0) to (cols-1, rows-1)
let diagonalProgress = ((x / (cols - 1)) + (y / (rows - 1))) / 2;
let hue = map(diagonalProgress, 0, 1, 0, 360);
fill(hue, saturation, lightness);
let coefficient = expoMap(count, 0, 24, 0.1, 4.5);
rectMode(CENTER)
rect(x * cellSize, y * cellSize, cellSize * coefficient, cellSize * coefficient);
pop();
It’s probably the simplist piece of code in the sketch but I find it to be the most important. The reason being the map functions. I explain later in the milestones.
Embedded Sketch
Milestones
Stage 1 – Conway’s Game of Life
I first rebuilt Conway’s Game of Life in p5.js. The main difference was making it responsive to the canvas instead of using a fixed-size grid. I also added randomization: pressing R randomizes the grid, and the sketch starts with a randomized state.
Stage 2 – Experimenting with Opacity

I quickly found that opacity was an easy and effective way to show motion. By adjusting background opacity over time, movement became visible through trails. During testing, I found a “walker,” and this screenshot made it clear: without opacity, you probably wouldn’t notice that it was moving at all.
Stage 3 – Experimenting with Saturation and Lightness (using colorMode(HSL))



After exploring opacity, I tried a heatmap-like idea: cells with more neighbors become more saturated. I started with immediate neighbors (a 3×3 region), then expanded to a 5×5 neighborhood. I also tested different hues, but saturation alone still didn’t give me the look I wanted.
Stage 4 – Dynamic Cell Size

This was actually one of my earliest ideas, but I initially overcomplicated it in my head. My first concept was that cells with more neighbors should not only get larger, but “bulge,” almost like pushing up on paper from underneath a grid cell.
I didn’t know how to implement that cleanly, so I simplified. Instead, I used an exponential mapping function (rather than p5’s linear map() for a more natural effect), considered neighbors in a 5×5 area, and made low-neighbor cells smaller than their grid square.
That small detail made a huge difference. It defined the edges of each “organism” more clearly, and it reduced visual emphasis on small, self-sustaining shapes (like plus signs or small ovals), while giving larger moving organisms more presence. It’s difficult to fully capture in words – the images, and especially the live sketch, tell the story best.
Reflection
There are some things I would do differently or try out. I want to see what the bulging effect might look like. Also, I want to try adding a function where, if there doesn’t appear to be enough movement, the grid would randomize and start all over again.
I used AI to create the exponential map function. I also wanted to make hue change along both the x and y axes and only knew how to do it one way, so I got help from AI.