Data publikacji: 10 października 2025 r.

Klasyczna gra planszowa Zgadnij kto? to mistrzowska lekcja dedukcji. Każdy gracz zaczyna z planszą pełną twarzy i za pomocą serii pytań, na które można odpowiedzieć „tak” lub „nie”, zawęża możliwości, aż będzie w stanie z pewnością zidentyfikować tajną postać przeciwnika.
Po obejrzeniu prezentacji wbudowanej AI na konferencji Google I/O Connect zastanawiałem się, co by było, gdybym mógł zagrać w „Zgadnij kto?” z AI, która działa w przeglądarce. Dzięki AI po stronie klienta zdjęcia byłyby interpretowane lokalnie, więc niestandardowa wersja gry „Zgadnij kto?” ze znajomymi i rodziną pozostałaby prywatna i bezpieczna na moim urządzeniu.
Mam doświadczenie głównie w projektowaniu interfejsu i UX, dlatego przyzwyczajony jestem do tworzenia rozwiązań dopracowanych w każdym szczególe. Miałem/am nadzieję, że uda mi się to osiągnąć dzięki mojej interpretacji.
Moja aplikacja AI Guess Who? została stworzona w React i korzysta z interfejsu Prompt API oraz wbudowanego w przeglądarkę modelu, aby stworzyć zaskakująco kompetentnego przeciwnika. W trakcie tego procesu odkryłem, że uzyskanie „idealnych” wyników nie jest takie proste. Ta aplikacja pokazuje jednak, jak można wykorzystać AI do tworzenia przemyślanej logiki gry, oraz jak ważne jest projektowanie promptów, aby udoskonalać tę logikę i uzyskiwać oczekiwane wyniki.
Czytaj dalej, aby dowiedzieć się więcej o wbudowanej integracji AI, napotkanych przeze mnie wyzwaniach i rozwiązaniach, które udało mi się znaleźć. Możesz zagrać w tę grę i znaleźć kod źródłowy na GitHubie.
Podstawa gry: aplikacja React
Zanim przyjrzymy się wdrożeniu AI, omówimy strukturę aplikacji. Utworzyłem standardową aplikację React w języku TypeScript z centralnym plikiem App.tsx, który pełni funkcję dyrygenta gry. Ten plik zawiera:
- Stan gry: wyliczenie, które śledzi bieżącą fazę gry (np.
PLAYER_TURN_ASKING,AI_TURN,GAME_OVER). Jest to najważniejszy element stanu, ponieważ określa, co jest wyświetlane w interfejsie i jakie działania są dostępne dla gracza. - Listy postaci: istnieje kilka list, które wskazują aktywne postacie, tajną postać każdego gracza oraz postacie wyeliminowane z planszy.
- Czat w grze: dziennik pytań, odpowiedzi i wiadomości systemowych.
Interfejs jest podzielony na logiczne komponenty:
Wraz z rozwojem funkcji gry rosła jej złożoność. Początkowo cała logika gry była zarządzana w ramach jednego dużego niestandardowego haka React useGameLogic, ale szybko stał się on zbyt duży, aby można było go przeglądać i debugować. Aby zwiększyć łatwość utrzymania, podzieliłem ten hook na kilka hooków, z których każdy ma jedno zadanie.
Na przykład:
useGameStatezarządza stanem podstawowymusePlayerActionsoznacza turę gracza.useAIActionsto logika AI,
Główny useGameLogic hook działa teraz jako kompozytor, który łączy te mniejsze hooki. Ta zmiana architektury nie wpłynęła na funkcjonalność gry, ale znacznie uprościła kod.
Logika gry z interfejsem Prompt API
Podstawą tego projektu jest korzystanie z interfejsu Prompt API.
Dodałem logikę gry AI do builtInAIService.ts. Do jego głównych obowiązków należą:
- Zezwalaj na odpowiedzi binarne z ograniczeniami.
- Nauczyć model strategii gry.
- uczyć model analizy;
- wymazać pamięć modelu,
Zezwalaj na restrykcyjne odpowiedzi binarne
Jak gracz wchodzi w interakcje z AI? Gdy gracz zapyta: „Czy Twoja postać ma kapelusz?”, AI musi „sprawdzić” obraz tajnej postaci i udzielić jasnej odpowiedzi.
Moje pierwsze próby były nieudane. Odpowiedź była konwersacyjna: „Nie, postać, o której myślę, Isabella, nie ma na głowie kapelusza”, zamiast prostej odpowiedzi „tak” lub „nie”. Początkowo rozwiązałem ten problem za pomocą bardzo ścisłego prompta, który w zasadzie dyktował modelowi, aby odpowiadał tylko „Tak” lub „Nie”.
Chociaż to działało, odkryłem jeszcze lepszy sposób korzystania z danych wyjściowych w formacie strukturalnym. Dostarczając modelowi schemat JSON, mogę zagwarantować odpowiedź typu prawda/fałsz.
const schema = { type: "boolean" };
const result = session.prompt(prompt, { responseConstraint: schema });
Dzięki temu mogłem uprościć prompt i sprawić, że mój kod będzie niezawodnie obsługiwał odpowiedź:
JSON.parse(result) ? "Yes" : "No"
Uczenie modelu strategii gry
Poproszenie modelu o odpowiedź na pytanie jest znacznie prostsze niż poproszenie go o zadawanie pytań. Dobry gracz w Zgadnij kto? nie zadaje przypadkowych pytań. Zadają pytania, które eliminują najwięcej znaków naraz. Idealne pytanie zmniejsza o połowę liczbę pozostałych znaków dzięki pytaniom binarnym.
Jak nauczyć model tej strategii? Ponownie, techniki tworzenia promptów. Prompt dla generateAIQuestion() to w zasadzie zwięzła lekcja teorii gier w kontekście gry „Zgadnij kto?”.
Początkowo poprosiłem model o „zadanie dobrego pytania”. Wyniki były nieprzewidywalne. Aby poprawić wyniki, dodałem ograniczenia negatywne. Prompt zawiera teraz instrukcje podobne do tych:
- „KRYTYCZNE: zadawaj pytania TYLKO o istniejące funkcje”
- „KRYTYCZNE: zachowaj oryginalność. NIE powtarzaj pytania”.
Te ograniczenia zawężają zakres działania modelu i zapobiegają zadawaniu przez niego nieistotnych pytań, co sprawia, że jest on znacznie przyjemniejszym przeciwnikiem. Możesz zapoznać się z pełną wersją pliku prompta na GitHubie.
Uczenie modelu analizy
To było zdecydowanie najtrudniejsze i najważniejsze wyzwanie. Gdy model zadaje pytanie, np. „Czy Twoja postać ma kapelusz?”, a gracz odpowiada „nie”, skąd model wie, które postacie na planszy gracza zostały wyeliminowane?
Model powinien wyeliminować wszystkie osoby w kapeluszach. Moje pierwsze próby były pełne błędów logicznych, a czasami model usuwał nieprawidłowe znaki lub nie usuwał żadnych znaków. Czym jest „kapelusz”? Czy „czapka typu beanie” jest rodzajem „czapki”? Przyznajmy, że to może się zdarzyć również w ludzkiej dyskusji. Oczywiście zdarzają się też ogólne błędy. Z perspektywy AI włosy mogą wyglądać jak kapelusz.

