Flerspillerarkitektur
Flerspiller i En Parlant~ bruker en WebSocket-reléserver for å koble sammen to spillere i sanntid. Det er ingen peer-to-peer — all kommunikasjon går gjennom reléet. Dette holder nettverkslaget enkelt og fungerer pålitelig på tvers av brannmurer og NAT-er.
Arkitekturoversikt
Section titled “Arkitekturoversikt”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 ----------->|Reléet er et tynt videresendingslag. Det holder rom i minnet, ruter hendelser mellom de to spillerne i hvert rom, og håndterer opprydding. Det er ingen spillogikk eller trekkvalidering på serveren — klientene er autoritative.
Teknologistabel
Section titled “Teknologistabel”- Frontend (klient): socket.io-client — den standard Socket.IO JavaScript-klienten
- Backend (relé): socketioxide — en Rust Socket.IO-server bygget på Axum
- Protokoll: Socket.IO over WebSocket (med automatisk fallback til HTTP long-polling ved behov)
Socket.IO ble valgt fremfor rå WebSocket fordi det gir automatisk gjenoppkobling, rom-/navneromsadministrasjon og strukturert hendelseshåndtering rett ut av boksen.
Spillflyt
Section titled “Spillflyt”1. Opprette et spill
Section titled “1. Opprette et spill”Verten klikker Multiplayer og skriver inn visningsnavnet sitt. Klienten sender en create_game-hendelse med navnet. Serveren:
- Genererer en unik romkode på 6 tegn
- Oppretter et rom og legger til verten som den første spilleren
- Svarer med
game_created(code)slik at verten kan dele koden
2. Bli med i et spill
Section titled “2. Bli med i et spill”Den som blir med skriver inn romkoden og visningsnavnet sitt. Klienten sender join_game(code, name). Serveren:
- Slår opp rommet etter kode
- Legger til den andre spilleren som spiller nummer to
- Sender
game_joined(name)til begge spillerne — hver mottar den andres visningsnavn
3. Spilling
Section titled “3. Spilling”Når begge spillerne er i rommet:
- Verten spiller alltid hvit, den som blir med spiller alltid svart. Dette bestemmes av rekkefølgen man ble med, ikke gjennom forhandling.
- Trekk sendes i UCI-format (f.eks.
e2e4) sammen med klokketider for begge spillerne. - Reléet videresender hver
game_move-hendelse til motstanderen. Begge spillerne mottar også sine egne trekk tilbake som bekreftelse.
4. Spillslutt-hendelser
Section titled “4. Spillslutt-hendelser”Flere hendelser håndterer scenarier ved spillslutt:
- Gi opp — videresendes til motstanderen for å utløse oppgivelses-dialogen
- Remistilbud — videresendes til motstanderen, som kan akseptere eller ignorere
- Akseptere remis — videresendes til begge spillerne for å avslutte partiet som remis
5. Revansje
Section titled “5. Revansje”Etter at et parti er over, kan begge spillerne sende en ready-hendelse for å signalisere at de ønsker revansje. Når begge spillerne har sendt ready, nullstiller klientene brettet og bytter farger (eller beholder dem — dette bestemmes på klientsiden).
Romkoder
Section titled “Romkoder”Romkoder er på 6 tegn, formatert som XX-XX-XX for lesbarhet ved muntlig deling. Tegnsettet ekskluderer visuelt tvetydige tegn:
- Ingen
0ellerO(null vs. bokstaven O) - Ingen
1,IellerL(én vs. stor I vs. stor L)
Dette unngår problemet med «er det en null eller en O?» når noen leser en kode over talchat eller skriver den inn fra en venns melding.
Hjerteslag-system
Section titled “Hjerteslag-system”Flerspillertilkoblinger må oppdage når en spiller kobler fra — enten det skyldes nettverkstap, lukking av appen eller at laptopen går i dvale. En Parlant~ bruker et hjerteslag-system for dette:
- Hver klient sender en
heartbeat-hendelse til serveren hvert 5. sekund - Serveren bekrefter hjerteslaget og videresender det til motstanderen som
peer_heartbeat - Klienten holder styr på når den sist mottok et
peer_heartbeatfra motstanderen - Funksjonen
isPeerAlive(timeoutMs)sjekker om motstanderens siste hjerteslag er innenfor akseptabel terskel
Dette driver tilkoblingsstatusindikatoren i brukergrensesnittet. Hvis hjerteslagene slutter å komme, ser spilleren at motstanderen kan ha koblet fra, og kan velge å vente eller forlate spillet.
Romopprydding
Section titled “Romopprydding”Reléserveren fjerner automatisk inaktive rom for å forhindre minnelekkasjer:
- Et rom anses som inaktivt etter 30 minutter uten aktivitet
- En oppryddingsoppgave kjører hvert 60. sekund og rydder bort rom som har overskredet inaktivitetsterskelen
- Når et rom ryddes bort, droppes eventuelle gjenværende tilkoblinger
Det er ingen persistent lagring. Hvis serveren starter på nytt, er alle rom borte. Dette er tilsiktet — reléet er tilstandsløst og flyktig. Pågående partier må startes på nytt, men dette skjer sjelden i praksis.
Utrulling
Section titled “Utrulling”Standard-reléet kjører på Fly.io, som gir WebSocket-tilkoblinger med lav latens og automatisk TLS. Se oppsettguiden for flerspillerserveren for instruksjoner om å kjøre ditt eget relé.
Testing lokalt
Section titled “Testing lokalt”For å teste flerspiller under utvikling:
-
Klon og kjør reléet:
Terminal window git clone https://github.com/DarrellThomas/en-parlant-relay.gitcd en-parlant-relaycargo runServeren starter på port 3210.
-
I En Parlant~, endre reléserverens URL til
ws://localhost:3210. -
Åpne to instanser av appen (eller én app og én i utviklingsmodus) for å simulere begge spillerne.
Trekk, hjerteslag og alle hendelser fungerer identisk med produksjonsreléet — den eneste forskjellen er tilkoblings-URL-en.