Опубликовано: 10 октября 2025 г.

Классическая настольная игра « Угадай кто? » — это мастер-класс по дедуктивному мышлению. Каждый игрок начинает с доски, на которой изображены лица, и, отвечая на ряд вопросов «да» или «нет», сужает круг возможных вариантов, пока не сможет с уверенностью определить секретного персонажа своего противника.
После демонстрации встроенного ИИ на конференции Google I/O Connect я задумался: а что если бы я мог сыграть в игру «Угадай кто?» против ИИ, работающего в браузере? При использовании клиентского ИИ фотографии обрабатывались бы локально, поэтому созданная мной игра «Угадай кто?» с друзьями и родственниками оставалась бы конфиденциальной и защищенной на моем устройстве.
Мой основной опыт связан с разработкой пользовательских интерфейсов (UI) и пользовательского опыта (UX), и я привык создавать идеально проработанные до пикселя интерфейсы. Я надеялся, что смогу сделать именно это в своей интерпретации.
Моё приложение AI Guess Who? создано на React и использует API Prompt и встроенную в браузер модель для создания удивительно сильного противника. В процессе работы я обнаружил, что добиться «идеально точных» результатов не так-то просто. Но это приложение демонстрирует, как ИИ можно использовать для построения продуманной игровой логики, и показывает важность использования API Prompt для уточнения этой логики и получения ожидаемых результатов.
Читайте дальше, чтобы узнать о встроенной интеграции ИИ, проблемах, с которыми я столкнулся, и решениях, к которым я пришел. Вы можете поиграть в игру и найти исходный код на GitHub .
Основа для игр: приложение на React
Прежде чем рассматривать реализацию ИИ, давайте рассмотрим структуру приложения. Я создал стандартное React-приложение на TypeScript с центральным файлом App.tsx , который выступает в роли дирижера игры. Этот файл содержит:
- Состояние игры : перечисление, отслеживающее текущую фазу игры (например,
PLAYER_TURN_ASKING,AI_TURN,GAME_OVER). Это наиболее важная часть состояния, поскольку она определяет, что отображается в интерфейсе и какие действия доступны игроку. - Списки персонажей : Существует несколько списков, в которых указаны активные персонажи, секретный персонаж каждого игрока и персонажи, выбывшие из игры.
- Игровой чат : постоянно обновляемый журнал вопросов, ответов и системных сообщений.
Интерфейс разбит на логические компоненты:


