Arquitectura Multijugador
El modo multijugador de En Parlant~ utiliza un servidor de retransmisión WebSocket para conectar a dos jugadores en tiempo real. No hay comunicación peer-to-peer: toda la comunicación pasa a través del servidor de retransmisión. Esto mantiene la red simple y funciona de manera fiable a través de firewalls y NATs.
Descripción general de la arquitectura
Sección titulada «Descripción general de la arquitectura»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 ----------->|El servidor de retransmisión es una capa de reenvío ligera. Mantiene las salas en memoria, enruta los eventos entre los dos jugadores de cada sala y gestiona la limpieza. No hay lógica de juego ni validación de movimientos en el servidor: los clientes son los que tienen la autoridad.
Stack tecnológico
Sección titulada «Stack tecnológico»- Frontend (cliente): socket.io-client — el cliente JavaScript estándar de Socket.IO
- Backend (retransmisión): socketioxide — un servidor Socket.IO en Rust construido sobre Axum
- Protocolo: Socket.IO sobre WebSocket (con respaldo automático a HTTP long-polling si es necesario)
Se eligió Socket.IO en lugar de WebSocket sin procesar porque proporciona reconexión automática, gestión de salas/namespaces y manejo estructurado de eventos de forma nativa.
Flujo de juego
Sección titulada «Flujo de juego»1. Crear una partida
Sección titulada «1. Crear una partida»El anfitrión hace clic en Multiplayer e introduce su nombre para mostrar. El cliente emite un evento create_game con el nombre. El servidor:
- Genera un código de sala único de 6 caracteres
- Crea una sala y añade al anfitrión como primer jugador
- Responde con
game_created(code)para que el anfitrión pueda compartir el código
2. Unirse a una partida
Sección titulada «2. Unirse a una partida»El jugador invitado introduce el código de sala y su nombre para mostrar. El cliente emite join_game(code, name). El servidor:
- Busca la sala por código
- Añade al invitado como segundo jugador
- Envía
game_joined(name)a ambos jugadores: cada uno recibe el nombre para mostrar del otro
3. Jugar
Sección titulada «3. Jugar»Una vez que ambos jugadores están en la sala:
- El anfitrión siempre juega con blancas, el invitado siempre con negras. Esto se determina por el orden de conexión, no por negociación.
- Los movimientos se envían en formato UCI (por ejemplo,
e2e4) junto con los tiempos del reloj de ambos jugadores. - El servidor de retransmisión reenvía cada evento
game_moveal oponente. Ambos jugadores también reciben de vuelta sus propios movimientos como confirmación.
4. Eventos de fin de partida
Sección titulada «4. Eventos de fin de partida»Varios eventos gestionan los escenarios de fin de partida:
- Rendición — se reenvía al oponente para activar el diálogo de rendición
- Oferta de tablas — se reenvía al oponente, quien puede aceptar o ignorar
- Aceptación de tablas — se reenvía a ambos jugadores para finalizar la partida en tablas
5. Revancha
Sección titulada «5. Revancha»Después de que termina una partida, cualquiera de los jugadores puede enviar un evento ready para indicar que quiere la revancha. Cuando ambos jugadores han enviado ready, los clientes reinician el tablero e intercambian colores (o los mantienen, según se determine en el cliente).
Códigos de sala
Sección titulada «Códigos de sala»Los códigos de sala tienen 6 caracteres, formateados como XX-XX-XX para facilitar la lectura al compartirlos verbalmente. El conjunto de caracteres excluye caracteres visualmente ambiguos:
- No se usa
0niO(cero frente a la letra O) - No se usa
1,IniL(uno frente a I mayúscula frente a L mayúscula)
Esto evita el problema de “¿eso es un cero o una O?” cuando alguien lee un código por chat de voz o lo escribe a partir del mensaje de un amigo.
Sistema de heartbeat
Sección titulada «Sistema de heartbeat»Las conexiones multijugador necesitan detectar cuándo un jugador se desconecta, ya sea por una caída de red, al cerrar la aplicación o porque su portátil entra en suspensión. En Parlant~ utiliza un sistema de heartbeat para esto:
- Cada cliente envía un evento
heartbeatal servidor cada 5 segundos - El servidor confirma el heartbeat y lo reenvía al oponente como
peer_heartbeat - El cliente registra cuándo recibió el último
peer_heartbeatdel oponente - La función
isPeerAlive(timeoutMs)comprueba si el último heartbeat del oponente está dentro del umbral aceptable
Esto controla el indicador de estado de conexión en la interfaz. Si los heartbeats dejan de llegar, el jugador ve que su oponente puede haberse desconectado y puede elegir esperar o abandonar la partida.
Limpieza de salas
Sección titulada «Limpieza de salas»El servidor de retransmisión elimina automáticamente las salas inactivas para prevenir fugas de memoria:
- Una sala se considera inactiva después de 30 minutos sin actividad
- Una tarea de limpieza se ejecuta cada 60 segundos, eliminando cualquier sala que haya superado el umbral de inactividad
- Cuando se limpia una sala, cualquier conexión restante se cierra
No hay almacenamiento persistente. Si el servidor se reinicia, todas las salas desaparecen. Esto es intencional: el servidor de retransmisión es sin estado y efímero. Las partidas en curso necesitarían reiniciarse, pero esto es poco frecuente en la práctica.
Despliegue
Sección titulada «Despliegue»El servidor de retransmisión predeterminado se ejecuta en Fly.io, proporcionando conexiones WebSocket de baja latencia con TLS automático. Consulte la guía de configuración del servidor multijugador para obtener instrucciones sobre cómo ejecutar su propio servidor de retransmisión.
Pruebas en local
Sección titulada «Pruebas en local»Para probar el modo multijugador durante el desarrollo:
-
Clone y ejecute el servidor de retransmisión:
Ventana de terminal git clone https://github.com/DarrellThomas/en-parlant-relay.gitcd en-parlant-relaycargo runEl servidor se inicia en el puerto 3210.
-
En En Parlant~, cambie la URL del servidor de retransmisión a
ws://localhost:3210. -
Abra dos instancias de la aplicación (o una aplicación y otra en modo de desarrollo) para simular a ambos jugadores.
Los movimientos, heartbeats y todos los eventos funcionan de forma idéntica al servidor de retransmisión en producción: la única diferencia es la URL de conexión.