Skip to content

Latest commit

 

History

History
224 lines (184 loc) · 11.8 KB

File metadata and controls

224 lines (184 loc) · 11.8 KB

CellWar

Сайт: cellwar.gg
Репозиторий: cellwar

Правила игры

Поле

  • Прямоугольное N×M клеток
  • Без препятствий и особых территорий

Игроки

  • Несколько игроков (минимум 2, обычно 2, поддержка 3+)
  • Ходят строго по очереди в фиксированном порядке
  • Порядок не меняется от раунда к раунду
  • Союзников нет — все игроки враги друг другу

Раунд

  • Раунд = каждый игрок сходил по одному разу
  • Внутри хода игрок сам выбирает порядок своих юнитов
  • Сначала ходят все боевые юниты (пехотинцы + укрепления) в произвольном порядке
  • Затем ходят фабрики
  • Каждый юнит обязан сделать ровно один ход за раунд
  • Пропуск невозможен

Юниты

Пехотинец

  • Радиус видимости: 1 клетка
  • Действия:
    • Move: переместиться на соседнюю клетку (до 8 направлений, меньше на краях)
      • Пустая клетка → перемещается
      • Клетка врага → атака (см. ниже)
      • Клетка своего юнита → невозможный ход
      • За пределы карты → невозможный ход
    • Stay: остаться на месте → превращается в Укрепление (счётчик = 1)

Укрепление

  • Радиус видимости: 2 клетки
  • Счётчик стояния: 1, 2, 3 (на 3 — принудительно становится Фабрикой)
  • Действия:
    • Stay: счётчик +1. При счётчике = 3 → превращается в Фабрику
    • Move: превращается в Пехотинца, перемещается, счётчик сбрасывается
    • Attack: атакует соседнего врага (см. правила атаки)

Фабрика (Академия)

  • Радиус видимости: 1 клетка
  • Возникает из Укрепления после 3 ходов стояния
  • Действия:
    • Produce: создаёт нового Пехотинца на свободной соседней клетке
      • Если нет свободных соседних клеток → Фабрика погибает (нарушение правила обязательного хода)
  • Новый пехотинец в ход появления: не может ходить, не становится укреплением (считается как сходивший)
  • Фабрики ходят последними после всех боевых юнитов

API действий — только вектор

Любой ход любого юнита задаётся вектором (dx, dy) где dx, dy ∈ {-1, 0, 1}.

(0, 0)  — стоять на месте (только боевые юниты; для фабрики невозможный ход)
(1, 0)  — вправо
(-1, 1) — влево-вверх
... и т.д. (8 направлений)

Движок сам интерпретирует вектор исходя из типа юнита и состояния целевой клетки:

  • Пехотинец (0,0) → становится Укреплением
  • Пехотинец (dx,dy) + пустая клетка → перемещение
  • Пехотинец (dx,dy) + клетка врага → атака
  • Укрепление (0,0) → счётчик +1 (при 3 → Фабрика)
  • Укрепление (dx,dy) → становится Пехотинцем, далее правила пехотинца
  • Фабрика (dx,dy) + пустая клетка → производит Пехотинца
  • Фабрика (0,0) → невозможный ход

Правила атаки

Ключевой принцип: Укрепление при любом движении сначала становится Пехотинцем, затем применяются правила пехотинца. Пехотинец никогда не остаётся на исходной клетке — он либо перемещается на целевую, либо уничтожается.

Пехотинец атакует Пехотинца

  • Враг уничтожается
  • Атакующий занимает его клетку

Пехотинец атакует Укрепление (1-й удар)

  • Атакующий Пехотинец уничтожается
  • Укрепление превращается в Пехотинца, остаётся на месте
  • Для уничтожения нужен 2-й Пехотинец в тот же ход

Пехотинец атакует Укрепление (2-й удар, после 1-го)

  • Укрепление уже стало Пехотинцем → стандартная атака пехотинца на пехотинца
  • Враг уничтожен, атакующий занимает клетку

Пехотинец атакует Фабрику

  • Фабрика уничтожается
  • Атакующий занимает её клетку

Укрепление атакует кого угодно

  • Укрепление сначала становится Пехотинцем
  • Затем применяются правила пехотинца для целевой клетки
  • Итог для каждой цели:
