Steven Morse personal website and research notes

Conway's Game of Life - Javascript

In 1970, British mathematician John Conway invented the Game of Life. (Not to be confused with the 1860 boardgame Life.) A zero-player game based on a simple set of rules applied to a grid, the Game of Life creates emergent behavior of cellular automaton. Conway is a heavy hitter, with a lot of serious and foundational work in mathematics, things named after him, etc, so I assume this idea was just a lark — but it has become a classic in the study of emergent systems behavior, cellular automata, and a staple of computer science and coding, as a simple idea that tests basic things like grid design, looping animation, and offers natural ways to incorporate UI and other customizations.

There’s many, many, many great tutorials on the topic, but I tinkered with this recently and figured I’d share my approach. Specifically, I noticed most Javascript implementations and tutorials use the HTML5 Canvas but I’ll offer here a DOM approach. I’m aiming for an audience of beginner (but not novice) level familiarity with HTML, CSS, and JS.

The finished project is live and you can play with it here, or check out the source on Github.

The Game of Life

The game occurs on a grid of cells which are either “alive” or “dead” and evolve turn-by-turn according to the following rules:

  1. Any cell adjacent to 3 alive neighbors becomes alive (if it wasn’t already).
  2. Any cell with more than 3 neighbors dies due to overpopulation.
  3. Any cell with fewer than 2 neighbors dies due to underpopulation.

Some notes (important when we start coding this up):

These simple rules lead to surprisingly interesting behavior that can seem lifelike, and there are many well-known patterns (or “lifeforms”) of interest. For example, some small still-life:

Block Boat

And some small moving ones:

Spinner Toad Spaceship

It’s worthwhile to pause and examine the behavior of these to understand, cell by cell, why they behave the way they do.

Then there’s some bigger ones, like Gosper’s glider gun. Interesting story (h/t Sagnik Battachaya) – the glider gun’s existence implies that initial patterns with a finite number of cells can eventually lead to configurations with an infinite number of cells. John Conway conjectured that such a thing was impossible but Bill Gosper discovered the Gosper Glider Gun in 1970, winning $50 from Conway.

Glider gun

Anyway, this is obviously a deep rabbit hole of experimentation, and there’s lots of resources if you want to keep going deeper, but let’s get on to our purpose which is to code it up so we can tinker with lifeforms ourselves.

Design choices

One of our first choices is whether to use a Canvas or DOM (Document Object Model) approach. If you’re unfamiliar with this distinction, this comparison is a good overview. In short, the Canvas allows us to directly and efficiently manipulate shapes on the screen, with fast performance but at the cost of coding ease – shape locations, event listening, etc. all need to be handled explicitly by us. Canvas is undeniably faster – I think Google Docs switched to Canvas a few years ago – and so a lot of Game of Life implementations use it, aiming for gigantic grids.

Using the DOM, whether that’s a bunch of <svg> elements or a bunch of <div>s, requires us to direct the browser to arrange objects the way we want, at the cost of control and performance but with the opportunity to benefit from coding ease (think CSS and event handlers). Since my goal is to make a medium-size Game of Life grid, with at most a thousand or so objects, and to be able to have fun with adding UI features, I thought it’d be nice to implement using the DOM and CSS. In fact, since we’re just dealing with a bunch of squares, I’ll just use strictly divs and no SVG at all.

Other notes:

This project is very small and we could do all in one index.html file, but I’ll split out CSS to a style.css file and the JS to a game.js file, all in one directory.

I will be using jQuery! No judgment! Of course this could be made without jQuery but it certainly makes life easier with this amount of DOM manipulation and working in straight JS.

Lastly, I’m attempting to keep this as unadorned as possible so I won’t use classes or really any frameworks at all for the code. The model will be an array of arrays, all functions will be in the global namespace, etc.

Now onto the code.

The grid

To code up a medium-size square grid, we’ll use a <div> in the body of our html to contain all the grid elements, use JS to populate this with a bunch of cells (also <div>s), and then use CSS Grid to structure everything.

So in index.html we’ll put this in <body>:

<div id="grid"></div>

and then setup the grid with some CSS:

:root {
    --gridwidth: 20;
    --gridheight: 10;
    --cellsize: 50px;
}

#grid {
    text-align: center;
    display: grid;
    grid-template-rows: repeat(var(--gridheight), var(--cellsize));
    grid-template-columns: repeat(var(--gridwidth), var(--cellsize));
    justify-content: center; /* centers cells in grid div */
}

.cell {
    background-color: white;
    position: relative;
    border: 0.5px solid lightgray;
    width: calc(var(--cellsize) - 1);
    height: calc(var(--cellsize) - 1);
}

