Files
battleship/docs/MVP.md
T
2026-08-27 22:16:23 +03:00

38 KiB

MVP: «Морской бой» на ESP32-C6 Mini

1. Назначение

Создать первую рабочую версию игры «Морской бой», полностью обслуживаемую одной платой ESP32-C6 Mini. Контроллер подключается к домашней Wi-Fi-сети, раздаёт веб-интерфейс и хранит всю игровую логику и текущее состояние в оперативной памяти.

Играть можно с телефонов и планшетов в двух режимах:

  1. игрок против игрока с двух разных устройств;
  2. игрок против ESP32.

К активной партии могут подключаться зрители. Одновременно ESP32 обслуживает только одну партию.

2. Цели MVP

  • Полностью провести партию от входа игроков до определения победителя.
  • Поддержать режимы «игрок против игрока» и «игрок против ESP32».
  • Обеспечить синхронное обновление интерфейсов через WebSocket.
  • Использовать HTTP-опрос как автоматический резервный механизм при недоступности WebSocket.
  • Не передавать игрокам и зрителям скрытое расположение неповреждённых кораблей соперника.
  • Поддержать зрителей, повторную игру и статистику в пределах текущего запуска ESP32.
  • Сделать русскоязычный адаптивный интерфейс прежде всего для телефонов и планшетов.

3. Что не входит в MVP

  • Несколько одновременных игровых комнат.
  • Игра через интернет без доступа к домашней сети.
  • Регистрация, пароли и полноценные учётные записи.
  • Сохранение партии и статистики после перезагрузки ESP32.
  • Ручная расстановка кораблей.
  • Чат, звук, таймер хода, рейтинги и история матчей.
  • Отдельная точка доступа Wi-Fi, создаваемая ESP32.
  • Гарантированная поддержка настольных компьютеров как целевой платформы.

4. Принятые правила

4.1. Поле и флот

  • Размер поля: 10 × 10 клеток.
  • Столбцы обозначаются буквами А–К без буквы Ё, строки — числами 1–10.
  • Классический флот каждого участника:
    • 1 корабль длиной 4 клетки;
    • 2 корабля длиной 3 клетки;
    • 3 корабля длиной 2 клетки;
    • 4 корабля длиной 1 клетку.
  • Корабли располагаются только горизонтально или вертикально.
  • Корабли не могут соприкасаться ни сторонами, ни углами.
  • Расстановка создаётся сервером автоматически и случайно перед каждой партией.

4.2. Ходы

  • Первый ход случайно назначается сервером после начала партии.
  • Игрок выбирает одну ещё не обстрелянную клетку поля соперника.
  • При попадании игрок сохраняет ход и стреляет снова.
  • При промахе ход переходит сопернику.
  • Потопленным считается корабль, у которого поражены все клетки.
  • После потопления сервер автоматически отмечает окружающие корабль клетки как гарантированные промахи.
  • Побеждает участник, первым уничтоживший все 10 кораблей соперника.
  • Повторный выстрел по уже обработанной клетке отклоняется сервером и не меняет ход.

Эти правила фиксируются для MVP и должны одинаково применяться к человеку и встроенному сопернику.

5. Роли

Игрок 1

  • Вводит отображаемое имя.
  • Выбирает режим игры.
  • Запускает партию, когда второй участник готов или выбран режим против ESP32.
  • Видит полностью своё поле и только результаты выстрелов на поле соперника.

Игрок 2

  • Доступен только в режиме «игрок против игрока».
  • Вводит отображаемое имя и занимает свободное место второго игрока.
  • Имеет тот же объём игровой информации, что и первый игрок.

Зритель

  • Вводит отображаемое имя и подключается без права выполнять игровые действия.
  • Видит имена игроков, текущий ход, статистику и результаты уже выполненных выстрелов на обоих полях.
  • Не видит неповреждённые корабли ни одного игрока во время партии.
  • После завершения партии видит полностью раскрытые поля.

ESP32-соперник

  • Отображается под именем «ESP32».
  • Ходит автоматически после небольшой задержки интерфейса, например 500–900 мс.
  • Использует серверную стратегию и не раскрывает своё поле клиенту.

