Conway's Life
11th October 2020
A simple implementation of Conway's game of life in P5js, with a side of nostalgia

In the mid 80’s, when I was around 11 or 12 I think, I regularly spent a lot of time at the local public library. I would ride my bike into my small-ish home town and disappear into the cool interior to browse the shelves and spend the afternoon reading. This went on for years, a likely result of being raised mostly by a single father and not really having many friends. It wasn’t the best library in the world, but there was more than enough mystery there for me to uncover.

I particularly remember reading a book called Ox by Piers Anthony. I read a lot of Piers Anthony around that age, which makes me cringe a bit when I look back because, well, let’s just say I probably didn’t get the greatest messages about women from his works. It was the third book in a series, the content of which I mostly don’t remember, but it did feature a reference to John Conway’s Game of Life which fascinated me.

In case you’re not familiar, Conway’s Game of Life is a simple cellular automaton, which is just a fancy way of saying it’s a sort of game or algorithm that is played on a grid and involves turning grid cells “on” or “off” as time passes according to a set of simple rules.

The rules of Life are:

  1. Any live cell with fewer than two live neighbors dies (as if by underpopulation)
  2. Any live cell with two or three live neighbors lives on
  3. Any live cell with more than three live neighbors dies (as if by overpopulation)
  4. Any dead cell with exactly three live neighbors becomes a live cell (as if by reproduction)

Here’s what it looks like in action:

If you have Javascript enabled you can click the above canvas to reset the grid with random data.

By following these simple rules any child with a pencil and a pad of graph paper can fill in some random squares and watch a tiny world evolve and change. All kinds of fascinating things develop as you evolve the system: Stable groupings of cells with beautiful symmetry, so called gliders that seem to walk across the grid, and various generators that produce those gliders. I was, of course, completely hooked.

This game was fictionalized somewhat in the book I was reading, but thankfully that book included a reference to the Scientific American article Martin Gardner wrote about the game. My library didn’t have copies of Scientific American from the 1970s just lying around and there wasn’t really a functional internet I could query yet. Thankfully, they did have a huge back catalog of Scientific American on microfiche.

A microfiche machine displaying old newspaper ads

Diving into the microfiche felt like unlocking a hidden archive of esoteric knowledge, which for a young boy obsessed with both technology and wizards was more or less like hitting the jackpot. While looking for the Game of Life I discovered that Martin Gardner had written a long running column about mathematical games. I spent many, many happy hours reading those old articles and thinking about weird math games. It was a magical time for me.

So, of course, when I recently decided to check out P5 (a Javascript based update of Processing) a simple implementation of Conway’s Game of Life seemed like the perfect thing to try out.

P5, like Processing, is based around two entry points:

  • Setup() - which runs once at the beginning
  • Draw() - which runs every frame

The setup bit is pretty straight forward:

  sketch.setup = function () {
    // Create a canvas appropriately sized for our parent element
    let parent = sketch.select('#lifesketch');
    canvas = sketch.createCanvas(parent.size().width, parent.size().height);

    canvas.mouseClicked(handleMouseClick);
    sketch.frameRate(3);

    columnCount = sketch.floor(sketch.width / cellSize);
    rowCount = sketch.floor(sketch.height / cellSize);

    lifeData = new Array(columnCount).fill(0).map(() => new Array(rowCount).fill(0));
    lifeNeighbors = new Array(columnCount).fill(0).map(() => new Array(rowCount).fill(0));

    sketch.background(0);
    fillRandom(lifeData);
    countAllNeighbors(lifeData, lifeNeighbors);
    updateLifeData(lifeData, lifeNeighbors);
    drawData(lifeData, true, cellColor, false, 0);
  }

Do a little sizing work, figure out how many rows and columns we need, set up some data storage and get on with things.

The draw function is even simpler:

  sketch.draw = function () {

    countAllNeighbors(lifeData, lifeNeighbors);
    updateLifeData(lifeData, lifeNeighbors);

    drawData(lifeData, true, cellColor, false, 255);
  }

drawData just involves looping through the array and drawing a bunch of squares of the right color, which is a first class operation in P5 so easy as can be. I have a little extra fanciness for displaying the value, a holdover from a different use case, but even so the method is dead simple.

  function drawData(d, bShowColor, cc, bShowValue, cv) {
    sketch.textAlign(sketch.CENTER, sketch.CENTER);
    sketch.textSize(cellSize * 0.5);

    for (let xIndex = 0; xIndex < columnCount; ++xIndex) {
      let x = xIndex * cellSize;

      for (let yIndex = 0; yIndex < rowCount; ++yIndex) {
        let y = yIndex * cellSize;

        let value = d[xIndex][yIndex];
        
        if (bShowColor) {
          sketch.stroke(0);
          sketch.fill(scaleColor(cc, value));
          sketch.rect(x, y, x + cellSize, y + cellSize);  
        }

        if (bShowValue) {
          sketch.fill(cv);
          sketch.stroke(cv);
          sketch.text(sketch.nf(value, 1, 0), x + halfCellSize, y + halfCellSize);
        }
      }
    }

This is the joy of these creative coding platforms like P5 and Processing. They require minimal fuss before getting something on the screen, which makes them a great way to try out visualization ideas, play with simple algorithms, and other such recreational coding.

The best part, of course, is that the actual logic that drives Conway’s Game of Life is so incredibly simple it makes all this setup seem long winded by comparision. In the end, this is really all it takes:

  function updateLifeData(d, nd) {
    for (let xIndex = 0; xIndex < columnCount; ++xIndex) {
      for (let yIndex = 0; yIndex < rowCount; ++yIndex) {
        let n = nd[xIndex][yIndex];
        if (n < 2 || n > 3) {
          d[xIndex][yIndex] = 0;
        }
        else if (n == 3) {
          d[xIndex][yIndex] = 1;
        }
      }
    }    
  }

d is the 2D array of the grid, and nd is a parallel 2D array where I store the count of neighbors.

When I started out working on this I was just looking to get familiar with P5 and brush up on my Javascript skills, since I hardly ever use it. The decision to implement Conway’s Game of Life was a whim, and I didn’t expect it to be such a nostalgic experience. What I thought was just an afternoon coding lark quickly turned into a stroll through memories I haven’t thought of in years.

It’s almost enough to make me want to re-read those Piers Anthony books.
Almost.