# 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. Состояния игры ```text 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. Модель данных Пример внутренних структур на уровне концепции: ```cpp 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/session/leave` | Освобождение только текущей сессии | | `POST` | `/api/session/profile-reset` | Освобождение сессии перед локальной очисткой профиля | | `POST` | `/api/game/config` | Выбор режима игроком 1 | | `POST` | `/api/game/start` | Запуск готовой партии | | `POST` | `/api/game/shot` | Выстрел по координатам | | `POST` | `/api/game/rematch` | Подтверждение повторной игры | | `POST` | `/api/game/reset` | Аварийный полный сброс игровой памяти игроком | | `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. Предлагаемая структура проекта ```text 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-обновление прошивки и веб-ресурсов. - Режим собственной точки доступа для игры без домашнего роутера.