Цель Результат
Пустая клетка Перемещается как пехотинец
Пехотинец врага Враг уничтожен, занимает клетку
Укрепление врага (1-й удар) Атакующий уничтожен, вражеское укрепление → пехотинец
Укрепление врага (2-й удар) Враг уничтожен, атакующий занимает клетку
Фабрика врага Фабрика уничтожена, занимает клетку
Свой юнит Невозможный ход

Туман войны

  • Игрок видит только клетки в радиусе видимости своих юнитов
  • Остальное — туман (неизвестное состояние)
  • При движении юнита: старая область видна до конца хода + новая область видна сразу
  • На следующем ходе: видна только область вокруг новой позиции

Условие победы

  • Последний игрок с живыми юнитами побеждает
  • Ничья невозможна

Отключение игрока

  • При отключении во время его хода:
    • Откат к снапшоту после хода предыдущего игрока
    • Все юниты отключившегося удаляются
    • Ход переходит следующему игроку
  • При отключении вне его хода:
    • Юниты удаляются немедленно
  • Потеря связи: ждём таймаут, затем как отключение
  • Если остался 1 игрок → победа
  • Если остались 2+ → продолжают без выбывшего

Снапшоты

  • Сервер хранит снапшот после хода каждого игрока
  • Снапшот включает: позиции всех юнитов, туман войны каждого игрока, счётчики укреплений
  • При rollback восстанавливается последний валидный снапшот

Архитектура

[Браузер]          [AI клиент Python]
    |                     |
    └──────────┬───────────┘
               ↓
            [Nginx]
               ↓
         [Game Server]
         (Rust / Actix-web)
               ↓
         [Game Engine]
         (Rust library)

Компоненты

1. Game Engine (/engine)

  • Rust библиотека (crate)
  • Чистая игровая логика без сети и I/O
  • Входные данные: состояние + действие
  • Выходные данные: новое состояние + результат

Основные типы:

GameConfig    // размер поля, кол-во игроков, начальные позиции
GameState     // полное состояние игры
PlayerView    // состояние с туманом войны для конкретного игрока
Action        // ход юнита: { unit_id, dx: i32, dy: i32 } где dx,dy ∈ {-1,0,1}
ActionResult  // Ok(GameState) | Err(InvalidAction)
UnitType      // Infantry | Fortification { turns_standing: u8 } | Factory

Основные функции:

fn new_game(config: GameConfig) -> GameState
fn get_valid_actions(state: &GameState, player_id: u8, unit_id: u32) -> Vec<Action>
fn apply_action(state: &mut GameState, player_id: u8, unit_id: u32, action: Action) -> Result<(), GameError>
fn end_player_turn(state: &mut GameState, player_id: u8) -> Result<(), GameError>
fn get_visible_state(state: &GameState, player_id: u8) -> PlayerView
fn get_winner(state: &GameState) -> Option<u8>
fn remove_player(state: &mut GameState, player_id: u8)

2. Game Server (/server)

  • Rust + Actix-web
  • Многосессионный, многопользовательский
  • Хранение сессий в памяти (без персистенции на первом этапе)
  • WebSocket для real-time обновлений

REST API:

POST /game/create              — создать сессию, вернуть game_id
POST /game/{id}/join           — подключиться как игрок, вернуть player_token
GET  /game/{id}/state          — получить PlayerView (с туманом войны)
POST /game/{id}/action         — сделать ход юнитом
POST /game/{id}/end_turn       — завершить ход (все юниты сходили)
WS   /game/{id}/stream         — подписаться на обновления состояния

Аутентификация: простой токен выдаётся при /join

3. Nginx (/nginx)

  • Проксирует запросы на Game Server
  • Раздаёт статику браузерного клиента
  • Конфиг: /nginx/nginx.conf

4. Браузерный клиент (/client)

  • Vanilla JS или простой React
  • Отображает поле с туманом войны
  • Принимает ввод от пользователя
  • Подключается к серверу через REST + WebSocket

5. AI клиент (/ai_client)

  • Python скрипт
  • Подключается к серверу как обычный игрок (тот же API)
  • Использует локальную модель через Ollama или обученную RL модель

Стек технологий

  • Engine: Rust (без внешних зависимостей кроме serde)
  • Server: Rust, Actix-web, tokio, serde_json
  • Client: HTML + CSS + Vanilla JS (без фреймворков на первом этапе)
  • AI Client: Python, requests, gymnasium (для RL обучения)
  • Proxy: Nginx