Como funciona o joguinho da minhoca e por que ele ainda mata seu tempo livre
O joguinho da minhoca é basicamente um simulador de cobra que você controla com as setas do teclado. Começa pequeno, come a maçã, cresce, e se bater na parede ou no próprio corpo, acabou. Parece bobeira, mas a implementação correta esconde uma quantidade razoável de armadilhas que quase ninguém conta.
O que é joguinho da minhoca e qual a ideia real por trás
Na essência, você tem um grid invisível. Cada célula do grid pode estar vazia ou ocupada por um segmento da cobra. A cobra se move um passo por tick de tempo, sempre na direção indicada pelo último input. Quando a cabeça cai sobre uma maçã, o corpo cresce um segmento e uma nova maçã aparece em posição aleatória. Fim da história teórica. O problema é que a versão teórica funciona perfeitamente no papel. A versão que você roda no browser ou no terminal é onde as coisas complicam. O grid precisa ser renderizado de forma consistente, os inputs não podem se sobrepor, e o movimento deve ser contínuo sem pulos de frame.
Mão na massa: implementando do zero em JavaScript puro
Vou partir do pressuposto de que você quer construir o seu próprio, não só rodar algum clone pronto. A versão em JS puro roda em qualquer navegador moderno sem dependência alguma. O primeiro arquivo é só o HTML com um canvas. Nada demais:
index.html Um canvas de 400 por 400 pixels, fundo escuro, e um script básico. O grid que eu uso é de 20 por 20 células, o que significa que cada célula tem 20 pixels. Se você mudar o tamanho do canvas, ajuste essa conta. Deixa tudo em quadros desproporcionais e o jogo fica visualmente errado.
Dentro do script.js, a estrutura central é uma lista de coordenadas que representa o corpo da cobra. Eu costumo usar um array de objetos {x, y}. A cabeça é o primeiro elemento. O movimento é dado por uma velocidade vetorial: {x: 1, y: 0} para direita, {x: -1, y: 0} para esquerda, e assim por diante. A função principal de atualização roda dentro de um setInterval. Eu recomendo 100ms como ponto de partida para a velocidade. Fica jogável para iniciantes. Se quiser mais rápido, desce para 70ms, mas a partir daqui a precisão dos inputs começa a dar problema.
O loop faz quatro coisas em sequência: 1. Calcula a nova posição da cabeça somando a velocidade ao vetor atual.
2. Verifica colisão com paredes. Se a coordenada x ou y sair de 0 a 19, o jogo acaba. 3. Verifica colisão com o próprio corpo. Aqui tem uma pegadinha que eu perdi horas debugando: você precisa verificar a colisão antes de adicionar a nova cabeça ao array, senão a cobra atravessa o próprio rastro num frame.
👉 Clique no botão abaixo para saber mais sobre o assunto!
4. Se a nova posição estiver em cima da maçã, adiciona a nova cabeça e chama a função de generar nova maçã. Se não estiver, remove o último segmento do array para manter o tamanho. A renderização é pura pintura no canvas. Um loop simples que percorre cada segmento do corpo e desenha um quadrado preenchido. A maçã recebe uma cor diferente. Nada sofisticado, mas funcional.
O bug que me pegou de calça caída e como resolvi
Aqui vai o detalhe técnico que todo tutorial pula. Quando você aperta duas teclas muito rápido antes do próximo tick do setInterval, a cobra pode receber dois inputs consecutivos na mesma direção ou até mesmo em direções opostas. O resultado clássico: a cobra vira 180 graus instantaneamente e colide consigo mesma no mesmo frame, morrendo de forma injusta. A solução é simples mas exige disciplina. Você mantém uma fila de inputs com no máximo um elemento. A cada tick, você consome apenas o primeiro input da fila e descarta o resto até o próximo frame. Assim, se o jogador apertar cima depois para a direita num espaço de 50ms, só o cima é processado. O próximo tick processará a direita. A cobra faz a curva que o jogador pretendia, e não um suicídio.
Outro detalhe: a geração da maçã não pode cair em cima do corpo da cobra. A abordagem mais ingênua é gerar aleatoriamente e checar se a posição está livre. Se não estiver, gera de novo. Isso funciona bem quando a cobra é curta. Quando ela ocupa mais da metade do grid, o loop de tentativas pode demorar. A versão robusta mantém uma lista de posições livres e sorteia diretamente dela. A cada movimento, você atualiza essa lista adicionando a posição que o segmento caudal liberou e removendo a que a cabeça ocupou.
Detalhes práticos que fazem diferença
O timer. setInterval é tolerável mas acumula drift se o navegador ficar sobrecarregado. Para um jogo simples como esse, não é desastre, mas se você quer consistência real, use requestAnimationFrame com controle manual de delta time. A diferença é perceptível em máquinas mais lentas. O grid invisível versus renderização. Muita gente desenha a cobra pixel por pixel sem respeitar o grid. O resultado é um rabisco que parece certo mas se move de forma errática quando a cobra cresce. Sempre manteve o desenho alinhado às coordenadas da grade. Cada segmento ocupa exatamente canvasWidth / gridColumns pixels de largura.
Input direction lock. além da fila de inputs, bloqueie direções opostas no momento do input. Se a cobra está indo para a direita, ignorar um input para a esquerda imediatamente evita que o jogador acidentalmente enfileire um reverso mesmo com a fila funcionando.
Limitações reais que ninguém avisa
O joguinho da minhoca, quando implementado nessa versão clássica, tem dois gargalos sérios. O primeiro é a escalabilidade. Conforme o grid cresce, a sensação de precisão diminui porque os segmentos ficam menores e a cobra preenche o espaço mais rápido. Em grids maiores, o jogador precisa de tempo de reação menor, e o jogo vira teste de reflexo em vez de estratégia. O segundo gargalo é a IA. Se você quiser criar um bot que jogue sozinho, a abordagem ingênua de seguir a maçã mais próxima falha miseravelmente quando a cobra começa a se enrolar. O caminho mais curto em grade (Manhattan distance) não considera o espaço disponível. Existem algoritmos como Hamiltonian cycle que garantem que a cobra visite todas as células numa ordem fixa, mas eles tornam o jogo extremamente lento e previsível. Ninguém quer jogar contra isso.
Se o seu objetivo é apenas ter um passatempo rápido, a versão clássica em JS puro atende. Se você quer algo com modos adicionais, ranking online ou variantes como modo especo ou obstáculos fixos, o trabalho de manutenção sobe drasticamente. Nesse caso, frameworks como Phaser ou até mesmo Pygame para desktop entregam muito mais resultado com menos dor de cabeça do que construir tudo do zero. O código que eu descrevi aqui cabe em pouco menos de duzentas linhas. Você pode copiar, colar, testar e modificar sem depender de nenhuma biblioteca. É assim que eu faço, e é assim que a maioria dos que levam isso a sério também faz.