Como criar um tabuleiro de campo minado em JavaScript
Tags: javascript, gamedev, webdev, tutorial
Published: 2020-10-09 13:41:33

---
title: Como criar um tabuleiro de campo minado em JavaScript
published: true
description: A lógica necessária para criar um tabuleiro de campo minado utilizando array multidimensional para acessar as casas através das coordenadas x e y.
tags: javascript, gamedev, webdev, tutorial
cover_image: https://ofcode.com.br/wp-content/uploads/2020/09/como-criar-um-tabuleiro-de-campo-minado-com-javascript-dev-to.jpg
---
O desenvolvimento de jogos é um ótimo modo de aplicar diversos conceitos de programação, neste tutorial, vamos entender qual foi a lógica utilizada para criar o tabuleiro da [minha versão do jogo de campo minado](https://ofcode.com.br/projects/minesweeper/).

## **Entendendo o problema**
Vamos comparar um tabuleiro de campo minado com um tabuleiro de xadrez, em ambos, cada casa pode ser acessada através da combinação de dois valores:

1. índice da linha;
2. índice da coluna.

A diferença é que, por convenção, as colunas no xadrez são representadas por letras, mas em nosso tabuleiro de campo minado, vamos usar números para identificar tanto as linhas quanto as colunas, além disso, vamos começar a nossa contagem no 0.

<img src="https://ofcode.com.br/wp-content/uploads/2020/09/tabuleiro-de-xadrez-e-campo-minado-com-indices-1.png" alt="Tabuleiro de xadrez e de campo minado com seus eixos x e y">

Qualquer combinação entre estes dois índices existentes representa uma posição no tabuleiro. Por exemplo, a posição “1, G” no tabuleiro de xadrez é equivalente à posição “0, 1” no tabuleiro de campo minado. É a mesma lógica usada também no jogo Batalha Naval.

Para que seja possível desenhar o tabuleiro utilizando Javascript é necessário encontrar uma estrutura de dados nesta linguagem de programação que possua o mesmo modo de acesso de seus valores, ou seja, através da combinação de suas linhas e colunas. Para isso, a estrutura ideal é o array multidimensional.

## **Array e Array multidimensional**

Array é um tipo de dado presente em diversas linguagens de programação, em Javascript ele pode ser entendido como um dado capaz de armazenar uma lista. Essa lista pode conter itens de diversos tipos, como números, strings, objetos, etc.

```javascript
const simpleArr = [ 1, '2', {c: 3} ];
```
Cada item do array pode ser acessado pelo seu índice. Como arrays em JavaScript começam com o índice 0, para acessarmos o primeiro item do array executamos:

```javascript
const firstItem = simpleArr[0];
console.log(firstItem); // 1
```

Um array pode também conter outros arrays, a essa estrutura se dá o nome de **Array multidimensional**:

```javascript
const multidimensionalArr = [
    [ 1, 2, 3 ],
    [ 4, 5, 6 ],
    [ 7, 8, 9 ]
];
```
Do mesmo modo que no exemplo anterior, utilizamos o índice para acessarmos um item do Array:

```javascript
const firstArr = multidimensionalArr[0];
console.log(firstArr); // [ 1, 2, 3, ];
```

A constante `firstArr` armazena o primeiro array da lista. Sendo assim, para acessarmos o primeiro item dentro do primeiro array, devemos continuar buscando pelo seu índice:

```javascript
const firstItem = multidimensionalArr[0][0];
console.log(firstItem); // 1
```

Portanto, assim como em um tabuleiro de campo minado, um Array multidimensional também tem os seus valores acessíveis através da combinação de dois índices, onde o primeiro se refere à linha e o segundo à coluna.

## **Criando o tabuleiro**

Agora que temos a estrutura que representará o nosso tabuleiro, são necessárias algumas etapas para a sua criação:

**1**. Definir a quantidade de linhas, colunas e minas;
**2**. Criar um Array multidimensional com todos os itens zerados;
**3**. Definir aleatoriamente a posição das minas;
**4**. Inserir as minas no Array multidimensional;
**5**. Para cada mina, incrementar em 1 o valor das casas ao seu redor;
**6**. Imprimir o tabuleiro no DOM.

### **1. Definir a quantidade de linhas, colunas e minas**

Para simplificar nosso exemplo, vamos criar um tabuleiro do nível mais fácil do jogo, com 9 linhas, 9 colunas e 10 minas:

```javascript
class Minesweeper {
    constructor() {
        this.boardRowsLength = 9;
        this.boardColumnsLength = 9;
        this.minesLength = 10;
    }
}
```

### **2. Criar um Array multidimensional com todos os itens zerados**

Agora é necessário criar um Array que represente uma linha no tabuleiro, esse Array irá conter nove zeros, referente ao número de colunas, e devemos inserir esse Array no nosso array multidimensional. Como nosso tabuleiro terá 9 linhas, devemos repetir essa ação por nove vezes.

```javascript
generateEmptyBoard() {
    for (let y = 0; y < this.boardRowsLength; y++) {
        this.board.push([]);
        for (let x = 0; x < this.boardColumnsLength; x++) {
            this.board[y][x] = 0;
        }
    }
}
```

Este é o array atribuído à propriedade `this.board` após a execução do método `generateEmptyBoard()`:

```javascript
this.board = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0,],
    [0, 0, 0, 0, 0, 0, 0, 0, 0,],
    [0, 0, 0, 0, 0, 0, 0, 0, 0,],
    [0, 0, 0, 0, 0, 0, 0, 0, 0,],
    [0, 0, 0, 0, 0, 0, 0, 0, 0,],
    [0, 0, 0, 0, 0, 0, 0, 0, 0,],
    [0, 0, 0, 0, 0, 0, 0, 0, 0,],
    [0, 0, 0, 0, 0, 0, 0, 0, 0,],
    [0, 0, 0, 0, 0, 0, 0, 0, 0,],
];
```

### **3. Definir aleatoriamente a posição das minas**

Vamos armazenar em uma outra lista todos os pares de coordenadas que representarão a posição das minas e que serão gerados aleatoriamente a partir dos índices possíveis de linhas e colunas. Mais uma vez, utilizamos um array multidimensional para isso:

```javascript
generateMinesPositions() {
    this.minesPositions = [];

    while (this.minesPositions.length < this.minesLength) {
        const y = this.getRandomInt(0, this.boardRowsLength);
        const x = this.getRandomInt(0, this.boardColumnsLength);

        if (!this.isAlreadyAMine([y, x])) {
            this.minesPositions.push([y, x]);
        }
    }
}

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

isAlreadyAMine(minePosition) {
    return this.minesPositions.join(" ").includes(minePosition.toString());
}
```
A propriedade `this.minesPositions` ficará mais ou menos assim (porém com outros índices, pois as posições são geradas aleatoriamente cada vez que o método é chamado):

```javascript
this.minesPositions = [
    [0, 4], [2, 5], [1, 2], [3, 3], [7, 5],
    [6, 4], [2, 7], [8, 7], [4, 5], [5, 1],
];
```

### **4. Inserir as minas no Array multidimensional**

O array `this.minesPositions` possui as dez posições onde as minas devem ser colocadas no tabuleiro. Para isso, acessamos essas posições no array `this.board` e trocamos o seu valor de 0 para “M” (“M” de mina)

```javascript
insertMines() {
    for (let i = 0; i < this.minesPositions.length; i++) {
        const y = this.minesPositions[i][0];
        const x = this.minesPositions[i][1];
        this.board[y][x] = "M";
    }
}
```

`this.board` ficará assim após a execução do método `insertMines()`:

```javascript
this.board = [
    [0, 0, 0, 0, M, 0, 0, 0, 0,],
    [0, 0, M, 0, 0, 0, 0, 0, 0,],
    [0, 0, 0, 0, 0, M, 0, M, 0,],
    [0, 0, 0, M, 0, M, 0, 0, 0,],
    [0, 0, 0, 0, 0, 0, 0, 0, 0,],
    [0, M, 0, 0, 0, 0, 0, 0, 0,],
    [0, 0, 0, 0, M, 0, 0, 0, 0,],
    [0, 0, 0, 0, 0, M, 0, 0, 0,],
    [0, 0, 0, 0, 0, 0, 0, M, 0,],
];
```

**ATENÇÃO**: A letra M presente nos arrays é na realidade uma string (“M”). Removi as aspas apenas para facilitar a visualização das posições.

### **5. Para cada mina, incrementar em 1 o valor das casas ao seu redor**

A partir da posição de uma mina devemos acessar todas as outra casas ao seu redor. Mais uma vez, utilizaremos o array `minesPositions` para nos guiar.

Se temos uma mina na posição [5][3] e queremos acessar a casa logo acima, então devemos apenas subtrair em 1 o índice da linha da mina, logo, a posição da casa será [4][3].
Ou se quisermos acessar a casa ao lado direto na mina, basta adicionar 1 ao índice de sua coluna: [5][4].

Para facilitar o processo, podemos então criar um novo array multidimensional fora de nossa classe `Minesweeper` que contenha todas as operações que devem ser realizadas para acessar as casas ao redor de uma mina:

```javascript
const AROUND_CELL_OPERATORS = [
    [-1, -1], [-1, 0], [-1, 1],
    [0, -1],           [0, 1],
    [1, -1],  [1, 0],  [1, 1],
];
```

Agora para cada uma das minas podemos utilizar este array para acessar as casas ao seu redor e incrementar o seu valor:

```javascript
updateBoardNumbers() {
    for (let i = 0; i < this.minesPositions.length; i++) {
        for (let j = 0; j < AROUND_CELL_OPERATORS.length; j++) {
            const minePosition = this.minesPositions[i];
            const around = AROUND_CELL_OPERATORS[j];
            const boardY = minePosition[0] + around[0];
            const boardX = minePosition[1] + around[1];

            if (boardY >= 0 && boardY < this.boardRowsLength &&
                boardX >= 0 && boardX < this.boardColumnsLength &&
                typeof this.board[boardY][boardX] === 'number') {
                this.board[boardY][boardX]++;
            }
        }
    }
}
```

`this.board` ficará assim:

```javascript
this.board = [
    [0, 1, 1, 2, M, 1, 0, 0, 0,],
    [0, 1, M, 2, 2, 2, 2, 1, 1,],
    [0, 1, 2, 2, 3, M, 3, M, 1,],
    [0, 0, 1, M, 3, M, 3, 1, 1,],
    [1, 1, 2, 1, 2, 1, 1, 0, 0,],
    [1, M, 1, 1, 1, 1, 0, 0, 0,],
    [1, 1, 1, 1, M, 2, 1, 0, 0,],
    [0, 0, 0, 1, 2, M, 2, 1, 1,],
    [0, 0, 0, 0, 1, 1, 2, M, 1,],
];
```

### **6. Imprimir o tabuleiro no DOM**

O método `printBoard($board)` colocar o tabuleiro no DOM a partir da propriedade `this.board`.

```javascript
printBoard($board) {
    for (let y = 0; y < this.board.length; y++) {
        const $row = document.createElement('DIV');
        $row.classList.add('row');
        
        for (let x = 0; x < this.board[y].length; x++) {
            const $cell = document.createElement('SPAN');
            
            $cell.innerHTML = this.board[y][x];
            $row.appendChild($cell);
        }

        $board.appendChild($row);
    }
}
```

Para deixar nossa classe mais genérica, vamos definir três parâmetros no seu constructor, desse modo, será possível criar tabuleiros com qualquer número de linhas, colunas e quantidade de minas. Além disso, vamos chamar o método `init()` também dentro do constructor:

```javascript
class Minesweeper {
    constructor(boardRowsLength, boardColumnsLength, minesLength) {
        this.boardRowsLength = boardRowsLength;
        this.boardColumnsLength = boardColumnsLength;
        this.minesLength = minesLength;
        this.board = [];
        this.minesPositions = [];
        
        this.init();
    }

    init() {
        this.generateEmptyBoard();
        this.generateMinesPositions();
        this.insertMines();
        this.updateBoardNumbers();
    }
    // ...
}
```

Para concluir, criamos uma instância da classe `Minesweeper`, e chamamos o método `printBoard($board)`, passando como parâmetro o elemento HTML no qual o tabuleiro deve ser renderizado:

```javascript
const minesweeper = new Minesweeper(9, 9, 10);
const $board = document.getElementById('board');

minesweeper.printBoard($board);
```

Veja o resultado no CodePen:
{% codepen https://codepen.io/manussakis/pen/PoNgjqN %}

Este é o código final do tabuleiro de campo minado feito com JavaScript no Github:

{% github 0fc0de/minesweeper-board %}

Embora este seja só o começo do desenvolvimento do jogo, esta é uma etapa importante do processo. [O código completo do Jogo de Campo Minado](https://github.com/manussakis/angular-minesweeper) está também disponível no GitHub.

Este é meu [Instagram](https://instagram.com/gabrielmanussakis) caso se interesse.

Abraço!