Spicy Yoghurt | Last updated: 25 January 2024 | JavaScript tutorial
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.
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:
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.
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.
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 download the final code here.
Spicy Yoghurt is here to help you create your own games and inspire you. Building the website takes a lot of spare time and coffee.
We supply the spare time and you can support us with a cup of coffee!
Leave a comment