По мере развития игрового функционала росла и его сложность. Изначально вся логика игры управлялась одним большим пользовательским React-хуком , useGameLogic , но он быстро стал слишком большим для навигации и отладки. Для улучшения удобства сопровождения я разделил этот хук на несколько отдельных хуков, каждый из которых выполняет одну единственную задачу. Например:
-
useGameStateуправляет основным состоянием -
usePlayerActionsиспользуется для обозначения хода игрока. -
useAIActionsпредназначен для логики ИИ.
Основной хук useGameLogic теперь выступает в роли чистого компоновщика, объединяя эти более мелкие хуки. Это архитектурное изменение не повлияло на функциональность игры, но значительно упростило кодовую базу.
Игровая логика с использованием API Prompt
В основе этого проекта лежит использование API Prompt.
Я добавил логику игры с использованием ИИ в файл builtInAIService.ts . Вот его основные функции:
- Допускаются ограничительные, бинарные ответы.
- Обучите стратегии игры на основе модели.
- Обучите анализу моделей.
- Привести модель к амнезии.
Допускаются ограничительные, бинарные ответы.
Как игрок взаимодействует с ИИ? Когда игрок спрашивает: «У вашего персонажа есть шляпа?», ИИ должен «посмотреть» на изображение своего секретного персонажа и дать четкий ответ.
Мои первые попытки были полны ошибок. Ответ был разговорным: «Нет, персонаж, о котором я думаю, Изабелла, похоже, не носит шляпу», вместо того чтобы дать однозначный ответ «да» или «нет». Первоначально я решил эту проблему с помощью очень строгой подсказки, по сути, диктуя модели отвечать только «Да» или «Нет».
Хотя этот способ работал, я узнал о ещё лучшем способе, использующем структурированный вывод . Предоставляя модели JSON-схему, я мог гарантировать ответ «истина» или «ложь».
const schema = { type: "boolean" };
const result = session.prompt(prompt, { responseConstraint: schema });
Это позволило мне упростить запрос и обеспечить надежную обработку ответа моим кодом:
JSON.parse(result) ? "Yes" : "No"
Обучите модель стратегии игры
Гораздо проще дать модели указание ответить на вопрос, чем позволить модели самой задавать вопросы. Хороший игрок в «Угадай кто?» не задает случайные вопросы. Он задает вопросы, которые позволяют исключить как можно больше персонажей за один раз. Идеальный вопрос, используя бинарные вопросы, сокращает число оставшихся персонажей вдвое.
Как научить модель этой стратегии? Опять же, с помощью подсказок. Подсказка для функции generateAIQuestion() на самом деле представляет собой краткий урок теории игр в игре «Угадай кто?».
Изначально я попросил модель «задать хороший вопрос». Результаты оказались непредсказуемыми. Чтобы улучшить результаты, я добавил отрицательные ограничения. Теперь запрос включает инструкции, похожие на следующие:
- «ВАЖНО: Задавайте вопросы ТОЛЬКО о существующих функциях»
- «КРИТИЧЕСКИ ВАЖНО: Будьте оригинальны. НЕ повторяйте вопрос».
Эти ограничения сужают фокус внимания модели, не позволяют ей задавать нерелевантные вопросы, что делает её гораздо более интересным противником. Полный текст задания можно посмотреть на GitHub .
Обучите анализу моделей.
Это была, безусловно, самая сложная и важная задача. Когда модель задает вопрос, например: «Есть ли у вашего персонажа шляпа?», и игрок отвечает «нет», как модель узнает, какие персонажи на его игровом поле выбыли из игры?
Модель должна исключать всех, кто носит шляпу. Мои первые попытки были полны логических ошибок, и иногда модель исключала не тех персонажей или вообще никого. Кроме того, что такое «шляпа»? Считается ли «шапка-бини» «шляпой»? Честно говоря, это тоже может произойти в человеческой дискуссии. И, конечно же, случаются общие ошибки. Волосы могут выглядеть как шляпа с точки зрения ИИ.