6. Идентификация и сессии

  • При первом открытии пользователь вводит имя длиной 1–20 символов.
  • Сервер очищает имя от управляющих символов и HTML-разметки.
  • После входа сервер выдаёт случайный непрозрачный sessionToken и назначает роль.
  • Браузер хранит токен и имя в localStorage.
  • При кратковременной потере связи пользователь может вернуться в прежнюю роль по токену, пока ESP32 не перезагружена и место не освобождено.
  • Одинаковые отображаемые имена допустимы: сервер различает клиентов по токену.
  • Если оба игровых места заняты, новый пользователь может подключиться только зрителем.
  • Рекомендуемый лимит MVP: до 2 игроков и 8 одновременно подключённых зрителей. Значение должно быть конфигурируемым.
  • После перезагрузки ESP32 все токены, партия и статистика сбрасываются.

Полноценная авторизация не требуется: приложение предназначено для доверенной домашней сети. Токен нужен для сохранения роли и предотвращения случайной отправки хода чужим браузером.

7. Пользовательские сценарии

7.1. Игрок против игрока

  1. Первый пользователь открывает адрес ESP32, вводит имя и становится игроком 1.
  2. Он выбирает режим «Два игрока».
  3. Второй пользователь открывает тот же адрес, вводит имя и занимает место игрока 2.
  4. Сервер сообщает обоим игрокам, что партия готова к запуску.
  5. Игрок 1 нажимает «Начать игру».
  6. Сервер автоматически и независимо расставляет оба флота, выбирает первого игрока и рассылает разрешённое состояние всем клиентам.
  7. Игроки по очереди стреляют до победы одного из них.
  8. На экране результата показываются победитель, итоговая статистика и кнопка «Сыграть ещё».

7.2. Игрок против ESP32

  1. Пользователь входит как игрок 1 и выбирает режим «Против ESP32».
  2. Место второго участника автоматически занимает ESP32.
  3. Игрок запускает партию.
  4. Сервер расставляет оба флота и выбирает первого участника.
  5. Во время хода ESP32 сервер сам выполняет один или несколько выстрелов с учётом правила продолжения хода после попадания.
  6. После завершения доступна повторная игра.

7.3. Зритель

  1. Пользователь выбирает «Наблюдать» или автоматически получает роль зрителя, если игровые места заняты.
  2. Сервер отправляет ему публичное представление текущей партии.
  3. Зритель получает обновления в реальном времени, но сервер отклоняет любые команды выстрела, старта или повторной игры.

7.4. Повторная игра

  • После завершения любой игрок может предложить повторную игру.
  • В режиме двух игроков новый матч начинается после подтверждения обоих игроков.
  • В режиме против ESP32 достаточно подтверждения человека.
  • Для нового матча сервер создаёт новые случайные расстановки и заново выбирает первого участника.
  • Имена, роли и накопленная статистика текущего запуска сохраняются.

8. Состояния игры

LOBBY -> PREPARING -> IN_PROGRESS -> FINISHED -> REMATCH_WAIT
  ^                                                |
  +------------------------------------------------+
  • LOBBY: выбор режима и ожидание участников.
  • PREPARING: генерация и проверка расстановок.
  • IN_PROGRESS: активная партия.
  • FINISHED: победитель определён, поля раскрыты.
  • REMATCH_WAIT: ожидание подтверждений повторной игры.

Каждое изменение состояния увеличивает монотонный номер version. Клиенты используют его для обнаружения пропущенных событий и резервного HTTP-опроса.

9. Интерфейс

9.1. Экраны

  1. Подключение — имя, кнопки «Играть» и «Наблюдать», сообщение о доступности мест.
  2. Лобби — участники, выбор режима игроком 1, состояние готовности и запуск.
  3. Игра — собственное поле, поле выстрелов, имя текущего игрока, статус последнего выстрела и краткая статистика.
  4. Наблюдение — два публичных поля и ход партии без элементов управления.
  5. Результат — победитель, раскрытые поля, статистика матча и повторная игра.

