Crea un gioco di indovinelli con l'API Prompt

Data di pubblicazione: 10 ottobre 2025

Bambini in età scolare che giocano a Indovina Chi nel 2014.

Il classico gioco da tavolo Indovina Chi? è un capolavoro di ragionamento deduttivo. Ogni giocatore inizia con un tabellone di volti e, attraverso una serie di domande con risposta sì o no, restringe le possibilità fino a quando non riesce a identificare con certezza il personaggio segreto dell'avversario.

Dopo aver visto una demo dell'AI integrata a Google I/O Connect, mi sono chiesto: cosa succederebbe se potessi giocare a Indovina Chi? contro un'AI che vive nel browser? Con l'AI lato client, le foto verrebbero interpretate localmente, quindi un custom Guess Who? di amici e familiari rimarrebbe privato e sicuro sul mio dispositivo.

Il mio background è principalmente nello sviluppo di UI e UX e sono abituato a creare esperienze perfette al pixel. Spero di esserci riuscito con la mia interpretazione.

La mia applicazione, AI Guess Who?, è creata con React e utilizza l'API Prompt e un modello integrato nel browser per creare un avversario sorprendentemente capace. Durante questo processo, ho scoperto che non è così semplice ottenere risultati "pixel perfect". Tuttavia, questa applicazione dimostra come l'AI possa essere utilizzata per creare una logica di gioco ponderata e l'importanza dell'ingegneria del prompt per migliorare questa logica e ottenere i risultati che ti aspetti.

Continua a leggere per scoprire di più sull'integrazione dell'AI integrata, sulle sfide che ho dovuto affrontare e sulle soluzioni che ho trovato. Puoi giocare e trovare il codice sorgente su GitHub.

Fondamenta del gioco: un'app React

Prima di esaminare l'implementazione dell'AI, esamineremo la struttura dell'applicazione. Ho creato un'applicazione React standard con TypeScript, con un file App.tsx centrale che funge da direttore del gioco. Questo file contiene:

  • Stato del gioco: un'enumerazione che tiene traccia della fase corrente del gioco (ad esempio PLAYER_TURN_ASKING, AI_TURN, GAME_OVER). Si tratta della parte più importante dello stato, in quanto determina cosa viene visualizzato nell'interfaccia e quali azioni sono disponibili per il giocatore.
  • Elenchi dei personaggi: esistono più elenchi che indicano i personaggi attivi, il personaggio segreto di ogni giocatore e i personaggi eliminati dal tabellone.
  • Chat di gioco: un registro continuo di domande, risposte e messaggi di sistema.

L'interfaccia è suddivisa in componenti logici:

GameSetup è la schermata iniziale.
GameBoard mostra la griglia dei personaggi e i controlli della chat per gestire tutti gli input degli utenti.

Con l'aumentare delle funzionalità del gioco, è aumentata anche la sua complessità. Inizialmente, l'intera logica del gioco era gestita all'interno di un unico, grande hook React personalizzato, useGameLogic, ma divenne rapidamente troppo grande per essere navigato e sottoposto a debug. Per migliorare la manutenibilità, ho refattorizzato questo hook in più hook, ognuno con una singola responsabilità. Ad esempio:

  • useGameState gestisce lo stato principale
  • usePlayerActions indica il turno del giocatore
  • useAIActions è per la logica dell'AI

L'hook principale useGameLogic ora funge da compositore pulito, raggruppando questi hook più piccoli. Questa modifica dell'architettura non ha alterato la funzionalità del gioco, ma ha reso il codebase molto più pulito.

Logica di gioco con l'API Prompt

Il fulcro di questo progetto è l'utilizzo dell'API Prompt.

Ho aggiunto la logica di gioco AI a builtInAIService.ts. Queste sono le sue principali responsabilità:

  1. Consenti risposte binarie restrittive.
  2. Insegnare al modello la strategia di gioco.
  3. Insegnare l'analisi del modello.
  4. Far perdere la memoria al modello.

Consenti risposte binarie restrittive

In che modo il giocatore interagisce con l'AI? Quando un giocatore chiede: "Il tuo personaggio ha un cappello?", l'AI deve "guardare" l'immagine del suo personaggio segreto e dare una risposta chiara.

