Игровые боты на LLM без Vision-моделей: практический кейс для Tabletop Simulator | AiManual
AiManual Logo Ai / Manual.
08 Фев 2026 Гайд

Как сделать умного игрового бота без Vision-моделей: кейс «Битвы Големов» и работа с текстовым представлением состояния

Разбираем создание умного бота для настольной игры через текстовое представление состояния. Оптимизация latency, выбор локальных моделей 20B и интеграция с Tabl

Зачем заставлять ИИ играть в настольные игры?

Когда я впервые запустил 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 12B12B параметров45-55Отличное
DeepSeek Coder V316B параметров35-45Хорошее
Qwen2.5 14B14B параметров40-50Среднее

Gemma 3 12B показала лучший баланс — достаточно умна для стратегических решений, достаточно быстра для реального времени. Запускал через Ollama на RTX 4070 с 12GB VRAM — идеально.

Не берите модели больше 20B для игровых ботов. Даже 20B уже будет тормозить. 12-16B — золотая середина между скоростью и интеллектом.

2Архитектура системы: три слоя вместо одного

Ошибка новичка — отправить полное состояние игры в промпт и ждать умного хода. Так не работает.

Моя архитектура:

  1. Слой анализа: Модель получает состояние и генерирует список возможных действий с оценками
  2. Слой фильтрации: Исключаются невозможные действия (нет ресурсов, вне диапазона)
  3. Слой принятия решения: Модель выбирает лучшее действие из отфильтрованного списка

Почему три слоя? Потому что 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
💡
WebSocket вместо HTTP — обязательно. Игра требует постоянного двустороннего общения, а не запрос-ответ.

Ошибки, которые сломают ваш проект

За два месяца разработки я наступил на все грабли. Вот главные:

ОшибкаПоследствиеРешение
Отправка полного состояния каждый ход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. Против экспертов — пока слабоват.

Планы на будущее:

  1. Обучение с подкреплением: Записывать партии, анализировать ошибки, корректировать промпты
  2. Ансамбли моделей: Gemma для тактики, DeepSeek для стратегии, маленькая модель для быстрых решений
  3. Прогнозирование: Не просто реакция на текущее состояние, а предсказание действий противника
  4. Адаптация к стилю: Если игрок агрессивен — переходить в защиту. Если пассивен — захватывать контроль

Самое интересное — этот подход работает для ЛЮБОЙ игры с дискретным состоянием. Шахматы, покер, даже некоторые видеоигры. Нужно только правильно описать правила и состояние.

Не пытайтесь засунуть ИИ во все игры подряд. Начните с одной. Доведите до ума. Потом масштабируйте. Иначе утонете в бесконечных правках промптов.

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%).

Последний совет: ваш первый бот будет глупым. Это нормально. Главное — начать. Сделайте простейшую версию, которая просто ходит случайно. Потом добавьте анализ. Потом стратегию. Итеративно.

И помните — если что-то не работает, посмотрите на проблему с другой стороны. Иногда проще изменить представление данных, чем заставить модель понять существующее.