Skip to content

Архітэктура шматкарыстальніцкай гульні

Шматкарыстальніцкі рэжым En Parlant~ выкарыстоўвае WebSocket-рэле-сервер для злучэння двух гульцоў у рэальным часе. Ніякага peer-to-peer — уся камунікацыя ідзе праз рэле. Гэта спрашчае сеткавую архітэктуру і надзейна працуе праз файрволы і NAT.

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 ----------->|

Рэле — гэта тонкі пераадрасавальны ўзровень. Яно захоўвае пакоі ў памяці, маршрутызуе падзеі паміж двума гульцамі ў кожным пакоі і апрацоўвае ачыстку. На серверы няма гульнявой логікі ці валідацыі хадоў — аўтарытэтнымі з’яўляюцца кліенты.

  • Франтэнд (кліент): socket.io-client — стандартны JavaScript-кліент Socket.IO
  • Бэкенд (рэле): socketioxide — Socket.IO-сервер на Rust, пабудаваны на Axum
  • Пратакол: Socket.IO праз WebSocket (з аўтаматычным пераключэннем на HTTP long-polling пры неабходнасці)

Socket.IO быў абраны замест звычайнага WebSocket, бо ён забяспечвае аўтаматычнае пераадключэнне, кіраванне пакоямі/прасторамі імёнаў і структураваную апрацоўку падзей з каробкі.

Гаспадар націскае Multiplayer і ўводзіць сваё імя для адлюстравання. Кліент адпраўляе падзею create_game з імем. Сервер:

  1. Генеруе ўнікальны 6-сімвальны код пакоя
  2. Стварае пакой і дадае гаспадара як першага гульца
  3. Адказвае game_created(code), каб гаспадар мог падзяліцца кодам

Удзельнік уводзіць код пакоя і сваё імя для адлюстравання. Кліент адпраўляе join_game(code, name). Сервер:

  1. Знаходзіць пакой па кодзе
  2. Дадае ўдзельніка як другога гульца
  3. Адпраўляе game_joined(name) абодвум гульцам — кожны атрымлівае імя суперніка

Калі абодва гульцы ў пакоі:

  • Гаспадар заўсёды гуляе белымі, удзельнік — заўсёды чорнымі. Гэта вызначаецца парадкам далучэння, а не дамоўленасцю.
  • Хады адпраўляюцца ў фармаце UCI (напрыклад, e2e4) разам з часам на гадзінніках абодвух гульцоў.
  • Рэле пераадрасоўвае кожную падзею game_move суперніку. Абодва гульцы таксама атрымліваюць свае ўласныя хады назад як пацвярджэнне.

4. Падзеі заканчэння гульні

Section titled “4. Падзеі заканчэння гульні”

Некалькі падзей апрацоўваюць сцэнарыі заканчэння гульні:

  • Здача — пераадрасоўваецца суперніку для выкліку дыялогу здачы
  • Прапанова нічыі — пераадрасоўваецца суперніку, які можа прыняць ці праігнараваць
  • Прыняцце нічыі — пераадрасоўваецца абодвум гульцам для завяршэння гульні нічыёй

Пасля заканчэння гульні любы гулец можа адправіць падзею ready, каб паказаць гатоўнасць да рэваншу. Калі абодва гульцы адправілі ready, кліенты скідваюць дошку і мяняюць колеры (або пакідаюць іх — вызначаецца на баку кліента).

Коды пакояў складаюцца з 6 сімвалаў і фарматуюцца як XX-XX-XX для зручнасці ўспрымання пры вусным абмене. Набор сімвалаў выключае візуальна неадназначныя літары:

  • Няма 0 ці O (нуль і літара O)
  • Няма 1, I ці L (адзінка, вялікая I і вялікая L)

Гэта дазваляе пазбегнуць праблемы «гэта нуль ці O?», калі хтосьці дыктуе код у галасавым чаце ці набірае яго з паведамлення сябра.

Шматкарыстальніцкія злучэнні патрабуюць выяўлення моманту адключэння гульца — ці то з-за абрыву сеткі, ці то з-за закрыцця праграмы, ці то з-за пераходу наўтбука ў рэжым сну. En Parlant~ выкарыстоўвае для гэтага сістэму серцабіцця:

  1. Кожны кліент адпраўляе падзею heartbeat на сервер кожныя 5 секунд
  2. Сервер пацвярджае серцабіццё і пераадрасоўвае яго суперніку як peer_heartbeat
  3. Кліент адсочвае, калі апошні раз атрымліваў peer_heartbeat ад суперніка
  4. Функцыя isPeerAlive(timeoutMs) правярае, ці знаходзіцца апошняе серцабіццё суперніка ў дапушчальным парозе

Гэта кіруе індыкатарам стану злучэння ў інтэрфейсе. Калі серцабіцці перастаюць паступаць, гулец бачыць, што суперніка, магчыма, адключана, і можа вырашыць чакаць або пакінуць гульню.

Рэле-сервер аўтаматычна выдаляе неактыўныя пакоі для прадухілення ўцечак памяці:

  • Пакой лічыцца неактыўным пасля 30 хвілін без актыўнасці
  • Задача ачысткі запускаецца кожныя 60 секунд, выдаляючы пакоі, якія перавысілі парог неактыўнасці
  • Пры ачыстцы пакоя ўсе злучэнні, што засталіся, разрываюцца

Пастаяннага сховішча няма. Калі сервер перазапускаецца, усе пакоі знікаюць. Гэта зроблена наўмысна — рэле з’яўляецца бяздзейным і эфемерным. Гульні, якія былі ў працэсе, прыйдзецца пачынаць нанова, але на практыцы гэта здараецца рэдка.

Стандартнае рэле працуе на Fly.io, забяспечваючы нізкалатэнтныя WebSocket-злучэнні з аўтаматычным TLS. Глядзіце кіраўніцтва па наладцы шматкарыстальніцкага сервера для інструкцый па запуску ўласнага рэле.

Лакальнае тэсціраванне

Section titled “Лакальнае тэсціраванне”

Каб пратэсціраваць шматкарыстальніцкі рэжым падчас распрацоўкі:

  1. Кланіруйце і запусціце рэле:

    Terminal window
    git clone https://github.com/DarrellThomas/en-parlant-relay.git
    cd en-parlant-relay
    cargo run

    Сервер запускаецца на порце 3210.

  2. У En Parlant~ змяніце URL рэле-сервера на ws://localhost:3210.

  3. Адкрыйце дзве асобнікі праграмы (або адну праграму і адну ў рэжыме распрацоўкі) для імітацыі абодвух гульцоў.

Хады, серцабіцці і ўсе падзеі працуюць ідэнтычна прадакшн-рэле — адзіная розніца ў URL злучэння.