I miei primi tentativi sono stati un disastro. La risposta era conversazionale: "No, il personaggio a cui sto pensando, Isabella, non sembra indossare un cappello", invece di offrire una risposta binaria sì o no. Inizialmente, ho risolto il problema con un prompt molto rigoroso, che essenzialmente imponeva al modello di rispondere solo con "Sì" o "No".

Anche se questo metodo funzionava, ho scoperto un modo ancora migliore di utilizzare l'output strutturato. Fornendo lo schema JSON al modello, ho potuto garantire una risposta vera o falsa.

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

In questo modo ho potuto semplificare il prompt e lasciare che il mio codice gestisse in modo affidabile la risposta:

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

Insegnare al modello la strategia di gioco

Chiedere al modello di rispondere a una domanda è molto più semplice che chiedergli di iniziare a porre domande. Un buon giocatore di Indovina Chi? non fa domande a caso. Pone domande che eliminano il maggior numero di caratteri contemporaneamente. Una domanda ideale riduce della metà i possibili caratteri rimanenti utilizzando domande binarie.

Come si insegna questa strategia a un modello? Ancora una volta, il prompt engineering. Il prompt per generateAIQuestion() è in realtà una lezione concisa sulla teoria dei giochi di Indovina Chi?.

Inizialmente, ho chiesto al modello di "fare una buona domanda". I risultati sono stati imprevedibili. Per migliorare i risultati, ho aggiunto vincoli negativi. Il prompt ora include istruzioni simili a:

  • "CRITICO: chiedi informazioni SOLO sulle funzionalità esistenti"
  • "CRITICO: sii originale. NON ripetere una domanda".

Questi vincoli restringono il focus del modello, impedendogli di porre domande irrilevanti, il che lo rende un avversario molto più piacevole. Puoi esaminare il file di prompt completo su GitHub.

Insegnare l'analisi del modello

Questa è stata di gran lunga la sfida più difficile e importante. Quando il modello pone una domanda, ad esempio "Il tuo personaggio ha un cappello?" e il giocatore risponde di no, come fa il modello a sapere quali personaggi sulla sua scacchiera sono eliminati?

Il modello dovrebbe eliminare tutte le persone con un cappello. I miei primi tentativi erano pieni di errori logici e a volte il modello eliminava i caratteri sbagliati o nessun carattere. Inoltre, che cos'è un "cappello"? Un "berretto" conta come un "cappello"? Questo è, diciamocelo, qualcosa che può accadere anche in un dibattito umano. E, ovviamente, capitano errori generali. I capelli possono sembrare un cappello dal punto di vista dell'AI.

Ho riprogettato l'architettura per separare la percezione dalla deduzione del codice:

  1. L'AI è responsabile dell'analisi visiva. I modelli eccellono nell'analisi visiva. Ho indicato al modello di restituire la domanda e un'analisi dettagliata in uno schema JSON rigoroso. Il modello analizza ogni personaggio sulla sua scheda e risponde alla domanda: "Questo personaggio ha questa caratteristica?" Il modello restituisce un oggetto JSON strutturato:

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

    Ancora una volta, i dati strutturati sono fondamentali per un risultato positivo.

  2. Il codice del gioco utilizza l'analisi per prendere la decisione finale. Il codice dell'applicazione controlla la risposta del giocatore ("Sì" o "No") e scorre l'analisi dell'AI. Se il giocatore ha risposto "No", il codice sa di dover eliminare ogni carattere in cui has_feature è true.

Ho scoperto che questa divisione del lavoro è fondamentale per creare applicazioni di AI affidabili. Utilizza l'AI per le sue funzionalità di analisi e lascia le decisioni binarie al codice dell'applicazione.

Per verificare la percezione del modello, ho creato una visualizzazione di questa analisi. In questo modo è stato più facile verificare se la percezione del modello era corretta.

Prompt engineering

Tuttavia, anche con questa separazione, ho notato che la percezione del modello potrebbe ancora essere imperfetta. Potrebbe giudicare erroneamente se un personaggio indossava gli occhiali, portando a un'eliminazione frustrante e errata. Per contrastare questo problema, ho sperimentato una procedura in due fasi: l'AI pone la domanda. Dopo aver ricevuto la risposta del giocatore, esegue una seconda analisi con la risposta come contesto. La teoria era che un secondo controllo avrebbe potuto rilevare gli errori del primo.

