Publicado em: 10 de outubro de 2025
O clássico jogo de tabuleiro Adivinhe Quem? (link em inglês) é uma aula de raciocínio dedutivo. Cada jogador começa com um tabuleiro de rostos e, com uma série de perguntas de sim ou não, reduz as possibilidades até identificar o personagem secreto do oponente.
Depois de assistir a uma demonstração da IA integrada no Google I/O Connect, me perguntei: e se eu pudesse jogar um jogo de adivinhação contra uma IA que vive no navegador? Com a IA do lado do cliente, as fotos seriam interpretadas localmente. Assim, um jogo personalizado de "Quem é Quem?" com amigos e familiares permaneceria particular e seguro no meu dispositivo.
Minha experiência é principalmente em desenvolvimento de UI e UX, e estou acostumado a criar experiências perfeitas em pixels. Espero ter feito exatamente isso com minha interpretação.
Meu aplicativo, AI Guess Who?, foi criado com React e usa a API Prompt e um modelo integrado ao navegador para criar um oponente surpreendentemente capaz. Nesse processo, descobri que não é tão simples obter resultados "pixel-perfect". No entanto, esse aplicativo demonstra como a IA pode ser usada para criar uma lógica de jogo inteligente e a importância da engenharia de comando para refinar essa lógica e obter os resultados esperados.
Continue lendo para saber mais sobre a integração de IA integrada, os desafios que enfrentei e as soluções que encontrei. Você pode jogar e encontrar o código-fonte no GitHub.
Fundamentos de jogos: um app do React
Antes de analisar a implementação da IA, vamos revisar a estrutura do aplicativo. Criei um aplicativo React padrão com TypeScript e um arquivo App.tsx
central para atuar como o condutor do jogo. Esse arquivo contém:
- Estado do jogo: um enum que rastreia a fase atual do jogo (como
PLAYER_TURN_ASKING
,AI_TURN
,GAME_OVER
). Essa é a parte mais importante do estado, já que determina o que a interface mostra e quais ações estão disponíveis para o jogador. - Listas de personagens: há várias listas que designam os personagens ativos, o personagem secreto de cada jogador e quais personagens foram eliminados do tabuleiro.
- Chat do jogo: um registro contínuo de perguntas, respostas e mensagens do sistema.
A interface é dividida em componentes lógicos:


