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 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
17 |
|
18 |
|
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 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
type="button" |
14 |
class="close" |
15 |
data-dismiss="modal" |
16 |
aria-label="Close" |
17 |
>
|
18 |
aria-hidden="true">×
|
19 |
|
20 |
|
21 |
|
22 |
|
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. SecircleTurn
é verdade, ocurrentClass
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 ocurrentClass
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 currentClass
significando 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 setBoardHoverClass
primeiro 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.