38 KiB
MVP: «Морской бой» на ESP32-C6 Mini
1. Назначение
Создать первую рабочую версию игры «Морской бой», полностью обслуживаемую одной платой ESP32-C6 Mini. Контроллер подключается к домашней Wi-Fi-сети, раздаёт веб-интерфейс и хранит всю игровую логику и текущее состояние в оперативной памяти.
Играть можно с телефонов и планшетов в двух режимах:
- игрок против игрока с двух разных устройств;
- игрок против 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. Игрок против игрока
- Первый пользователь открывает адрес ESP32, вводит имя и становится игроком 1.
- Он выбирает режим «Два игрока».
- Второй пользователь открывает тот же адрес, вводит имя и занимает место игрока 2.
- Сервер сообщает обоим игрокам, что партия готова к запуску.
- Игрок 1 нажимает «Начать игру».
- Сервер автоматически и независимо расставляет оба флота, выбирает первого игрока и рассылает разрешённое состояние всем клиентам.
- Игроки по очереди стреляют до победы одного из них.
- На экране результата показываются победитель, итоговая статистика и кнопка «Сыграть ещё».
7.2. Игрок против ESP32
- Пользователь входит как игрок 1 и выбирает режим «Против ESP32».
- Место второго участника автоматически занимает ESP32.
- Игрок запускает партию.
- Сервер расставляет оба флота и выбирает первого участника.
- Во время хода ESP32 сервер сам выполняет один или несколько выстрелов с учётом правила продолжения хода после попадания.
- После завершения доступна повторная игра.
7.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, состояние готовности и запуск.
- Игра — собственное поле, поле выстрелов, имя текущего игрока, статус последнего выстрела и краткая статистика.
- Наблюдение — два публичных поля и ход партии без элементов управления.
- Результат — победитель, раскрытые поля, статистика матча и повторная игра.
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.
Клиентская стратегия соединения:
- открыть WebSocket;
- при обрыве выполнить повторные подключения с растущей задержкой, например 1, 2, 5 и 10 секунд;
- пока WebSocket недоступен, запрашивать
/api/stateкаждые 2 секунды; - после восстановления WebSocket прекратить опрос;
- если полученная версия не следует за текущей, запросить полный снимок.
HTTP остаётся каналом для команд и резервной синхронизации, а WebSocket используется для немедленной доставки изменений. Такой подход проще отлаживать и позволяет выполнить ход даже во время кратковременного восстановления WebSocket.
15. Алгоритмы
15.1. Автоматическая расстановка
- Очистить поле.
- Перемешать порядок кораблей или обрабатывать их от длинных к коротким.
- Случайно выбрать ориентацию и начальную клетку.
- Проверить границы поля.
- Проверить клетки корабля и все соседние клетки вокруг него.
- Разместить корабль либо повторить попытку.
- Если лимит попыток исчерпан, очистить поле и запустить генерацию заново.
- Перед стартом проверить количество и длины кораблей, отсутствие касаний и выходов за границы.
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 считается готовым, когда:
- ESP32-C6 Mini стабильно подключается к заданной домашней Wi-Fi-сети и открывает русскоязычный интерфейс.
- Два пользователя могут войти по именам и полностью сыграть одну партию с разных устройств.
- Один пользователь может полностью сыграть партию против ESP32.
- Все расстановки соответствуют классическому набору и запрету касаний.
- Сервер отклоняет недопустимые и несвоевременные выстрелы.
- Скрытые корабли не присутствуют в сетевых ответах для соперника или зрителя.
- Минимум два зрителя могут наблюдать партию без влияния на неё; целевая конфигурация поддерживает до восьми.
- Изменения обычно появляются через WebSocket, а при его отключении интерфейс автоматически продолжает обновляться через HTTP.
- Обновление страницы восстанавливает роль и актуальное состояние, если ESP32 не перезагружалась.
- После окончания отображаются победитель и корректная статистика.
- Повторная игра создаёт новые расстановки и сохраняет накопительную статистику до перезагрузки.
- Интерфейс остаётся удобным на телефоне и планшете.
- Не менее 20 последовательных тестовых партий проходят без зависания, заметной утечки памяти или необходимости перезапуска устройства.
23. Возможные расширения после MVP
- Настройка Wi-Fi через временную точку доступа и captive portal.
- Сохранение статистики в NVS.
- Несколько комнат и больше одновременных игроков.
- PIN-код партии и управление зрительским доступом.
- Ручная расстановка кораблей.
- Выбор варианта правил, включая строго один выстрел за ход.
- Таймер хода, чат, звук и анимации.
- Уровни сложности ESP32.
- Локальное имя устройства через mDNS, например
battleship.local. - OTA-обновление прошивки и веб-ресурсов.
- Режим собственной точки доступа для игры без домашнего роутера.