Implementação Inicial do Tabuleiro
Para começar, vamos configurar uma estrutura básica do jogo da velha usando componentes Vue. O objetivo é criar um tabuleiro interativo que responda a cliques do usuário.
Vue.component('Celula', {
template: `
<button class="celula">
{{ conteudo }}
</button>
`
});
Este componente representa uma célula individual do tabuleiro. Em seguida, definimos o componente principal do tabuleiro que organiza as células em uma grade.
Vue.component('Tabuleiro', {
data() {
return {
statusAtual: 'Próximo jogador: X',
grade: [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
]
};
},
template: `
<div>
<div class="status">{{ statusAtual }}</div>
<div :key="idx" class="linha" v-for="(linha, idx) in grade">
<celula :key="celula" v-for="celula in linha"></celula>
</div>
</div>
`
});
O componente Tabuleiro utiliza v-for para iterar sobre a grade bidimensional, renderizando cada célula. Note que data deve ser uma função que retorna um objeto para manter a reatividade.
Adicionando Interatividade com Props e Eventos
Para permitir que o tabuleiro se comunique com as células, passamos propriedades (props) e emitimos eventos. Primeiro, atualizamos a célula para exibir um valor recebido via prop.
Vue.component('Celula', {
props: ['marcacao'],
template: `
<button class="celula">
{{ marcacao }}
</button>
`
});
No tabuleiro, vinculamos o valor de cada célula a um array de estado e adicionamos um evento de clique.
Vue.component('Tabuleiro', {
data() {
return {
statusAtual: 'Próximo jogador: X',
grade: [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
],
estadoCelulas: Array(9).fill(null)
};
},
methods: {
aoClicar(posicao) {
let copia = this.estadoCelulas.slice();
if (copia[posicao]) {
alert('Posição já ocupada!');
return;
}
copia[posicao] = 'X';
this.estadoCelulas = copia;
}
},
template: `
<div>
<div class="status">{{ statusAtual }}</div>
<div :key="idx" class="linha" v-for="(linha, idx) in grade">
<celula :key="celula" :marcacao="estadoCelulas[celula]" v-for="celula in linha"></celula>
</div>
</div>
`
});
Para propagar o evento de clique da célula para o tabuleiro, usamos $emit no componente filho.
Vue.component('Celula', {
props: ['marcacao'],
methods: {
manipularClique() {
this.$emit('clique');
}
},
template: `
<button class="celula">
{{ marcacao }}
</button>
`
});
Lógica de Jogo: Turnos Alternados e Verificação de Vitória
Para alternar entre os jogadores X e O, adicionamos uma flag no estado do tabuleiro e atualizamos o status após cada jogada.
data() {
return {
statusAtual: 'Próximo jogador: X',
grade: [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
],
estadoCelulas: Array(9).fill(null),
turnoX: true
};
},
methods: {
aoClicar(posicao) {
let copia = this.estadoCelulas.slice();
if (copia[posicao] || verificarVencedor(copia)) {
return;
}
copia[posicao] = this.turnoX ? 'X' : 'O';
this.estadoCelulas = copia;
this.turnoX = !this.turnoX;
this.statusAtual = `Próximo jogador: ${this.turnoX ? 'X' : 'O'}`;
}
}
Implementamos uma função para verificar se há um vencedor, analisando combinações vencedoras.
function verificarVencedor(celulas) {
const combinacoes = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], // linhas
[0, 3, 6], [1, 4, 7], [2, 5, 8], // colunas
[0, 4, 8], [2, 4, 6] // diagonais
];
for (let i = 0; i < combinacoes.length; i++) {
const [a, b, c] = combinacoes[i];
if (celulas[a] && celulas[a] === celulas[b] && celulas[a] === celulas[c]) {
return celulas[a];
}
}
return null;
}
Após cada jogada, chamamos essa função para atualizar o status e prevenir jogadas adicionais se houver um vencedor.
Implementando Histórico de Jogadas para Navegação Temporal
Para permitir que o usuário volte a jogadas anteriores, armazenamos um histórico de estados do tabuleiro. Movemos a lógica de estado para um componente superior, Jogo.
Vue.component('Jogo', {
data() {
return {
historico: [{
celulas: Array(9).fill(null)
}],
turnoX: true,
statusAtual: 'Próximo jogador: X',
etapaAtual: 0
};
},
methods: {
aoClicar(posicao) {
let historicoAtual = this.historico.slice(0, this.etapaAtual + 1);
let estadoAtual = historicoAtual[historicoAtual.length - 1];
let copiaCelulas = estadoAtual.celulas.slice();
if (copiaCelulas[posicao] || verificarVencedor(copiaCelulas)) {
return;
}
copiaCelulas[posicao] = this.turnoX ? 'X' : 'O';
this.historico = historicoAtual.concat([{
celulas: copiaCelulas
}]);
this.etapaAtual = this.historico.length - 1;
this.turnoX = !this.turnoX;
this.statusAtual = `Próximo jogador: ${this.turnoX ? 'X' : 'O'}`;
},
voltarPara(etapa) {
this.etapaAtual = etapa;
this.turnoX = (etapa % 2) === 0;
this.statusAtual = `Próximo jogador: ${this.turnoX ? 'X' : 'O'}`;
}
},
template: `
<div class="jogo">
<div class="tabuleiro-area">
<tabuleiro :celulas="historico[etapaAtual].celulas"></tabuleiro>
</div>
<div class="info-jogo">
<div>{{ statusAtual }}</div>
<ol>
<li :class="{'ativo': idx === etapaAtual}" :key="idx" v-for="(registro, idx) in historico">
<button>
{{ idx === 0 ? 'Início' : 'Jogada #' + idx }}
</button>
</li>
</ol>
</div>
</div>
`
});
O componente Tabuleiro agora recebe as células como prop e delega o clique ao pai.
Vue.component('Tabuleiro', {
props: ['celulas'],
data() {
return {
grade: [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
]
};
},
methods: {
repassarClique(posicao) {
this.$emit('clique', posicao);
}
},
template: `
<div>
<div :key="idx" class="linha" v-for="(linha, idx) in grade">
<celula :key="celula" :marcacao="celulas[celula]" v-for="celula in linha"></celula>
</div>
</div>
`
});
Com isso, o jogo suporta alternância de turnos, detecção de vitórias e navegação pelo histórico de jogadas, demonstrando conceitos fundamentais do Vue.js como reatividade, componentes e comunicação entre componentes.