Crea un juego de adivinanzas con la API de Prompt

Publicado: 10 de octubre de 2025

Niños en edad escolar jugando al juego ¿Quién es quién? en 2014.

El clásico juego de mesa Adivina quién es una clase magistral de razonamiento deductivo. Cada jugador comienza con un tablero de rostros y, a través de una serie de preguntas de sí o no, reduce las posibilidades hasta que puede identificar con confianza el personaje secreto de su oponente.

Después de ver una demostración de la IA integrada en Google I/O Connect, me pregunté: ¿Qué pasaría si pudiera jugar a ¿Quién es quién? contra una IA que vive en el navegador? Con la IA del cliente, las fotos se interpretarían de forma local, por lo que un ¿Quién es quién? personalizado de amigos y familiares seguiría siendo privado y seguro en mi dispositivo.

Mi experiencia se centra principalmente en el desarrollo de IU y UX, y estoy acostumbrado a crear experiencias perfectas en píxeles. Esperaba poder hacer exactamente eso con mi interpretación.

Mi aplicación, AI Guess Who?, se creó con React y usa la API de Prompt y un modelo integrado en el navegador para crear un oponente sorprendentemente capaz. En este proceso, descubrí que no es tan simple obtener resultados "perfectos en píxeles". Sin embargo, esta aplicación demuestra cómo se puede usar la IA para crear una lógica de juego reflexiva y la importancia de la ingeniería de instrucciones para refinar esta lógica y obtener los resultados esperados.

Sigue leyendo para obtener más información sobre la integración de IA incorporada, los desafíos que enfrenté y las soluciones que encontré. Puedes jugar el juego y encontrar el código fuente en GitHub.

Base del juego: Una app de React

Antes de analizar la implementación de la IA, revisaremos la estructura de la aplicación. Compilé una aplicación estándar de React con TypeScript, con un archivo App.tsx central que actúa como el director del juego. Este archivo contiene lo siguiente:

  • Estado del juego: Es una enumeración que hace un seguimiento de la fase actual del juego (como PLAYER_TURN_ASKING, AI_TURN, GAME_OVER). Este es el elemento más importante del estado, ya que determina qué muestra la interfaz y qué acciones están disponibles para el jugador.
  • Listas de personajes: Hay varias listas que designan los personajes activos, el personaje secreto de cada jugador y los personajes que se eliminaron del tablero.
  • Chat del juego: Es un registro continuo de preguntas, respuestas y mensajes del sistema.

La interfaz se divide en componentes lógicos:

GameSetup es la pantalla inicial.
GameBoard muestra la cuadrícula de caracteres y los controles de chat para controlar todas las entradas del usuario.

A medida que crecían las funciones del juego, también lo hacía su complejidad. Inicialmente, toda la lógica del juego se administraba dentro de un solo y gran hook de React personalizado, useGameLogic, pero rápidamente se volvió demasiado grande para navegar y depurar. Para mejorar el mantenimiento, refactoricé este hook en varios hooks, cada uno con una sola responsabilidad. Por ejemplo:

  • useGameState administra el estado principal
  • usePlayerActions es para el turno del jugador
  • useAIActions es para la lógica de la IA

El gancho useGameLogic principal ahora actúa como un compositor limpio, ya que une estos ganchos más pequeños. Este cambio arquitectónico no alteró la funcionalidad del juego, pero hizo que la base de código fuera mucho más clara.

Lógica del juego con la API de Prompt

El núcleo de este proyecto es el uso de la API de Prompt.

Agregué la lógica del juego de IA a builtInAIService.ts. Estas son sus responsabilidades clave:

  1. Permite respuestas restrictivas y binarias.
  2. Enseñar al modelo la estrategia del juego
  3. Enseña el análisis del modelo.
  4. Hacer que el modelo tenga amnesia

Permite respuestas binarias y restrictivas

¿Cómo interactúa el jugador con la IA? Cuando un jugador pregunta: "¿Tu personaje tiene un sombrero?", la IA debe "mirar" la imagen de su personaje secreto y dar una respuesta clara.

