Create Conway's Game of Life on the HTML5 canvas

Create Conway's Game of Life in JavaScript

Learn how to create Conway's Game of Life on the HTML5 canvas with JavaScript. Implement the game rules and check which cells will live or die each generation, to create your own simulation of life!

Conway's Game of Life

In 1970 the mathematician John Conway invented The Game of Life. It's not so much a game as you know them, it's more like a simulation (the more technical term would be cellular automaton). The game consists of a grid of cells, who can all be either dead or alive. Every step of the game, the grid will evolve and determine who will keep living and who will not.

Your browser does not support the HTML5 canvas tag.
Restart the game of life

The game requires no input, except for an initial state of the cells. All cells apply a set of rules to each step of the evolution to determine their fate. You can click the restart button to restart the game from the beginning. All cells will get a new random initial state.

The game rules

The rules a pretty simple. Every cell observes its surrounding neighbours to check whether its living area is underpopulated, overpopulated or suitable to live in. Each cell has 8 neighbours (except for the ones at the edge of the canvas).

  • A dead cell will come alive if exactly 3 neighbours are living
  • A living cell will stay alive if 2 or 3 neighbours are living
  • Cells with less than 2 neighbours will die of underpopulation, cells with 4 or more neighbours will die of overpopulation

You can play this game on paper and think of initial starting states that will result in interesting shapes or even moving objects. Real fanatics are even looking for so called guns and spaceships (or gliders), patterns that will emit cells or look like a moving object. Here's an example of a Gosper glider gun:

An animated GIF of a Gosper glider gun creating gliders

For this tutorial you're not going to use paper, but going to create The Game of Life with JavaScript on the HTML5 canvas and generate starting positions and new generations through code.

Define the appearance and behaviour of a single cell

Let's start by creating the framework for a single cell. It doesn't have to be smart, it's just a square on a grid that can be either alive or dead. Each state will be drawn with a different color.

When a new cell is created, its state of being is determined randomly. A cell has about 50% chance to start alive, but you can easily tweak that percentage to create different starting situations.

Start by creating a new Cell class and implement a draw() method. You can choose to draw a square or go for another shape, like the circles used in this tutorial.


            class Cell
            {
                // Set the default size and color for each cell
                static width = 10;
                static height = 10;
                static colorAlive = '#ff8080'
                static colorDead = '#303030'

                constructor (context, gridX, gridY)
                {
                    this.context = context;

                    // Store the position of this cell in the grid
                    this.gridX = gridX;
                    this.gridY = gridY;

                    // Make random cells alive
                    this.isAlive = Math.random() > 0.5;
                }

                draw() {
                    // Draw a square, let the state determine the color
                    this.context.fillStyle = this.isAlive ? Cell.colorAlive : Cell.colorDead;
                    this.context.fillRect(this.gridX * Cell.width, this.gridY * Cell.height, Cell.width, Cell.height);
                }
            }
        

Build a grid with a lot of cells

Once you have your cell framework ready, you can start to create a lot of cells. You're going to build a grid of 75x40 items. That's 3000 cells in total! The grid has the right measurements to completely fill the canvas element since each cell is 10x10 pixels and the canvas is 750x400. You can create a new grid of cells with a nested for loop:



        this.gameObjects = [];

        createGrid()
        {
            for (let y = 0; y < GameWorld.numRows; y++) {
                for (let x = 0; x < GameWorld.numColumns; x++) {
                    this.gameObjects.push(new Cell(this.context, x, y));
                }
            }
        }
        

Add a game loop to repeat all operations

The creation of new generations of cells doesn't happen just once, it has to happen for hundreds of times. You need a way to keep calculating the current situation. A loop would be perfect for this. In games, a core loop like this is called a game loop.

There are a lot of ways to create a game loop, but a really robust way of doing it is by using requestAnimationFrame(). We have a nice tutorial on game loops if you're looking for a more in-depth explanation. Here's an example:


        // Start your loop for the first time
        window.requestAnimationFrame(() => this.gameLoop());

        gameLoop() {
            // Check the surroundings of each cell and update the state
            this.updateCells();

            // Clear the screen
            this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

            // Draw all the gameobjects
            this.gameObjects.forEach((gameObject) => {
              gameObject.draw()
            });

            // The loop function has reached it's end, keep requesting new frames
            setTimeout( () => {
                window.requestAnimationFrame(() => this.gameLoop());
            }, 100) // The delay will make the game easier to follow
        }
        

