Сайт: cellwar.gg
Репозиторий: cellwar
- Прямоугольное N×M клеток
- Без препятствий и особых территорий
- Несколько игроков (минимум 2, обычно 2, поддержка 3+)
- Ходят строго по очереди в фиксированном порядке
- Порядок не меняется от раунда к раунду
- Союзников нет — все игроки враги друг другу
- Раунд = каждый игрок сходил по одному разу
- Внутри хода игрок сам выбирает порядок своих юнитов
- Сначала ходят все боевые юниты (пехотинцы + укрепления) в произвольном порядке
- Затем ходят фабрики
- Каждый юнит обязан сделать ровно один ход за раунд
- Пропуск невозможен
- Радиус видимости: 1 клетка
- Действия:
- Move: переместиться на соседнюю клетку (до 8 направлений, меньше на краях)
- Пустая клетка → перемещается
- Клетка врага → атака (см. ниже)
- Клетка своего юнита → невозможный ход
- За пределы карты → невозможный ход
- Stay: остаться на месте → превращается в Укрепление (счётчик = 1)
- Move: переместиться на соседнюю клетку (до 8 направлений, меньше на краях)
- Радиус видимости: 2 клетки
- Счётчик стояния: 1, 2, 3 (на 3 — принудительно становится Фабрикой)
- Действия:
- Stay: счётчик +1. При счётчике = 3 → превращается в Фабрику
- Move: превращается в Пехотинца, перемещается, счётчик сбрасывается
- Attack: атакует соседнего врага (см. правила атаки)
- Радиус видимости: 1 клетка
- Возникает из Укрепления после 3 ходов стояния
- Действия:
- Produce: создаёт нового Пехотинца на свободной соседней клетке
- Если нет свободных соседних клеток → Фабрика погибает (нарушение правила обязательного хода)
- Produce: создаёт нового Пехотинца на свободной соседней клетке
- Новый пехотинец в ход появления: не может ходить, не становится укреплением (считается как сходивший)
- Фабрики ходят последними после всех боевых юнитов
Любой ход любого юнита задаётся вектором (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)→ невозможный ход
Ключевой принцип: Укрепление при любом движении сначала становится Пехотинцем, затем применяются правила пехотинца. Пехотинец никогда не остаётся на исходной клетке — он либо перемещается на целевую, либо уничтожается.
- Враг уничтожается
- Атакующий занимает его клетку
- Атакующий Пехотинец уничтожается
- Укрепление превращается в Пехотинца, остаётся на месте
- Для уничтожения нужен 2-й Пехотинец в тот же ход
- Укрепление уже стало Пехотинцем → стандартная атака пехотинца на пехотинца
- Враг уничтожен, атакующий занимает клетку
- Фабрика уничтожается
- Атакующий занимает её клетку
- Укрепление сначала становится Пехотинцем
- Затем применяются правила пехотинца для целевой клетки
- Итог для каждой цели:
| Цель | Результат |
|---|---|
| Пустая клетка | Перемещается как пехотинец |
| Пехотинец врага | Враг уничтожен, занимает клетку |
| Укрепление врага (1-й удар) | Атакующий уничтожен, вражеское укрепление → пехотинец |
| Укрепление врага (2-й удар) | Враг уничтожен, атакующий занимает клетку |
| Фабрика врага | Фабрика уничтожена, занимает клетку |
| Свой юнит | Невозможный ход |
- Игрок видит только клетки в радиусе видимости своих юнитов
- Остальное — туман (неизвестное состояние)
- При движении юнита: старая область видна до конца хода + новая область видна сразу
- На следующем ходе: видна только область вокруг новой позиции
- Последний игрок с живыми юнитами побеждает
- Ничья невозможна
- При отключении во время его хода:
- Откат к снапшоту после хода предыдущего игрока
- Все юниты отключившегося удаляются
- Ход переходит следующему игроку
- При отключении вне его хода:
- Юниты удаляются немедленно
- Потеря связи: ждём таймаут, затем как отключение
- Если остался 1 игрок → победа
- Если остались 2+ → продолжают без выбывшего
- Сервер хранит снапшот после хода каждого игрока
- Снапшот включает: позиции всех юнитов, туман войны каждого игрока, счётчики укреплений
- При rollback восстанавливается последний валидный снапшот
[Браузер] [AI клиент Python]
| |
└──────────┬───────────┘
↓
[Nginx]
↓
[Game Server]
(Rust / Actix-web)
↓
[Game Engine]
(Rust library)
- 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)- 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
- Проксирует запросы на Game Server
- Раздаёт статику браузерного клиента
- Конфиг:
/nginx/nginx.conf
- Vanilla JS или простой React
- Отображает поле с туманом войны
- Принимает ввод от пользователя
- Подключается к серверу через REST + WebSocket
- 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