Pular para o conteúdo
murad.guide

As Três Camadas

Capítulo 24

Jogo: alimente a capivara

5 min de leitura 930 palavras 1 gravura

Último capítulo do volume: um jogo no terreiro da Oficina. Dez buracos. De vez em quando uma capivara faminta aparece. Você clica a tempo e ela come. Se perder o tempo, ela fica triste e vai embora. De vez em quando vem a real, com coroa: vale 2 pontos. Dez pontos vencem.

A arte é deste material (PNG da capivara), não de outro jogo. A solução completa está em exemplos/24-jogo/. Não tente escrever os 200 linhas de uma vez. Siga os passos.

Trabalhe na cópia inicio desta etapa. Leia e digite o código indicado; mude apenas uma coisa. Antes de recarregar, escreva o resultado que espera. Depois compare com a referência completa ao lado. Se algo falhar, confira primeiro o arquivo salvo e o caminho aberto.

O que você vai aprender

Passo a passo, não o jogo inteiro

Ordem que funciona:

  1. HTML e CSS: dez buracos, placar, barra de comida. Sem JavaScript ainda.
  2. Só aparecer e sumir (ausente ↔ faminta). Sem clique.
  3. Clique na faminta soma 1.
  4. Aos 10 pontos, esconda o campo e mostre a vitória.
  5. A barra de comida cresce com os pontos.
  6. 10% das aparições são reais (coroa) e valem 2.
  7. Se o tempo da fome acabar sem clique: triste, depois saindo, depois ausente.
  8. Se houver clique a tempo: alimentada, depois saindo, depois ausente.

Abra exemplos/24-jogo/index.html quando quiser conferir o destino. Recarregue para jogar de novo.

Cinco estados da capivara e vitória aos dez pontos

Os dez buracos e os arquivos de arte

Cada buraco é um botão com uma img. O src troca conforme o estado.

<button type="button" class="buraco" id="buraco-0" aria-label="Buraco 1">
  <img alt="" width="120" height="120" src="imagens/capivara-ausente.png">
</button>

Arquivos na pasta imagens/:

alt="" no img interno: o botão já tem aria-label. Não leia “imagem” duas vezes.

Tempos do exemplo: faminta visível por 2 s; triste, alimentada e saindo por 0,5 s; espera ausente entre 2 s e 12 s.

Date.now e requestAnimationFrame

Date.now() devolve milissegundos desde 1º de janeiro de 1970. Serve para marcar “quando este buraco muda de novo”.

requestAnimationFrame pede ao navegador: “quando for desenhar o próximo quadro, chame esta função”. Se a aba não está visível, a chamada pausa. É melhor que um setInterval solto para jogo.

let proximo = Date.now() + 2000;

function quadro() {
  if (Date.now() > proximo) {
    // muda o estado deste buraco
    proximo = Date.now() + 2000;
  }
  requestAnimationFrame(quadro);
}

requestAnimationFrame(quadro);

Cada capivara guarda o próprio proximo. O loop único visita as dez.

Erro comum: criar dez setInterval e esquecer de limpar. Os timers se acumulam. Um requestAnimationFrame e um array de objetos bastam.

O objeto de cada capivara

const capivaras = [];
for (let i = 0; i < 10; i += 1) {
  capivaras.push({
    status: "ausente",
    proximo: Date.now() + 2000 + Math.random() * 10000,
    real: false,
    no: document.getElementById(`buraco-${i}`)
  });
}

A cadeia é previsível:

function arquivo(capivara, estado) {
  const prefixo = capivara.real ? "capivara-real" : "capivara";
  const mapa = {
    faminta: `${prefixo}-faminta.png`,
    triste: `${prefixo}-triste.png`,
    alimentada: `${prefixo}-alimentada.png`,
    saindo: `${prefixo}-saindo.png`,
    ausente: "capivara-ausente.png"
  };
  return `imagens/${mapa[estado]}`;
}

Só alimente se o buraco ainda tem a classe faminta. Clique num buraco vazio não pontua.

pontos += capivara.real ? 2 : 1;
document.getElementById("pontos").textContent = pontos;
document.getElementById("comida").style.width = `${Math.min(pontos * 10, 100)}%`;
if (pontos >= 10) {
  document.getElementById("campo").hidden = true;
  document.getElementById("vitoria").hidden = false;
}

Quando pontos >= 10, pare de pedir requestAnimationFrame. O terreiro some. A mensagem de vitória aparece.

Pare e experimente. No exemplo, jogue até vencer. Recarregue. Depois abra jogo.js e leia proximoStatus. Mude FAMINTA de 2000 para 4000: as capivaras esperam mais. Recarregue e sinta o ritmo.

O que já está na pasta

Não copie sprite de outro bicho. Use os PNG desta etapa. O CSS põe os dez buracos numa grade de cinco colunas. O foco do botão tem contorno de 3 px. Clique e teclado (Enter no buraco focado) alimentam, porque o ouvinte está no #campo e o controle é um button.

Se o seu jogo “não sai do ausente”, proximo provavelmente está no futuro demais, ou quadro não chama requestAnimationFrame de novo. Se pontua sempre 2, a sorte Math.random() > 0.9 ficou invertida ou sempre verdadeira.

O exemplo também oferece Pausar/Retomar. Pausar interrompe a atualização; retomar ajusta os horários para não perder a rodada. Os nomes acessíveis dos buracos indicam se a capivara está faminta, alimentada ou ausente. Use Tab e Enter para jogar. Menos movimento desativa animações decorativas.

Atividades

  1. Qual o próximo status depois de alimentada? E depois de triste?
  2. Por que a capivara real vale 2 e aparece cerca de uma em dez vezes?
  3. O que requestAnimationFrame faz de diferente de um setInterval de 16 ms?
  4. No exemplo, baixe a vitória para 4 pontos. O que você altera e o que espera ver?

Respostas comentadas

  1. Os dois vão para saindo e depois para ausente. A diferença é só a figurinha do meio (sorriso ou cara triste) e o ponto no placar.
  2. A coroa é rara de propósito: o jogo fica mais tenso. Math.random() > 0.9 é verdadeiro em cerca de 10% das aparições. Vale 2 para compensar a espera.
  3. O navegador chama a função quando for pintar, e pausa em aba oculta. setInterval tenta rodar no relógio mesmo se a tela não precisa.
  4. Troque pontos >= 10 (e o cálculo da barra, se quiser) para 4. Depois de quatro pontos comuns, o campo some e a vitória aparece. Recarregue para voltar ao combinado de 10 se for estudar o exemplo original.

Referências

O arquivo completo

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

jogo.css

body { font-family: "Segoe UI", Tahoma, sans-serif; background: #e8f0d8; margin: 0; padding: 1rem; }
.campo {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0.6rem;
  max-width: 40rem;
}
.buraco {
  background: #6b4f32;
  border: 0;
  border-radius: 50%;
  aspect-ratio: 1;
  cursor: pointer;
  padding: 0.4rem;
}
.buraco:focus { outline: 3px solid #c45c26; }
.buraco img { width: 100%; height: auto; pointer-events: none; }
.buraco.faminta img { pointer-events: auto; cursor: pointer; }
.barra { height: 1rem; background: #fff; border: 2px solid #1b365d; max-width: 40rem; margin-bottom: 1rem; }
#comida { display: block; height: 100%; width: 0; background: #c45c26; }
.vitoria { font-size: 1.4rem; font-weight: 700; }