El objetivo
Quería un hogar para los juegos pequeños que construyo, donde jugar con un amigo fuera un clic y no una descarga. Eso significaba cuentas reales, un lobby que se actualiza en vivo y partidas online que se sintieran justas. También tenía que costar casi nada de mantener mientras fuera pequeño.
La forma que tiene
Un único Worker sirve el sitio estático y una pequeña API. Todo lo que necesita recordar estado vive en un Durable Object: una única instancia de una clase que Cloudflare mantiene corriendo, con su propio almacenamiento.
- Hub es una instancia con su propia base de datos SQLite. Guarda cuentas, sesiones, el lobby en vivo, el historial de partidas y los resultados.
- Room se crea por cada código de 5 caracteres. Reenvía mensajes entre exactamente dos sockets, anfitrión e invitado. A un tercer socket se le informa que la sala está llena.
- Arena ejecuta las partidas de Knife Duels, de 1 contra 1 hasta 4 contra 4.
Del reto a la partida
- Abres la pestaña de Jugadores. Tu navegador mantiene un WebSocket abierto con el Hub, que le dice a todos quién está conectado.
- Retas a alguien. Lo ve al instante y acepta o rechaza.
- Al aceptar, el Hub crea un código de sala y les da luz verde a ambos jugadores.
- Ambos juegos se unen a esa Room, que reenvía las entradas entre ellos.
- Cuando termina la partida, los juegos de ambos jugadores reportan el resultado. Solo cuenta si coinciden.
El socket del lobby usa la API de hibernación de WebSocket, así que un lobby tranquilo no mantiene al Hub despierto ni generando costes mientras la gente simplemente está en la página.
Manteniéndolo justo
Ejecutar cada partida en el servidor es demasiado caro para un sitio pequeño, así que en su lugar repartí la confianza. En Oathsteel, el duelo de espadas, el anfitrión decide golpes y rondas, cada jugador controla su propio movimiento, y al otro luchador lo maneja la entrada reenviada. El movimiento se mantiene responsivo en ambos lados, y un lado tiene la última palabra sobre lo que decide un ganador.
Los resultados solo llegan a la tabla de clasificación en las partidas creadas en el lobby, y solo cuando los juegos de ambos jugadores reportan el mismo resultado. Knife Duels tiene más jugadores por partida, así que un resultado cuenta una vez que la mayoría de los jugadores humanos están de acuerdo.
Cuentas hechas como se debe
Las contraseñas se hashean con PBKDF2-SHA256 a 100.000 iteraciones. Los tokens de sesión también se almacenan hasheados, así que una base de datos filtrada no entregaría el acceso de nadie.
Probando el multijugador
Room y Hub tienen pruebas automatizadas que corren contra un Worker local: 8 para el relay, y 21 para las cuentas, el lobby y los resultados. Knife Duels mantiene su lógica de juego separada de three.js y de la página, así que se puede probar por su cuenta con pruebas unitarias, con 178 pruebas.
Para el caso real, un script conduce a dos navegadores headless a través de un duelo online completo. El problema es que dos navegadores con renderizado por software en una sola máquina se estorban entre sí, así que van muy por debajo de la velocidad real y cada espera de la prueba tiene que ser generosa.
Lo que aprendí
- Los Durable Objects hacen que razonar sobre el estado multijugador sea fácil: un objeto, un único lugar donde vive la verdad.
- Añadir un juego es una carpeta y una línea en una lista, por eso el hub sigue creciendo.
- Sin paso de build y con three.js autoalojado, el sitio se despliega en segundos y no tiene nada que romperse entre lanzamientos.