Files
battleship/docs/MVP.md
T

539 lines
38 KiB
Markdown

# 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-обновление прошивки и веб-ресурсов.
- Режим собственной точки доступа для игры без домашнего роутера.