Como construir um jogo da velha com JavaScript puro

Aqui está o jogo da velha que vamos codificar. Clique em cada quadrado para colocar as peças e tente fazer três em uma fileira:

Estrutura HTML

A estrutura HTML consistirá dos seguintes elementos:

  • Um quadro que contará com 9 div elementos que representam a grade 3×3
  • Um modal Bootstrap será exibido quando o jogo terminar. O modal exibirá o vencedor.
  • Um botão que quando clicado reiniciará o jogo

Crie um contêiner contendo a placa e o botão de reinicialização com o Bootstrap.

1
 class="container">
2
   class="text-center mt-5">Tic Tac Toe Game
3
   class="board" id="board">
4
     class="cell" data-cell>
5
     class="cell" data-cell>
6
     class="cell" data-cell>
7
     class="cell" data-cell>
8
     class="cell" data-cell>
9
     class="cell" data-cell>
10
     class="cell" data-cell>
11
     class="cell" data-cell>
12
     class="cell" data-cell>
13
  
14
  
17
   class="text-center mt-3">
18
     id="restartButton" class="btn btn-primary">Restart Game
19
  
20

Abaixo do contêiner, adicione o modal de resultados.

1

2
  class="modal fade"
3
  id="resultModal"
4
  tabindex="-1"
5
  aria-labelledby="resultModalLabel"
6
  aria-hidden="true"
7
>
8
   class="modal-dialog">
9
     class="modal-content">
10
       class="modal-header">
11
         class="modal-title" id="resultModalLabel">Game Over
12
        
13
          type="button"
14
          class="close"
15
          data-dismiss="modal"
16
          aria-label="Close"
17
        >
18
           aria-hidden="true">×
19
        
20
      
21
       class="modal-body" id="results">
22
       class="modal-footer">
23
        
24
          type="button"
25
          class="btn btn-primary"
26
          data-dismiss="modal"
27
          id="playBtn"
28
        >
29
          Play Again
30
        
31
      
32
    
33
  
34

Estilizando com CSS

Para garantir a

os elementos do tabuleiro são organizados em uma grade 3×3, aplique os seguintes estilos:

1
.board {
2
    display: grid;
3
    grid-template-columns: repeat(3, 1fr);
4
    gap: 10px;
5
    max-width: 300px;
6
    margin: 50px auto;
7
  }

Para cada célula na grade, adicione os seguintes estilos.

1
.cell {
2
    width: 100px;
3
    height: 100px;
4
    display: flex;
5
    align-items: center;
6
    justify-content: center;
7
    font-size: 2rem;
8
    cursor: pointer;
9
    border: 1px solid #000;
10
  }

Crie os estilos que adicionarão o X e o O nas células do tabuleiro. Quando for a vez do jogador O, adicionaremos a classe circle; quando for a vez do jogador X, adicionaremos a classe x.

1
.x::before {
2
    content: "X";
3
    color: blue;
4
    position: absolute;
5
  }
6
  .circle::before {
7
    content: "O";
8
    color: red;
9
    position: absolute;
10
  }

Funcionalidade JavaScript

Vamos definir algumas variáveis:

1
const X_CLASS = "x";
2
const CIRCLE_CLASS = "circle";
3
constante COMBINAÇÕES_VENCEDORAS = [
4
[0, 1, 2],
5
[3, 4, 5],
6
[6, 7, 8],
7
[0, 3, 6],
8
[1, 4, 7],
9
[2, 5, 8],
10
[0, 4, 8],
11
[2, 4, 6],
12
];

X_CLASS define a classe CSS que será adicionada na vez do jogador X, enquanto CIRCLE_CLASS representa a classe CSS que será adicionada a cada célula quando for a vez do jogador CIRCLES.

Cada matriz nas combinações vencedoras representa os índices de uma linha vencedora horizontalmente, verticalmente ou diagonalmente.

Selecione os elementos usando o DOM (Document Object Model)