9.2. Адаптивность

  • На телефоне поля показываются по одному с переключателем «Моё поле / Поле соперника».
  • На планшете при достаточной ширине поля показываются рядом.
  • Размер игрового поля подстраивается под ширину экрана, клетки остаются квадратными.
  • Основные кнопки имеют крупную сенсорную область.
  • Нельзя полагаться только на цвет: попадание, промах и корабль дополнительно различаются символом или формой.
  • Перед отправкой выстрела выбранная клетка визуально подтверждается. Для уменьшения ошибочных касаний допустим режим «выбрать клетку → нажать “Огонь”».
  • Интерфейс и все сообщения — только на русском языке.

9.3. Обозначения клеток

  • вода — пустая синяя клетка;
  • собственный корабль — контрастная заливка;
  • промах — точка;
  • попадание — крест;
  • потопленный корабль — кресты с отдельным оформлением контура;
  • выбранная цель — заметная рамка до подтверждения выстрела.

10. Предлагаемая техническая реализация

10.1. Базовый стек

  • Среда: Visual Studio Code + PlatformIO.
  • Целевая плата: ESP32-C6 Mini.
  • Платформа: Espressif 32. Точный идентификатор board в platformio.ini выбирается по производителю и маркировке конкретной ESP32-C6 Mini; если готового описания платы нет, используется совместимая конфигурация ESP32-C6 с явно заданными параметрами flash и разделов памяти.
  • Фреймворк: Arduino для ESP32 как наиболее быстрый путь к MVP в PlatformIO.
  • Сеть: штатный WiFi в режиме клиента домашней сети.
  • Файловая система: LittleFS для HTML, CSS и JavaScript.
  • HTTP и WebSocket: асинхронный веб-сервер с поддержкой ESP32-C6; конкретную совместимую библиотеку следует закрепить по версии в platformio.ini.
  • JSON: ArduinoJson либо небольшой собственный сериализатор с контролируемыми буферами.
  • Фронтенд: нативные HTML, CSS и JavaScript без обязательного фреймворка.

Vanilla JavaScript рекомендуется для MVP, потому что игровому интерфейсу не нужен крупный UI-фреймворк. Это уменьшает размер файлов, потребление памяти и зависимость от доступа в интернет. Все необходимые ресурсы должны храниться на ESP32; CDN не должен быть обязательным для запуска игры.

Если выбранная асинхронная библиотека окажется нестабильной на конкретной версии Arduino Core для ESP32-C6, запасной вариант — PlatformIO с ESP-IDF и встроенным esp_http_server, который поддерживает WebSocket. Игровая модель и протокол при этом останутся теми же.

10.2. Компоненты прошивки

  • WiFiManager — подключение к заранее заданной домашней сети и отображение состояния связи.
  • WebServer — статические файлы, HTTP API и WebSocket.
  • SessionManager — токены, имена, роли, подключения и переподключения.
  • GameEngine — правила, очередь хода, выстрелы, победа и повторная игра.
  • FleetGenerator — случайная корректная расстановка флота.
  • BotPlayer — выбор цели ESP32.
  • StatePresenter — формирует отдельное безопасное представление состояния для каждого игрока и зрителей.
  • Statistics — статистика матча и накопительные показатели до перезагрузки.

Сетевая обработка не должна напрямую изменять массивы поля. Любая команда сначала проходит проверку сессии, роли, фазы, номера партии и очереди хода, после чего передаётся в GameEngine.

11. Модель данных

Пример внутренних структур на уровне концепции:

enum class Cell : uint8_t { Water, Ship, Miss, Hit };
enum class Phase : uint8_t { Lobby, Preparing, InProgress, Finished, RematchWait };
enum class Mode : uint8_t { HumanVsHuman, HumanVsBot };
enum class Role : uint8_t { Player1, Player2, Spectator };

struct Ship {
    uint8_t x;
    uint8_t y;
    uint8_t length;
    bool horizontal;
    uint8_t hits;
};

struct Board {
    Cell cells[10][10];
    Ship ships[10];
    uint8_t shipsAlive;
};