Mis primeros intentos fueron un desastre. La respuesta fue conversacional: "No, el personaje en el que estoy pensando, Isabella, no parece llevar un sombrero", en lugar de ofrecer un sí o un no binario. Inicialmente, resolví esto con una instrucción muy estricta, que esencialmente le dictaba al modelo que solo respondiera con "Sí" o "No".

Si bien esto funcionó, descubrí una forma aún mejor de hacerlo con salida estructurada. Al proporcionar el esquema JSON al modelo, pude garantizar una respuesta verdadera o falsa.

const schema = { type: "boolean" };
const result = session.prompt(prompt, { responseConstraint: schema });

Esto me permitió simplificar la instrucción y dejar que mi código controlara la respuesta de forma confiable:

JSON.parse(result) ? "Yes" : "No"

Enseña al modelo la estrategia del juego

Indicarle al modelo que responda una pregunta es mucho más simple que hacer que el modelo inicie y haga preguntas. Un buen jugador de ¿Quién es quién? no hace preguntas al azar. Hacen preguntas que eliminan la mayor cantidad de caracteres a la vez. Una pregunta ideal reduce a la mitad los posibles caracteres restantes con preguntas binarias.

¿Cómo le enseñas esa estrategia a un modelo? Nuevamente, ingeniería de instrucciones. La instrucción para generateAIQuestion() es, en realidad, una lección concisa sobre la teoría de juegos de ¿Quién es quién?

Al principio, le pedí al modelo que "hiciera una buena pregunta". Los resultados fueron impredecibles. Para mejorar los resultados, agregué restricciones negativas. Ahora, la instrucción incluye indicaciones similares a las siguientes:

  • "CRÍTICO: Pregunta SOLO sobre las funciones existentes"
  • "CRÍTICO: Sé original. NO repitas una pregunta".

Estas restricciones reducen el enfoque del modelo y evitan que haga preguntas irrelevantes, lo que lo convierte en un oponente mucho más agradable. Puedes revisar el archivo de instrucciones completo en GitHub.

Enseña el análisis del modelo

Este fue, sin duda, el desafío más difícil e importante. Cuando el modelo hace una pregunta, como "¿Tu personaje tiene un sombrero?", y el jugador responde que no, ¿cómo sabe el modelo qué personajes de su tablero se eliminan?

El modelo debería eliminar a todas las personas que usan sombrero. Mis primeros intentos estuvieron plagados de errores lógicos y, a veces, el modelo eliminaba los caracteres incorrectos o no eliminaba ninguno. Además, ¿qué es un "sombrero"? ¿Un “gorro” cuenta como un “sombrero”? Siendo honestos, esto también puede ocurrir en un debate entre humanos. Y, por supuesto, también se cometen errores generales. Desde la perspectiva de la IA, el cabello puede parecer un sombrero.

Rediseñé la arquitectura para separar la percepción de la deducción de código:

  1. La IA es responsable del análisis visual. Los modelos son excelentes para el análisis visual. Le indiqué al modelo que devolviera su pregunta y un análisis detallado en un esquema JSON estricto. El modelo analiza cada carácter de su tablero y responde la pregunta: "¿Este carácter tiene este rasgo?". El modelo devuelve un objeto JSON estructurado:

    { "character_id": "...", "has_feature": true }
    

    Una vez más, los datos estructurados son clave para obtener un resultado exitoso.

  2. El código del juego usa el análisis para tomar la decisión final. El código de la aplicación verifica la respuesta del jugador ("Sí" o "No") y realiza iteraciones en el análisis de la IA. Si el jugador dijo "No", el código sabe que debe eliminar cada carácter donde has_feature es true.

Descubrí que esta división del trabajo es clave para crear aplicaciones de IA confiables. Usa la IA por sus capacidades analíticas y deja las decisiones binarias al código de tu aplicación.

Para verificar la percepción del modelo, creé una visualización de este análisis. Esto facilitó la confirmación de si la percepción del modelo era correcta.

Ingeniería de instrucciones

Sin embargo, incluso con esta separación, noté que la percepción del modelo aún podía ser defectuosa. Podría juzgar mal si un personaje usaba anteojos, lo que llevaría a una eliminación frustrante e incorrecta. Para combatir esto, experimenté con un proceso de dos pasos: la IA haría su pregunta. Después de recibir la respuesta del jugador, realizaría un segundo análisis nuevo con la respuesta como contexto. La teoría era que una segunda revisión podría detectar errores de la primera.

