Construindo o Jogo da Velha com Vue.js: Uma Adaptação do Tutorial do React

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.

Tags: Vue.js javascript Jogo da Velha Gerenciamento de Estado Desenvolvimento Front-end

Publicado em 6-4 04:41 por Thomas