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
- Modelar cada buraco como objeto com status e relógio.
- Seguir a cadeia ausente → faminta → triste ou alimentada → saindo → ausente.
- Usar
Date.nowerequestAnimationFramepara o tempo. - Dar 10% de chance à capivara real.
- Encerrar na vitória aos 10 pontos.
Passo a passo, não o jogo inteiro
Ordem que funciona:
- HTML e CSS: dez buracos, placar, barra de comida. Sem JavaScript ainda.
- Só aparecer e sumir (ausente ↔ faminta). Sem clique.
- Clique na faminta soma 1.
- Aos 10 pontos, esconda o campo e mostre a vitória.
- A barra de comida cresce com os pontos.
- 10% das aparições são reais (coroa) e valem 2.
- Se o tempo da fome acabar sem clique: triste, depois saindo, depois ausente.
- 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.

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/:
capivara-ausente.png,capivara-faminta.png,capivara-triste.png,capivara-alimentada.png,capivara-saindo.png- os mesmos com prefixo
capivara-real-e coroa, para a variante rara
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:
ausente→faminta(sorteiarealcomMath.random() > 0.9, cerca de 10%)famintasem clique →tristefamintacom clique →alimentadae soma 1 ou 2tristeoualimentada→saindosaindo→ausente(erealvolta afalse)
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
- Qual o próximo status depois de
alimentada? E depois detriste? - Por que a capivara real vale 2 e aparece cerca de uma em dez vezes?
- O que
requestAnimationFramefaz de diferente de umsetIntervalde 16 ms? - No exemplo, baixe a vitória para 4 pontos. O que você altera e o que espera ver?
Respostas comentadas
- Os dois vão para
saindoe depois paraausente. A diferença é só a figurinha do meio (sorriso ou cara triste) e o ponto no placar. - 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. - O navegador chama a função quando for pintar, e pausa em aba oculta.
setIntervaltenta rodar no relógio mesmo se a tela não precisa. - 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; }