Jogo Da Minhoca Que Come Frutas - Jogo da Minhoca | Achamos uma grandona | Comendo Frutinhas || Nanny ...
Jogo da Minhoca | Achamos uma grandona | Comendo Frutinhas || Nanny ...

Criando um jogo da minhoca que come frutas do zero

A maior parte dos tutoriais que você encontra por aí começa com Canvas, JavaScript e um loop de renderização. Funciona para algo simples. Mas quando você vai montar isso de verdade, sem depender de uma biblioteca pronta, aparecem problemas que ninguém menciona nos artigos. O jogo básico é simples: uma minhoca se move em um grid, come frutas e cresce. A complexidade aparece quando você quer que o jogo responda de forma consistente, sem lag, sem bugs estranhos de colisão e com uma lógica de crescimento que não quebre o tabuleiro depois de dez minutos de jogo.

O que é o jogo da minhoca que come frutas

Não é nada além do clássico Snake, só que com frutas em vez de pontinhos genéricos. A minhoca avança em um tabuleiro bidimensional, cada célula do grid tem um tamanho fixo (geralmente entre 15 e 25 pixels), e o objetivo é recolher as frutas que aparecem em posições aleatórias. Cada fruta comeada aumenta o comprimento do corpo da minhoca. O jogo acaba quando a minhoca bate na própria cauda ou nas paredes do campo. A diferença prática entre um código que funciona no tutorial e um que funciona no seu projeto são os detalhes de implementação. Vou explicar direto o que você precisa fazer e onde as coisas dão errado.

Implementação prática

Comece pelo grid. Defina a largura e altura em células, não em pixels. Um tabuleiro de 20 por 20 células com células de 20 pixels dá um canvas de 400x400. Isso é irrelevante para a lógica, mas importa para o layout visual. A minhoca é uma lista de coordenadas. O corpo inteiro vive como um array de objetos {x, y}. A cabeça é sempre o primeiro elemento. Quando você move, adiciona uma nova posição na frente baseada na direção atual e remove o último elemento, exceto se comeu uma fruta, aí não remove nada. O loop principal roda em um intervalo. 100 a 150 milissegundos é o padrão. Quanto menor o intervalo, mais rápida a minhoca, mais difícil o jogo. Use setTimeout em vez de setInterval se quiser controle mais fino, porque setInterval acumula erros de execução se o navegador estiver ocupado.

A fruta precisa ser gerada em uma posição que não sobreponha o corpo da minhoca. Isso é mais importante do que parece. Se você gerar a fruta de forma aleatória sem verificar, ela pode aparecer dentro do corpo, e o jogador vai comer uma fruta que já está colidindo com a minhoca antes mesmo do movimento acontecer. A solução é manter uma lista de posições ocupadas e filtrar antes de escolher. Controles: setas ou WASD. Armazene a direção atual e a próxima direção em variáveis separadas. Se você aplicar a direção diretamente sem buffer, o jogador pode pressionar duas teclas rapidas no mesmo frame e a minhoca vai virar 180 graus instantaneamente, causando colisão consigo mesma e encerrando o jogo de forma injusta. O buffer de entrada resolve isso.

Colisão com parede: depende do estilo do jogo. Na versão clássica, bater na parede mata. Na versão moderna, a minhoca atravessa e aparece no outro lado. Decida isso no início e não mude no meio do desenvolvimento, porque afeta todo o resto do código.

👉 Clique no botão abaixo para saber mais sobre o assunto!

O problema que ninguém conta

Eu tentei implementar isso usando requestAnimationFrame com um controle de FPS interno. Deu certo no papel. No Chrome, com abas em segundo plano, o jogo entrava em câmera lenta porque o navegador reduz a taxa de atualização de abas ativas. A minhoca paramava no meio do movimento, e o jogador achava que o jogo estava bugado. A solução foi abandonar requestAnimationFrame para a lógica do jogo e manter tudo em setInterval com um fallback para setTimeout que recalcula o delta de tempo entre frames. Assim, mesmo com lag de renderização, a lógica de movimento continua sincronizada. O visual pode pular, mas a mecânica não. Outro problema que surgiu foi a geração de frutas múltiplas. Quando implementei mais de uma fruta na tela ao mesmo tempo para tornar o jogo mais dinâmico, a probabilidade de colisão entre frutas e corpo aumentou exponencialmente em tabuleiros pequenos. Em um grid de 15x15 com três frutas, o espaço disponível era tão limitado que o jogo ficava impossível após cerca de vinte frutas coletadas. A solução foi limitar o número máximo de frutas ao tamanho do tabuleiro dividido por doze, arredondado para baixo, e usar um sistema de prioridades: fruta vermelha vale mais mas aparece com menos frequência, fruta amarela é mais comum e vale menos.