Для каждой партии сервер хранит:

  • уникальный gameId;
  • фазу и режим;
  • два внутренних поля;
  • текущего участника;
  • победителя;
  • номер версии состояния;
  • подтверждения повторной игры;
  • статистику обоих участников;
  • последнее публичное событие.

Координаты внутри прошивки и протокола рекомендуется хранить числами x и y от 0 до 9. Буквенные обозначения формирует интерфейс.

12. Скрытие информации

Сервер не должен отправлять единый полный объект партии всем клиентам.

  • Игрок получает своё полное поле и только известные клетки поля соперника.
  • Зритель получает только известные клетки обоих полей.
  • Внутренняя расстановка ESP32 никогда не попадает в браузер до завершения партии.
  • После FINISHED сервер может включить в представление полные поля обоих участников.
  • Все игровые проверки выполняются на ESP32. Клиентская проверка нужна только для удобства интерфейса и не считается защитой.

13. HTTP API

Предлагаемый минимальный набор:

Метод Путь Назначение
GET / Основная страница
GET /assets/* CSS, JavaScript и локальные ресурсы
GET /api/info Состояние устройства и доступность мест без скрытых данных
POST /api/session/join Вход по имени и желаемой роли
POST /api/session/resume Восстановление роли по токену
POST /api/game/config Выбор режима игроком 1
POST /api/game/start Запуск готовой партии
POST /api/game/shot Выстрел по координатам
POST /api/game/rematch Подтверждение повторной игры
GET /api/state?version=N Снимок разрешённого состояния и резервный опрос
GET /api/health Проверка доступности сервера

Все изменяющие запросы содержат токен сессии и gameId. Сервер возвращает JSON с полями ok, code, message и при необходимости новой version.

Основные коды ошибок:

  • INVALID_NAME;
  • NO_PLAYER_SLOT;
  • UNAUTHORIZED;
  • FORBIDDEN_ROLE;
  • WRONG_PHASE;
  • NOT_YOUR_TURN;
  • CELL_ALREADY_SHOT;
  • STALE_GAME;
  • SERVER_BUSY.

14. WebSocket и резервный опрос

  • Точка подключения: /ws.
  • После открытия клиент передаёт токен сессии и последнюю известную version.
  • Сервер подтверждает сессию и отправляет персонализированный снимок состояния.
  • После каждого принятого действия сервер увеличивает version и рассылает новые безопасные представления всем подключённым клиентам.

Рекомендуемые серверные события:

  • state — полный разрешённый снимок;
  • player_joined и player_left;
  • game_started;
  • shot_result;
  • turn_changed;
  • ship_sunk;
  • game_finished;
  • rematch_status;
  • error;
  • ping/pong.

Клиентская стратегия соединения:

  1. открыть WebSocket;
  2. при обрыве выполнить повторные подключения с растущей задержкой, например 1, 2, 5 и 10 секунд;
  3. пока WebSocket недоступен, запрашивать /api/state каждые 2 секунды;
  4. после восстановления WebSocket прекратить опрос;
  5. если полученная версия не следует за текущей, запросить полный снимок.

HTTP остаётся каналом для команд и резервной синхронизации, а WebSocket используется для немедленной доставки изменений. Такой подход проще отлаживать и позволяет выполнить ход даже во время кратковременного восстановления WebSocket.

15. Алгоритмы

15.1. Автоматическая расстановка

  1. Очистить поле.
  2. Перемешать порядок кораблей или обрабатывать их от длинных к коротким.
  3. Случайно выбрать ориентацию и начальную клетку.
  4. Проверить границы поля.
  5. Проверить клетки корабля и все соседние клетки вокруг него.
  6. Разместить корабль либо повторить попытку.
  7. Если лимит попыток исчерпан, очистить поле и запустить генерацию заново.
  8. Перед стартом проверить количество и длины кораблей, отсутствие касаний и выходов за границы.

15.2. Соперник ESP32

Для MVP предлагается стратегия hunt/target:

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

Стратегия достаточно понятна и интереснее чистого случайного выбора, но остаётся небольшой по объёму кода и памяти.

16. Статистика

Для каждого участника в текущем матче показываются:

  • количество выстрелов;
  • попадания;
  • промахи;
  • точность в процентах;
  • число потопленных кораблей;
  • результат матча.

До перезагрузки ESP32 дополнительно накапливаются:

  • сыгранные партии;
  • победы и поражения;
  • общие выстрелы и попадания;
  • общая точность.

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

17. Обработка отключений и ошибок

  • При отключении игрока активная партия не завершается сразу; его место и состояние сохраняются в памяти ESP32.
  • Интерфейс остальных клиентов показывает статус «Игрок переподключается».
  • После возвращения с тем же токеном игрок получает актуальный снимок и продолжает партию.
  • Для MVP партия может ожидать отключившегося игрока неограниченно; игрок 1 может вернуть систему в лобби отдельной командой подтверждения.
  • Отключение зрителя не влияет на игру.
  • Потеря WebSocket автоматически включает HTTP-опрос.
  • Потеря Wi-Fi или перезапуск ESP32 завершает текущую партию. После восстановления пользователи входят заново.
  • При переполнении лимита подключений новый зритель получает понятное сообщение, а действующая партия продолжается.

18. Предлагаемая структура проекта

esp32-battleship/
├── platformio.ini
├── include/
│   ├── AppConfig.h
│   ├── GameTypes.h
│   ├── GameEngine.h
│   ├── FleetGenerator.h
│   ├── BotPlayer.h
│   ├── SessionManager.h
│   ├── StatePresenter.h
│   └── Statistics.h
├── src/
│   ├── main.cpp
│   ├── GameEngine.cpp
│   ├── FleetGenerator.cpp
│   ├── BotPlayer.cpp
│   ├── SessionManager.cpp
│   ├── StatePresenter.cpp
│   └── WebApi.cpp
├── data/
│   ├── index.html
│   └── assets/
│       ├── app.css
│       └── app.js
└── test/
    ├── test_fleet_generator/
    ├── test_game_engine/
    └── test_bot_player/

Wi-Fi-данные не следует фиксировать в публичном репозитории. Для MVP их можно вынести в локальный файл конфигурации, исключённый из Git. В дальнейшем можно добавить страницу первичной настройки сети.

19. Ограничения для ESP32-C6 Mini

До начала реализации необходимо зафиксировать точного производителя/модель платы, объём flash и наличие встроенного светодиода. Название «ESP32-C6 Mini» используется несколькими платами, поэтому эти параметры нельзя надёжно определить только по общему названию. Игровая архитектура от них не зависит, но они влияют на platformio.ini, таблицу разделов и назначение выводов.

  • Хранить игровые поля в компактных фиксированных массивах, а не в динамических коллекциях.
  • Не создавать отдельную полную JSON-копию состояния для каждого клиента одновременно.
  • Ограничить размер входящих HTTP- и WebSocket-сообщений.
  • Не загружать изображения и крупные внешние библиотеки без необходимости.
  • Сжимать статические файлы (gzip) при сборке и отдавать их с правильными заголовками.
  • Настроить кэширование CSS и JavaScript, но не кэшировать персонализированное API-состояние.
  • Не выполнять длительные циклы генерации, хода бота или рассылки внутри критического сетевого обработчика.
  • Добавить периодическую очистку истёкших зрительских сессий.

20. Этапы реализации

Этап 1. Каркас устройства

  • Создать PlatformIO-проект и конфигурацию конкретной ESP32-C6 Mini.
  • Подключить ESP32 к домашней сети.
  • Настроить LittleFS и выдачу тестовой страницы.
  • Добавить /api/health и журналирование через Serial.

Этап 2. Игровое ядро

  • Реализовать структуры поля и кораблей.
  • Реализовать и протестировать генератор флота.
  • Реализовать выстрел, попадание, промах, потопление, смену хода и победу.
  • Реализовать статистику матча.

Этап 3. Сессии и API

  • Реализовать вход по имени, роли и токены.
  • Добавить лобби и запуск партии.
  • Добавить персонализированные представления состояния.
  • Реализовать HTTP-команды и проверки доступа.

Этап 4. Синхронизация

  • Подключить WebSocket.
  • Добавить версионирование состояния и рассылку событий.
  • Реализовать переподключение и HTTP fallback.

Этап 5. Веб-интерфейс

  • Реализовать экраны подключения, лобби, игры и результата.
  • Добавить адаптивные поля для телефона и планшета.
  • Добавить режим зрителя и понятные сообщения об ошибках.

Этап 6. Бот и повторная игра

  • Реализовать стратегию hunt/target.
  • Добавить задержку и визуализацию хода ESP32.
  • Добавить подтверждение повторной игры и накопительную статистику.

Этап 7. Проверка на устройстве

  • Проверить два телефона и несколько зрителей одновременно.
  • Проверить переподключение, потерю WebSocket и переход на опрос.
  • Проверить расход памяти и устойчивость нескольких последовательных партий.
  • Оптимизировать и сжать статические ресурсы.

21. Тестирование

Автоматические тесты логики

  • Генератор создаёт ровно 10 кораблей нужных размеров.
  • Ни один корабль не выходит за поле и не касается другого.
  • Выстрел по воде создаёт промах и меняет ход.
  • Попадание сохраняет ход.
  • Повторный выстрел отклоняется без изменения состояния.
  • Потопление корректно отмечает корабль и соседние клетки.
  • Уничтожение последнего корабля завершает партию.
  • Бот никогда не стреляет дважды в одну клетку.
  • Представление игрока и зрителя не содержит скрытых кораблей.

Ручные сценарии

  • Полная партия «игрок против игрока» с двух устройств.
  • Полная партия против ESP32.
  • Подключение и отключение нескольких зрителей во время партии.
  • Обновление страницы игроком и восстановление по токену.
  • Отключение WebSocket с продолжением через HTTP-опрос.
  • Повторная игра в обоих режимах.
  • Перезапуск ESP32 с ожидаемым сбросом партии.
  • Проверка интерфейса на узком телефоне и планшете в обеих ориентациях.

22. Критерии готовности MVP

MVP считается готовым, когда:

  1. ESP32-C6 Mini стабильно подключается к заданной домашней Wi-Fi-сети и открывает русскоязычный интерфейс.
  2. Два пользователя могут войти по именам и полностью сыграть одну партию с разных устройств.
  3. Один пользователь может полностью сыграть партию против ESP32.
  4. Все расстановки соответствуют классическому набору и запрету касаний.
  5. Сервер отклоняет недопустимые и несвоевременные выстрелы.
  6. Скрытые корабли не присутствуют в сетевых ответах для соперника или зрителя.
  7. Минимум два зрителя могут наблюдать партию без влияния на неё; целевая конфигурация поддерживает до восьми.
  8. Изменения обычно появляются через WebSocket, а при его отключении интерфейс автоматически продолжает обновляться через HTTP.
  9. Обновление страницы восстанавливает роль и актуальное состояние, если ESP32 не перезагружалась.
  10. После окончания отображаются победитель и корректная статистика.
  11. Повторная игра создаёт новые расстановки и сохраняет накопительную статистику до перезагрузки.
  12. Интерфейс остаётся удобным на телефоне и планшете.
  13. Не менее 20 последовательных тестовых партий проходят без зависания, заметной утечки памяти или необходимости перезапуска устройства.

23. Возможные расширения после MVP

  • Настройка Wi-Fi через временную точку доступа и captive portal.
  • Сохранение статистики в NVS.
  • Несколько комнат и больше одновременных игроков.
  • PIN-код партии и управление зрительским доступом.
  • Ручная расстановка кораблей.
  • Выбор варианта правил, включая строго один выстрел за ход.
  • Таймер хода, чат, звук и анимации.
  • Уровни сложности ESP32.
  • Локальное имя устройства через mDNS, например battleship.local.
  • OTA-обновление прошивки и веб-ресурсов.
  • Режим собственной точки доступа для игры без домашнего роутера.