In this example the loops starts by checking the surroundings of each cell, it then draws all cells to the canvas. It has a small delay build in before requesting the next frame, to make the evolution of the game easier to follow.

If you want to learn more about drawing graphics on the HTML5 canvas, check out this tutorial.

Check the surrounding cells

For every step in the evolution of this Game of Life, all cells need to check their neighbours to see if their area is under- or overpopulated. You can do this by looping over all cells and check how many neighbours are alive per cell.

Counting living neighbours

In the example above, the red cell is the one currently checking its environment. It should count all green cells as living and exclude itself, coming to a total of 3 living neighbours.

You'll need to build in some safety to skip checking beyond the edges of the grid. All cells beyond the grid count as being dead.


        static neighbourCellsMap = [
            [-1, -1], [0, -1], [1, -1],
            [-1, 0],           [1, 0],
            [-1, 1],  [0, 1],  [1, 1]
        ];
          
        updateCells() {
            // Loop over all cells and calculate the amount of alive neighbours
            for (let x = 0; x < GameWorld.numColumns; x++) {
                for (let y = 0; y < GameWorld.numRows; y++) {
                    this.getNumAliveNeighbours(x, y)
                }
            }
        }
        
        getNumAliveNeighbours(x, y) {
            // Check every surrounding cell, using the neighbourCellsMap
            return GameWorld.neighbourCellsMap.reduce((numNeighbours, [dx, dy]) => {
                return numNeighbours + (this.isCellAlive(x + dx, y + dy) ? 1 : 0);
            }, 0);
        }
        
        isCellAlive(x, y) {
            // Respect grid boundaries
            if (x < 0 || x >= GameWorld.numColumns || y < 0 || y >= GameWorld.numRows){
                return false;
            }

            return this.gameObjects[this.gridToIndex(x, y)].isAlive;
        }

        gridToIndex(x, y){
            return x + (y * GameWorld.numColumns);
        }
        

Implement the game rules

Now that you know how many cells are alive, you can implement the game rules. Basically, they boil down to these three conditional statements:

  • A cell with 2 living neighbours keeps its current state
  • A cell with 3 living neighbours always comes alive
  • Every other cell ends up dead

When you translate this into code, you'll end up with something like this:


                let centerCell = this.gameObjects[this.gridToIndex(x, y)];

                if (numAliveNeighbours == 2){
                    // Do nothing, don't change state
                }else if (numAliveNeighbours == 3){
                    // Make alive
                    centerCell.isAlive = true;
                }else{
                    // Make dead
                    centerCell.isAlive = false;
                }
        

What's going wrong here?

Ok, that's it! You should have a running example now. Let's check it out in the canvas below.

Your browser does not support the HTML5 canvas tag.

That's odd, it's not looking like the patterns you would expect coming from The Game of Life. The cells aren't behaving like they're supposed to, their evolution seems to be a bit too aggressive.

Calculate each generation simultaneously

The solution is to not change the current state of any cells when you're still checking their surroundings. Only change the state when all cells have been checked, so the whole new generation is created simultaneously. It's quite easy to implement by temporarily storing the new state of a cell and applying it after all cells are checked. Here's an example:



            if (numAliveNeighbours == 2){
                // Do nothing
                centerCell.isAliveNextFrame = centerCell.isAlive;
            }else if (numAliveNeighbours == 3){
                // Make alive
                centerCell.isAliveNextFrame = true;
            }else{
                // Make dead
                centerCell.isAliveNextFrame = false;
            }

            // Apply the new state to all the cells at once
            this.gameObjects.forEach((gameObject) => {
                gameObject.isAlive = gameObject.isAliveNextFrame
            });
        

When you try to run you game again, the simulation will look a lot different. It should now resemble the example at the top of this page. Well done, you have your own Game of Life running!

Experiment with different variations

You can experiment a bit further and try different colors, shapes, or even game rules. A fun one is to keep count of how long a cell has been dead and assign a color to each level of decay, so you get a nice fade-out effect. Here's a quick example:

Conclusion

The Game of Life is a cellular automaton that can easily be implemented through code with JavaScript. The game rules decide which cell will live or die and this results in interesting patterns. With a game loop and just a few conditional statements you can make your HTML canvas come alive!

That's all for now, if you liked this tutorial or have any questions, please let us know in the comment section! You can

Leave a comment

(Login with Facebook to view the comment section)