DEV Community

loading...
Cover image for Conway's Game of Life ๐ŸŽฎ on JavaScript

Conway's Game of Life ๐ŸŽฎ on JavaScript

vladimirschneider
Hi, I am Frontend developer and I learn Software Engineering
ใƒป4 min read

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.

I know that this code is NOT a best practice, actually I think that this code is terrible. DO NOT WRITE like that.

Last night I thought that I cannot write this game and It was sad ๐Ÿ˜ตโ€๐Ÿ’ซ but I was able.

For start I define constants.

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";
Enter fullscreen mode Exit fullscreen mode

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

class Cell {
    //...

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

        this.x = x
        this.y = y
    }

    //...
}

Enter fullscreen mode Exit fullscreen mode

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

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)
    }

    //...
}
Enter fullscreen mode Exit fullscreen mode

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

class Cell {
    #neighbors = 0

    //...

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

    get neighbors() {
        return this.#neighbors
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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
            }
        }

        //...
    }

    //...
}
Enter fullscreen mode Exit fullscreen mode

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.

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() {
        //...
    }
}
Enter fullscreen mode Exit fullscreen mode

After initialization the game board I have left write rules of the game 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

//...

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++) {
            //...
        }
    }
}

//...
Enter fullscreen mode Exit fullscreen mode

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.

//...

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)
            }
        }
    }

    //...
}

//...
Enter fullscreen mode Exit fullscreen mode

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.

//...

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
                }
            }
        }
    }
}

//...
Enter fullscreen mode Exit fullscreen mode

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 or live demo right now

Discussion (2)

Collapse
link2twenty profile image
Andrew Bone

Very cool

Collapse
vladimirschneider profile image
vladimirschneider Author

Thank u ๐Ÿ˜Œ