1
const cellElements = document.querySelectorAll("[data-cell]");
2
const board = document.getElementById("board");

Comece definindo a vez inicial do jogador.

Em seguida, crie uma função chamada starGame() que se parece com isto:

1
 function startGame() {
2
    cellElements.forEach((cell) => {
3
      cell.classList.remove(X_CLASS);
4
      cell.classList.remove(CIRCLE_CLASS);
5
      cell.removeEventListener("click", handleClick);
6
      cell.addEventListener("click", handleClick, { once: true });
7
    });
8
  
9
  
10
 startGame();

Esta função reiniciará o jogo removendo qualquer X ou O das células, limpando assim o tabuleiro.

1
cell.classList.remove(X_CLASS);
2
cell.classList.remove(CIRCLE_CLASS);

Ele também remove quaisquer eventos de clique existentes para garantir que não haja manipuladores de eventos em nenhuma célula quando o jogo começar. A função também garantirá que, uma vez que o jogo comece, cada célula só possa ser clicada uma vez usando o { once: true } opção que será responsável por alternar os jogadores e adicionar O’s e X’s no tabuleiro.

Em seguida, crie uma função chamada handleClick()que cuidará da lógica do que acontece quando um jogador clica em uma célula.

1
function handleClick(e) {
2
    const cell = e.target;
3
    const currentClass = circleTurn ? CIRCLE_CLASS : X_CLASS;
4
    cell.classList.add(currentClass);
5
    circleTurn = !circleTurn;
6
}

No handleClick função, porque a cada elemento da célula foi atribuída a handleClick função através de ouvintes de eventos, e.target refere-se ao elemento de célula específico que foi clicado.

  • const currentClass=circleTurn?CIRCLE_CLASS:X_CLASS; esta classe determinará de quem é a vez. Se circleTurn é verdade, o currentClass será atribuído à vez de O; caso contrário, X_CLASS será usado. Isso garantirá que as classes continuarão alternando em todas as instâncias.
  • cell.classList.add(currentClass); irá adicionar o currentClass para a célula
  • circleTurn=!circleTurn; alternará os turnos entre os dois jogadores.

Agora precisamos verificar o vencedor. Crie um checkWin() função que irá assumir o currentClass e verificar, entre as combinações vencedoras, se há uma correspondência no tabuleiro.

1
 function checkWin(currentClass) {
2
    return WINNING_COMBINATIONS.some((combination) => {
3
      return combination.every((index) => {
4
        return cellElements[index].classList.contains(currentClass);
5
      });
6
    });
7
  }

Aqui, usamos o .some() método, que verifica se pelo menos uma matriz no WINNING_COMBINATIONS array retorna verdadeiro para a condição interna every().O .every() método verificará se cada índice na matriz interna contém currentClasssignificando que todos os elementos naquele array são marcados pelo mesmo jogador. Se esta condição for verdadeira para qualquer combinação, o jogador representado por currentClass ganha o jogo.

Para verificar se o jogo está empatado, o isDraw() a função verifica se cada célula do quadro contém o X_CLASS ou CIRCLE_CLASS classe. Se qualquer jogador marcar todas as células, o jogo é considerado um empate.

1
function isDraw() {
2
    return [...cellElements].every((cell) => {
3
      return (
4
        cell.classList.contains(X_CLASS) ||
5
        cell.classList.contains(CIRCLE_CLASS)
6
      );
7
    });
8
  }

Agora atualize o handleClick() função para mostrar os resultados apropriados se checkWin() ou isDraw() funções retornam verdadeiro.

1
function handleClick(e) {
2
    const cell = e.target;
3
    const currentClass = circleTurn ? CIRCLE_CLASS : X_CLASS;
4
    console.log(currentClass);
5

6
    cell.classList.add(currentClass);
7
    circleTurn = !circleTurn;
8

9
    if (checkWin(currentClass)) {
10
      showResult(`${currentClass.toUpperCase()} wins`);
11
    } else if (isDraw()) {
12
      showResult(`It's a Draw`);
13
    }
14
  }

Agora, vamos criar theshowResult() função, que exibirá os resultados.

1
const resultModal = new bootstrap.Modal(
2
    document.getElementById("resultModal"),
3
    {
4
      keyboard: false,
5
    }
6
);
7
 function showResult(message) {
8
    resultMessage.textContent = message;
9
    resultModal.show();
10

11
  }

No código acima, inicializamos uma instância modal do Bootstrap resultModal para o modal com ID “resultModal”. Em seguida, usamos o showResult(message) função para atualizar o conteúdo de texto do corpo do modal (resultMessage.textContent) com o parâmetro de mensagem e exibir o modal usando resultModal.show().

Quando o jogo terminar, o modal será exibido conforme mostrado abaixo:

Para fechar o modal, adicione um ouvinte de eventos para close e PlayAgain botões. Quando cada botão for clicado, o jogo deve reiniciar invocando o startGame() função. O modal também ficará oculto.

1
const closeButton = document.querySelector(".modal .close");
2
document.getElementById("playBtn").addEventListener("click", startGame);
3
closeButton.addEventListener("click", () => {
4
    resultModal.hide();
5
    startGame();
6
  });

A última parte deste jogo é adicionar indicadores visuais que mostram de quem é a vez. Adicione as classes CSS para eles.

1
.board.hover-x [data-cell]:hover:not(.x):not(.circle) {
2
    background-color: lightblue;
3
  }
4

5
  .board.hover-circle [data-cell]:hover:not(.x):not(.circle) {
6
    background-color: lightcoral;
7
  }

Na parte superior do arquivo, defina essas variáveis, que representam as classes hover.

1
const HOVER_X_CLASS = "hover-x";
2
const HOVER_CIRCLE_CLASS = "hover-circle";

Em seguida, crie uma função chamada setBoardHoverClass()cujo propósito será adicionar um efeito de cor de hover dependendo de quem é a vez. Isso garantirá que os jogadores saibam de quem é a vez quando passarem o mouse sobre as células do tabuleiro.

No setBoardHoverClassprimeiro removemos todas as classes existentes.

1
function setBoardHoverClass() {
2
  board.classList.remove(HOVER_X_CLASS);
3
  board.classList.remove(HOVER_CIRCLE_CLASS);
4
  
5
}

Em seguida, criamos uma instrução if-else que verifica de quem é a vez e aplica as classes apropriadas às células do tabuleiro.

1
function setBoardHoverClass() {
2
  board.classList.remove(HOVER_X_CLASS);
3
  board.classList.remove(HOVER_CIRCLE_CLASS);
4
  if (circleTurn) {
5
    board.classList.add(HOVER_CIRCLE_CLASS);
6
  } else {
7
    board.classList.add(HOVER_X_CLASS);
8
  }
9
}

Finalmente volte para o handleClick() função e atualizá-la da seguinte forma:

1
function handleClick(e) {
2
  const cell = e.target;
3
  const currentClass = circleTurn ? CIRCLE_CLASS : X_CLASS;
4
  console.log(currentClass);
5

6
  cell.classList.add(currentClass);
7
  circleTurn = !circleTurn;
8

9
  if (checkWin(currentClass)) {
10
    showResult(`${currentClass.toUpperCase()} wins`);
11
  } else if (isDraw()) {
12
    showResult(`It's a Draw`);
13
  } else {
14
    setBoardHoverClass();
15
  }
16
}

Ao passar o mouse sobre qualquer célula após o início do jogo, você verá o efeito.

Resultado final

Vamos nos lembrar do que construímos!

Conclusão

Este tutorial abordou como criar um jogo da velha em JavaScript. Os conceitos abordados servirão como uma base sólida para construir jogos ainda mais complexos.

Deixe uma resposta