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 działającą 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 przyzwyczaiłem się 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? jest oparta na 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 haczyka ReactuseGameLogic
, ale szybko stał się on zbyt duży, aby można było go przeglądać i debugować. Aby zwiększyć łatwość utrzymania, podzieliśmy ten hook na kilka hooków, z których każdy ma jedno zadanie.
Na przykład:
useGameState
zarządza stanem podstawowymusePlayerActions
oznacza turę gracza.useAIActions
to logika AI.
Główny useGameLogic
hook działa teraz jako czysty kompozytor, łącząc 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 restrykcyjne odpowiedzi binarne.
- 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 wykorzystujący dane wyjściowe 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
Wydanie modelowi polecenia, aby odpowiedział na pytanie, jest znacznie prostsze niż zainicjowanie przez model pytań i ich zadanie. 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 wykluczające ograniczenia. 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 uniemożliwiają mu zadawanie nieistotnych pytań, dzięki czemu jest on znacznie przyjemniejszym przeciwnikiem. Możesz zapoznać się z pełną wersją pliku prompta na GitHubie.
Uczenie modelu analizy
Było to 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 zimowa” jest rodzajem „czapki”? Przyznajmy, że to może się zdarzyć także podczas debaty między ludźmi. 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 przetwarza analizę AI. Jeśli gracz odpowie „Nie”, kod usunie wszystkie znaki, w których
has_feature
jest równetrue
.
Uważam, że ten podział pracy jest kluczowy w tworzeniu niezawodnych aplikacji AI. Korzystaj z AI w celu analizy, a decyzje binarne pozostaw kodowi aplikacji.
Aby sprawdzić, jak model postrzega 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.
Proces ten wyglądałby tak:
- 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 się tego spodziewał. Podstawowy błąd percepcji nie został naprawiony, a dodatkowy krok tylko opóźnił wyniki. Gdy grasz przeciwko człowiekowi, możesz uzgodnić z nim tę kwestię lub ją wyjaśnić. W obecnej konfiguracji z przeciwnikiem AI nie jest to możliwe.
Ten proces powodował opóźnienia wynikające z drugiego wywołania interfejsu API, nie zwiększając 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 wewnętrznej analizy 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_feature
jest spełniony. - Obraca on w dół postacie Brada i Giny. 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łą.
Ale 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 w niej pytanie dotyczące cechy wspólnej dla wszystkich postaci, 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ż wszystkie postacie 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ę, pojawiały się 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 końcu 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, która doskonale nadaje się 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.Wpisywanie 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 pytań.
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 braku konieczności korzystania z internetu. To wciąż eksperyment, więc 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 podpowiadanie stało się bardzo ważną częścią procesu, ale nie zawsze najprzyjemniejszą. Najważniejsza lekcja, jaką wyciągnąłem, dotyczyła jednak architektury aplikacji, która oddziela 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ą:
Działanie 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 nadal ulepszać. 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ć.