Detalhes técnicos que fazem diferença

O render do canvas deve ser separado da lógica do jogo. Mantenha o estado do jogo em um objeto puro, atualize a cada frame, e apenas desenhe o resultado. Se você misturar renderização com lógica de colisão no mesmo bloco de código, fica quase impossível testar e debugar. Eu costumava colocar console.log em tudo até entender que bastava imprimir as coordenadas da cabeça e da fruta a cada movimento. Depois de um tempo, parei de precisar disso também, mas é útil nas primeiras rodadas de desenvolvimento. Para o armazenamento de high score, use localStorage. Não complicar com backend num jogo assim. A chave deve ser específica o suficiente para não conflitar com outros jogos no mesmo domínio. Uma string como "snake_fruit_highscore_v1" é suficiente.

O tamanho do canvas deve ser calculado com base no tamanho da célula multiplicado pelas dimensões do grid. Se você fixar o tamanho no HTML e o grid for menor, vai ter bordas pretas estranhas. Se o grid for maior, parte dele corta fora. Calcule dinamicamente.

Limitações reais

Esse tipo de jogo funciona bem em navegadores modernos e em dispositivos móveis com controles de toque. O problema é que controles de toque exigem um sistema de swipe detection que não existe nativamente. Sem ele, o jogo é jogável só no desktop. Para mobile, você precisa implementar detecção de gesto de deslize manualmente ou usar uma biblioteca pequena de gestos. Não vale a pena embutir uma biblioteca inteira para isso. O código de detecção de swipe é curto e direto. O outro limitante é a escalabilidade do tabuleiro. Tabuleiros muito grandes (acima de 40x40) começam a ter problemas de performance se você renderizar cada célula individualmente com fillRect. A solução é usar ImageData e manipular pixels diretamente, o que reduz drasticamente o custo de renderização, mas exige um nível de conhecimento técnico que a maioria dos tutoriais não cobre. Para a maioria dos projetos, tabuleiros de 20x20 a 30x30 são o ponto ideal entre jogabilidade e performance.

Download e código

O código-fonte completo fica disponível em repositórios públicos. Você pode baixar uma versão funcional com todos os detalhes implementados corretamente — buffer de direção, geração segura de frutas, separação de lógica e render, high score no localStorage e suporte a swipe para mobile — a partir de repositorios como o GitHub buscando por snake fruit game javascript. Procure por repositórios com issues abertas e Pull Requests recentes, porque código de 2018 costuma ter problemas de compatibilidade com navegadores modernos. Se você está construindo isso para um portfólio ou para aprender, o exercício vale a pena. É um projeto pequeno que cobra conhecimento de lógica de grid, gestão de estado, event handling e otimização de renderização. Tudo o que você precisa para algo mais sério.

Jogo da minhoca que come frutas — versão final

A versão madura do jogo inclui feedback visual ao comer frutas, som simples de coleta, e um game over screen com estatísticas de tempo e frutas coletadas. O código é organizado em módulos: gameState.js para o estado, input.js para os controles, renderer.js para o canvas e gameLoop.js para o controle de tempo. Essa separação facilita a manutenção e permite que qualquer um dessas partes seja substituída sem quebrar o resto. Se o objetivo é só jogar, existe uma versão online que roda diretamente no navegador. Se o objetivo é aprender, montar do zero é o caminho mais rápido para entender como cada peça se encaixa. A primeira versão que eu fiz tinha um bug onde a minhoca crescia duas vezes quando comia uma fruta na borda do grid. Levei três horas pra descobrir que o problema era a ordem de processamento: eu verificava colisão com a fruta antes de remover a cauda, então a minhoca crescia e ainda mantinha o segmento que deveria ser removido. A correção foi inverter a ordem: remover a cauda primeiro, depois verificar colisão com a fruta. Simple, mas o tipo de coisa que você só aprende errando.