Así habría funcionado ese flujo:

  1. Turno de la IA (llamada a la API 1): La IA pregunta: "¿Tu personaje tiene barba?".
  2. Turno del jugador: El jugador mira a su personaje secreto, que está afeitado, y responde: "No".
  3. Turno de la IA (llamada a la API 2): La IA se pide a sí misma que vuelva a mirar todos sus personajes restantes y determine cuáles eliminar según la respuesta del jugador.

En el segundo paso, es posible que el modelo siga percibiendo erróneamente a un personaje con barba incipiente como "sin barba" y no lo elimine, aunque el usuario esperaba que lo hiciera. El error de percepción principal no se corrigió, y el paso adicional solo retrasó los resultados. Cuando jugamos contra un oponente humano, podemos especificar un acuerdo o una aclaración sobre esto. En la configuración actual con nuestro oponente de IA, este no es el caso.

Este proceso agregó latencia desde una segunda llamada a la API, sin obtener un aumento significativo en la precisión. Si el modelo se equivocó la primera vez, a menudo también se equivocaba la segunda. Revertí la instrucción para que se revise solo una vez.

Mejora en lugar de agregar más análisis

Me basé en un principio de UX: la solución no era más análisis, sino un análisis mejor.

Invertí mucho tiempo en definir mejor la instrucción, agregando instrucciones explícitas para que el modelo verificara su trabajo y se enfocara en características distintas, lo que resultó ser una estrategia más eficaz para mejorar la precisión. Así funciona el flujo actual, que es más confiable:

  1. Turno de IA (llamada a la API): Se le solicita al modelo que genere su pregunta y su análisis interno al mismo tiempo, y que devuelva un solo objeto JSON.

    1. Pregunta: "¿Tu personaje usa anteojos?"
    2. Análisis (datos):
    [
      {character_id: 'brad', has_feature: true},
      {character_id: 'alex', has_feature: false},
      {character_id: 'gina', has_feature: true},
      ...
    ]
    
  2. Turno del jugador: El personaje secreto del jugador es Alex (sin anteojos), por lo que responde "No".

  3. Finaliza la ronda: El código JavaScript de la aplicación toma el control. No necesita preguntarle nada más a la IA. Itera los datos de análisis del paso 1.

    1. El jugador dijo "No".
    2. El código busca cada carácter en el que has_feature es verdadero.
    3. Baja a Brad y Gina. La lógica es determinística y instantánea.

Esta experimentación fue fundamental, pero requirió muchas pruebas y errores. No sabía si iba a mejorar. A veces, incluso empeoraba. Determinar cómo obtener los resultados más coherentes no es una ciencia exacta (todavía, si es que alguna vez lo será…).

Pero, después de algunas rondas con mi nuevo oponente de IA, apareció un nuevo problema fantástico: un punto muerto.

Cómo evitar el interbloqueo

Cuando solo quedaban dos o tres caracteres muy similares, el modelo se quedaba atascado en un bucle. Se les haría una pregunta sobre una característica que todos compartían, como "¿Tu personaje usa sombrero?".

Mi código identificaría correctamente esto como un turno perdido, y la IA intentaría otra característica igualmente amplia que los personajes también compartieran, como "¿Tu personaje usa anteojos?".

Mejoré la instrucción con una regla nueva: si falla un intento de generación de preguntas y quedan tres o menos caracteres, la estrategia cambia.

La nueva instrucción es explícita: "En lugar de una característica amplia, debes preguntar sobre una característica visual más específica, única o combinada para encontrar una diferencia". Por ejemplo, en lugar de preguntar si el personaje usa un sombrero, se le indica que pregunte si usa una gorra de béisbol.

Esto obliga al modelo a observar las imágenes con mayor detalle para encontrar el pequeño detalle que finalmente puede conducir a un avance, lo que hace que su estrategia de juego tardío funcione un poco mejor la mayoría de las veces.

Cómo hacer que el modelo tenga amnesia

La mayor fortaleza de un modelo de lenguaje es su memoria. Pero, en este juego, su mayor fortaleza se convirtió en una debilidad. Cuando comencé un segundo juego, me hizo preguntas confusas o irrelevantes. Por supuesto, mi oponente inteligente de IA conservaba todo el historial de chat de la partida anterior. Intentaba comprender dos (o incluso más) juegos a la vez.

En lugar de reutilizar la misma sesión de IA, ahora la destruyo explícitamente al final de cada juego, lo que esencialmente le da amnesia a la IA.

Cuando haces clic en Volver a jugar, la función startNewGameSession() restablece el tablero y crea una sesión de IA completamente nueva. Esta fue una lección interesante sobre la administración del estado de la sesión no solo en la app, sino también dentro del propio modelo de IA.

Funciones adicionales: Juegos personalizados y entrada de voz

Para que la experiencia sea más atractiva, agregué dos funciones adicionales:

  1. Personajes personalizados: Con getUserMedia(), los jugadores pueden usar la cámara para crear su propio conjunto de 5 personajes. Usé IndexedDB para guardar los caracteres, una base de datos del navegador perfecta para almacenar datos binarios, como BLOB de imágenes. Cuando creas un conjunto personalizado, se guarda en tu navegador y aparece una opción de repetición en el menú principal.

  2. Entrada de voz: El modelo del cliente es multimodal. Puede procesar texto, imágenes y audio. Con la API de MediaRecorder para capturar la entrada del micrófono, podría enviar el blob de audio resultante al modelo con una instrucción: "Transcribe el siguiente audio…". Esto agrega una forma divertida de jugar (y una forma divertida de ver cómo interpreta mi acento flamenco). Creé esto principalmente para mostrar la versatilidad de esta nueva capacidad web, pero, la verdad, estaba cansado de escribir preguntas una y otra vez.

Reflexiones finales

Crear "¿Quién es quién con IA?" fue un desafío. Pero con un poco de ayuda de la lectura de documentos y algo de IA para depurar la IA (sí… Lo hice, y resultó ser un experimento divertido. Destacó el inmenso potencial de ejecutar un modelo en el navegador para crear una experiencia privada, rápida y sin necesidad de Internet. Este sigue siendo un experimento y, a veces, el oponente no juega a la perfección. No es perfecto en términos de píxeles ni de lógica. Con la IA generativa, los resultados dependen del modelo.

En lugar de esforzarme por alcanzar la perfección, me enfocaré en mejorar el resultado.

Este proyecto también destacó los desafíos constantes de la ingeniería de instrucciones. Esa indicación se convirtió en una parte muy importante del proceso, y no siempre fue la más divertida. Sin embargo, la lección más importante que aprendí fue la de diseñar la aplicación para separar la percepción de la deducción, y dividir las capacidades de la IA y el código. Incluso con esa separación, descubrí que la IA aún podía cometer errores obvios (para un humano), como confundir tatuajes con maquillaje o perder la pista de qué personaje secreto se estaba discutiendo.

En cada ocasión, la solución fue hacer que las instrucciones fueran aún más explícitas, agregando indicaciones que parecen obvias para un humano, pero que son esenciales para el modelo.

A veces, el juego parecía injusto. En ocasiones, sentía que la IA "conocía" el carácter secreto de antemano, aunque el código nunca compartió esa información de forma explícita. Aquí se muestra una parte fundamental de la diferencia entre humanos y máquinas:

El comportamiento de una IA no solo debe ser correcto, sino que también debe sentirse justo.

Por eso, actualicé las instrucciones con indicaciones directas, como "NO sabes qué personaje elegí" y "No hagas trampa". Aprendí que, cuando se crean agentes de IA, se debe dedicar tiempo a definir las limitaciones, probablemente incluso más que a las instrucciones.

Se podría seguir mejorando la interacción con el modelo. Si trabajas con un modelo integrado, pierdes parte de la potencia y la confiabilidad de un modelo masivo del servidor, pero ganas privacidad, velocidad y capacidad sin conexión. Para un juego como este, valió la pena experimentar con esa compensación. El futuro de la IA del cliente mejora día a día, los modelos también se vuelven más pequeños y no puedo esperar a ver qué podremos crear a continuación.