Ecco come avrebbe funzionato il flusso:

  1. Turno dell'AI (chiamata API 1): l'AI chiede: "Il tuo personaggio ha la barba?"
  2. Turno del giocatore: il giocatore guarda il suo personaggio segreto, che è rasato, e risponde "No".
  3. Turno dell'AI (chiamata API 2): l'AI si chiede di esaminare di nuovo tutti i caratteri rimanenti e di determinare quali eliminare in base alla risposta del giocatore.

Nel secondo passaggio, il modello potrebbe ancora percepire un personaggio con una barba leggera come "senza barba" e non riuscire a eliminarlo, anche se l'utente si aspettava che lo facesse. L'errore di percezione principale non è stato corretto e il passaggio aggiuntivo ha solo ritardato i risultati. Quando giochi contro un avversario umano, possiamo specificare un accordo o un chiarimento in merito; nella configurazione attuale con il nostro avversario AI, non è così.

Questo processo ha aggiunto latenza da una seconda chiamata API, senza ottenere un aumento significativo dell'accuratezza. Se il modello era sbagliato la prima volta, spesso lo era anche la seconda volta. Ho ripristinato la richiesta di revisione una sola volta.

Migliorare anziché aggiungere altre analisi

Mi sono basato su un principio UX: la soluzione non era un'analisi più approfondita, ma un'analisi migliore.

Ho investito molto tempo per perfezionare il prompt, aggiungendo istruzioni esplicite per il modello per controllare il proprio lavoro e concentrarsi su caratteristiche distinte, il che si è rivelato una strategia più efficace per migliorare l'accuratezza. Ecco come funziona il flusso attuale, più affidabile:

  1. Turno dell'AI (chiamata API): al modello viene chiesto di generare contemporaneamente sia la domanda sia l'analisi interna, restituendo un singolo oggetto JSON.

    1. Domanda: "Il tuo personaggio porta gli occhiali?"
    2. Analisi (dati):
    [
      {character_id: 'brad', has_feature: true},
      {character_id: 'alex', has_feature: false},
      {character_id: 'gina', has_feature: true},
      ...
    ]
    
  2. Turno del giocatore: il personaggio segreto del giocatore è Alex (senza occhiali), quindi risponde "No".

  3. Termina il round: il codice JavaScript dell'applicazione prende il sopravvento. Non deve chiedere altro all'AI. Esegue l'iterazione dei dati di analisi del passaggio 1.

    1. Il giocatore ha risposto "No".
    2. Il codice cerca ogni carattere in cui has_feature è true.
    3. Si abbassano Brad e Gina. La logica è deterministica e istantanea.

Questa sperimentazione è stata fondamentale, ma ha richiesto molti tentativi ed errori. Non sapevo se sarebbe andata meglio. A volte, la situazione peggiorava ulteriormente. Determinare come ottenere i risultati più coerenti non è una scienza esatta (ancora, se mai lo sarà...).

Ma dopo qualche partita con il mio nuovo avversario AI, è apparso un nuovo problema fantastico: una situazione di stallo.

Escape deadlock

Quando rimanevano solo due o tre caratteri molto simili, il modello si bloccava in un ciclo. Poneva una domanda su una caratteristica condivisa da tutti, ad esempio: "Il tuo personaggio indossa un cappello?".

Il mio codice lo identificherebbe correttamente come un turno sprecato e l'AI proverebbe un'altra funzionalità altrettanto ampia condivisa anche dai personaggi, ad esempio: "Il tuo personaggio porta gli occhiali?"

Ho migliorato il prompt con una nuova regola: se un tentativo di generazione di domande non va a buon fine e rimangono tre o meno caratteri, la strategia cambia.

La nuova istruzione è esplicita: "Invece di una funzionalità generica, devi chiedere informazioni su una funzionalità visiva più specifica, unica o combinata per trovare una differenza". Ad esempio, invece di chiedere se il personaggio indossa un cappello, viene chiesto se indossa un cappellino da baseball.

In questo modo, il modello è costretto a esaminare le immagini molto più da vicino per trovare il piccolo dettaglio che può finalmente portare a una svolta, migliorando un po' la sua strategia di fine partita, la maggior parte delle volte.

Far dimenticare al modello

