Como criar um jogo de memória com frutas usando apenas HTML, CSS e JavaScript
A maioria dos tutoriais que aparecem no Google quando você pesquisa jogo memória frutas mostra código pronto que não explica por que as coisas funcionam. Eu passei horas depurando um desses exemplos porque os autores não mencionavam um detalhe importante sobre o embaralhamento. Vou colocar o que realmente funciona aqui, junto com os problemas que eu encontrei na prática.
O jogo memória frutas que eu construí para um projeto escolar
O conceito é simples: dezesseis cartas dispostas em um grid 4x4, oito pares de frutas, duas cartas viradas por vez, e o jogador deve encontrar os pares. O que muitos esquecem é que a lógica de embaralhamento precisa ser implementada manualmente se você quiser evitar bugs de pares duplicados no mesmo slot. Eu usei o algoritmo Fisher-Yates reverso, que garante uma distribuição verdadeiramente aleatória. Código abaixo, mas vou explicar os pontos problemáticos primeiro porque a ordem convencional não ajuda muito. O problema do embaralhamento: Se você usar sort com Math.random() sem callback, o resultado em alguns navegadores é enviesado. Isso faz com que certas cartas apareçam mais frequentemente em posições específicas do grid. O workaround que eu encontrei foi garantir que cada par fosse criado explicitamente como dois elementos separados antes de embaralhar, nunca tentar replicar um array existente com push duplicado.
A estrutura de dados que funcionou para mim foi um array de oito objetos fruta, cada um repetido duas vezes, gerando um array de dezesseis elementos. Depois disso, o Fisher-Yates com swap aleatório funciona perfeitamente.
👉 Clique no botão abaixo para saber mais sobre o assunto!
CSS Grid e a questão do layout responsivo
O grid 4x4 com CSS é trivial, mas o comportamento em telas menores exige media queries ou simplesmente usar width e padding relativos. Eu configurei cada carta com position relative, transform rotateY para o efeito de virar, e backface-visibility hidden. A parte que costuma quebrar é o z-index durante a animação — sem definir explicitamente, as cartas podem sobrepor incorretamente em navegadores mais antigos. O state management também merece atenção. Você precisa rastrear três variáveis principais: quais cartas estão viradas atualmente, se o jogador já fez uma seleção, e quantos pares foram encontrados. Uma variável isLocked impede que o jogador clique em uma terceira carta enquanto a animação de duas incorretas ainda está rodando. Esse é o erro mais comum que eu vejo em código de iniciantes — esquecer completamente o isLocked e permitir cliques múltiplos simultâneos.
O código JavaScript completo ficaria assim:
Limitações e quando não usar essa abordagem
Essa implementação funciona bem para fins educacionais e para crianças pequenas, mas tem limitações sérias se você quiser escalar. Primeiro, não há persistência de progresso — se a página recarregar, o jogador começa do zero. Segundo, a lógica de verificação de par é O(n) em tempo real e não escala se você aumentar o grid para 6x6 ou mais, porque o delay fixo de 1000ms entre desvirar cria uma experiência ruim em jogos mais longos. Terceiro, a versão com emojis funciona em todos os navegadores modernos, mas se você substituir por imagens personalizadas, o carregamento assíncrono pode causar flash de conteúdo antes das cartas aparecerem — algo que eu resolvi pré-carregando todas as imagens em um Image() constructor invisível. Se o objetivo é algo mais robusto, considerar um framework como React ou Vue vale o investimento, especialmente porque eles resolvem automaticamente o estado de renderização e evitam exatamente os bugs de renderização que eu encontrei manualmente com o DOM vanilla. O código acima leva cerca de 30 minutos para ser escrito e testado, mas manter esse tipo de jogo com funcionalidades extras (timer, pontuação, múltiplos níveis) rapidamente dobra esse tempo. Para um projeto escolar ou brincadeira rápida, a abordagem vanilla está mais do que suficiente.