Praticando HTML, CSS e Javascript Vanilla - Reproduzindo o Jogo da Vida de John Conway
Tags: javascript, braziliandevs, html, tutorial
Published: 2021-12-02 18:27:16

> 
English version [here.](https://dev.to/akadot_/learning-html-css-and-javascript-vanilla-reproducing-the-john-conways-game-of-life-9pn)

##  Introdução:

E ae, se você está procurando projetos legais para praticar seus conhecimentos em **Javascript**, **CSS** e **HTML**, neste tutorial eu vou te ensinar (ou tentar) como reproduzir sua própria versão do Jogo da Vida, do matemático britânico **John Conway**.

Caso você nunca tenha ouvido falar desse jogo, ele faz parte de uma categoria chamada “autómatos celulares”, que de acordo com a nossa querida Wikipédia: *“são os modelos de evolução temporal mais simples com capacidade para exibir comportamento complicado”.*

Mas não se preocupe com essas definições complicadas, basicamente o que vamos construir é um jogo que não precisa de jogador, que “se joga sozinho”, quase como se você criasse algo vivo e observasse sua evolução (daí o nome).

O resultado final consiste em um campo preenchido aleatoriamente por quadrados iguais que, ao decorrer do tempo e seguindo algumas regras de sobrevivência, podem gerar estruturas fascinantes e imprevisíveis, como a figura abaixo.


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

Ok, sem mais enrolação, bora fazer essa bagaça. Deixarei abaixo o link do meu repositório com o projeto finalizado:

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

## Construção:

Para construir esse projeto, usaremos um recurso bastante poderoso do HTML chamado [**Canvas API**](https://developer.mozilla.org/pt-BR/docs/Web/API/Canvas_API), que permite desenhar formas **2D** e até **3D** utilizando somente **Javascript puro**. Mas não se assuste, não precisaremos configurar nada e nem elaborar modelos matemáticos de álgebra linear complexos, o Canvas API é uma simples tag nativa do HTML e esse projeto vai depender puramente de lógica.

O primeiro passo é preparar os três arquivos que iremos utilizar, começando pela estrutura do **HTML:**

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

```

Como pode ver, usaremos apenas uma tag `<canvas>` com um **id** de referência para o JS.

Em seguida, podemos preparar o arquivo **CSS**, aqui vai da criatividade de cada um:

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

```
Pronto, agora é só abrir o arquivo **HTML** em seu navegador e...


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

Eu sei, parece só um quadrado comum, mas eu prometo que vao se tornar algo legal. Para isso devemos começar a definir as propriedades e funcionalidades desse Canvas.
Existem várias maneiras de definir as propriedades de um Canva, porém optaremos por fazer tudo no nosso arquivo **Javascript**. É o que iremos fazer a seguir.

<br/>

## Implementando a Lógica:

Hora de construir de fato as coisas. O primeiro passo é referenciar a tag `<canva>` no nosso arquivo de Javascript e, logo depois, informar se queremos trabalhar com o canva **2D** ou **3D** (no nosso caso é o 2D):

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

Em seguida, definiremos algumas constantes que nos ajudarão no decorrer do código, como altura, largura e a resolução dos blocos:

```javascript
const GRID_WIDTH = 500;             //largura do campo
const GRID_HEIGHT = 500;           //altura do campo
const RES = 5;                    //tamanho dos lados dos quadrados
const COL = GRID_WIDTH / RES;    //quantidade de colunas
const ROW = GRID_HEIGHT / RES;  //quantidade de linhas
```

Feito isso, vamos utilizar essas constantes para definir as informações do canvas:

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

Pronto, agora eu prometo que vamos conseguir visualizar algo no navegador, mas para ter certeza que tudo carregará corretamente, colocarei nosso código dentro de um evento, que só irá disparar quando todo o HTML estiver carregado. Assim não precisaremos nos preocupar caso o código inicie antes das criação do canvas:

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

O próximo passo é desenhar nossos blocos dentro do nosso campo. Para isso, criaremos um **array**, com duas dimensões, que irá armazenar a mesma quantidade de linhas e colunas do nosso canvas, além do tamanho dos nossos blocos.

Para isso, definiremos uma função chamada **createGrid()**, que vai receber o número de **linhas (ROW)** e **colunas (COL)**. Em seguida retornaremos um novo array com o mesmo tamanho do número de colunas e, para cada item/coluna nesse array, criaremos um novo array do mesmo tamanho das nossas linhas. Pode parecer complexo mas é bem simples na prática, aproveitaremos também para preencher todos os valores desses arrays com **zero (0)** e **um (1)** de forma aleatória, mas explicarei isso mais tarde:

```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); //por fim, executamos a função e armazenamos os arrays em uma variável grid
```

Com nosso array criado, podemos começar a desenhar os blocos na tela, baseado no valor de cada célula dentro do array, onde os valores **1** serão pintados e os valores **0** ficarão apagados.

Para isso, precisaremos de uma função que percorra todo o array e desenhe no canvas seguindo essas regras. Criaremos então a função **drawGrid()**, que receberá nosso **grid**, as **linhas e colunas** do nosso canvas e a **resolução/dimensão** dos nossos blocos:

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

Como podem ver, primeiramente executaremos a função **clearRect()** nativa do Canvas API, que tem o papel de limpar o canvas antes de começarmos a desenhar. Ela recebe nos dois primeiros parâmetros, as coordenadas de onde deverá começar a limpar, já nos dois últimos, onde deve terminar sua limpeza.

Feito isso, criamos dois laços de repetição **for** que percorrerá as linhas e colunas do nosso array *(eu sei que dava pra fazer algo melhor ou usar a função **.map()**, mas ficaremos no for por enquanto)*. Dentro do laço, adicionaremos a célula atual em uma constante **cell**, para logo em seguida verificar se ela possui um **0** ou um **1** usando um **if ternário**.

Nessa linha usamos outra propriedade nativa do Canvas API, o **fillStyle**, que recebe a cor que usaremos para pintar nossos blocos. No nosso caso, ele pode receber a cor **#5c3ec9** caso a célula possua um valor 1 (que no javascript significa verdadeiro/true) ou um valor 0 (que no javascript significa vazio/falso/inexistente).

Na linha abaixo, mai uma tag nativa, mas dessa vez é a função **fillRect()**, que irá desenhar de fato nosso bloco, que será um simples retângulo. Essa função precisa de 4 parâmetros:

- Primeiro: a coordenada X de onde o retângulo deverá ser iniciado (no nosso caso será a resolução x a posição no array);
- Segundo: a coordenada Y de onde o retângulo será iniciado (no nosso caso será a resolução x a posição no array);
- Terceiro: a largura do retângulo (a resolução);
- Quarto: a altura do retângulo (a resolução);

Feito isso, agora você pode desenhar os quadrados dentro do canvas, executando a função que acabamos de criar desse jeito:

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

##  Explicando as Regras do Jogo

Antes de prosseguirmos, precisamos entender as regras propostas por John Conway, para que o jogo seja de fato "auto-jogável".

Felizmente, existem apenas 4 regras bem simples, tanto de entender quanto de implementar, que definem se uma célula está **"viva"**, que no nosso caso serão as células roxas, ou **"morta"**, que aqui serão as células vazias. Para isso, as condições propostas são:

- **1:** Qualquer célula viva com menos de dois vizinhos vivos morre de solidão;
- **2:** Qualquer célula viva com mais de três vizinhos vivos morre de superpopulação;
- **3:** Qualquer célula morta com exatamente três vizinhos vivos se torna uma célula viva;
- **4:** Qualquer célula viva com dois ou três vizinhos vivos continua no mesmo estado para a próxima geração.


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

Seguindo essas regras, vamos desenvolver uma função que aplique todas essas condições ao passar do tempo que o jogo está executando. Ela terá o papel de percorrer todas as células do array, aplicar as condições de vida ou morte e gerar um novo array que será novamente desenhado na tela com a função **drawGrid()**.

A cada repetição desse ciclo, consideraremos que o array gerado é uma nova geração de indivíduos que herdaram as condições da geração anterior. Agora vamos começar a implementar essa função.

Chamaremos a função responsável pela aplicação das regras de **nexGen()** e, como primeiro passo para não afetarmos o grid anterior, definiremos uma constante capaz de armazenar uma cópia da geração anterior a ela.

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

Caso ainda não conheça, no trecho `[...arr]` utilizamos o operador **SPREAD**, que foi adicionado ao Javascript a partir da versão 6 e tem como finalidade, armazenar um maior número de informações de uma vez só, muito utilizado com arrays e objetos. Você também pode utilizar as funções `.push()` ou `.slice()` ao invés do operador spread, não há problema algum nisso.

O próximo passo é começar os loops que irão percorrer o array para aplicar as regras do jogo. Como fizemos lá em cima, precisamos percorrer todas as linhas, utilizando o `grid.length` e depois todas as colunas, utilizando o `grid[col].length` (o parâmetro **col** é apenas o nome que eu dei a variável de controle do **for**, mas você pode usar as letras **i** e **j** como normalmente é feito). 

Já aproveitaremos para capturar a célula inicial em uma constante e criaremos uma variável para contabilizar o número de células vizinha vivas.

```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;
```

A próxima etapa é, para cada célula, percorrer todos os seus **8 vizinhos** e verificar se eles estão vivos ou não. Pode parecer um pouco difícil de entender o código a primeira vista, mas aqui vai uma explicação com imagens:


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

Sim, eu utilizei o Google Sheets para isso, mas o importante é que nosso próximo loop vai percorrer os valores entre **-1** e **1**, localizando o número de vizinhos vivos.

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

Colocamos a condição `if (i === 0 && j === 0)`, pois essa é a posição da célula atual, que não queremos somar no número de vizinhos.

O próximo trecho vai tratar dos "cantos" do nosso campo. Pense assim, se uma célula está colada na lateral esquerda do nosso canvas, não teremos como acessar os vizinhos que estão em uma coluna anterior a ela, ou seja, mais a esquerda, pois eles não existem. Sendo assim, vamos somar valores à variável `sumNeighbors` somente se suas coordenadas estiverem dentro dos limites do 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;
```

Satisfeitas as condições, a variável `sumNeighbors` receberá seu valor anterior, mais o valor das células vivas, lembrando que as células mortas aqui recebem o valor **zero**, que não impacta na soma.

Feito isso, podemos aplicar as regras descritas pelo John Conway com um simples `if/else`:

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

Explicando, a primeira condição testa se a célula atual está vazia e se ela possui 3 vizinhos, se for verdadeiro a próxima geração receberá nessa mesma posição o valor 1 ou vivo.

Já a segunda condição reúne as demais regras em uma só, testando se a célula atual está **viva** e; se existirem menos de dois vizinhos próxima geração receberá **zero**, se existirem mais do que 3 vizinhos a próxima geração também receberá **zero**.

Por fim basta retornar a próxima geração `return nextGen;`, e a função ficará assim:

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

Fazendo isso, estamos quase perto de finalizar nosso projeto, o próximo passo é bem simples, criaremos uma função chamada **update()** para executar todas as funções criadas em ordem, e utilizaremos a função **requestAnimationFrame()**, nativa do Javascript, para repetir o processo em loop no navegador.

```javascript
requestAnimationFrame(update);

function update() {
  grid = nextGen(grid);
  drawGrid(grid, COL, ROW, RES);
  requestAnimationFrame(update); //executando novamente para que o loop não pare
}
```

Pronto, agora está tudo pronto e seu arquivo deve ter ficado assim:

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

Agora basta executar o arquivo HTML para vermos isso (ou algo melhor no seu caso, pois tive alguns problemas para gravar minha tela):


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


## Considerações Finais

Apesar de não parecer grande coisa, esse projeto é muito interessante para treinar os conhecimentos básicos de HTML, CSS e JS, principalmente na manipulação de arrays. Caso tenha se interessado, vou deixar alguns links de projetos maiores que utilizaram os mesmos conceitos desse jogo. 

Criando o Jogo da Vida em Excel - https://github.com/asgunzi/JogodaVidaExcel

O Vídeo que me Inspirou, do canal **O Programador** - https://youtu.be/qTwqL69PK_Y

Espero que tenha gostado e que tenha conseguido aprender algo legal, lembre-se sempre do que **Bob Ross** dizia: _"contanto que esteja aprendendo, você não está falhando"._ 

Apenas continue, mesmo que devagar.

Até mais. ✌️