Writing Conway's Game of Life on JavaScript
Tags: webdev, javascript, programming, tutorial
Published: 2023-12-25 15:44:06

Hi there 👋🏼,

In this post I'm going to share with you how I created the Game of Life using JavaScript (again).

Two years ago I wrote my initial version of Game of Life. However, in this new iteration, I aim to introduce improvements based on the knowledge and experience I have gained since then.

To give you a glimpse into my previous work, you can check out [my earlier article on Conway's Game of Life](https://dev.to/vladimirschneider/conway-s-game-of-life-on-javascript-212o).

In addition, I will guide you through the process of recreating this fascinating game and explain how I approached it.

I hope you'll find this article helpful and inspiring. Let's embark on an exciting journey into the captivating realm of the Game of Life!

## Conway's Game of Life

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7pvmltfngmjm495m71ei.png)

> The Game of Life devised British mathematician John Horton Conways in 1970. It is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input. One interacts with the Game of Life by creating an initial configuration and observing how it evolves
> 
> [read more on wiki](https://en.m.wikipedia.org/wiki/Conway's_Game_of_Life)

The game follows four rules that govern its progression. Let's delve into the rules:

1. Any live cell with fewer than two live neighbours dies, as if by underpopulation.
2. Any live cell with two or three live neighbours lives on to the next generation.
3. Any live cell with more than three live neighbours dies, as if by overpopulation.
4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

These rules dictate the evolution of the game and determine the fate of each cell in each generation.

For this challenge I'm going to use a canvas for drawing board and cells. To process, please ensure that you have a canvas element on your web page.

```html
<canvas id="game-board"></canvas>
```

If you nothing know about Canvas API for continue reading this article I recommend to study this [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API). In short:

> The Canvas API provides a means for drawing graphics via JavaScript and the HTML &lt;canvas&gt; element
> 
> [read more on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)

I decided to use ES6 modules and vanilla JavaScript for this game project. I made a conscious choice to utilize ES6 classes instead of functions this time around.

However, feel free to explore ES6 modules or any existing boilerplate that suits your needs. You could even opt TypeScript for writing the project to gain a different experience or prefer functions over classes.

To use `export` and `import` keywords in your JavaScript files as ES6 modules you need to include them in your HTML document just like regular JavaScript files. However, there is one key difference. You should use the `type` attribute with a value `module` in the HTML attribute.

```html
<canvas id="game-board"></canvas>

<!-- do this for each module -->
<script src="./main.js" type="module"></script>
```

As in the previous article, I will begin by writing the code with the definitions of constants. These constants will be used to specify the size of the cell, the width and height of the game board, as well as the colors for the cell and the board.

```javascript
export const CELL_SIZE = 10

export const BOARD_WIDTH = document.documentElement.offsetWidth
export const BOARD_HEIGHT = document.documentElement.offsetHeight

export const CELL_COLOR = "rgb(0, 255, 0)"
export const BOARD_COLOR = "rgb(0, 0, 0)"
```

By utilizing constants I can easily configure my game, as you already aware.

In the previous article, I followed a different approach compared to this one. I described the methods of one class and then proceeded to another class to do the same. However, in this article, I will be guiding you through the process of describing and evolving the game together.

The first step is to obtain the `canvas` element. In the `main.js` you can retrieve the `canvas`.

```javascript
const canvas = document.getElementById("game-board")
```

Let's consider the game processes. Based on the game rules you need to have an initial state of the game. To achieve this, you will require a game board and cells. In the `main.js` file you can initialize and launch the game by following these steps: draw background of the game board, initialize cells and finally launch the game.

How could you guess I was referring to three classes and their methods. Let's unveil the details! The following classes are essential for our game:

- `Game` class. This class handles the drawing and management of cells and the game board.
- `Board` class. With this class, we can receive game parameters and draw the game board
- `Cell` class. This class is responsible for drawing current and next generation of cells.

To start the game, all we need is an instance of the `Game` class, and we can initialize it whenever we're ready.

Since we already have the canvas element, let's take care about of the rest.

```javascript
import { Game } from "./modules/game.mjs"

const canvas = document.getElementById("game-board")

const game = new Game(canvas)

game.initialize()
```

This sets the stage for the most challenging part of our adventure. I hope you continue this journey with me to the end.


## Intro

For a wile I pondered where to begin I've settled on describing the `Board` class first. Not only because this class is simpler, but also because it forms the fundamental layer of our game.

## class Board

I use private properties for constants within classes that are exclusively used within those classes. You will see this in each of them.

In brief about `Bord` class:
– it features a method for drawing the game board
– it includes getters for board size and canvas context

We've already defined constants for cells size and board color, utilizing private properties. In the `drawBackground` method we simply draw the background using the `width` and `height` of the context along with the board color from the private property.

For the size getter we return the number of cells at the `x` and `y` coordinates and the cell size. This is achieved by dividing the board sizes by cell size from the constant.

Next, for the context getter we should just return canvas context, nothing more.

Now, let's lay our cards on the table!

```javascript
import {
    CELL_SIZE,
    BOARD_COLOR
} from './constants.mjs'

export class Board {
    #cellSize = CELL_SIZE
    #backgroundColor = BOARD_COLOR

    constructor(canvas) {
        this.canvas = canvas
        this.ctx = this.canvas.getContext("2d")
    }

    drawBackground() {
        const { width, height } = this.canvas

        this.ctx.fillStyle = this.#backgroundColor
        this.ctx.fillRect(0, 0, width, height)
    }

    get size() {
        const { width, height } = this.canvas

        return {
            cellNumberX: Math.ceil(width / this.#cellSize),
            cellNumberY: Math.ceil(height / this.#cellSize),
            cellSize: this.#cellSize,
        }
    }

    get context() {
        return this.ctx
    }
}
```

## class Cell

The `Cell` class can only draws the cell but also determines whether the cell is alive or death. In fact, this class encapsulates all rules of the game in only one little method!

In brief about `Cell` class:
– it features a method to decide whether the cell is alive or death
– it features a method to draw the cell depending of its state
– it comprises a private `position` getter and a public `alive` getter
– it includes setters to set the `alive` and `neighbor` count

The key details is that the `x` and `y` coordinates stored in the `Game` class. The `Cell` class receives the context and cell size from the `Board` class instance getter.

Every game iteration we launch `nextGeneration` method to determine whether the cell alive or dead and the `drawCells` method to draw the cell.

The `position` getter just holds an array to destructure all these values for the `fillRect` context method used in cell drawing.

The setter and getter for the `alive` simply set and get the private property, just like the `neighbors` setter.


```javascript
export class Cell {
    #alive = true
    #neighbors = 0

    constructor(ctx, x, y, cellSize) {
        this.ctx = ctx

        this.x = x
        this.y = y
        this.cellSize = cellSize
    }

    nextGeneration() {
        if (!this.#alive && this.#neighbors === 3) {
            this.#alive = true
        } else {
            this.#alive = this.#alive && (this.#neighbors === 2 || this.#neighbors === 3)
        }
    }

    draw() {
        if (this.#alive) {
            this.ctx.fillStyle = CELL_COLOR
            this.ctx.fillRect(...this.#position)
        }
    }

    get #position() {
        return [
            this.x * this.cellSize,
            this.y * this.cellSize,
            this.cellSize,
            this.cellSize
        ]
    }

    set alive(alive) {
        this.#alive = alive
    }

    get alive() {
        return this.#alive
    }

    set neighbors(neighbors) {
        this.#neighbors = neighbors
    }
}
```


## class Game

So, now, let's bring it all together.

The first step I suggest is to combine all our classes and classes and their methods without writing introducing new logic.

What do we have on our hands?

We already know that knowledge about the cells is stores in the `Game` class; let's use a private property for it. In fact, it will be the only one private property in this class.

First, initialize everything. We should obtain a board instance to draw it and use the board's getters `size` and `context`. We will be use the class constructor for this, also setting the canvas size.

```javascript
export class Game {
    #cells = []

    constructor(canvas) {
        this.canvas = canvas
        this.board = new Board(this.canvas)

        this.canvas.width = BOARD_WIDTH
        this.canvas.height = BOARD_HEIGHT
    }
}
```

As a quick remember, the `Game` class should have the `initialize` method. Just do it and let's move on.


```javascript
export class Game {
    // ...

    initialize = () => {}
}
```

At the point, we've successfully implemented numerous methods for drawing and managing our cells. However, a crucial piece missing – we don't have cells yet! Let's address this gap!

The `initializeCells` method will iterate over every cell, push it to our `#cells`,  set the cell's `alive` status randomly and then `draw` it!

In the board we already have a `size` getter to obtain `x` and `y` dimensions of this board. We will use it for iteration.

```javascript
export class Game {
    // ...

    initializeCells = () => {
        for (let i = 0; i < this.board.size.cellNumberX; i++) {
            this.#cells[i] = []

            for (let j = 0; j < this.board.size.cellNumberY; j++) {
                this.#cells[i][j] = new Cell(this.board.context, i, j, this.board.size.cellSize)
                this.#cells[i][j].alive = Math.random() > 0.8
                this.#cells[i][j].draw()
            }
        }
    }
}
```

Now that we have the cells and can to operate with them, in the `updateCells` method, we will iterate over each cell twice. The first iteration is to calculate and set all neighbors of the cell, and the second iteration to draw new state.

Each cell already has a setter `neighbors` for the first iteration and the methods we need for the second iteration: `nextGeneration` and `draw` too.

Calculation how many neighbors a cell has may seem easy, but it's a signification piece of logic, so I've moved it to another method called `updateCellNeighbors`.

```javascript
export class Game {
    // ...

    updateCells = () => {
        for (let i = 0; i < this.board.size.cellNumberX; i++) {
            for (let j = 0; j < this.board.size.cellNumberY; j++) {
                this.updateCellNeighbors(i, j);
            }
        }

        for (let i = 0; i < this.board.size.cellNumberX; i++) {
            for (let j = 0; j < this.board.size.cellNumberY; j++) {
                this.#cells[i][j].nextGeneration()
                this.#cells[i][j].draw()
            }
        }
    }

    updateCellNeighbors = (x, y) => {
}
```

Up until now, we've defined methods, setters and getters but nothing was has been drawn yet. We don't have much left to complete our game. Now I want to describe last piece of our puzzle to be ready to write final method `updateCellNeighbors`.

The `this.board.drawBackground` and `updateCells` are called in another method called `launch` and calling it again using `requestAnimationFrame`.

```javascript
export class Game {
    // ...

    initialize = () => {
        this.initializeCells()
        this.launch()
    }

    // ...

    launch = () => {
        this.board.drawBackground()

        this.updateCells()

        requestAnimationFrame(this.launch)
    }

    // ...
}
```

## updateCellNeighbors

Let's take a moment to summarize and outline what else needs to be written. The last thing we need to be write is `updateCellNeighbors`.

First, we get a map of all neighbors of the cell be `x` and `y`.

```javascript
export class Game {
    // ...

    updateCellNeighbors = (x, y) => {
        const neighborCoords = [
            [x, y + 1],
            [x, y - 1],
            [x + 1, y],
            [x - 1, y],
            [x + 1, y + 1],
            [x - 1, y - 1],
            [x + 1, y - 1],
            [x - 1, y + 1]
        ]
    }
}
```

Some coordinates might be out of the game board bounds. We can easily check this by verifying if the `x` coordinate less than 0 or if the `x` coordinate is more than this.board.size.cellNumberX. For the `y` is the same.

```javascript
export class Game {
    // ...

    updateCellNeighbors = (x, y) => {
        // ...

        for (const coords of neighborCoords) {
            let [xCord, yCord] = coords;

            const xOutOfBounds = xCord < 0 || xCord >= this.board.size.cellNumberX
            const yOutOfBounds = yCord < 0 || yCord >= this.board.size.cellNumberY
        }
    }
}
```

The last thing we need to do to finish is to choose the neighbor cell, check if the cell is alive or dead, and count it. After this, set neighbors using cell's `neighbors` setter.

```javascript
export class Game {
    // ...

    updateCellNeighbors = (x, y) => {
        let aliveNeighborsCount = 0

        // ...

        for (const coords of neighborCoords) {
            // ...

            const wrappedX = xOutOfBounds ? (xCord + this.board.size.cellNumberX) % this.board.size.cellNumberX : xCord
            const wrappedY = yOutOfBounds ? (yCord + this.board.size.cellNumberY) % this.board.size.cellNumberY : yCord

            if (this.#cells[wrappedX]?.[wrappedY]?.alive) {
                aliveNeighborsCount++
            }
        }

        this.#cells[x][y].neighbors = aliveNeighborsCount
    }
}
```

Congratulations! It's the end of our journey. You can run the game, make changes, improvements – just feel free with it.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jj8g07ple6z2rjm3j2wf.png)

[Repo](https://github.com/vladimirschneider/game-of-life)

[Demo #1](https://vladimirschneider.github.io/game-of-life/simple-app/)

[Demo #2](https://vladimirschneider.github.io/game-of-life/)


Thank you 💛 dear reader