Я перепроектировал архитектуру, чтобы разделить восприятие и дедукцию кода:
Искусственный интеллект отвечает за визуальный анализ . Модели превосходно справляются с визуальным анализом. Я дал модели указание возвращать свой вопрос и подробный анализ в строгой JSON-схеме. Модель анализирует каждого персонажа на своей доске и отвечает на вопрос: «Обладает ли этот персонаж данной особенностью?» Модель возвращает структурированный JSON-объект:
{ "character_id": "...", "has_feature": true }Еще раз подчеркнем, что структурированные данные являются ключом к успешному результату.
Игровой код использует анализ для принятия окончательного решения . Код приложения проверяет ответ игрока («Да» или «Нет») и проходит через анализ ИИ. Если игрок ответил «Нет», код знает, что нужно удалить все символы, где
has_featuretrue.
Я обнаружил, что такое разделение труда является ключом к созданию надежных приложений на основе ИИ. Используйте ИИ за его аналитические возможности, а бинарные решения оставьте коду вашего приложения.
Чтобы проверить правильность восприятия модели, я создал визуализацию этого анализа. Это позволило легче подтвердить, была ли оценка модели верной.
Оперативное проектирование
Однако, даже с учетом этого разделения, я заметил, что восприятие модели все еще может быть ошибочным. Она может неправильно определить, носит ли персонаж очки, что приводит к досадному и неверному исключению. Чтобы бороться с этим, я экспериментировал с двухэтапным процессом: ИИ задавал свой вопрос. После получения ответа игрока он проводил второй, новый анализ, используя ответ в качестве контекста. Теория заключалась в том, что повторный анализ может выявить ошибки первого.
Вот как бы это работало:
- Поворот ИИ (вызов API 1) : ИИ спрашивает: "У вашего персонажа есть борода?"
- Ход игрока : игрок смотрит на своего секретного персонажа, который чисто выбрит, и отвечает: «Нет».
- Ход ИИ (вызов API 2) : ИИ фактически снова задает себе вопрос обо всех оставшихся персонажах и определяет, каких из них следует исключить, исходя из ответа игрока.
На втором этапе модель может ошибочно принять персонажа со светлой щетиной за «отсутствующую бороду» и не удалить его, даже если пользователь этого ожидал. Основная ошибка восприятия не была исправлена, и дополнительный шаг лишь задержал получение результатов. При игре против человека мы можем оговорить это или уточнить; в текущей конфигурации с нашим ИИ-противником это невозможно.
Этот процесс добавил задержку из-за второго вызова API, не обеспечив при этом существенного повышения точности. Если модель оказалась неверной в первый раз, она часто оказывалась неверной и во второй раз. Я лишь однажды вернул запрос на проверку.
Вместо добавления новых аналитических данных, лучше улучшать существующий.
Я опирался на принцип UX: решение заключалось не в увеличении объема анализа, а в улучшении качества анализа.
Я вложил значительные средства в доработку запроса, добавив четкие указания для модели перепроверять свою работу и сосредотачиваться на отдельных признаках, что оказалось более эффективной стратегией повышения точности. Вот как работает текущий, более надежный алгоритм:
Вызов ИИ (API-запрос) : Модель получает запрос на одновременное генерирование вопроса и проведение внутреннего анализа, возвращая единый JSON-объект.
- Вопрос : «Ваш персонаж носит очки?»
- Анализ (данных) :
[ {character_id: 'brad', has_feature: true}, {character_id: 'alex', has_feature: false}, {character_id: 'gina', has_feature: true}, ... ]Ход игрока : Секретный персонаж игрока — Алекс (без очков), поэтому он отвечает: «Нет».
Завершение раунда : JavaScript-код приложения берет на себя управление. Ему больше не нужно ничего запрашивать у ИИ. Он перебирает данные анализа из шага 1.
- Игрок ответил: «Нет».
- Код ищет каждый символ, для которого
has_featureравно true. - В итоге понижают рейтинг Брэда и Джины. Логика предсказуема и мгновенна.
Этот эксперимент был крайне важен, но требовал множества проб и ошибок. Я понятия не имел, станет ли лучше. Иногда становилось даже хуже. Определение того, как получить наиболее стабильные результаты, — это не точная наука (пока, если вообще когда-либо...).
Но после нескольких раундов с моим новым противником-ИИ возникла новая, удивительная проблема: тупиковая ситуация.
Выход из тупика
Когда оставалось всего два или три очень похожих персонажа, модель застревала в цикле. Она задавала вопрос о какой-либо общей черте всех персонажей, например: «Носит ли ваш персонаж шляпу?»
Мой код правильно определил бы это как потраченный впустую ход, и ИИ попытался бы использовать другую, столь же общую характеристику, присущую всем персонажам, например: «Носит ли ваш персонаж очки?»
Я дополнил подсказку новым правилом: если попытка генерации вопроса не удалась и осталось три или менее символов, стратегия меняется.