Przeprojektowałem architekturę, aby oddzielić percepcję od wnioskowania na podstawie kodu:
Za analizę wizualną odpowiada AI. Modele doskonale radzą sobie z analizą wizualną. Wydałem modelowi polecenie, aby zwracał pytanie i szczegółową analizę w ramach ścisłego schematu JSON. Model analizuje każdą postać na planszy i odpowiada na pytanie: „Czy ta postać ma tę cechę?”. Model zwraca ustrukturyzowany obiekt JSON:
{ "character_id": "...", "has_feature": true }Ponownie kluczem do sukcesu są dane strukturalne.
Kod gry wykorzystuje analizę do podjęcia ostatecznej decyzji. Kod aplikacji sprawdza odpowiedź gracza („Tak” lub „Nie”) i przechodzi przez analizę AI. Jeśli gracz odpowie „Nie”, kod usunie wszystkie znaki, w których
has_featurejest równetrue.
Uważam, że ten podział pracy jest kluczowy w tworzeniu niezawodnych aplikacji AI. Wykorzystuj AI do analizy, a decyzje binarne pozostaw kodowi aplikacji.
Aby sprawdzić, jak model postrzega te dane, utworzyłem wizualizację tej analizy. Ułatwiło to potwierdzenie, czy model prawidłowo interpretuje dane.
Tworzenie promptów
Nawet po tym rozdzieleniu zauważyłem jednak, że postrzeganie modelu może być nadal wadliwe. Może błędnie ocenić, czy postać nosi okulary, co prowadzi do frustrującego i nieprawidłowego wyeliminowania. Aby temu zapobiec, przeprowadziłem eksperyment z 2-etapowym procesem: AI zadawała pytanie. Po otrzymaniu odpowiedzi gracza przeprowadzi drugą, nową analizę, w której kontekstem będzie odpowiedź. Teoria zakładała, że drugie sprawdzenie może wykryć błędy, które umknęły podczas pierwszego.
Oto jak wyglądałby ten proces:
- Tura AI (wywołanie interfejsu API 1): AI pyta: „Czy Twoja postać ma brodę?”.
- Tura gracza: gracz patrzy na swoją tajną postać, która jest ogolona, i odpowiada: „Nie”.
- Tura AI (wywołanie interfejsu API 2): AI prosi samą siebie o ponowne przyjrzenie się wszystkim pozostałym znakom i określenie, które z nich należy wyeliminować na podstawie odpowiedzi gracza.
W kroku 2 model może nadal błędnie interpretować postać z lekkim zarostem jako „nieposiadającą brody” i nie wyeliminować jej, mimo że użytkownik oczekiwał, że to zrobi. Podstawowy błąd percepcji nie został naprawiony, a dodatkowy krok tylko opóźnił wyniki. W przypadku gry z przeciwnikiem będącym człowiekiem możemy określić porozumienie lub wyjaśnienie w tej sprawie. W obecnej konfiguracji z przeciwnikiem AI nie jest to możliwe.
Ten proces powodował opóźnienia wynikające z drugiego wywołania interfejsu API, ale nie zwiększał znacząco dokładności. Jeśli model pomylił się za pierwszym razem, często mylił się też za drugim. Przywróciłem prompt, aby sprawdzić go tylko raz.
Ulepszanie zamiast dodawania kolejnych analiz
Zastosowałem zasadę UX: rozwiązaniem nie była większa liczba analiz, ale lepsze analizy.
Wiele wysiłku włożyłem w dopracowanie promptu, dodając wyraźne instrukcje, aby model sprawdzał swoją pracę i skupiał się na charakterystycznych cechach. Okazało się, że jest to skuteczniejsza strategia zwiększania dokładności. Obecny, bardziej niezawodny proces wygląda tak:
Odpowiedź AI (wywołanie interfejsu API): model jest proszony o wygenerowanie zarówno pytania, jak i analizy wewnętrznej w tym samym czasie, zwracając pojedynczy obiekt JSON.
- Pytanie: „Czy Twoja postać nosi okulary?”
- Analiza (danych):
[ {character_id: 'brad', has_feature: true}, {character_id: 'alex', has_feature: false}, {character_id: 'gina', has_feature: true}, ... ]Tura gracza: tajną postacią gracza jest Alex (bez okularów), więc odpowiada on „Nie”.
Koniec rundy: kontrolę przejmuje kod JavaScript aplikacji. Nie musi już o nic więcej pytać AI. Iteruje dane analityczne z kroku 1.
- Gracz odpowiedział „Nie”.
- Kod wyszukuje każdy znak, dla którego warunek
has_featurejest spełniony. - Przewraca Brada i Ginę. Logika jest deterministyczna i natychmiastowa.
Eksperymentowanie było kluczowe, ale wymagało wielu prób i błędów. Nie wiedziałem, czy będzie lepiej. Czasami było jeszcze gorzej. Określenie, jak uzyskać najbardziej spójne wyniki, nie jest jeszcze (i być może nigdy nie będzie) nauką ścisłą.
Po kilku rundach z nowym przeciwnikiem AI pojawił się fantastyczny nowy problem: pat.
Wyjście z blokady wzajemnej
Gdy pozostawały tylko 2 lub 3 bardzo podobne znaki, model wpadał w pętlę. Zadawano pytanie dotyczące wspólnej cechy, np. „Czy Twoja postać nosi kapelusz?”.
Mój kod prawidłowo zidentyfikowałby to jako zmarnowaną turę, a AI spróbowałaby innej, równie ogólnej cechy, którą również wszyscy bohaterowie mają wspólną, np. „Czy Twoja postać nosi okulary?”.
Ulepszyłem prompta o nową regułę: jeśli próba wygenerowania pytania się nie powiedzie i pozostały 3 znaki lub mniej, strategia ulega zmianie.

