Conway's Game of Life 🎮 on JavaScript
Tags: javascript, gamedev, programming, webdev
Published: 2021-06-07 09:43:14

Hi, last night I was watching video on YouTube with title *"Math Has a Fatal Flaw"*. I saw Conway's Game of Life there.

I think every programmer should write the game, but for 4 years of programming experience I have never written this 😔.

The Post about How did I write Conway's Game of Life on JavaScript.

Last night I thought that I cannot write this game and It was sad 😵‍💫 but I was able.

For start I define constants.

```javascript
const START_NUMBERS_OF_CELL = 2000
const CELL_SIZE = 10

const LIFE_WIDTH = document.documentElement.offsetWidth
const LIFE_HEIGHT = document.documentElement.offsetHeight

const GAME_BOARD_BACKGROUND_COLOR = "#000000";
```

I am using the screen size of the user's device for the size of the game board. I defined `START_NUMBERS_OF_CELL`, `CELL_SIZE` and `GAME_BOARD_BACKGROUND_COLOR` too so I can to configuration my game.


## Cell's Class

I am using ECMAScript classes in my JavaScript code and canvas for drawing game for users.

I wanna start for Cell's class because this class is very simple class.

In order to draw Cell on canvas I need canvas context and `x` and `y` coordinates

```javascript
class Cell {
    //...

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

        this.x = x
        this.y = y
    }

    //...
}

```

I know that I should kill cell If cell has not 2 or 3 neighbors so I need draw and dead methods.

```javascript
class Cell {
    //...

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

    draw(color = "#ffffff") {
        this.ctx.fillStyle = color
        this.ctx.fillRect(...this.position)
    }

    dead() {
        this.ctx.fillStyle = GAME_BOARD_BACKGROUND_COLOR
        this.ctx.fillRect(...this.position)
    }

    //...
}
```

I defined neighbors variable like privet variable and did setter and getter methods for work with it.

```javascript
class Cell {
    #neighbors = 0

    //...

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

    get neighbors() {
        return this.#neighbors
    }
}
```


## Life's Class

Let's start Life class.

In Life class' constructor I passed `HTMLCanvasElement` and define canvas context, draw background and define array of cell. I have array of arrays so that I filled `this.cells` a empty items.

```javascript
class Life {
    constructor(canvas) {
        this.canvas = canvas

        this.canvasWidth = LIFE_WIDTH / CELL_SIZE
        this.canvasHeight = LIFE_HEIGHT / CELL_SIZE

        this.canvas.width = LIFE_WIDTH
        this.canvas.height = LIFE_HEIGHT

        this.ctx = this.canvas.getContext("2d")

        this.ctx.fillStyle = GAME_BOARD_BACKGROUND_COLOR
        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)

        this.cells = []

        for (let i = 0; i < this.canvasWidth; i++) {
            this.cells[i] = []

            for (let j = 0; j < this.canvasHeight; j++) {
                this.cells[i][j] = undefined
            }
        }

        //...
    }

    //...
}
```


After that I did cycle from 0 to our START_NUMBERS_OF_CELL constant so that I fill of cells the game board. I generate random random position for cells and check If the cell is not in `this.cells` I create new a cell and drawing it. After that I need run the game. I am using `requestAnimationFrame`.

```javascript
class Life {
    constructor(canvas) {
        //...

        for (let i = 0; i < START_NUMBERS_OF_CELL; i++) {
            const cellXPosition = Math.floor(Math.random() * this.canvasWidth)
            const cellYPosition = Math.floor(Math.random() * this.canvasHeight)

            if (!this.cells[cellXPosition][cellYPosition]) {
                this.cells[cellXPosition][cellYPosition] = new Cell(this.ctx, cellXPosition, cellYPosition, false)

                this.cells[cellXPosition][cellYPosition].draw()
            }
        }

        this.deadWave = this.deadWave.bind(this)

        requestAnimationFrame(this.deadWave)
    }

    deadWave() {
        //...
    }
}
```

After initialization the game board I have left write [rules of the game](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Rules) in `deadWave` method:

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.

In `deadWave` method I have cycle for check cell neighbor and boring new cells using rules and cycle for dead cells

Cycles start like that

```javascript
//...

deadWave() {
    for (let i = 0; i < this.canvasWidth; i++) {
        for (let j = 0; j < this.canvasHeight; j++) {
            //...
        }
    }

    for (let i = 0; i < this.canvasWidth; i++) {
        for (let j = 0; j < this.canvasHeight; j++) {
            //...
        }
    }
}

//...
```

In first cycle in start of iteration I check that cell by `i, j` address is exist and if it is I set `neighbor` of cell is 0.

Next I check every neighbor cell (in total is 8) and If neighbor cell is exist I count it.

In the end of cycle I check that cell is exist and if it is I set count of neighbors to cell If cell is not exist I do boring of cell and set neighbor is 2 because next cycle If cell have not neighbor cell will be dead.

```javascript
//...

deadWave() {
    for (let i = 0; i < this.canvasWidth; i++) {
        for (let j = 0; j < this.canvasHeight; j++) {
            if (this.cells[i][j]) {
                this.cells[i][j].neighbors = 0
            }

            let countAroundCells = 0

            //...

            if (this.cells[i][j]) {
                this.cells[i][j].neighbors = countAroundCells
            } else if (countAroundCells === 3) {
                this.cells[i][j] = new Cell(this.ctx, i, j)

                this.cells[i][j].draw(this.randomColor)
            }
        }
    }

    //...
}

//...
```

Next cycle if a cell is exist I check that the cell is newborn and if is it I set newborn `false` value. If the cell is not newborn I kill the cell.

```javascript
//...

deadWave() {
    //...

    for (let i = 0; i < this.canvasWidth; i++) {
        for (let j = 0; j < this.canvasHeight; j++) {
            if (this.cells[i][j]) {
                if (this.cells[i][j].newborn) {
                    this.cells[i][j].newborn = false;
                } else if (this.cells[i][j].neighbors !== 2 && this.cells[i][j].neighbors !== 3) {
                    this.cells[i][j].dead()

                    this.cells[i][j] = undefined
                }
            }
        }
    }
}

//...
```

Finally I should call deadWave method again and again so that I call `requestAnimationFrame(this.deadWave)` in the end of the method.

Thank you for reading the post ☺️

Full code you can see in [GitHub repository](https://github.com/vladimirschneider/the-game-of-life) or live demo [right now](https://vladimirschneider.github.io/the-game-of-life/)