Новая инструкция предельно ясна: «Вместо общего признака, чтобы найти различие, необходимо спросить о более конкретном, уникальном или комбинированном визуальном признаке». Например, вместо вопроса о том, носит ли персонаж шляпу, предлагается спросить, носит ли он бейсболку.
Это заставляет модель гораздо внимательнее изучать изображения, чтобы найти ту единственную маленькую деталь, которая в конечном итоге может привести к прорыву, благодаря чему ее стратегия на поздней стадии игры будет работать немного лучше, в большинстве случаев.
Привести модель к амнезии
Главное преимущество языковой модели — это её память. Но в этой игре её главное преимущество превратилось в слабость. Когда я начал вторую игру, она задавала непонятные или не относящиеся к делу вопросы. Конечно, мой умный противник с искусственным интеллектом сохранял всю историю чата из предыдущей игры. Он пытался разобраться в двух (или даже более) играх одновременно.
Вместо того чтобы повторно использовать одну и ту же сессию ИИ, я теперь намеренно уничтожаю её в конце каждой игры, по сути, вызывая у ИИ амнезию.
При нажатии кнопки «Играть снова » функция startNewGameSession() сбрасывает игровое поле и создает совершенно новую сессию ИИ. Это был интересный урок по управлению состоянием сессии не только в приложении, но и внутри самой модели ИИ.
Дополнительные функции: пользовательские игры и голосовой ввод.
Чтобы сделать взаимодействие с пользователем более увлекательным, я добавил две дополнительные функции:
Пользовательские персонажи : с помощью
getUserMedia()игроки могут использовать свою камеру для создания собственного набора из 5 символов. Для сохранения персонажей я использовал IndexedDB — базу данных для браузера, идеально подходящую для хранения бинарных данных, таких как изображения. Когда вы создаете пользовательский набор, он сохраняется в вашем браузере, и в главном меню появляется опция воспроизведения.Голосовой ввод : Клиентская модель является многомодальной . Она может обрабатывать текст, изображения, а также аудио. Используя API MediaRecorder для захвата микрофонного ввода, я мог передавать полученный аудиофайл модели с подсказкой: «Расшифруйте следующее аудио...». Это добавляет забавный способ поиграть (и забавный способ посмотреть, как она интерпретирует мой фламандский акцент). Я создал это в основном для того, чтобы показать универсальность этой новой веб-возможности, но, честно говоря, мне надоело снова и снова набирать вопросы.
Демо
Вы можете протестировать игру прямо здесь или запустить её в новом окне , а исходный код найти на GitHub .
Заключительные мысли
Создание игры «Угадай кто?» с использованием ИИ, безусловно, было непростой задачей. Но с небольшой помощью в изучении документации и отладке ИИ (да... я это сделал) это оказалось забавным экспериментом. Он продемонстрировал огромный потенциал запуска модели в браузере для создания приватного, быстрого и не требующего интернета игрового процесса. Это всё ещё эксперимент, и иногда противник просто играет не идеально. Это не идеально до пикселя или логики. В случае с генеративным ИИ результаты зависят от модели.
Вместо того чтобы стремиться к совершенству, я буду стремиться к улучшению результата.
Этот проект также подчеркнул постоянные сложности разработки подсказок. Подсказки действительно стали огромной частью процесса, и не всегда самой приятной. Но самый важный урок, который я усвоил, заключался в том, чтобы спроектировать приложение таким образом, чтобы разделить восприятие и дедукцию, разграничив возможности ИИ и кода. Даже с этим разделением я обнаружил, что ИИ все еще может совершать (для человека) очевидные ошибки, например, путать татуировки с макияжем или терять из виду, чей секретный персонаж обсуждается.
Каждый раз решением было сделать подсказки еще более подробными, добавив инструкции, которые кажутся очевидными человеку, но необходимы для работы модели.
Иногда игра казалась несправедливой. Порой у меня возникало ощущение, что ИИ «знал» секретного персонажа заранее, хотя код никогда явно не сообщал эту информацию. Это демонстрирует важнейший аспект противостояния человека и машины:
Поведение ИИ должно быть не просто правильным; оно должно восприниматься как справедливое.
Вот почему я обновил подсказки, добавив четкие инструкции, такие как: «Вы НЕ знаете, какого персонажа я выбрал» и «Не жульничайте». Я понял, что при создании агентов с искусственным интеллектом следует уделять время определению ограничений, вероятно, даже больше, чем инструкциям.

Взаимодействие с моделью можно было бы и дальше улучшать. Работая со встроенной моделью, вы теряете часть мощности и надежности огромной серверной модели, но зато получаете конфиденциальность, скорость и возможность работы в автономном режиме. Для такой игры, как эта, такой компромисс действительно стоил того, чтобы с ним поэкспериментировать. Будущее клиентского ИИ с каждым днем становится лучше, модели также становятся меньше, и я с нетерпением жду, что мы сможем создать дальше.