Pular para o conteúdo
murad.guide

Aplicações Web

Capítulo 6

React: jogo do Ateliê

3 min de leitura 452 palavras 1 gravura

Neste capítulo você vai montar um jogo da velha temático para praticar estado imutável. O projeto-fio é o Ateliê Boa Viagem, ateliê digital fictício em Boa Viagem, Recife.

Seu ponto de partida

Use o exemplo 06-react-jogo do pacote anexo e copie inicio/ para uma pasta de prática fora do original. A raiz do exemplo é a referência completa desta etapa.

Use servidor só em 127.0.0.1 quando o README do exemplo pedir HTTP local. Abrir ou testar não publica nada.

Como praticar

Leia → copie ou digite → mude uma coisa → preveja → salve → recarregue → confira. Restaure cada experiência antes da próxima. Faça as atividades antes de abrir as respostas comentadas.

Mapa visual de React: jogo do Atelie

Estado do tabuleiro em array

Nove casas em um array. Cada jogada cria um novo array em vez de alterar o anterior. Isso facilita voltar jogadas depois.

Faça na sua cópia

Confira o trecho no contexto do exemplo antes de mudar.

const next = squares.slice();
next[i] = xIsNext ? 'X' : 'O';
setSquares(next);

Mude uma coisa e preveja

Impida clicar numa casa já preenchida.

Anote o que espera observar antes de salvar e recarregar.

Recarregue e confira

A casa ocupada ignora o clique.

Se não acontecer

Return cedo se squares[i] já tiver valor.

Calcular vencedor

Uma função pura recebe o array e devolve o vencedor ou null. Funções puras são fáceis de testar mentalmente.

Faça na sua cópia

Confira o trecho no contexto do exemplo antes de mudar.

function calculateWinner(sq) {
  const lines = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
  for (const [a,b,c] of lines) {
    if (sq[a] && sq[a] === sq[b] && sq[a] === sq[c]) return sq[a];
  }
  return null;
}

Mude uma coisa e preveja

Mostre o vencedor no status.

Anote o que espera observar antes de salvar e recarregar.

Recarregue e confira

Três iguais anunciam o fim.

Se não acontecer

Pare de aceitar jogadas quando houver vencedor.

Reiniciar sem recarregar a página

Um botão redefine o estado inicial. Não use location.reload como única solução.

Faça na sua cópia

Confira o trecho no contexto do exemplo antes de mudar.

<button onClick={() => setSquares(Array(9).fill(null))}>Nova partida</button>

Mude uma coisa e preveja

Reinicie também o indicador de vez.

Anote o que espera observar antes de salvar e recarregar.

Recarregue e confira

Tabuleiro limpo e X começa de novo.

Se não acontecer

Redefina xIsNext para true.

Atividades

Atividade 1

Por que copiar o array com slice antes de alterar?

Atividade 2

O que é uma função pura neste jogo?

Atividade 3

Como reiniciar sem location.reload?

Respostas comentadas

Resposta da atividade 1

Preservar imutabilidade e permitir histórico/comparação.

Resposta da atividade 2

calculateWinner: só depende da entrada e não altera fora.

Resposta da atividade 3

Resetar os estados com os valores iniciais.

O arquivo completo

Referência do exemplo 06-react-jogo, reproduzida aqui para que você possa conferir sem sair da página.

styles.css

:root{--ink:#1a1a1a;--bg:#f7f4ef;--brand:#1f3a5f;--accent:#c45c26;--line:#d9d2c5;--focus:#0b57d0}
*{box-sizing:border-box}html{font-family:"Segoe UI",Tahoma,sans-serif;color:var(--ink);background:var(--bg)}
body{margin:0;line-height:1.5}a:focus-visible,button:focus-visible,input:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
.skip{position:absolute;left:-999px}.skip:focus{left:1rem;top:1rem;z-index:10;background:#fff;padding:.5rem}
header,footer{background:var(--brand);color:#fff;padding:1rem 1.25rem}header a,footer a{color:#fff}
main{max-width:52rem;margin:0 auto;padding:1.25rem}h1{color:var(--brand);margin-top:0}
.card{background:#fff;border:1px solid var(--line);padding:1rem;margin:.75rem 0}
button{background:var(--accent);color:#fff;border:0;padding:.55rem 1rem;cursor:pointer}
.board{display:grid;grid-template-columns:repeat(3,4.5rem);gap:.4rem}
.square{width:4.5rem;height:4.5rem;font-size:1.5rem;background:#fff;border:2px solid var(--brand)}
label{display:block;margin:.5rem 0 .2rem;font-weight:600}
input,textarea{width:100%;max-width:28rem;padding:.45rem;border:1px solid var(--line)}
ul.nav{list-style:none;padding:0;display:flex;gap:1rem;flex-wrap:wrap;margin:0}
.status{font-weight:600;margin:.75rem 0}