Files
cacto a9d2a6b15c Каркас проекта: ТЗ, nginx+gameserver(uWebSockets) в Docker, /ws echo
Frontend — чистый HTML/CSS/JS без сборки (резюме, проекты, игра-заглушки).
Gameserver — C++ на uWebSockets (CMake FetchContent), протокол hello/lobby.list_rooms/error проверен end-to-end.
2026-07-26 19:31:44 +05:00

11 KiB
Raw Permalink Blame History

ТЗ: cactoz.su

1. Общее

Личный сайт-визитка на домене cactoz.su. Три страницы, единая Matrix-стилистика.

Тема: чёрный фон, зелёный и серебро, моноширинный шрифт (JetBrains Mono / Share Tech Mono), строго и технично — без лишних декоративных элементов.

Ник cacto (кактус) — сквозной мотив:

  • Пиксельный кактус (16×16 или 32×32 спрайт, зелёный/серебро на чёрном) как лого в шапке и favicon.
  • ASCII-арт кактуса в hero-блоке главной страницы.
  • Терминальные префиксы вместо обычных заголовков/навигации: cacto@matrix:~$ whoami, cacto@matrix:~$ ls projects/, cacto@matrix:~$ ./battlecity.
  • Названия комнат в лобби генерируются в формате cactus-room-01, cactus-room-02, ...
  • Характер, а не просто ник: живучесть, минимум ресурсов, "колючий" стиль текста в отдельных местах (например, error-сообщения).

2. Страницы

# Файл Назначение
1 index.html Резюме — PHP/Go backend, опыт КИПиА/АСУТП
2 projects.html Карточки проектов со ссылками на репозитории
3 game.html BattleCity: лобби + игра

3. Структура проекта

cactoz.su/
├── docker-compose.yml
├── nginx/
│   ├── Dockerfile
│   └── nginx.conf
├── frontend/                     # чистый HTML/CSS/JS, без сборки
│   ├── index.html
│   ├── projects.html
│   ├── game.html
│   ├── favicon.ico
│   ├── css/
│   │   ├── theme.css             # переменные темы, общий сброс
│   │   ├── resume.css
│   │   ├── projects.css
│   │   └── game.css
│   ├── js/
│   │   ├── matrix-rain.js        # canvas-эффект цифрового дождя
│   │   ├── projects.js           # рендер карточек проектов из data
│   │   └── game/
│   │       ├── network.js        # обёртка над WebSocket, очередь сообщений
│   │       ├── lobby.js           # UI лобби: список комнат, создание/вход
│   │       ├── protocol.js        # константы типов сообщений, (де)сериализация
│   │       ├── entities.js        # клиентские представления Tank/Bullet/Wall для Quintus
│   │       └── main.js            # инициализация Quintus, игровой цикл рендера
│   └── vendor/
│       └── quintus/               # Quintus.js, подключается напрямую <script>
├── gameserver/                    # C++ / uWebSockets
│   ├── CMakeLists.txt
│   ├── Dockerfile
│   ├── third_party/               # uWebSockets, uSockets, nlohmann/json (submodules)
│   └── src/
│       ├── main.cpp               # запуск uWS::App, роутинг /ws
│       ├── Protocol.hpp           # структуры сообщений + (де)сериализация JSON
│       ├── Player.hpp             # id, nickname, ws-handle, room_id
│       ├── Lobby.hpp / .cpp       # реестр комнат, create/join/leave/list
│       ├── Room.hpp / .cpp        # жизненный цикл комнаты (waiting/playing/finished)
│       ├── Game.hpp / .cpp        # игровой цикл тика, применение input, коллизии
│       ├── Map.hpp                # тайловые карты (кирпич/сталь/вода/куст), парсинг
│       ├── Tank.hpp
│       ├── Bullet.hpp
│       └── Ai.hpp / .cpp          # AI противника для coop-режима
└── TZ.md

4. Docker

Два контейнера, как договорились:

  • nginx — раздаёт статику из frontend/, проксирует /ws на gameserver:9001 (с Upgrade/Connection заголовками для WebSocket). При необходимости терминирует TLS (certbot/сертификаты монтируются volume'ом).
  • gameserver — multi-stage Dockerfile: build-стадия (g++/cmake, сборка uWebSockets из submodule), финальный минимальный runtime-образ с одним бинарником.

docker-compose.yml связывает оба сервиса общей сетью; наружу открыт только nginx (80/443), gameserver доступен только изнутри docker-сети.

5. WebSocket-протокол

Один WS-канал на клиента на всё: hello → лобби → комната → игра. Формат сообщений — JSON-конверт:

{ "type": "namespace.action", "payload": { ... } }

Client → Server

type payload Когда
hello { nickname: string } Сразу после открытия соединения
lobby.list_rooms {} При входе в лобби / по запросу обновления
lobby.create_room { mode: "coop" | "pvp" } Создание комнаты (имя генерируется сервером: cactus-room-NN)
lobby.join_room { room_id: string } Присоединение к существующей комнате
lobby.leave_room {} Выход из комнаты обратно в лобби
game.ready {} Игрок готов к старту матча
game.input { seq: number, up: bool, down: bool, left: bool, right: bool, fire: bool } При каждом изменении состояния клавиш

Server → Client

type payload Когда
hello.ack { player_id: string } Ответ на hello
error { code: string, message: string } Любая ошибка протокола/логики
lobby.rooms { rooms: [{ id, name, mode, players: number, max_players: 2, state }] } Ответ на lobby.list_rooms, и при любом изменении списка (broadcast всем в лобби)
lobby.room_joined { room_id, name, mode, players: [{ id, nickname, slot }] } Игроку, который вошёл в комнату
lobby.room_updated { players: [...] } Broadcast остальным в комнате при join/leave
game.start { map: Tile[][], mode, players: [...], tick_rate } Когда оба игрока готовы (или для coop — один готов + таймаут)
game.state { tick, tanks: [...], bullets: [...], base?: {...}, enemies?: [...] } Каждый серверный тик (полный снапшот, без дельт — упрощение для MVP)
game.over { result: "win" | "lose" | "draw", reason } Конец матча

Снапшоты, а не дельты — сознательное упрощение ради простоты клиента и отладки; при необходимости оптимизации это единственное место, которое переделывается (дельты/бинарный формат), протокол на это не завязан жёстко.

6. Игровая логика на сервере

  • Тик 20 Гц (50 мс), authoritative-симуляция: клиент не считает физику, только рендерит game.state и шлёт game.input.
  • Сущности: Tank {id, x, y, direction, lives, fire_cooldown}, Bullet {id, owner_id, x, y, direction, speed}, тайлы карты (кирпич — разрушается, сталь — нет, вода — препятствие для танков/не для пуль, куст — визуально скрывает).
  • Движение — по классике, сеточное/пошаговое (проще для коллизий и портфолио, чем free-form пиксельная физика).
  • Коллизии: пуля-стена (разрушает кирпич), пуля-танк (урон/уничтожение), танк-стена/танк-танк (блокировка).
  • Режим coop: сервер спавнит AI-противников волнами (спавн-точки сверху карты, простая логика: движение к базе + случайный огонь), оба игрока защищают базу (орла). Поражение — база или оба игрока уничтожены; победа — все враги волны уничтожены.
  • Режим pvp: без AI, симметричная карта, у каждого игрока пул жизней (например, 3), поражение — жизни закончились.
  • Жизненный цикл комнаты: waiting (сбор игроков/ready) → playing (активен игровой цикл) → finished (отправлен game.over, комната закрывается через короткий таймаут).
  • Дисконнект: при обрыве соединения во время матча — пауза с таймаутом (например, 30 сек), затем техническое поражение отключившегося.

7. Лобби

Отдельного REST нет — всё через тот же WS-канал (см. таблицы сообщений в разделе 5). Список комнат обновляется broadcast'ом при любом изменении (создание/удаление/смена состава), не только по запросу клиента.

8. MVP-этапы (предложение)

  1. Каркас: nginx + gameserver в Docker, статика отдаётся, /ws echo-проверка.
  2. Вёрстка страниц 1–2 (резюме, проекты) в теме Matrix/cacto.
  3. Лобби: список/создание/присоединение комнат (без игровой логики).
  4. Игровая логика PvP (проще двух режимов) → затем coop + AI.
  5. Полировка: спрайты танков, звук (опционально), доп. карты.

Открытые вопросы, требующие решения до реализации: количество и вид карт для MVP (сколько уровней/волн для coop), спрайты (рисовать самим/найти свободные ассеты в стиле Matrix), таймаут ready-фазы в лобби.