Зачем заставлять ИИ играть в настольные игры?
Когда я впервые запустил Tabletop Simulator с мыслью "а что если...", я столкнулся с очевидной проблемой. Современные LLM прекрасно генерируют текст, анализируют код, даже рисуют картинки. Но попросите их сыграть в игру, где нужно видеть поле — и получаете полный провал. Vision-модели дороги, медленны и требуют GPU, который стоит как хороший автомобиль.
А ведь есть десятки настольных игр с простыми правилами, но сложной стратегией. И я подумал — почему бы не превратить текстовое описание состояния игры в преимущество, а не ограничение?
Ключевая идея: если ИИ не может видеть — не заставляйте его. Дайте ему то, что он понимает лучше всего — структурированный текст.
Битва Големов: идеальный полигон
Выбрал я простую, но глубокую игру: каждый игрок управляет големом с четырьмя элементами (огонь, вода, земля, воздух). На поле — шестиугольные клетки с разными типами местности. Задача — захватить контрольные точки и уничтожить противника.
Проблема номер один: ИИ не понимает геометрию. Абсолютно. Дайте ему координаты (x=3, y=2) — и он теряется. Но дайте ему отношения: "вы стоите на лесе, справа от вас гора, слева река" — и появляется понимание.
От скриншота к JSON: трансформация восприятия
Вместо того чтобы кормить модель скриншотами (как в Lenswalker), я создал текстовое представление состояния:
{
"current_player": "golem_red",
"position": {
"terrain": "forest",
"adjacent": [
{"direction": "north", "terrain": "mountain", "occupied": false},
{"direction": "south", "terrain": "river", "occupied": true, "unit": "golem_blue"}
]
},
"resources": {"fire": 3, "water": 2, "earth": 4, "air": 1},
"objectives": [
{"id": "control_point_1", "distance": 2, "controlled_by": "none"},
{"id": "control_point_2", "distance": 5, "controlled_by": "golem_blue"}
],
"turn_number": 7
}Это не просто JSON. Это специально спроектированный язык, который LLM понимает интуитивно. Каждый элемент имеет четкую семантику, отношения описаны через простые связи.
1Выбор модели: почему не ChatGPT и не GPT-4
Первая мысль — использовать OpenAI API. Вторая мысль — посчитать стоимость. Третья — отказаться от этой идеи.
Игра требует 10-15 ходов на партию. Каждый ход — 2-3 запроса к модели (анализ состояния, генерация хода, проверка). При цене $0.01 за 1K токенов — одна партия обойдется в $0.15-0.30. Многовато для тестирования.
Локальные модели — другое дело. После тестов с десятком вариантов остановился на двух:
| Модель | Размер | Скорость (токен/с) | Качество решений |
|---|---|---|---|
| Gemma 3 12B | 12B параметров | 45-55 | Отличное |
| DeepSeek Coder V3 | 16B параметров | 35-45 | Хорошее |
| Qwen2.5 14B | 14B параметров | 40-50 | Среднее |
Gemma 3 12B показала лучший баланс — достаточно умна для стратегических решений, достаточно быстра для реального времени. Запускал через Ollama на RTX 4070 с 12GB VRAM — идеально.
Не берите модели больше 20B для игровых ботов. Даже 20B уже будет тормозить. 12-16B — золотая середина между скоростью и интеллектом.
2Архитектура системы: три слоя вместо одного
Ошибка новичка — отправить полное состояние игры в промпт и ждать умного хода. Так не работает.
Моя архитектура:
- Слой анализа: Модель получает состояние и генерирует список возможных действий с оценками
- Слой фильтрации: Исключаются невозможные действия (нет ресурсов, вне диапазона)
- Слой принятия решения: Модель выбирает лучшее действие из отфильтрованного списка
Почему три слоя? Потому что LLM плохо справляется с одновременным анализом и выбором. Разделение задач улучшает качество на 40-60%.
# Упрощенная версия архитектуры
class GolemBot:
def __init__(self, model):
self.model = model
def analyze_state(self, game_state):
prompt = f"""Проанализируй игровое состояние:
{json.dumps(game_state, indent=2)}
Сгенерируй список возможных действий с оценкой от 0 до 10.
Формат: [{"action": "move_north", "score": 7, "reason": "..."}]"""
analysis = self.model.generate(prompt)
return parse_analysis(analysis)
def filter_actions(self, actions, game_state):
# Убираем невозможные действия
possible = []
for action in actions:
if self.is_action_possible(action, game_state):
possible.append(action)
return possible
def decide_action(self, possible_actions):
prompt = f"""Выбери лучшее действие из списка:
{json.dumps(possible_actions, indent=2)}
Верни JSON: {"chosen_action": "action_name", "final_reason": "..."}"""
decision = self.model.generate(prompt)
return parse_decision(decision)Оптимизация latency: как уложиться в 3 секунды
Игроки не будут ждать 30 секунд, пока ИИ "подумает". Мой целевой показатель — 3 секунды на ход максимум.
Достигается это четырьмя техниками:
- Кэширование состояний: Если состояние изменилось минимально — используем предыдущий анализ
- Предварительная генерация: Пока игрок думает, ИИ уже анализирует возможные ответные ходы
- Квантование модели: Gemma 3 12B в 4-битном формате работает почти так же хорошо, но в 2 раза быстрее
- Параллельные вычисления: Несколько экземпляров модели для разных типов анализа
Самый важный трюк — инкрементальное обновление. Вместо того чтобы каждый раз отправлять полное состояние, отправляем только изменения:
{
"delta": {
"player_moved": {"from": "A3", "to": "B4"},
"resource_changed": {"fire": -1},
"new_threats": [{"unit": "golem_blue", "direction": "south", "distance": 2}]
},
"full_state": false
}Промпт-инжиниринг для игрового ИИ
Здесь нельзя просто сказать "сыграй хорошо". Нужно объяснять правила, стратегию, приоритеты.
Мой основной промпт выглядит так:
Ты — опытный игрок в Битву Големов. Твоя цель — победить.
ПРАВИЛА:
1. Контрольные точки дают дополнительные ресурсы каждый ход
2. Горы блокируют движение, но дают защиту
3. Реки замедляют движение, но дают бонус к водным атакам
4. Огонь силен против воздуха, но слаб против воды
СТРАТЕГИЧЕСКИЕ ПРИНЦИПЫ:
1. Всегда захватывай ближайшую контрольную точку
2. Не атакуй без численного преимущества 2:1
3. Сохраняй хотя бы один ресурс каждого типа для специальных способностей
4. Отступай, если потеряешь больше чем 40% здоровья
ТЕКУЩЕЕ СОСТОЯНИЕ:
{state_json}
ТВОИ ВОЗМОЖНОСТИ:
{moves_json}
СФОРМАТИРУЙ ОТВЕТ В JSON:Ключевой момент — стратегические принципы. Без них ИИ действует хаотично. С ними — предсказуемо и разумно.
Интеграция с Tabletop Simulator
Tabletop Simulator имеет Lua API. Мой бот работает как отдельный сервис на Python, который общается с игрой через WebSocket.
Схема работы:
# Серверная часть (Python)
import websocket
import json
class GameServer:
def __init__(self):
self.bot = GolemBot.load_model("gemma3:12b")
def handle_game_update(self, data):
# Получаем обновление от игры
if data["type"] == "state_update":
analysis = self.bot.analyze_state(data["state"])
decision = self.bot.decide_action(analysis["possible_actions"])
# Отправляем ход обратно в игру
self.send_to_game({
"type": "bot_action",
"action": decision["chosen_action"],
"params": decision.get("params", {})
})-- Клиентская часть (Lua в Tabletop Simulator)
local websocket = require("websocket")
function onUpdate(state_json)
-- Отправляем состояние на сервер бота
ws.send(json.encode({
type = "state_update",
state = state_json,
player = "golem_bot"
}))
end
function onBotAction(action_data)
-- Выполняем действие от бота
if action_data.action == "move" then
moveGolem(action_data.params.direction)
elseif action_data.action == "attack" then
attackTarget(action_data.params.target)
end
endОшибки, которые сломают ваш проект
За два месяца разработки я наступил на все грабли. Вот главные:
| Ошибка | Последствие | Решение |
|---|---|---|
| Отправка полного состояния каждый ход | Latency 10+ секунд | Инкрементальные обновления |
| Одна модель для всего | Плохое качество решений | Специализированные микромодели |
| Нет валидации ответов ИИ | Невозможные ходы, краш игры | Строгая проверка перед исполнением |
| Игнорирование кэширования | Лишние вызовы модели | LRU-кэш на 100 последних состояний |
Почему это работает лучше vision-подхода?
Сравнивал с VLA-моделями. Разница колоссальная:
- Скорость: Текстовый анализ — 1-2 секунды. Vision-анализ — 5-10 секунд
- Точность: Текст дает 100% точность данных. Vision ошибается в 15-20% случаев
- Стоимость: Локальная модель 12B — бесплатно после скачивания. Vision API — $0.01-0.05 за кадр
- Стабильность: Текстовый формат всегда одинаков. Скриншоты зависят от разрешения, освещения, интерфейса
Единственный минус — нужно писать конвертер из игрового состояния в текст. Но это делается один раз, а пользуешься всегда.
Что дальше? Эволюция игровых ботов
Сейчас мой бот выигрывает у новичков в 80% случаев. У средних игроков — 50/50. Против экспертов — пока слабоват.
Планы на будущее:
- Обучение с подкреплением: Записывать партии, анализировать ошибки, корректировать промпты
- Ансамбли моделей: Gemma для тактики, DeepSeek для стратегии, маленькая модель для быстрых решений
- Прогнозирование: Не просто реакция на текущее состояние, а предсказание действий противника
- Адаптация к стилю: Если игрок агрессивен — переходить в защиту. Если пассивен — захватывать контроль
Самое интересное — этот подход работает для ЛЮБОЙ игры с дискретным состоянием. Шахматы, покер, даже некоторые видеоигры. Нужно только правильно описать правила и состояние.
Не пытайтесь засунуть ИИ во все игры подряд. Начните с одной. Доведите до ума. Потом масштабируйте. Иначе утонете в бесконечных правках промптов.
FAQ: ответы на частые вопросы
Q: Почему не использовать готовые игровые ИИ вроде AlphaZero?
A: AlphaZero требует миллионов игр для обучения. Мой подход работает из коробки. Не лучше, но быстрее и дешевле.
Q: Можно ли использовать этот подход для MMO или шутеров?
A: Для шутеров — нет, там нужна реакция в миллисекунды. Для пошаговых MMO — да, если можно описать состояние текстом.
Q: Какой минимум VRAM нужен для Gemma 3 12B?
A: В 4-битном формате — около 8GB. В 8-битном — 12-14GB. Без GPU можно на CPU, но будет медленно (1-2 токена в секунду).
Q: Что делать, если ИИ постоянно нарушает правила?
A: Добавьте слой валидации. Перед исполнением хода проверяйте его на соответствие правилам. Если нарушает — отправляйте ошибку обратно в модель с объяснением.
Q: Как измерять качество бота?
A: Тремя метриками: процент побед против эталонных противников, время на ход (должно быть < 5 секунд), процент валидных ходов (должен быть > 95%).
Последний совет: ваш первый бот будет глупым. Это нормально. Главное — начать. Сделайте простейшую версию, которая просто ходит случайно. Потом добавьте анализ. Потом стратегию. Итеративно.
И помните — если что-то не работает, посмотрите на проблему с другой стороны. Иногда проще изменить представление данных, чем заставить модель понять существующее.