.active {
    background-color: blue;
}

Some key things to highlight:

Finally, in game.js we’ll have the following code:

// grab these variables from :root
let gridheight = document.documentElement.style.getProperty('--gridheight');
let gridwidth = document.documentElement.style.getProperty('--gridwidth');

// initialize a state array to zeros
// this will represent the grid of cells -- 0 for dead, 1 for alive
let state = Array(gridheight).fill(0).map(x => Array(gridwidth).fill(0));

// set a few cells to alive
state[2][2] = 1;
state[2][3] = 1;
state[2][4] = 1;

function initGrid() {
    // grab the grid div
    let $grid = $('#grid');

    for (let i=0; i<gridheight; i++) { // row
        for (let j=0; j<gridwidth; j++) { // col
            // define this cell
            let $cell = $('<div>').addClass('cell');

            // anywhere we turned on state, set to active
            if (state[i][j] == 1) {
                $cell.addClass('active');
            } 

            // append this cell to grid
            $grid.append($cell);
        }
    }
}    

Now let’s cover the basics of the main game loop.

The game loop

Now we’ll setup a loop to update the model (state) and visualization. Basically, we’ll create a function called step() that does one update to the grid, then call it iteratively.

function step() {
    // create temp state to store active/non for next step
    let temp = Array(gridheight).fill(0).map(x => Array(gridwidth).fill(0));

    // check surrounding
    for (let i=0; i<gridheight; i++) {
        for (let j=0; j<gridwidth; j++) {
            // count the surrounding population
            let numAlive = 0;
            for (let m=-1; m<=1; m++) {
                for (let n=-1; n<=1; n++) {
                    numAlive += isAlive(i+m, j+n);
                }
            }
            numAlive -= isAlive(i,j); // don't count center

            // game logic
            if (numAlive == 2) { // do nothing
                temp[i][j] = state[i][j];
            } else if (numAlive == 3) { // make alive
                temp[i][j] = 1;
            } else { // make dead
                temp[i][j] = 0;
            }
        }
    }

    // apply new state to state
    for (let i=0; i<gridheight; i++) {
        for (let j=0; j<gridwidth; j++) {
            state[i][j] = temp[i][j];

            // apply to viz
            let $c = $(`#cell_${i}_${j}`);
            if (state[i][j] == 1) {
                $c.addClass('active');
            } else {
                $c.removeClass('active');
            }
        }
    }

    // update state counter
    numSteps++;
    $('#stepcounter').html(`Step: ${numSteps}`);

    // take another step
    setTimeout( () => {
        step();
    }, 100);
}

All that’s left is to call these two functions:

// wait to run until DOM is ready
$(document).ready(function() {
    initGrid();
    step();
})

And we’re off!

Bells and whistles

There are seemingly endless ways we can take this basic structure and jazz it up. For a few basic UI ideas:

 <div class="button" id="btn-run">Run</div>
 <div class="button" id="btn-pause">Pause</div>

then style it with css:

 .button {
    width: 100px;
    text-align: center;
    margin: 2px;
    padding: 2px;
    border: 1px solid black;
    border-radius: 10px;
}

and give it functionality on click with JS:

// maintain a global to track if we're running or not
let isRunning = false;

// RUN button
$('#btn_run').click(function(e) {
    if (isRunning) {
        return;  // if we're already running, do nothing
    }

    // need to set this outside the step(), which is called internally
    isRunning = true;
    step();
});

// PAUSE button
$('#btn_pause').click(function(e) {
    isRunning = false;
});
function initGrid() {
    ...
    // add this line after you define $cell
    // this allows us to select a specific cell later
    $cell.attr('id', `cell_${i}_${j}`);
    ...
}

function random(p) {
    // p needs to be a number between 0 and 1, 
    // to set the amount of randomness
    
    // stop the game
    isRunning = false;

    // clear all cells
    state = Array(gridheight).fill(0).map(x => Array(gridwidth).fill(0));
    $('.cell').removeClass('active');

    // make proportion `p` of cells active
    for (let i=0; i<gridheight; i++) {
        for (let j=0; j<gridwidth; j++) {
            if (Math.random() < p) {
                $(`#cell_${i}_${j}`).addClass('active');
                state[i][j] = 1;
            }
        }
    }
}

More interesting mods

More interesting than UI/UX fiddling, I think, is modifying the structure of the game. Here’s a version of the game on a hexagonal grid, which is a slightly more daunting coding challenge. There’s tons of these different variations on the rules, like different colors of cells, 3-dimensions, or constrained grids.

That’s all for now! Hope you’ve enjoyed, let me know your thoughts.