À medida que os recursos do jogo cresciam, a complexidade também aumentava. Inicialmente, toda a lógica do jogo era gerenciada em um único e grande hook do React (link em inglês) personalizado, useGameLogic
, mas rapidamente ficou muito grande para navegar e depurar. Para melhorar a capacidade de manutenção, refatorei esse hook em vários hooks, cada um com uma única responsabilidade.
Exemplo:
- O
useGameState
gerencia o estado principal. usePlayerActions
é para a vez do jogadoruseAIActions
é para a lógica da IA
O principal hook useGameLogic
agora funciona como um compositor limpo, colocando esses hooks menores juntos. Essa mudança na arquitetura não alterou a funcionalidade do jogo,
mas deixou a base de código muito mais limpa.
Lógica de jogo com a API Prompt
O principal deste projeto é o uso da API Prompt.
Adicionei a lógica do jogo de IA a builtInAIService.ts
. Estas são as principais responsabilidades:
- Permitir respostas binárias e restritivas.
- Ensine a estratégia de jogo ao modelo.
- Ensinar a análise de modelos.
- Faça o modelo ter amnésia.
Permitir respostas binárias e restritivas
Como o jogador interage com a IA? Quando um jogador pergunta: "Seu personagem tem um chapéu?", a IA precisa "olhar" a imagem do personagem secreto e dar uma resposta clara.
Minhas primeiras tentativas foram uma bagunça. A resposta foi conversacional: "Não, a personagem em que estou pensando, Isabella, não parece estar usando um chapéu", em vez de oferecer um sim ou não binário. Inicialmente, resolvi isso com um comando muito estrito, essencialmente ditando ao modelo para responder apenas com "Sim" ou "Não".
Embora isso tenha funcionado, aprendi uma maneira ainda melhor de usar a saída estruturada. Ao fornecer o esquema JSON ao modelo, pude garantir uma resposta verdadeira ou falsa.
const schema = { type: "boolean" };
const result = session.prompt(prompt, { responseConstraint: schema });
Isso me permitiu simplificar o comando e deixar meu código processar a resposta de forma confiável:
JSON.parse(result) ? "Yes" : "No"
Ensinar a estratégia do jogo ao modelo
É muito mais simples pedir que o modelo responda a uma pergunta do que pedir que ele inicie e faça perguntas. Um bom jogador de Adivinhe Quem? não faz perguntas aleatórias. Eles fazem perguntas que eliminam o maior número de caracteres de uma só vez. Uma pergunta ideal reduz pela metade os possíveis caracteres restantes usando perguntas binárias.
Como ensinar essa estratégia a um modelo? Mais uma vez, engenharia de comando. O comando para generateAIQuestion()
é uma lição concisa sobre a teoria dos jogos do jogo Adivinhe Quem?.
Inicialmente, pedi ao modelo para "fazer uma boa pergunta". Os resultados foram imprevisíveis. Para melhorar os resultados, adicionei restrições negativas. O comando agora inclui instruções semelhantes a:
- "CRÍTICO: pergunte APENAS sobre recursos atuais"
- "CRÍTICO: seja original. NÃO repita uma pergunta".
Essas restrições diminuem o foco do modelo e impedem que ele faça perguntas irrelevantes, o que o torna um oponente muito mais agradável. Confira o arquivo de comandos completo no GitHub.
Ensinar a análise de modelos
Esse foi, de longe, o desafio mais difícil e importante. Quando o modelo faz uma pergunta, como "Seu personagem tem um chapéu?", e o jogador responde que não, como o modelo sabe quais personagens no tabuleiro foram eliminados?
O modelo deve eliminar todos que estiverem usando um chapéu. Minhas primeiras tentativas foram repletas de erros lógicos, e às vezes o modelo eliminava os caracteres errados ou nenhum caractere. Além disso, o que é um "chapéu"? Um "gorro" conta como um "chapéu"? Isso também pode acontecer em um debate entre pessoas. E, claro, erros gerais acontecem. Do ponto de vista da IA, o cabelo pode parecer um chapéu.
Redesenhei a arquitetura para separar a percepção da dedução de código:
A IA é responsável pela análise visual. Os modelos são excelentes em análise visual. Instruí o modelo a retornar a pergunta e uma análise detalhada em um esquema JSON estrito. O modelo analisa cada personagem no tabuleiro e responde à pergunta: "Esse personagem tem essa característica?" O modelo retorna um objeto JSON estruturado:
{ "character_id": "...", "has_feature": true }
Mais uma vez, os dados estruturados são essenciais para um resultado positivo.
O código do jogo usa a análise para tomar a decisão final. O código do aplicativo verifica a resposta do jogador ("Sim" ou "Não") e itera pela análise da IA. Se o jogador disser "Não", o código vai eliminar todos os caracteres em que
has_feature
étrue
.
Descobri que essa divisão do trabalho é fundamental para criar aplicativos de IA confiáveis. Use a IA para recursos analíticos e deixe as decisões binárias para o código do aplicativo.
Para verificar a percepção do modelo, criei uma visualização dessa análise. Isso facilitou a confirmação de que a percepção do modelo estava correta.
Engenharia de comando
No entanto, mesmo com essa separação, percebi que a percepção do modelo ainda podia ser falha. Ele pode julgar mal se um personagem usava óculos, levando a uma eliminação frustrante e incorreta. Para combater isso, testei um processo de duas etapas: a IA fazia a pergunta. Depois de receber a resposta do jogador, ele faria uma segunda análise com a resposta como contexto. A teoria era que uma segunda análise poderia detectar erros da primeira.
Veja como esse fluxo teria funcionado:
- Turno da IA (chamada de API 1): a IA pergunta: "Seu personagem tem barba?"
- Vez do jogador: o jogador olha para o personagem secreto, que está sem barba, e responde: "Não".
- Turno da IA (chamada de API 2): a IA pede a si mesma para analisar todos os caracteres restantes novamente e determinar quais eliminar com base na resposta do jogador.
Na etapa dois, o modelo ainda pode entender mal um personagem com uma barba rala como "sem barba" e não eliminá-lo, mesmo que o usuário esperasse isso. O erro de percepção principal não foi corrigido, e a etapa extra apenas atrasou os resultados. Ao jogar contra um humano, podemos especificar um acordo ou esclarecimento sobre isso. Na configuração atual com nosso oponente de IA, isso não acontece.
Esse processo adicionou latência de uma segunda chamada de API, sem ganhar um aumento significativo na acurácia. Se o modelo estava errado na primeira vez, ele também estava errado na segunda. Reverti o comando para que ele seja revisado apenas uma vez.
Melhorar em vez de adicionar mais análises
Confiei em um princípio de UX: a solução não era mais análise, mas uma melhor análise.
Investi muito em refinar o comando, adicionando instruções explícitas para que o modelo verificasse o trabalho e se concentrasse em recursos distintos, o que se mostrou uma estratégia mais eficaz para melhorar a acurácia. Confira como funciona o fluxo atual, mais confiável:
Turno da IA (chamada de API): o modelo é solicitado a gerar a pergunta e a análise interna ao mesmo tempo, retornando um único objeto JSON.
- Pergunta: "O personagem usa óculos?"
- Análise (dados):
[ {character_id: 'brad', has_feature: true}, {character_id: 'alex', has_feature: false}, {character_id: 'gina', has_feature: true}, ... ]
Vez do jogador: o personagem secreto do jogador é Alex (sem óculos), então ele responde "Não".
A rodada termina: o código JavaScript do aplicativo assume o controle. Não é necessário perguntar mais nada à IA. Ele itera os dados de análise da etapa 1.
- O jogador disse "Não".
- O código procura todos os caracteres em que
has_feature
é verdadeiro. - Ele vira Brad e Gina para baixo. A lógica é determinista e instantânea.
Essa experimentação foi crucial, mas exigiu muitas tentativas e erros. Eu não tinha ideia se isso ia melhorar. Às vezes, ficava ainda pior. Determinar como conseguir os resultados mais consistentes não é uma ciência exata (ainda, se é que algum dia será).
Mas depois de algumas partidas com meu novo oponente de IA, surgiu um novo problema fantástico: um impasse.
Impasse de fuga
Quando restavam apenas dois ou três caracteres muito semelhantes, o modelo ficava preso em um loop. Ele faria uma pergunta sobre uma característica que todos compartilhavam, como: "Seu personagem usa um chapéu?"
Meu código identificaria corretamente isso como uma rodada desperdiçada, e a IA tentaria outro recurso igualmente amplo que os personagens também compartilham, como: "Seu personagem usa óculos?"
Melhorei o comando com uma nova regra: se uma tentativa de geração de perguntas falhar e houver três ou menos caracteres restantes, a estratégia muda.
A nova instrução é explícita: "Em vez de um recurso amplo, pergunte sobre um recurso visual mais específico, exclusivo ou combinado para encontrar uma diferença". Por exemplo, em vez de perguntar se o personagem usa um chapéu, ele é solicitado a perguntar se ele está usando um boné de beisebol.
Isso força o modelo a analisar as imagens com mais atenção para encontrar o pequeno detalhe que pode levar a uma descoberta, fazendo com que a estratégia de fim de jogo funcione um pouco melhor na maioria das vezes.
Dar amnésia ao modelo
A maior vantagem de um modelo de linguagem é a memória. Mas, neste jogo, o maior ponto forte se tornou uma fraqueza. Quando eu iniciava um segundo jogo, ele fazia perguntas confusas ou irrelevantes. É claro que meu oponente de IA inteligente estava retendo todo o histórico de conversa do jogo anterior. Ele tentava entender dois (ou até mais) jogos ao mesmo tempo.
Em vez de reutilizar a mesma sessão de IA, agora eu a destruo explicitamente no final de cada jogo, basicamente causando amnésia à IA.
Quando você clica em Jogar de novo, a função startNewGameSession()
redefine o
tabuleiro e cria uma nova sessão de IA. Essa foi uma lição interessante sobre gerenciar o estado da sessão não apenas no app, mas no próprio modelo de IA.
Recursos extras: jogos personalizados e entrada de voz
Para tornar a experiência mais envolvente, adicionei dois recursos extras:
Personagens personalizados: com o
getUserMedia()
, os jogadores podem usar a câmera para criar um conjunto de cinco personagens. Usei o IndexedDB para salvar os caracteres, um banco de dados do navegador perfeito para armazenar dados binários, como blobs de imagem. Quando você cria um conjunto personalizado, ele é salvo no seu navegador, e uma opção de repetição aparece no menu principal.Entrada de texto por voz: o modelo do lado do cliente é multimodal. Ele pode processar texto, imagens e áudio. Usando a API MediaRecorder para capturar a entrada do microfone, posso transmitir o blob de áudio resultante para o modelo com um comando: "Transcreva o áudio a seguir...". Isso adiciona uma maneira divertida de jogar (e uma maneira divertida de ver como ele interpreta meu sotaque flamengo). Criei isso principalmente para mostrar a versatilidade desse novo recurso da Web, mas, na verdade, eu estava cansado de digitar perguntas sem parar.
Considerações finais
Criar o "Quem é Quem da IA?" foi um desafio. Mas com um pouco de ajuda da leitura de documentos e de uma IA para depurar IA (sim... Fiz isso), acabou sendo um experimento divertido. Ele destacou o imenso potencial de executar um modelo no navegador para criar uma experiência privada, rápida e sem necessidade de Internet. Esse ainda é um experimento, e às vezes o oponente não joga perfeitamente. Não é perfeito em termos de pixels ou lógica. Com a IA generativa, os resultados dependem do modelo.
Em vez de buscar a perfeição, vou tentar melhorar o resultado.
Esse projeto também destacou os desafios constantes da engenharia de comando. Essa solicitação se tornou uma parte enorme do processo, e nem sempre a mais divertida. Mas a lição mais importante que aprendi foi arquitetar o aplicativo para separar percepção de dedução, dividindo as capacidades da IA e do código. Mesmo com essa separação, descobri que a IA ainda podia cometer erros óbvios (para um humano), como confundir tatuagens com maquiagem ou perder a noção de quem era o personagem secreto em discussão.
Em cada caso, a solução foi tornar os comandos ainda mais explícitos, adicionando instruções que parecem óbvias para um humano, mas são essenciais para o modelo.
Às vezes, o jogo parecia injusto. Às vezes, parecia que a IA "sabia" o personagem secreto antes da hora, mesmo que o código nunca tivesse compartilhado essa informação explicitamente. Isso mostra uma parte crucial da disputa entre humanos e máquinas:
O comportamento de uma IA não precisa apenas ser correto, mas também parecer justo.
Por isso, atualizei os comandos com instruções diretas, como "Você NÃO sabe qual personagem eu escolhi" e "Sem trapaça". Aprendi que, ao criar agentes de IA, é importante definir limitações, talvez até mais do que instruções.
A interação com o modelo pode continuar sendo aprimorada. Ao trabalhar com um modelo integrado, você perde parte do poder e da confiabilidade de um modelo massivo do lado do servidor, mas ganha privacidade, velocidade e capacidade off-line. Para um jogo como esse, valeu a pena testar essa troca. O futuro da IA do lado do cliente está melhorando a cada dia, os modelos também estão ficando menores, e mal posso esperar para ver o que vamos poder criar em seguida.