Архітектура мультиплеєра
Мультиплеєр En Parlant~ використовує WebSocket-реле-сервер для з’єднання двох гравців у реальному часі. Жодного peer-to-peer — уся комунікація проходить через реле. Це спрощує мережеву архітектуру та забезпечує надійну роботу через файрволи та NAT.
Огляд архітектури
Section titled “Огляд архітектури”Player A (host) Relay Server Player B (joiner) | | | |--- create_game(name) ----------->| | |<-- game_created(code) -----------| | | |<--- join_game(code, name) -----| |<-- game_joined(name) ------------|--- game_joined(name) -------->| | | | |--- game_move(uci, times) ------->|--- game_move(uci, times) --->| |<-- game_move(uci, times) --------|<-- game_move(uci, times) ----| | | | |--- heartbeat ------------------>|--- peer_heartbeat ----------->|Реле — це тонкий шар пересилання. Воно зберігає кімнати в пам’яті, маршрутизує події між двома гравцями в кожній кімнаті та обробляє очищення. На сервері немає ігрової логіки чи валідації ходів — авторитетними є клієнти.
Технологічний стек
Section titled “Технологічний стек”- Фронтенд (клієнт): socket.io-client — стандартний JavaScript-клієнт Socket.IO
- Бекенд (реле): socketioxide — Socket.IO-сервер на Rust, побудований на Axum
- Протокол: Socket.IO поверх WebSocket (з автоматичним fallback на HTTP long-polling за потреби)
Socket.IO було обрано замість чистого WebSocket, оскільки він надає автоматичне перепідключення, керування кімнатами/просторами імен та структуровану обробку подій із коробки.
Ігровий процес
Section titled “Ігровий процес”1. Створення гри
Section titled “1. Створення гри”Хост натискає Multiplayer і вводить своє ім’я для відображення. Клієнт надсилає подію create_game з іменем. Сервер:
- Генерує унікальний 6-символьний код кімнати
- Створює кімнату та додає хоста як першого гравця
- Відповідає
game_created(code), щоб хост міг поділитися кодом
2. Приєднання до гри
Section titled “2. Приєднання до гри”Гравець, що приєднується, вводить код кімнати та своє ім’я для відображення. Клієнт надсилає join_game(code, name). Сервер:
- Знаходить кімнату за кодом
- Додає гравця як другого учасника
- Надсилає
game_joined(name)обом гравцям — кожен отримує ім’я суперника
3. Гра
Section titled “3. Гра”Коли обидва гравці в кімнаті:
- Хост завжди грає білими, гравець, що приєднався — чорними. Це визначається порядком приєднання, а не узгодженням.
- Ходи надсилаються у форматі UCI (наприклад,
e2e4) разом із часом на годиннику для обох гравців. - Реле пересилає кожну подію
game_moveсупернику. Обидва гравці також отримують свої власні ходи назад як підтвердження.
4. Події завершення гри
Section titled “4. Події завершення гри”Кілька подій обробляють сценарії завершення гри:
- Здача — пересилається супернику для виклику діалогу капітуляції
- Пропозиція нічиєї — пересилається супернику, який може прийняти або проігнорувати
- Прийняття нічиєї — пересилається обом гравцям для завершення гри внічию
5. Реванш
Section titled “5. Реванш”Після завершення гри будь-який гравець може надіслати подію ready, щоб сигналізувати про бажання реваншу. Коли обидва гравці надіслали ready, клієнти скидають дошку та міняють кольори (або залишають їх — визначається на стороні клієнта).
Коди кімнат
Section titled “Коди кімнат”Коди кімнат складаються з 6 символів у форматі XX-XX-XX для зручності при усному повідомленні. Набір символів виключає візуально неоднозначні символи:
- Без
0абоO(нуль проти літери O) - Без
1,IабоL(одиниця проти великої I проти великої L)
Це дозволяє уникнути проблеми «це нуль чи O?», коли хтось диктує код через голосовий чат або набирає його з повідомлення друга.
Система серцебиття (Heartbeat)
Section titled “Система серцебиття (Heartbeat)”Мультиплеєрні з’єднання повинні виявляти відключення гравця — чи то через обрив мережі, закриття додатку, чи перехід ноутбука в сплячий режим. En Parlant~ використовує для цього систему серцебиття:
- Кожен клієнт надсилає подію
heartbeatна сервер кожні 5 секунд - Сервер підтверджує серцебиття та пересилає його супернику як
peer_heartbeat - Клієнт відстежує, коли він востаннє отримав
peer_heartbeatвід суперника - Функція
isPeerAlive(timeoutMs)перевіряє, чи останнє серцебиття суперника знаходиться в межах допустимого порогу
Це забезпечує роботу індикатора стану з’єднання в інтерфейсі. Якщо серцебиття перестають надходити, гравець бачить, що суперник, можливо, відключився, і може вирішити почекати або покинути гру.
Очищення кімнат
Section titled “Очищення кімнат”Реле-сервер автоматично видаляє неактивні кімнати для запобігання витокам пам’яті:
- Кімната вважається неактивною після 30 хвилин без активності
- Завдання очищення запускається кожні 60 секунд, видаляючи будь-які кімнати, що перевищили поріг неактивності
- При очищенні кімнати всі активні з’єднання розриваються
Постійне сховище відсутнє. Якщо сервер перезапуститься, всі кімнати зникнуть. Це зроблено навмисно — реле є stateless та ефемерним. Ігри, що тривають, доведеться перезапустити, але на практиці це трапляється рідко.
Розгортання
Section titled “Розгортання”Реле за замовчуванням працює на Fly.io, забезпечуючи WebSocket-з’єднання з низькою затримкою та автоматичним TLS. Див. посібник з налаштування мультиплеєрного сервера для інструкцій щодо запуску власного реле.
Локальне тестування
Section titled “Локальне тестування”Для тестування мультиплеєра під час розробки:
-
Клонуйте та запустіть реле:
Terminal window git clone https://github.com/DarrellThomas/en-parlant-relay.gitcd en-parlant-relaycargo runСервер запускається на порту 3210.
-
В En Parlant~ змініть URL реле-сервера на
ws://localhost:3210. -
Відкрийте два екземпляри додатку (або один додаток і один у режимі розробки), щоб імітувати обох гравців.
Ходи, серцебиття та всі події працюють ідентично до продакшн-реле — єдина відмінність — це URL підключення.