Arquitetura Multijogador
O modo multijogador do En Parlant~ utiliza um servidor de retransmissão WebSocket para ligar dois jogadores em tempo real. Não existe ligação peer-to-peer — toda a comunicação passa pelo servidor de retransmissão. Isto mantém a rede simples e funciona de forma fiável através de firewalls e NATs.
Visão Geral da Arquitetura
Seção intitulada “Visão Geral da Arquitetura”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 ----------->|O servidor de retransmissão é uma camada fina de encaminhamento. Mantém salas em memória, encaminha eventos entre os dois jogadores de cada sala e trata da limpeza. Não existe lógica de jogo nem validação de jogadas no servidor — os clientes são a autoridade.
Stack Tecnológica
Seção intitulada “Stack Tecnológica”- Frontend (cliente): socket.io-client — o cliente JavaScript padrão do Socket.IO
- Backend (retransmissão): socketioxide — um servidor Socket.IO em Rust construído sobre Axum
- Protocolo: Socket.IO sobre WebSocket (com fallback automático para HTTP long-polling, se necessário)
O Socket.IO foi escolhido em vez de WebSocket puro porque oferece reconexão automática, gestão de salas/namespaces e tratamento estruturado de eventos de forma nativa.
Fluxo de Jogo
Seção intitulada “Fluxo de Jogo”1. Criar um Jogo
Seção intitulada “1. Criar um Jogo”O anfitrião clica em Multiplayer e introduz o seu nome de exibição. O cliente emite um evento create_game com o nome. O servidor:
- Gera um código de sala único com 6 caracteres
- Cria uma sala e adiciona o anfitrião como primeiro jogador
- Responde com
game_created(code)para que o anfitrião possa partilhar o código
2. Entrar num Jogo
Seção intitulada “2. Entrar num Jogo”O segundo jogador introduz o código da sala e o seu nome de exibição. O cliente emite join_game(code, name). O servidor:
- Procura a sala pelo código
- Adiciona o segundo jogador como o segundo participante
- Envia
game_joined(name)a ambos os jogadores — cada um recebe o nome de exibição do outro
3. Jogar
Seção intitulada “3. Jogar”Assim que ambos os jogadores estão na sala:
- O anfitrião joga sempre com as Brancas, o segundo jogador joga sempre com as Pretas. Isto é determinado pela ordem de entrada, não por negociação.
- As jogadas são enviadas em formato UCI (por exemplo,
e2e4) juntamente com os tempos do relógio de ambos os jogadores. - O servidor de retransmissão encaminha cada evento
game_moveao adversário. Ambos os jogadores também recebem de volta as suas próprias jogadas como confirmação.
4. Eventos de Fim de Jogo
Seção intitulada “4. Eventos de Fim de Jogo”Vários eventos tratam dos cenários de fim de jogo:
- Desistência — encaminhada ao adversário para acionar o diálogo de desistência
- Oferta de empate — encaminhada ao adversário, que pode aceitar ou ignorar
- Aceitação de empate — encaminhada a ambos os jogadores para terminar o jogo como empate
5. Revanche
Seção intitulada “5. Revanche”Após o fim de um jogo, qualquer jogador pode enviar um evento ready para sinalizar que pretende uma revanche. Quando ambos os jogadores tiverem enviado ready, os clientes reiniciam o tabuleiro e trocam as cores (ou mantêm-nas — determinado do lado do cliente).
Códigos de Sala
Seção intitulada “Códigos de Sala”Os códigos de sala têm 6 caracteres, formatados como XX-XX-XX para facilitar a leitura ao partilhar verbalmente. O conjunto de caracteres exclui caracteres visualmente ambíguos:
- Sem
0ouO(zero vs. letra O) - Sem
1,IouL(um vs. I maiúsculo vs. L maiúsculo)
Isto evita o problema “isto é um zero ou um O?” quando alguém lê um código por chat de voz ou o digita a partir da mensagem de um amigo.
Sistema de Heartbeat
Seção intitulada “Sistema de Heartbeat”As ligações multijogador precisam de detetar quando um jogador se desliga — seja por uma falha de rede, ao fechar a aplicação ou ao adormecer o portátil. O En Parlant~ utiliza um sistema de heartbeat para isso:
- Cada cliente envia um evento
heartbeatao servidor a cada 5 segundos - O servidor confirma o heartbeat e encaminha-o ao adversário como
peer_heartbeat - O cliente regista quando recebeu o último
peer_heartbeatdo adversário - A função
isPeerAlive(timeoutMs)verifica se o último heartbeat do adversário está dentro do limiar aceitável
Isto alimenta o indicador de estado da ligação na interface. Se os heartbeats deixarem de chegar, o jogador vê que o adversário pode ter-se desligado e pode optar por aguardar ou abandonar o jogo.
Limpeza de Salas
Seção intitulada “Limpeza de Salas”O servidor de retransmissão remove automaticamente salas inativas para evitar fugas de memória:
- Uma sala é considerada inativa após 30 minutos sem atividade
- Uma tarefa de limpeza executa a cada 60 segundos, eliminando quaisquer salas que tenham excedido o limiar de inatividade
- Quando uma sala é limpa, quaisquer ligações restantes são encerradas
Não existe armazenamento persistente. Se o servidor reiniciar, todas as salas desaparecem. Isto é intencional — o servidor de retransmissão é stateless e efémero. Jogos em curso precisariam de ser reiniciados, mas isto é raro na prática.
Implementação
Seção intitulada “Implementação”O servidor de retransmissão predefinido corre no Fly.io, proporcionando ligações WebSocket de baixa latência com TLS automático. Consulte o guia de configuração do Servidor Multijogador para instruções sobre como executar o seu próprio servidor de retransmissão.
Testar Localmente
Seção intitulada “Testar Localmente”Para testar o multijogador durante o desenvolvimento:
-
Clone e execute o servidor de retransmissão:
Terminal window git clone https://github.com/DarrellThomas/en-parlant-relay.gitcd en-parlant-relaycargo runO servidor inicia na porta 3210.
-
No En Parlant~, altere o URL do servidor de retransmissão para
ws://localhost:3210. -
Abra duas instâncias da aplicação (ou uma aplicação e outra em modo de desenvolvimento) para simular ambos os jogadores.
Jogadas, heartbeats e todos os eventos funcionam de forma idêntica ao servidor de retransmissão de produção — a única diferença é o URL de ligação.