Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 

Repository files navigation

⚓ Sea Battle 3D — Online

Трёхмерный «Морской бой» в браузере: бой против ИИ и полноценный онлайн 1 vs 1.
Клиент — PWA на Three.js. Сервер — чистый Node.js без единой внешней зависимости.

Node Dependencies Tests PWA


Содержание


Возможности

Общее

  • Полноценная 3D-сцена на Three.js: море с многооктавными волнами, небо, модели кораблей, ракеты, взрывы, дым и осколки.
  • Берег и горизонт: горная гряда вокруг акватории со снегом на вершинах и воздушной перспективой, прибой у подножия, маяк с красно-белыми поясами, галереей и вращающейся линзой, дальний конвой с дымом из труб, парусники, буйки и стаи чаек.
  • Обстановка боя: четыре времени суток (рассвет, день, закат, ночь) × пять погод (ясно, облачно, дождь, шторм, туман). Меняются высота и азимут светила, палитра воды и неба, высота волны, видимость, освещение полей, тон гор и поведение птиц. На рассвете и закате солнце за бой заметно проходит по дуге; ночью поле освещают прожекторы на мачтах, а луч маяка виден в воздухе.
  • Три ракурса — «Своё», «Оба», «Поле соперника» — переключаются кнопками (клавиши 1, 2, 3, V). Камера переезжает между ними плавным перелётом и никогда не перебрасывается сама.
  • Одно касание — одно действие: тап по клетке ставит корабль или бьёт по полю соперника, тянущий жест вращает камеру, долгое нажатие снимает корабль.
  • PWA: устанавливается на телефон и рабочий стол. Движок и шрифты лежат в репозитории, поэтому игра поднимается полностью офлайн с первого запуска, без единого внешнего запроса.
  • Адаптивный интерфейс: в книжной ориентации телефона поля встают одно за другим — своё ближе к игроку, — и клетка становится в два с половиной раза крупнее. Кадр строится по свободной от панелей области экрана, поэтому шторки и вкладки не накрывают поле ни в портрете, ни в альбоме.
  • Звук и музыка целиком генерируются через Web Audio API — ни одного аудиофайла.
  • Диалоги доступны с клавиатуры: фокус забирается, держится по Tab и возвращается на место.

Игра с движком

  • Четыре уровня сложности: Юнга, Мичман, Капитан, Адмирал.
  • Охота по шахматному паттерну, точное добивание, карта вероятностей по остатку флота, а на «Адмирале» — перебор расстановок, совместимых со всеми известными выстрелами.
  • Расчёт идёт целиком на устройстве игрока: сеть не нужна. Панель «Замысел соперника» показывает, чем движок руководствовался.

Онлайн-режим

  • Регистрация ника (сохраняется в localStorage вместе с токеном владения — чужой ник занять нельзя).
  • Лобби со списком игроков онлайн и поиском соперника по нику.
  • Приглашения: отправка, принятие, отклонение, отмена, тайм-аут.
  • Бой полностью идентичен игре с ботом — только вместо ИИ живой человек.
  • Доска соперника скрыта: сервер физически не отправляет клиенту чужой флот.
  • Реванш в один клик, корректная обработка сдачи, выхода и обрыва связи.
  • Ники, статистика и глобальный лидерборд.

Быстрый старт

Нужен Node.js 18+. Внешние зависимости не требуются.

cd seabattle/server
npm start                 # http://localhost:8080

Сервер раздаёт статику игры из seabattle/game, держит WebSocket-лобби, судит онлайн-матчи и ведёт лидерборд.

Игру с движком можно открыть и вообще без сервера — достаточно отдать seabattle/game/index.html любым статическим хостингом. Онлайн-режим при этом будет недоступен: ему нужен ws-эндпоинт.

Отладочные параметры адреса

Параметр Что делает
?gfx=low · ?gfx=mid · ?gfx=high принудительный класс графики — посмотреть на игру глазами слабого устройства, не имея его под рукой