Nowa instrukcja jest jednoznaczna: „Aby znaleźć różnicę, zamiast ogólnej cechy musisz zapytać o bardziej szczegółową, unikalną lub połączoną cechę wizualną”. Na przykład zamiast pytać, czy postać nosi kapelusz, system pyta, czy ma na głowie czapkę z daszkiem.
Zmusza to model do dokładniejszego przyjrzenia się obrazom, aby znaleźć jeden mały szczegół, który może w końcu doprowadzić do przełomu, dzięki czemu strategia w późniejszej fazie gry działa nieco lepiej w większości przypadków.
Wymazywanie pamięci modelu
Największą zaletą modelu językowego jest jego pamięć. Ale w tej grze jego największa zaleta stała się wadą. Gdy zaczynałem drugą grę, zadawał mi mylące lub nieistotne pytania. Oczywiście mój inteligentny przeciwnik AI zachował całą historię czatu z poprzedniej gry. Próbował zrozumieć 2 (lub więcej) gier naraz.
Zamiast ponownie wykorzystywać tę samą sesję AI, na koniec każdej gry wyraźnie ją niszczę, co w zasadzie powoduje, że AI traci pamięć.
Gdy klikniesz Zagraj jeszcze raz, funkcja startNewGameSession() zresetuje planszę i utworzy zupełnie nową sesję AI. To była interesująca lekcja zarządzania stanem sesji nie tylko w aplikacji, ale też w samym modelu AI.
Dodatkowe funkcje: niestandardowe gry i wpisywanie głosowe
Aby zwiększyć zaangażowanie użytkowników, dodałem 2 dodatkowe funkcje:
Niestandardowe postacie: dzięki
getUserMedia()gracze mogą używać aparatu do tworzenia własnych zestawów 5-znakowych. Do zapisywania znaków użyłem IndexedDB, czyli bazy danych przeglądarki idealnej do przechowywania danych binarnych, takich jak obiekty blob obrazów. Gdy utworzysz niestandardowy zestaw, zostanie on zapisany w Twojej przeglądarce, a w menu głównym pojawi się opcja powtórki.Wprowadzanie głosowe: model po stronie klienta jest multimodalny. Może przetwarzać tekst, obrazy i dźwięk. Korzystając z interfejsu MediaRecorder API do przechwytywania danych z mikrofonu, mogę przekazywać wynikowy obiekt audio do modelu z promptem: „Przepisz poniższy dźwięk…”. To świetny sposób na zabawę (i sprawdzenie, jak interpretuje mój flamandzki akcent). Utworzyłem tę funkcję głównie po to, aby pokazać wszechstronność tej nowej możliwości internetowej, ale prawdę mówiąc, miałem już dość ciągłego wpisywania tych samych pytań.
Prezentacja
Możesz przetestować grę bezpośrednio tutaj lub zagrać w nowym oknie i znaleźć kod źródłowy na GitHubie.
Uwagi końcowe
Stworzenie „AI Guess Who?” było z pewnością wyzwaniem. Ale z pomocą dokumentacji i AI do debugowania AI (tak… Okazało się, że to fajny eksperyment. Podkreślono w nim ogromny potencjał uruchamiania modelu w przeglądarce w celu zapewnienia prywatności, szybkości i działania bez dostępu do internetu. To wciąż eksperyment, a przeciwnik nie zawsze gra idealnie. Nie jest idealny pod względem pikseli ani logiki. W przypadku generatywnej AI wyniki zależą od modelu.
Zamiast dążyć do perfekcji, będę się starać poprawić wynik.
Ten projekt podkreślił też ciągłe wyzwania związane z inżynierią promptów. To zachęcanie stało się bardzo ważną częścią procesu, ale nie zawsze najprzyjemniejszą. Najważniejszą lekcją, jaką wyciągnąłem, było jednak zaprojektowanie aplikacji w taki sposób, aby oddzielić percepcję od wnioskowania, dzieląc możliwości AI i kodu. Nawet po tym rozdzieleniu zauważyłem, że AI nadal może popełniać (z ludzkiego punktu widzenia) oczywiste błędy, takie jak mylenie tatuaży z makijażem lub gubienie się w tym, czyja tajna postać jest omawiana.
Za każdym razem rozwiązaniem było jeszcze bardziej precyzyjne sformułowanie promptów, dodanie instrukcji, które wydają się oczywiste dla człowieka, ale są niezbędne dla modelu.
Czasami gra wydawała się niesprawiedliwa. Czasami miałem wrażenie, że AI „zna” tajny znak z wyprzedzeniem, mimo że kod nigdy nie udostępniał tych informacji wprost. Pokazuje to kluczową różnicę między człowiekiem a maszyną:
Zachowanie AI musi być nie tylko prawidłowe, ale też sprawiedliwe.
Dlatego zaktualizowałem prompty, dodając do nich bezpośrednie instrukcje, takie jak „NIE wiesz, którą postać wybrałem” i „Nie oszukuj”. Dowiedziałem się, że podczas tworzenia agentów AI należy poświęcić czas na określenie ograniczeń, prawdopodobnie nawet więcej niż na instrukcje.

Interakcję z modelem można jeszcze ulepszyć. Korzystając z wbudowanego modelu, tracisz część mocy i niezawodności ogromnego modelu po stronie serwera, ale zyskujesz prywatność, szybkość i możliwość działania w trybie offline. W przypadku takiej gry warto było przeprowadzić eksperyment, aby sprawdzić, czy ta zmiana się opłaca. Przyszłość AI po stronie klienta z każdym dniem staje się coraz lepsza, a modele są coraz mniejsze. Nie mogę się doczekać, aż zobaczymy, co jeszcze uda nam się stworzyć.