Architettura Multiplayer
Il multiplayer di En Parlant~ utilizza un server relay WebSocket per connettere due giocatori in tempo reale. Non c’è peer-to-peer: tutta la comunicazione passa attraverso il relay. Questo mantiene la rete semplice e funziona in modo affidabile attraverso firewall e NAT.
Panoramica dell’architettura
Sezione intitolata “Panoramica dell’architettura”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 ----------->|Il relay è un sottile livello di inoltro. Mantiene le stanze in memoria, instrada gli eventi tra i due giocatori in ciascuna stanza e gestisce la pulizia. Non c’è logica di gioco né validazione delle mosse sul server: i client fanno fede.
Stack tecnologico
Sezione intitolata “Stack tecnologico”- Frontend (client): socket.io-client — il client JavaScript standard di Socket.IO
- Backend (relay): socketioxide — un server Socket.IO in Rust basato su Axum
- Protocollo: Socket.IO su WebSocket (con fallback automatico a HTTP long-polling se necessario)
Socket.IO è stato scelto rispetto ai WebSocket puri perché fornisce riconnessione automatica, gestione di stanze/namespace e gestione strutturata degli eventi già inclusi.
Flusso di gioco
Sezione intitolata “Flusso di gioco”1. Creazione di una partita
Sezione intitolata “1. Creazione di una partita”L’host clicca su Multiplayer e inserisce il proprio nome visualizzato. Il client emette un evento create_game con il nome. Il server:
- Genera un codice stanza univoco di 6 caratteri
- Crea una stanza e aggiunge l’host come primo giocatore
- Risponde con
game_created(code)in modo che l’host possa condividere il codice
2. Accesso a una partita
Sezione intitolata “2. Accesso a una partita”Il giocatore che si unisce inserisce il codice stanza e il proprio nome visualizzato. Il client emette join_game(code, name). Il server:
- Cerca la stanza tramite il codice
- Aggiunge il giocatore come secondo partecipante
- Invia
game_joined(name)a entrambi i giocatori: ciascuno riceve il nome visualizzato dell’altro
3. Gioco
Sezione intitolata “3. Gioco”Una volta che entrambi i giocatori sono nella stanza:
- L’host gioca sempre con il Bianco, chi si unisce gioca sempre con il Nero. Questo è determinato dall’ordine di accesso, non da una negoziazione.
- Le mosse vengono inviate in formato UCI (ad es.
e2e4) insieme ai tempi dell’orologio per entrambi i giocatori. - Il relay inoltra ogni evento
game_moveall’avversario. Entrambi i giocatori ricevono anche le proprie mosse come conferma.
4. Eventi di fine partita
Sezione intitolata “4. Eventi di fine partita”Diversi eventi gestiscono gli scenari di fine partita:
- Resa — inoltrata all’avversario per attivare la finestra di resa
- Offerta di patta — inoltrata all’avversario, che può accettare o ignorare
- Accettazione della patta — inoltrata a entrambi i giocatori per terminare la partita in parità
5. Rivincita
Sezione intitolata “5. Rivincita”Al termine di una partita, ciascun giocatore può inviare un evento ready per segnalare la volontà di giocare una rivincita. Quando entrambi i giocatori hanno inviato ready, i client resettano la scacchiera e scambiano i colori (o li mantengono — determinato lato client).
Codici stanza
Sezione intitolata “Codici stanza”I codici stanza sono di 6 caratteri, formattati come XX-XX-XX per facilitare la lettura quando vengono condivisi a voce. Il set di caratteri esclude quelli visivamente ambigui:
- Nessun
0oO(zero vs. lettera O) - Nessun
1,IoL(uno vs. I maiuscola vs. L maiuscola)
Questo evita il problema del “è uno zero o una O?” quando qualcuno legge un codice in chat vocale o lo digita dal messaggio di un amico.
Sistema di heartbeat
Sezione intitolata “Sistema di heartbeat”Le connessioni multiplayer devono rilevare quando un giocatore si disconnette, che sia per un’interruzione di rete, la chiusura dell’app o un laptop che va in sospensione. En Parlant~ utilizza un sistema di heartbeat per questo scopo:
- Ogni client invia un evento
heartbeatal server ogni 5 secondi - Il server conferma l’heartbeat e lo inoltra all’avversario come
peer_heartbeat - Il client tiene traccia dell’ultimo
peer_heartbeatricevuto dall’avversario - La funzione
isPeerAlive(timeoutMs)verifica se l’ultimo heartbeat dell’avversario rientra nella soglia accettabile
Questo alimenta l’indicatore di stato della connessione nell’interfaccia utente. Se gli heartbeat smettono di arrivare, il giocatore vede che l’avversario potrebbe essersi disconnesso e può scegliere se attendere o abbandonare la partita.
Pulizia delle stanze
Sezione intitolata “Pulizia delle stanze”Il server relay rimuove automaticamente le stanze inattive per prevenire perdite di memoria:
- Una stanza è considerata inattiva dopo 30 minuti senza attività
- Un task di pulizia viene eseguito ogni 60 secondi, eliminando le stanze che hanno superato la soglia di inattività
- Quando una stanza viene eliminata, le eventuali connessioni rimanenti vengono interrotte
Non è presente alcuno storage persistente. Se il server si riavvia, tutte le stanze vengono perse. Questo è intenzionale: il relay è stateless ed effimero. Le partite in corso dovrebbero essere ricominciate, ma nella pratica questo accade raramente.
Deployment
Sezione intitolata “Deployment”Il relay predefinito è ospitato su Fly.io, fornendo connessioni WebSocket a bassa latenza con TLS automatico. Consulta la guida alla configurazione del server Multiplayer per le istruzioni su come eseguire il proprio relay.
Test in locale
Sezione intitolata “Test in locale”Per testare il multiplayer durante lo sviluppo:
-
Clona e avvia il relay:
Terminal window git clone https://github.com/DarrellThomas/en-parlant-relay.gitcd en-parlant-relaycargo runIl server si avvia sulla porta 3210.
-
In En Parlant~, cambia l’URL del server relay in
ws://localhost:3210. -
Apri due istanze dell’app (o un’app e una in modalità sviluppo) per simulare entrambi i giocatori.
Mosse, heartbeat e tutti gli eventi funzionano in modo identico al relay di produzione: l’unica differenza è l’URL di connessione.