Как играть вдвоём

  1. Запустите сервер.
  2. Оба игрока открывают его адрес (в одной сети — http://IP-хоста:8080).
  3. На первом экране выбираем «Против человека».
  4. Вводим ник. Он сохраняется в браузере и закрепляется за вами на сервере.
  5. В лобби вводим ник соперника или выбираем его в списке → «Вызвать на бой».
  6. Соперник принимает вызов, оба расставляют флот, начинается бой.

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

seabattle/
  game/                    клиент — одна страница, устанавливается как PWA
    index.html             разметка, оформление, сцена и вся игровая логика
    vendor/                three.js и OrbitControls (не CDN — см. ниже)
    fonts/                 Oswald, Golos Text, JetBrains Mono (woff2)
    icons/  manifest.webmanifest  sw.js
  server/                  сервер без внешних зависимостей
    server.js              HTTP, статика, лидерборд
    ws.js                  реализация WebSocket (RFC 6455) с нуля
    rules.js               правила и судейство, общие с клиентом
    test/                  протокол и сквозной тест двух браузеров
  tools/
    check-text.mjs         проверка текстовой гигиены (см. «Тесты»)

Почему движок лежит в репозитории, а не берётся с CDN. Раньше three.js подключался с unpkg.com, и под этот отказ в коде был написан отдельный экран ошибки. Собственный файл убирает целый класс отказов, делает установленную PWA работоспособной офлайн с первого запуска и позволяет автоматически тестировать рендер.


Как устроен онлайн

Сервер — единственный источник правды. Клиент знает только свою доску и результаты своих выстрелов.

  1. Клиент подключается к ws://host/ws, получает hello.
  2. register закрепляет ник за токеном из localStorage.
  3. inviteinviteResp создают матч.
  4. ready с расстановкой флота — сервер её проверяет.
  5. fire — сервер судит выстрел и рассылает результат обеим сторонам.
  6. gameOver — только здесь сервер раскрывает флот проигравшего.

Античит

  • Расстановку проверяет сервер: ровно 10 кораблей 4/3/3/2/2/2/1/1/1/1, без касаний бортами и углами, всё в пределах поля.
  • Чужой флот не отправляется в браузер до конца боя, поэтому подглядеть его нельзя ни через инструменты разработчика, ни через камеру: соответствующих данных в клиенте просто нет. Это проверяется тестом, который читает и память клиента, и число видимых моделей в сцене.
  • Выстрел вне своего хода, по обстрелянной клетке и до готовности флота отклоняется сервером.
  • Ник закрепляется за токеном владения: занятый ник чужим игроком не берётся.

Рейтинг и лидерборд

рейтинг = победы×100 − поражения×40 + точность×120 + потопленные корабли×2

Виден из лобби и доступен по HTTP.


Протокол WebSocket

От клиента Назначение
register занять ник
lobby запросить список игроков в сети
invite · inviteResp · cancelInvite вызов на бой
ready отправить расстановку флота
fire выстрел
leave · rematch покинуть бой, предложить реванш
leaderboard запросить таблицу
От сервера Назначение
hello · registered · lobby подключение и лобби
inviteFrom · inviteDeclined · inviteCancelled вызовы
matchStart · readyOk · battleStart начало матча и боя
shotResult · incoming результаты выстрелов
gameOver · opponentLeft · rematchOffer финал
error отказ с причиной

HTTP API

Метод Назначение
GET /api/leaderboard таблица рейтинга
GET /api/health состояние сервера: игроков в сети, активных матчей

Переменные окружения

Переменная Назначение По умолчанию
PORT порт HTTP и WebSocket 8080
SB_DATA_DIR папка для players.json с рейтингами server/data
SB_QUIET 1 — не писать логи в консоль выкл.

Тесты

cd seabattle/server
npm test                     # текстовая гигиена + протокол (32 проверки)

npm run test:text            # только проверка текста
npm run test:protocol        # только протокол и правила

# сквозной тест: два браузера играют онлайн-матч целиком
PORT=8099 node server.js &
node test/test-e2e.js        # нужен playwright

tools/check-text.mjs ловит то, что легко просачивается незаметно: битые символы U+FFFD, файлы не в UTF-8, дробные размеры шрифта, приёмы, размывающие текст, захардкоженные ключи доступа и эмодзи вместо иконок.

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


Деплой

Любая площадка, где можно запустить Node.js 18+ и держать открытым WebSocket. Внешних зависимостей нет, сборка не нужна.

PORT=8080 SB_DATA_DIR=/var/lib/seabattle node seabattle/server/server.js

За обратным прокси нужно пропустить Upgrade и Connection для /ws. Рейтинги хранятся в players.json в SB_DATA_DIR — эту папку стоит сделать постоянным томом.


Лицензия

MIT.

About

3D «Морской бой» как PWA: игра против ИИ (4 уровня сложности) и онлайн-режим человек против человека. Ники, лобби, поиск соперника, лидерборд. Сервер на чистом Node.js без зависимостей: собственная реализация WebSocket, авторитетные правила, античит - флот соперника скрыт до конца боя.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages