Learning HTML, CSS and Javascript Vanilla - Reproducing the John Conway's Game of Life
Tags: javascript, braziliandevs, html, tutorial
Published: 2021-12-03 14:29:32

> 
Versão PT-BR [aqui.](https://dev.to/akadot_/praticando-html-css-e-javascript-vanilla-reproduzindo-o-jogo-da-vida-de-john-conway-2iog)

##  Introduction:

Hi there, are you looking for cool projects to improve your **Javascript**, **CSS** and **HTML** skills?

In this tutorial I will teach how you can do your own version of the Game of Life, a game idea developed by the british mathematic **John Conway**.

This game is a part of an tag called “cellular automata”,which according to wikipedia mean: _"simpler temporal evolution models capable of exhibiting complicated behavior"_

But don't worry about this complex explanation, we're basically going to make a game without players, almost like it's alive.

This is the final result, a field populated by blocks that change their state based on predefined rules.

![Exemplo de Estrutura do Jogo da Vida](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jda7yz161d5fg4d547av.gif)

This is my Github repository below, to help you:

https://github.com/akadot/game-of-life

Okay, let's do it.

## Construction:

To build this project, we will use a powerfull HTML resource called [**Canvas API**](https://developer.mozilla.org/pt-BR/docs/Web/API/Canvas_API), which allows shapes **2D** or **3D** forms using only **Vanilla Javascript**. But don't worry about this, everything is very simple, the Canvas API is a native HTML tag.

The first step is create the three files that we will use, starting by the **HTML** file:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Jogin da Vida</title>
  </head>
  <body>
    <canvas id="board"></canvas>

    <script src="game.js"></script>
  </body>
</html>

```

We will just use a `<canvas>` tag, with an **id**reference to use in our JS file.

Now we'll create a simple CSS file:

```css
* {
  padding: 0;
  margin: 0;
  outline: 0;
  box-sizing: border-box;
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background-color: #000000;
}

#board {
  border: 5px solid #5c3ec9;
  border-radius: 5px;
  background-color: #f8f8f2;
  box-shadow: 0px 0px 10px #5c3ec9;
}

```

Done, now we can open on our browser and...


![Canvas Vazio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jrv4e866zalgxr8b159.png)

Okay, I know, it's just an empty board, but I promise it will be very cool.

Now we need to set the Canvas properties, there are a lot of ways to do this, but I prefer to do everything inside the **Javascript** file.

## Logic:

Let's really get your hands on the code. First we need to reference the `<canvas>` tag id in our JS file, to define what context we will work (2D or 3D):

```javascript
const canvas = document.querySelector("#board");
const ctx = canvas.getContext("2d"); 
//ctx define o contexto do nosso canvas, no caso será 2D
```

Then, we set some constants that will help us along the code:

```javascript
const GRID_WIDTH = 500;             
const GRID_HEIGHT = 500;       
const RES = 5;                     
const COL = GRID_WIDTH / RES;    
const ROW = GRID_HEIGHT / RES;  
```

Now, we can use this constants to define the canvas height and width:

```javascript
canvas.width = GRID_WIDTH;
canvas.height = GRID_HEIGHT;
```

Done, now I promisse we will can see something. But to get sure, let's put an `addEventListener()` around the code, to wait our HTML content load before code runs:

```javascript
document.addEventListener("DOMContentLoaded", () => {
  const canvas = document.querySelector("#board");
  const ctx = canvas.getContext("2d");

  const GRID_WIDTH = 500;
  const GRID_HEIGHT = 500;
  const RES = 5;
  const COL = GRID_WIDTH / RES;
  const ROW = GRID_HEIGHT / RES;

  canvas.width = GRID_WIDTH;
  canvas.height = GRID_HEIGHT;
}
```

The next step is draw our blocks. They will be simples squares inside an 2D array, like a cell inside a Excel grid or an Google Sheets file.

We will create a function called **createGrid()**, that will receive the numbers of **rows** and **columns**. Then, the function will return a new empty array with the length equal the number of the columns and, for each position insite this array, it will be created a new array with the same number of the rows but filled filled with 0 or 1 randomly:

```javascript
function createGrid(cols, rows) {
    return new Array(cols)
      .fill(null)
      .map(() => new Array(rows)
                 .fill(null)
                 .map(() => Math.round(Math.random())));
}

let grid = createGrid(COL, ROW); //we will keep the array inside a variable "grid"
```

We can start to draw our blocks now, based on the cell values, where the numbers **1** will be filled and the numbers **0** will be blanked:

To do it, we need a new function called **drawGrid()**, that will receive our **grid**, our **rows** and **columns** and our **block resolution/size**:

```javascript
function drawGrid(grid, cols, rows, reslution) {
    ctx.clearRect(0, 0, cols, rows);
    for (let i = 0; i < cols; i++) {
      for (let j = 0; j < rows; j++) {
        const cell = grid[i][j];
        ctx.fillStyle = cell ? "#5c3ec9" : "#f8f8f2";
        ctx.fillRect(i * reslution, j * reslution, reslution, reslution);
      }
    }
  }
});
```

As you can see, to paint the cells fisrt we need to run the Canvas API native function **clearRect()**, that will clear our board before everything. It's receive on two first parameters the initial coordinates to start the cleaning, and on the last two parameters, we need to set the full size of our board, where the function will stop the cleaning.

Once that's done, let's do two repeat loops, to go through our entire array. For each loop, we will keep the current cell inside a constant called **cell** and, with a **ternary if**, we will check if the cell has a **0** or an **1**.

If the cell's value is **1**, we will apply a color **#5c3ec9**, using another Canvas API native property called **fillStyle**, else we just apply the same color of our background (remeber, in JS the value 1 means true/existing, and the value 0 means false/nonexistent).

On the next line, another native tag, but this time we will usa the function **fillRect()**, that will draw our square following 4 parameters:

- First: the X coordinate where the square inits (in this case, we will put our resolution * the current array position);
- Second: the Y coordinate where the square inits (in this case, we will put our resolution * the current array position again);
- Third: the square width (our resolution);
- Fourth: the square heigth (our resolution again).

Now we can draw our squares inside the canvas:

```javascript
drawGrid(grid, COL, ROW, RES);
```


![Canvas Desenhado com os Retângulos Aleatórios](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jo8z2vj0rx2qksmj4b1h.png)

##  Explaining the Game Rules

Before we proceed, we need to understand the rules proposed by John Conway, so that the game is actually "self-playing".

There are four simple rules to do this, that defines if a cell is **alive** (our purple/1 cells), or **dead** (our black/0 cells). The rules are:

- **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.


![Regras do Jogo da Vida de John Conway](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o6kefkya5x9wfz94hmku.png)

Following this rules, let's create our function to do this. It's will go through our entire array, apply the rules and generate a new array to be drawn by the **drawGrid()** function.

At each repetition of this cycle, we will consider that the new array is a new generation of cells that inherit the last generation conditions.

This function will be called **nexGen()** and, as a first step we will keep the last geeration in a constant.

```javascript
function nextGen(grid) {
    const nextGen = grid.map((arr) => [...arr]);
```

In case you don't already know, in the `[...arr]` excerpt we use the **SPREAD** operator, which was added to Javascript from version 6 and is intended to store a greater number of information at once, widely used with arrays and objects. You can also use the `.push()` or `.slice()` functions instead of the spread operator, there's no problem with that.

The next step is to start the loops, that will go through the array to apply the game rules. As we did above, we need to run through all the lines, using `grid.length` and then all the columns, using `grid[col].length` (the **col** parameter is just the name I gave the **for** control variable, but you can use the letters **i** and **j** as usual).

We'll take the opportunity to capture the initial cell in a constant and create a variable to count the number of living neighbor cells.

```javascript
 for (let col = 0; col < grid.length; col++) {
      for (let row = 0; row < grid[col].length; row++) {
        const currentCell = grid[col][row];
        let sumNeighbors = 0;
```


The next step is, for each cell, to go through all its **8 neighbors** and check whether they are alive or not.It might seem a little difficult to understand the code at first glance, but here's an explanation with screenshot:


![Ilustrando as Células Vizinhas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9sk0i6kv48r8fx389tcx.png)

Yes, I used Google Sheets for that, but the important thing is that our next loop will iterate through the values ​​between **-1** and **1**, finding the number of live neighbors.

```javascript
for (let i = -1; i < 2; i++) {
 for (let j = -1; j < 2; j++) {
   if (i === 0 && j === 0) {
     continue; 
   }
```

We put the condition `if (i === 0 && j === 0)`, because this is the position of the current cell, which we don't want to add to the number of neighbors.

The next section will deal with the "corners" of our field. Think about it like this, if a cell is pasted on the left side of our canvas, we won't be able to access the neighbors that are in a column before it, that is, the leftmost one, as they don't exist. So, we're going to add values ​​to the `sumNeighbors` variable only if its coordinates are within the bounds of the canvas.

```javascript
const x = col + i
const y = row + j;

if (x >= 0 && y >= 0 && x < COL && y < ROW) {
    const currentNeighbor = grid[col + i][row + j];
    sumNeighbors += currentNeighbor;
```

Once the conditions are satisfied, the `sumNeighbors` variable will receive its previous value, plus the value of the live cells, remembering that the dead cells here receive the value **zero**, which does not impact the sum.

Once that's done, we can apply the rules described by John Conway with a simple `if/else`:

```javascript
if (currentCell === 0 && sumNeighbors === 3) {
   nextGen[col][row] = 1;
} else if (currentCell === 1 && (sumNeighbors < 2 || sumNeighbors > 3)){
    nextGen[col][row] = 0;
}
```

Explaining, the first condition tests if the current cell is empty and if it has 3 neighbors, if it is true the next generation will receive in that same position the value 1 or alive.

The second condition gathers the other rules into one, testing whether the current cell is **live** and;if there are less than two neighbors next generation will receive **zero**, if there are more than 3 neighbors the next generation will also receive **zero**. 

Finally, just return the next generation `return nextGen;`, and the function will look like this:

```javascript

  function nextGen(grid) {
    const nextGen = grid.map((arr) => [...arr]); //make a copy of grid with spread operator

    for (let col = 0; col < grid.length; col++) {
      for (let row = 0; row < grid[col].length; row++) {
        const currentCell = grid[col][row];
        let sumNeighbors = 0; //to verify the total of neighbors

        //Verifying the 8 neigbours of current cell
        for (let i = -1; i < 2; i++) {
          for (let j = -1; j < 2; j++) {
            if (i === 0 && j === 0) {
              continue; // because this is the current cell's position
            }

            const x = col + i;
            const y = row + j;

            if (x >= 0 && y >= 0 && x < COL && y < ROW) {
              const currentNeighbor = grid[col + i][row + j];
              sumNeighbors += currentNeighbor;
            }
          }
        }

        //Aplying rules
        if (currentCell === 0 && sumNeighbors === 3) {
          nextGen[col][row] = 1;
        } else if (
          currentCell === 1 &&
          (sumNeighbors < 2 || sumNeighbors > 3)
        ) {
          nextGen[col][row] = 0;
        }
      }
    }
    return nextGen;
  }
```

By doing this, we are almost close to finishing our project, the next step is very simple, we will create a function called **update()** to execute all the created functions in order, and we will use the **requestAnimationFrame()** function, native Javascript, to repeat the looping process in the browser.

```javascript
requestAnimationFrame(update);

function update() {
  grid = nextGen(grid);
  drawGrid(grid, COL, ROW, RES);
  requestAnimationFrame(update); //running again to repeat the loop
}
```

Okay, now everything is ready and your file should have looked like this:

```javascript
document.addEventListener("DOMContentLoaded", () => {
  const canvas = document.querySelector("#board");
  const ctx = canvas.getContext("2d");

  const GRID_WIDTH = 500;
  const GRID_HEIGHT = 500;
  const RES = 5;
  const COL = GRID_WIDTH / RES;
  const ROW = GRID_HEIGHT / RES;

  canvas.width = GRID_WIDTH;
  canvas.height = GRID_HEIGHT;

  //Making a grid and filling with 0 or 1
  function createGrid(cols, rows) {
    return new Array(cols)
      .fill(null)
      .map(() =>
        new Array(rows).fill(null).map(() => Math.round(Math.random()))
      );
  }

  let grid = createGrid(COL, ROW);

  requestAnimationFrame(update);
  function update() {
    grid = nextGen(grid);
    drawGrid(grid, COL, ROW, RES);
    requestAnimationFrame(update);
  }

  //Generate nex generation
  function nextGen(grid) {
    const nextGen = grid.map((arr) => [...arr]); //make a copy of grid with spread operator

    for (let col = 0; col < grid.length; col++) {
      for (let row = 0; row < grid[col].length; row++) {
        const currentCell = grid[col][row];
        let sumNeighbors = 0; //to verify the total of neighbors

        //Verifying the 8 neigbours of current cell
        for (let i = -1; i < 2; i++) {
          for (let j = -1; j < 2; j++) {
            if (i === 0 && j === 0) {
              continue; // because this is the current cell's position
            }

            const x = col + i;
            const y = row + j;

            if (x >= 0 && y >= 0 && x < COL && y < ROW) {
              const currentNeighbor = grid[col + i][row + j];
              sumNeighbors += currentNeighbor;
            }
          }
        }

        //Aplying rules
        if (currentCell === 0 && sumNeighbors === 3) {
          nextGen[col][row] = 1;
        } else if (
          currentCell === 1 &&
          (sumNeighbors < 2 || sumNeighbors > 3)
        ) {
          nextGen[col][row] = 0;
        }
      }
    }
    return nextGen;
  }

  //Draw cells on canvas
  function drawGrid(grid, cols, rows, reslution) {
    ctx.clearRect(0, 0, cols, rows);
    for (let i = 0; i < cols; i++) {
      for (let j = 0; j < rows; j++) {
        const cell = grid[i][j];
        ctx.fillStyle = cell ? "#5c3ec9" : "#f8f8f2";
        ctx.fillRect(i * reslution, j * reslution, reslution, reslution);
      }
    }
  }
});
```

Now just run the HTML file to see this (or something better in your case, as I had some problems recording my screen):


![Demonstração Final do Projeto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/homroa5pff8dgfsabp4c.gif)


## Final Considerations

Although it doesn't seem like a big deal, this project is very interesting to train the basic knowledge of HTML, CSS and JS, mainly in the manipulation of arrays.In case you're interested, I'll leave some links to larger projects that used the same concepts as this game. 

Creating the Game of Life in Excel - https://github.com/asgunzi/JogodaVidaExcel

The Video that inspired me, from the youtube channel **O Programador** (PT/BR) - https://youtu.be/qTwqL69PK_Y

I hope you enjoyed it and that you were able to learn something cool, always remember what **Bob Ross** said: _"as long as you are learning, you are not failing"._

Just keep going, however slowly.

See you. ✌️