How I built Conway’s Game of Life in JavaScript

EDIT (MAY 2016): Since writing this post in July 2015 I noticed it’s started to get a bit of traffic from Google. If you’re just interested in the end result, here’s a JSFiddle of my final version of Conway’s Life.  That version has cleaner and more slightly more optimised code than shown here. Original post below:

I’ve recently been focusing on strengthening my JavaScript skills. I used a few JS snippets when I built Influential Blogs a couple of years ago, but it was mostly code I cut and paste from the web to solve specific challenges – the bulk of the site was built on PHP.

The best way to learn a language is to build stuff, so the first challenge I set myself was to build a version of Conway’s Game of Life. It’s a simple concept; the game consists of a grid of cells, each of which can be alive or dead. For every cycle of the game, the cells can be turned on or off based on the following rules:

  • If a dead cell has exactly three live neighbours, it comes to life
  • If a live cell has less than two live neighbours, it dies
  • If a live cell has more than three live neighbours, it dies
  • If a live cell has two or three live neighbours, it continues living

By repeating the cycle over and over, these simple rules create interesting, often unpredictable patterns. I was fascinated by the idea as a kid and wrote a few versions of it in Basic on my ZX Spectrum, so it seemed like a good place to start with JavaScript.

Step 1 – Creating the grid

The grid of cells needs to be stored somewhere, so my first job was to create a two dimensional array. This was an instant stumbling block because I learned JavaSript does not support multi-dimensional arrays. However, I learned I could solve the problem easily because each element of an array can be any type of variable, including an array so, for example, I could create an array of 100 elements, and each of those would contain another 100 element array, which would give us a 100 by 100 cell grid to work with.

function createArray(rows) { //creates a 2 dimensional array of required height

var arr = [];

for (var i = 0; i < rows; i++) {

arr[i] = [];

}

return arr;

}

This function returns an array with n elements and places an empty array in each of them using a FOR loop. We don’t need to worry about specifying the number of elements in those sub-arrays, because JavaScript lets you dynamically add new elements to an array. This means we can simply add as many variables as we need when we populate the grid.

We can now create our grid by calling this function and assigning its output to a variable:

var theGrid = createArray(gridWidth);

gridWidth is a variable defined earlier in the code simply stating how big we want our grid to be – I wanted this to be easy to change because I didn’t know at this stage how quickly the game would run with large grids.

Step 2 – Populating the grid

For the sake of simplicity I wanted the starting game state to be random. So I wrote this function to randomly populate the grid array with ones and zeros, live or dead cells. Since the theGrid is a global variable (more on this decision late), we don’t need to pass anything to this function or return anything from it, we just call it directly after we have created the array.

To make this work, I had to learn how to do random numbers in JavaScript. Math.random() returns a floating point number between 0 and 1, so I poked around on StackExchange to learn how to convert that into the nice clean 1 or 0 that I wanted to fill each cell with.

function fillRandom() { //fill the grid randomly

for (var j = 0; j < gridHeight; j++) { //iterate through rows

for (var k = 0; k < gridWidth; k++) { //iterate through columns

var rawRandom = Math.random(); //get a raw random number

var improvedNum = (rawRandom * 2); //convert it to an int

var randomBinary = Math.floor(improvedNum);

if (randomBinary === 1) {

theGrid[j][k] = 1;

} else {

theGrid[j][k] = 0;

}

}

}

}

Step 3 – Drawing the grid on screen

At this stage I had only really learned core JavaScript and didn’t know anything about Canvas, other than I should probably use it for any kind of graphical output. I needed to write a function to draw each grid cell in the array as a pixel on a Canvas, so I asked the internet how to draw a single pixel on a Canvas, and then cannibalised that code to work with my drawGrid function. I hard-coded the Canvas size to 400 by 400, because I didn’t envisage using a grid larger than that and I could use a smaller grid without changing the Canvas dimensions.

function drawGrid() { //draw the contents of the grid onto a canvas

var c = document.getElementById(“myCanvas”);

var ctx = c.getContext(“2d”);

ctx.clearRect(0, 0, 400, 400); //this should clear the canvas ahead of each redraw

for (var j = 1; j < gridHeight; j++) { //iterate through rows

for (var k = 1; k < gridWidth; k++) { //iterate through columns

if (theGrid[j][k] === 1) {

ctx.fillStyle = “#FF0000”;

ctx.fillRect(j, k, 1, 1);

}

}

}

}

Step 4 – Update the grid

Now I’ve created a grid, randomly populated with living and dead cells, and drawn that grid to the screen. The next thing I need to do is apply the game rules to the current grid state, switching the cells on or off as required to create the subsequent state. This is the main chunk of game-logic.

It was easy enough in theory: we simply look at each element in theGrid array, count up the number of live cells around it (each cell has a total of eight neighbours which could be dead or alive) and then use that total to decide whether the current cell lives or dies.

The problem this creates is that you cannot update theGrid array as you’re doing this, because if you change the state of a cell that means the you’ve changed the state of the grid before you’ve finished updating all of the other cells.

The way I tried to get around this was by reading the current state of the grid from the Canvas, so I could update theGrid array whilst referencing the as-yet unchanged game-grid on the screen. Simple, update the entire array, redraw the Canvas, repeat.

I learned that the Canvas method, getImageData(), would allow me to get the current state of each pixel in the grid, so I used that to calculate the total number of live neighbours for each cell. I thought I was being clever and efficient by using this approach – I was wrong. It turns out that reading from and writing to the Canvas is relatively slow, and even using this approach for a small 100×100 grid was clunky, with maybe one or two updates per second.

