joshbright.com

Game of Life: Part 1

Published on: Tuesday, January 23, 2024 at 4:00 PM PST

Written by Josh Bright

What is the Game of Life?

The Game of Life is a “game” that lets you visualize how simple rules can end in complex behavior. It uses cellular automata, which is a system that uses cells and steps to update those cells using rules in steps. This may not be the textbook definition but it is how I think of it. This was created back in the 1970 by a guy named John Conway. I find it interesting to watch how the game proceeds and to see how different patterns emerge.

The main rules that run the game of life are pretty simple. They are applied for each step of the simulation. The rules below are applied to each cell:

  1. If a cell is alive, and has less than two neighbors, it will die.
  2. If a cell is alive, and has two or three neighbors, it stays alive.
  3. If a cell is alive, and has more than three neighbors, it dies.
  4. If a cell is dead, and has three live neighbors, it will become alive.

Let’s Build

Here is a preview of what we will end up building. The preview lets us load in pre-defined shapes, randomizing the grid, as well as resizing it. Go ahead and play with it a bit! I would probably try clicking the “Generate Random Tiles” button and then click “Run”. You can also load in some pre-defined shapes that show different aspects of how the simple rules will end in interesting results.

Tiles Wide: Tiles High: Cell Size:

This example uses the HTML Canvas and plain javascript to work, so there won’t be any special libraries to learn at all. This first post will build our foundation for all this.

First off, we need to start with an HTML file that contains a canvas element.

<html>
    <head>
        <title>Game of Life</title>
    </head>
    <body>
        <h1>Game of Life</h1>
        <canvas id="canvas"></canvas>
        <script>
            // our code is going to go here
        </script>
    </body>
</html>

Save that file and bring it into your browser, and you should see the text, “Game of Life”.

We next need to use some javascript to give us a way to interact with that canvas element. All of this code will be going in between the script tags.

let canvas = document.querySelector("#canvas"); //1
let ctx = canvas.getContext("2d"); //2

Line 1: Here we are just selecting the canvas element itself.

Line 2: Next, we are creating a variable to interact with the context of the canvas element. This is used to draw and manipulate the canvas itself. You can think of this as our “control panel” to make the canvas do its thing.

Next, lets draw a box on the canvas to be sure things are working correctly.

let canvas = document.querySelector("#canvas");
let ctx = canvas.getContext("2d");

ctx.fillStyle = "red";  //3
ctx.fillRect(10, 10, 20, 30);  //4

This should show a red rectangle on the screen, like so:

Line 3: Here we are telling the canvas context that when we draw something that is filled in, it sohuld color that fill as red.

Line 4: This line runs a command that draws a rectangle. The first two parameters to this call are the x and y positions that the rectangle should be drawn at, and then the next two are the width and height.

When drawing with the canvas, our 0, 0 position is going to be in the upper left corner of the canvas element.

Draw a Grid

Next, lets draw a checkerboard type of pattern that will give us an idea on how we will be drawing these cells. We need to figure out how big our cells are going to be, as well as what color the alive and dead cells are. For now, we will have 20 pixel sized squares for the cells, our dead cells will be black and the alive cells will be white. We also need to figure out how many rows and columns we will be drawing. That information will give us enough information to know how big our canvas needs to be, so that we can fit all of these cells.

let canvas = document.querySelector("#canvas");
let ctx = canvas.getContext("2d");

// Color information
let aliveColor = "white";
let deadColor = "black";

// Size information
let cellSize = 20;
let cellsWide = 10;
let cellsHigh = 10;

// Our canvas size
let canvasWidth = cellSize * cellsWide;
let canvasHeight = cellSize * cellsHigh;

With those variables set, we have enough to start drawing our grid.

// Set the size of our canvas
canvas.width = canvasWidth;
canvas.height = canvasHeight;

// Our first cell will be alive
let cellStatus = "alive";

// Draw the grid
for (let row = 0; row < cellsHigh; row++) {
    for (let col = 0; col < cellsWide; cell++) {
        // Figure out what color our cell is going to be
        if (cellStatus == "alive") {
            // First we set the fill style to the current color
            ctx.fillStyle = aliveColor;

            // We then set the cell status for our next cell
            cellStatus = "dead";
        } else {
            // The same thing here but for the other situation
            // when we have a dead cell
            ctx.fillStyle = deadColor;
            cellStatus = "alive";
        }

        // Draw the cell
        ctx.fillRect(
            col * cellSize,// x
            row * cellSize,// y
            cellSize,// width
            cellSize,// height
        )
    }

    // After each row, we need to update our cell status
    // Otherwise we will get stripes instead of a checkerboard
    if (cellStatus == "alive") {
        cellStatus = "dead";
    } else {
        cellStatus = "alive";
    }
}

This should result in a checkerboard looking canvas

Wrap up

We now have an idea on what the Game of Life is, and how the canvas works. We can draw a grid of cells on our canvas, and are prepared to start adding the interactive bits. This will come in the next part of this series. Below is the full example that we built in this part.

<html>
    <head>
        <title>Game of Life</title>
    </head>
    <body>
        <h1>Game of Life</h1>
        <canvas id="canvas"></canvas>
        <script>
            let canvas = document.querySelector("#canvas");
            let ctx = canvas.getContext("2d");

            // Color information
            let aliveColor = "white";
            let deadColor = "black";

            // Size information
            let cellSize = 20;
            let cellsWide = 10;
            let cellsHigh = 10;

            // Our canvas size
            let canvasWidth = cellSize * cellsWide;
            let canvasHeight = cellSize * cellsHigh;

            // Set the size of our canvas
            canvas.width = canvasWidth;
            canvas.height = canvasHeight;

            // Our first cell will be alive
            let cellStatus = "alive";

            // Draw the grid
            for (let row = 0; row < cellsHigh; row++) {
                for (let col = 0; col < cellsWide; col++) {
                    // Figure out what color our cell is going to be
                    if (cellStatus == "alive") {
                        // First we set the fill style to the current color
                        ctx.fillStyle = aliveColor;

                        // We then set the cell status for our next cell
                        cellStatus = "dead";
                    } else {
                        // The same thing here but for the other situation
                        // when we have a dead cell
                        ctx.fillStyle = deadColor;
                        cellStatus = "alive";
                    }

                    // Draw the cell
                    ctx.fillRect(
                        col * cellSize,// x
                        row * cellSize,// y
                        cellSize,// width
                        cellSize,// height
                    )
                }

                // After each row, we need to update our cell status
                // Otherwise we will get stripes instead of a checkerboard
                if (cellStatus == "alive") {
                    cellStatus = "dead";
                } else {
                    cellStatus = "alive";
                }
            }
        </script>
    </body>
</html>