Il punto di forza di un modello linguistico è la sua memoria. Ma in questo gioco, il suo punto di forza è diventato un punto debole. Quando ho iniziato una seconda partita, mi ha posto domande confuse o non pertinenti. Ovviamente, il mio avversario di AI intelligente stava conservando l'intera cronologia della chat della partita precedente. Stava cercando di dare un senso a due (o anche più) partite contemporaneamente.

Invece di riutilizzare la stessa sessione di IA, ora la distruggo esplicitamente alla fine di ogni partita, dando in pratica all'IA l'amnesia.

Quando fai clic su Gioca di nuovo, la funzione startNewGameSession() reimposta il tabellone e crea una nuova sessione di AI. È stata una lezione interessante sulla gestione dello stato della sessione non solo nell'app, ma all'interno del modello di AI stesso.

Funzionalità avanzate: giochi personalizzati e input vocale

Per rendere l'esperienza più coinvolgente, ho aggiunto due funzionalità extra:

  1. Personaggi personalizzati: con getUserMedia(), i giocatori possono usare la fotocamera per creare il proprio set di 5 caratteri. Ho utilizzato IndexedDB per salvare i caratteri, un database del browser perfetto per archiviare dati binari come i blob di immagini. Quando crei un set personalizzato, questo viene salvato nel browser e nel menu principale viene visualizzata un'opzione di replay.

  2. Input vocale: il modello lato client è multimodale. Può gestire testo, immagini e anche audio. Utilizzando l'API MediaRecorder per acquisire l'input del microfono, potrei inviare il blob audio risultante al modello con un prompt: "Trascrivi il seguente audio...". In questo modo, si aggiunge un modo divertente di giocare (e un modo divertente di vedere come interpreta il mio accento fiammingo). Ho creato questo principalmente per mostrare la versatilità di questa nuova funzionalità web, ma a dire il vero, ero stanco di digitare le domande più e più volte.

Considerazioni finali

Creare "Indovina Chi? AI" è stata sicuramente una sfida. Ma con un po' di aiuto dalla lettura di documenti e un po' di AI per eseguire il debug dell'AI (sì… Si è rivelato un esperimento divertente. Ha evidenziato l'immenso potenziale dell'esecuzione di un modello nel browser per creare un'esperienza privata, veloce e senza necessità di internet. Si tratta ancora di un esperimento e a volte l'avversario non gioca in modo perfetto. Non è perfetto in termini di pixel o logica. Con l'AI generativa, i risultati dipendono dal modello.

Invece di puntare alla perfezione, cercherò di migliorare il risultato.

Questo progetto ha anche evidenziato le sfide costanti dell'ingegneria dei prompt. I prompt sono diventati una parte fondamentale, anche se non sempre la più divertente. Ma la lezione più importante che ho imparato è stata progettare l'applicazione per separare la percezione dalla deduzione, dividendo le capacità dell'AI e del codice. Nonostante questa separazione, ho scoperto che l'AI poteva ancora commettere errori ovvi (per un essere umano), come confondere i tatuaggi con il trucco o perdere il filo del discorso sul personaggio segreto di qualcuno.

Ogni volta, la soluzione è stata rendere i prompt ancora più espliciti, aggiungendo istruzioni che sembrano ovvie per un essere umano, ma sono essenziali per il modello.

A volte, la partita sembrava ingiusta. A volte, mi sembrava che l'AI "conoscesse" il personaggio segreto in anticipo, anche se il codice non condivideva mai esplicitamente queste informazioni. Mostra un aspetto cruciale del confronto tra uomo e macchina:

Il comportamento di un'AI non deve solo essere corretto, ma deve sembrare equo.

Per questo motivo ho aggiornato i prompt con istruzioni dirette, ad esempio "NON sai quale personaggio ho scelto" e "Non barare". Ho imparato che quando crei agenti AI, devi dedicare tempo a definire le limitazioni, probabilmente anche più che alle istruzioni.

L'interazione con il modello potrebbe continuare a essere migliorata. Se utilizzi un modello integrato, perdi parte della potenza e dell'affidabilità di un modello lato server di grandi dimensioni, ma guadagni in termini di privacy, velocità e funzionalità offline. Per un gioco come questo, valeva davvero la pena sperimentare questo compromesso. Il futuro dell'AI lato client migliora di giorno in giorno, i modelli diventano sempre più piccoli e non vedo l'ora di scoprire cosa potremo creare in futuro.