So I switched to the obvious alternative – using two arrays: theGrid holds the current state of the game board, and a second array mirrorGrid is used in the update function to store the new state of the board. Once the board has been completely updated, the contents of mirrorGrid are copied to theGrid ahead of the screen being updated. The performance was instantly and significantly improved – even on a much larger grid the update cycle ran at least ten times faster.

Here’s the function which performs this:

function updateGrid() { //perform one iteration of grid update

for (var j = 1; j < gridHeight – 1; j++) { //iterate through rows

for (var k = 1; k < gridWidth – 1; k++) { //iterate through columns

var totalCells = 0;

//add up the total values for the surrounding cells

totalCells += theGrid[j – 1][k – 1]; //top left

totalCells += theGrid[j – 1][k]; //top center

totalCells += theGrid[j – 1][k + 1]; //top right

totalCells += theGrid[j][k – 1]; //middle left

totalCells += theGrid[j][k + 1]; //middle right

totalCells += theGrid[j + 1][k – 1]; //bottom left

totalCells += theGrid[j + 1][k]; //bottom center

totalCells += theGrid[j + 1][k + 1]; //bottom right

//apply the rules to each cell

if (theGrid[j][k] === 0) {

switch (totalCells) {

case 3:

mirrorGrid[j][k] = 1; //if cell is dead and has 3 neighbours, switch it on

break;

default:

mirrorGrid[j][k] = 0; //otherwise leave it dead

}

} else if (theGrid[j][k] === 1) { //apply rules to living cell

switch (totalCells) {

case 0:

case 1:

mirrorGrid[j][k] = 0; //die of lonelines

break;

case 2:

case 3:

mirrorGrid[j][k] = 1; //carry on living

break;

case 4:

case 5:

case 6:

case 7:

case 8:

mirrorGrid[j][k] = 0; //die of overcrowding

break;

default:

mirrorGrid[j][k] = 0; //

}

}

}

}

//copy mirrorGrid to theGrid

for (var j = 0; j < gridHeight; j++) { //iterate through rows

for (var k = 0; k < gridWidth; k++) { //iterate through columns

theGrid[j][k] = mirrorGrid[j][k];

}

}

}
Step 5 – Creating the game loop

Now I’d written all of the main components of the game: create a grid, randomly populate it, draw the current grid state on the screen, update the grid by applying the rules to each cell. What I wanted to do next is run the updateGrid() and drawGrid() functions in some kind of loop so the board would keep updating for as long as I wanted.

At first I tried simply setting up a FOR loop and calling the two functions within it for a hundred or so iterations, but this didn’t work. The code would either hang completely or take a really long time to draw just one frame before hanging. I didn’t understand why the drawGrid() function wasn’t working every time I called it in the loop.

The internet rescued be again and I learned about requestAnimationFrame(), which is ideal for this kind of problem because it makes the browser update the screen whenever it’s called. So, the function to run the game loop infinitely is like so:

function tick() { //main loop

drawGrid();

updateGrid();

requestAnimationFrame(tick);

}

When function tick() is called, it first draws the current state of the grid, then updates the grid, then tells the browser to update the screen and calls itself again to repeat the loop. So the flow of the code goes like this:

  1. Create an array to store the grid
  2. Create a mirror array to use when updating the grid
  3. Fill the grid with random cells
  4. Draw the current grid state to the screen
  5. Apply the rules to each cell and update the grid
  6. Keep repeating the last two steps

You can see the complete code in action here: http://jsfiddle.net/xcs1y127/10/

Obviously there are lots of refinements that could be added, such as allowing the user to pause the game, reset the grid, draw their own patterns on the grid, etc, but at this stage all I really wanted to do is get a functioning version of Life up and running. I’ll add in all the window dressing as my next project.

Performance improvements

The first thing I was keen to do is find out if there were any ways in which I could make the code run faster, so I could use larger grids without sacrificing update speed. Switching to the two-array update approach I mentioned in step 4 really made a huge difference to performance, but I thought I could learn a few things about code optimisation by trying to squeeze any additional performance from my code.

The first thing I learned was that you can use console.time() and console.timeEnd() to find out how much time different parts of your code take to execute, so I tried using it on my functions while I experimented with potential optimisations.

I read that using locally scoped variables in functions is faster than global variables, so I tried making local copies of theGrid array in both the updateGrid() and drawGrid() functions, but this didn’t seem to make any discernible difference to the execution time of either.

I’ve also read an article about pre-rendering to an off-screen Canvas before writing to the on-screen one, as this apparently improves performance, although I haven’t yet tried it as my Canvas knowledge is still shaky.

I was hoping that there would be some easy performance tweaks I could make to the updateGrid() function, as this is clearly where most of the work is taking place, but I’ve not learned anything yet that will help with that.

(EDIT MAY 2016: I tried this off screen rendering method eventually, but it made almost no different to performance. At this stage, the only way I can think of to make a JS version of this game to show significant performance is to use a better algorithm for updating the grid. I recently read about the “List Life” approach, which speeds things up by only updating the parts of the grid which feature live cells, instead of checking every single cell on each iteration. It sounds interesting, but i haven’t had time to give it a try yet. Please let me know if you produce a JS version of this, I’d love to see it. )

3 thoughts on “How I built Conway’s Game of Life in JavaScript”

  1. Thank you! Very good tutorial! I think that, when filling the grid, you could use the Math.round() function, instead of the lengthy workaround, of multiplying by 2 and then getting the floor number. It removes a few lines of code, like this:
    var rawRandom = Math.random();
    var randomBinary = Math.round(rawRandom);
    theGrid[j][k] = randomBinary;

Leave a Reply

Your email address will not be published. Required fields are marked *