Pitch: Manda5, um jogo de palavras multiplayer no navegador, e o que aprendi com tempo real e sala vazia
Há alguns meses construo o Manda5, um jogo de palavras para jogar em grupo pelo navegador: https://manda5.com
A mecânica: aparece um tema ("o que tem dentro de um carro"), cinco respostas ficam escondidas e todo mundo na sala tem 60 segundos para adivinhar, digitando ao mesmo tempo. Resposta óbvia vale pouco; a que ninguém lembrou vale mais. Não tem cadastro: escolhe um apelido e joga, ou cria uma sala e manda o link no grupo.
A stack é Laravel 12, Reverb (o WebSocket oficial do Laravel), Alpine, Tailwind, MySQL e Redis. Separei quatro problemas que me custaram mais do que eu esperava.
- O relógio da tela é só desenho
Cada jogador tem um relógio diferente, e o celular de alguém sempre está adiantado. Por isso nada que valha ponto é decidido no cliente: a rodada tem um encerra_em gravado no banco, e o palpite que chega depois dele é recusado, não importa o que a tela mostrava.
O encerramento e as dicas saem como jobs com delay calculado a partir do fim da rodada (não de "agora"), porque a contagem regressiva inicial desloca tudo alguns segundos. E como job atrasa ou se perde, a sala também encerra pelo estado: se o prazo passou e o job não veio, a próxima consulta de estado encerra a rodada. Sem isso, uma fila engasgada deixava a sala parada para sempre no placar.
- O teclado do iOS come a tela
No Safari do iPhone, 100dvh continua valendo a tela inteira com o teclado aberto. O navegador rola a página para mostrar o campo e empurra o tema e as lacunas para fora da tela, justo o que o jogador precisa ver enquanto digita.
A saída foi amarrar a altura do container à visualViewport: quando o teclado sobe, a mesa encolhe em vez de rolar.
-
Trocar de aba não é sair
Eu usava pagehide para avisar o servidor que o jogador saiu. No iOS, trocar de aba dispara pagehide com a página indo para o bfcache (persisted: true). Resultado: quem jogava sozinho e olhava outra aba por dois segundos tinha a própria partida encerrada. Hoje só conta como saída pagehide com persisted falso, e ainda com uma exceção: ir para a tela de resultado também dispara o evento, e sem essa guarda todo mundo que terminava a partida saía dela marcado como quem abandonou. -
O problema que não é de código: a sala vazia
Jogo multiplayer com pouca gente tem um problema matemático. Se cada visita joga uns 8 minutos, duas pessoas só começam a coincidir na mesma sala perto de 700 visitas por dia. Abaixo disso, jogar sozinho é o normal. E, de fato, a maioria das partidas até agora teve um jogador só.
Em vez de inventar jogadores falsos (que mentem para quem joga e estragam os dados de dificuldade dos temas), fiz três coisas:
Referência: entre as rodadas, o jogo mostra quantas das cinco você achou e a média de quem já jogou aquele tema, além do seu recorde. Sozinho, "achei 3 de 5" passa a dizer alguma coisa.
Convite na hora certa: no intervalo entre rodadas, se não há mais ninguém conectado, aparece um botão para chamar alguém no WhatsApp direto para a mesma partida.
Juntar quem chega: a tela de entrada já vem marcada no nível onde tem gente jogando, em vez de sempre no fácil.
Ainda não sei se isso basta. É o que estou medindo agora.
Sendo transparente
O jogo é grátis, mas a intenção é ter anúncio entre as partidas e uma assinatura opcional que só muda o visual. Ela nunca dá vantagem no jogo.
Se alguém já passou pelo começo do zero em produto que depende de ter gente junta, queria muito saber o que funcionou. E se algo quebrar no seu celular, me conta aqui.