Акты Становления

850: Пакет/Игровой Сервис

Автономный, независящий от транспорта бэкенд-сервис, предназначенный для управления жизненным циклом пошаговых игр. Он действует как авторитетный сервер для состояния игры, обрабатывая действия игроков, развивая логику через подключаемые движки и сохраняя результаты.

Игровой Сервис (@idealic/game-service) — это модульный Node.js бэкенд, созданный для валидации и хостинга пошаговых игр. Изначально он был ориентирован на покер, но его архитектура является универсальной и служит безопасной песочницей для разработки и тестирования игровой логики в отрыве от проприетарной платформы клиента.

Основная архитектура

Сервис построен на модели API без сохранения состояния (Stateless API). Он взаимодействует с внешним миром через единую конечную точку, которая получает клиентскую версию состояния игры. Сервер объединяет её со своим авторитетным состоянием, проверяет и обрабатывает действия, а затем возвращает обновление.

Подключаемые игровые движки

Сервис строго отделён от игровой логики. Он функционирует как контейнер-хост для подключаемых движков.

  • Паттерн Реестра: Сервис использует реестр для загрузки различных игровых движков.
  • Единый интерфейс: Движки должны предоставлять стандартный интерфейс (State.advance(), State.join() и т.д.).
  • Разделение ответственности:
    • Игровой движок (например, покер): Отвечает за правила, валидацию и переходы состояний.
    • Игровой Сервис (этот пакет): Управляет «метаигровой» логикой, такой как сетевое взаимодействие, сохранение данных, управление сессиями и таймаутами.

«Состояние» как источник истины

Сервис полагается на концепцию объекта состояния от базового движка. Это не просто снимок, а полная, сериализуемая запись истории (подобно шахматной нотации).

  • Обмен данными без сохранения состояния: Клиенты и серверы обмениваются полным объектом состояния.
  • Детерминизм: Состояние включает в себя случайное начальное число (random seed), что позволяет идеально восстановить историю игры для проверки и разрешения споров.
  • Перспектива игрока: Состояние поддерживает «маскировку», при которой сервер генерирует персонализированное представление для каждого игрока (скрывая карманные карты противников) перед отправкой данных.

Слой абстракции ввода-вывода (I/O)

Сервис не зависит от транспорта. Все внешние взаимодействия абстрагированы в выделенный слой ввода-вывода (service.io.ts), что позволяет интегрировать сервис в любую среду (Express, WebSockets, Serverless) путем замены заглушек.

  • Сохранение состояния: saveGame и loadGame отвечают за хранение авторитетного состояния.
  • Управление сессиями: fetchPlayerStacks и savePlayerStacks интегрируются с внешними системами кошельков.
  • Обмен данными в реальном времени: broadcastToPlayers отвечает за отправку обновлений состояния клиентам (например, через WebSockets).
  • Фоновые процессы: fetchTimedOutGames позволяет опрашивать сервис для контроля времени хода игроков.

Концепция сервиса столов

Сервис реализует модель сервиса столов, где столы — это не статические записи в базе данных, а динамически определяемые активные состояния игры.

  • Динамическое выделение: Столы создаются по требованию. Если игрок ищет игру (например, «Техасский Холдем, $1/$2») и за столами нет свободных мест, создается новое состояние игры.
  • Жизненный цикл:
    1. Поиск: Клиент запрашивает стол с определёнными параметрами. Система находит существующую игру или создаёт новую.
    2. Наблюдение: Игрок получает состояние игры, чтобы наблюдать за ходом действия.
    3. Присоединение: Игрок отправляет действие join, чтобы занять место.
    4. Игра: Игровой цикл продолжается.
    5. Завершение: Игроки удаляются по таймауту или из-за банкротства (недостаточно средств для следующей раздачи).

Как это работает

Операционный цикл сервиса выглядит так:

  1. Инициация: Внешнее событие (действие игрока) поступает в сервис.
  2. Загрузка: Авторитетное состояние загружается из хранилища.
  3. Слияние: Входящее клиентское состояние объединяется с состоянием сервера.
  4. Продвижение: Сервис вызывает advance(), который циклически выполняет логику игрового движка до тех пор, пока не потребуется ввод от игрока.
  5. Завершение: Если раунд заканчивается, сервис завершает раздачу, распределяет банки и начинает следующий раунд.
  6. Сохранение и рассылка: Обновлённое состояние сохраняется и отправляется всем игрокам.