Determinismo: la misma partida, dos veces
Por qué una partida de Green Games se repite igual con la misma semilla y las mismas entradas, qué lo rompe, cómo se comprueba y qué hacer si el juego no es reproducible.
Corresponde aFormato 1, API 1 Para todos
En esta página
Una partida de Green Games es reproducible: con la misma semilla y las mismas entradas, pasa exactamente lo mismo, cuadro por cuadro. Las monedas caen en los mismos lugares, la pelota rebota igual y el puntaje final es el mismo. A eso se le llama determinismo.
No es un detalle técnico: es lo que permite que un bot pruebe tu juego y que el resultado de esa prueba signifique algo.
De qué está hecho#
Sesenta cuadros fijos#
La simulación avanza a 60 cuadros por segundo, siempre. Cada cuadro dura lo mismo: el paso de tiempo dt vale 1/60 en todos los equipos. Si tu pantalla dibuja más rápido o más lento, cambia cuántas veces se dibuja, no cómo avanza la partida.
Por eso una prueba sin pantalla puede correr una partida de minutos en uno o dos segundos: calcula los mismos cuadros, uno tras otro, sin esperar al reloj de la pared.
La semilla#
Cada partida tiene una semilla: un número del que sale todo el azar. Con la semilla 1, el "azar" da siempre la misma lista de números; con la 2, otra lista, siempre la misma también.
El script la lee en juego.semilla, pero casi nunca hace falta: basta con usar el servicio azar.
El servicio azar#
azar es la forma de pedir números al azar que salen de la semilla: azar.numero(), azar.entero(min, max), azar.elegir(lista), azar.mezclar(lista), azar.suerte(p). Referencia: azar.
Así siembra sus monedas Circuito Luz Verde (games/circuito-luz-verde/scripts/principal.ts):
// azar sale de la semilla de la partida: con la misma semilla, las monedas quedan en los mismos lugares.
const elegidos = azar.mezclar(LUGARES).slice(0, AJUSTES.monedas);Dentro de la sala, Math.random también sale de la semilla. Pero el validador avisa si lo usas: azar deja claro, a quien lea el script, que ese azar es el de la partida.
El reloj de la partida#
El tiempo se lee con reloj.ahora (segundos de partida, que se detienen con la pausa) y reloj.cuadro. Para esperar, reloj.despues; para repetir, reloj.cada. Todos cuentan cuadros de la simulación, no tiempo real. Referencia: reloj.
La matemática propia de la sala#
Hay una fuente de diferencias menos evidente. Funciones como Math.sin o Math.pow no dan exactamente el mismo resultado en todos los motores de JavaScript: difieren en el último bit entre versiones de Node y entre Node y el navegador. Es una diferencia invisible, hasta que un juego con física la amplifica: una pelota que rebota un poco distinto termina en otro lugar, y la partida cambia.
Por eso, dentro de la sala, Math.sin, cos, tan, atan, atan2, asin, acos, exp, log, pow, hypot, cbrt y afines son versiones propias de la sala, que dan el mismo resultado en todas partes. Coinciden con las de siempre hasta unas 14 cifras. No tienes que hacer nada: escribes Math.sin y usas la versión reproducible.
Qué no es reproducible#
Dos cosas, y nada más:
| Qué | Por qué | Qué usar |
|---|---|---|
Date (Date.now(), new Date()) | Lee la fecha y la hora reales, que cambian en cada partida. | reloj.ahora |
performance.now() | Mide tiempo real del equipo. | reloj.ahora, reloj.cuadro |
El validador avisa si el script los usa.
Hay una tercera forma de romper la reproducibilidad sin usar nada prohibido: depender del orden en que un objeto entrega sus claves cuando ese orden no lo controlas. Si recorres una colección para decidir algo, recórrela en un orden que tú definas.
Lo que sí cambia entre dos partidas, a propósito, son las entradas: lo que hace el jugador. Determinismo no quiere decir que todas las partidas sean iguales; quiere decir que, si repites las mismas entradas, se repite la partida.
Por qué importa#
- Pruebas con bot.
greengames probarjuega tu juego con un guion. Si la partida no fuera reproducible, una prueba aprobada hoy podría fallar mañana sin que nadie tocara nada, y un fallo no se podría repetir para estudiarlo. Con semilla, "falla con la semilla 7 en difícil" es una instrucción exacta para volver a verlo. - Lo que se prueba es lo que se juega. El resultado de una partida es el mismo en Node, donde corre la herramienta, y en el navegador, donde corre el portal.
- Repetir una partida. El reproductor acepta una semilla al iniciar, y la vista previa de la herramienta la acepta en la dirección (
semilla=7). Con el bot al mando, eso reproduce la partida entera.
El mismo diseño es el que haría posible jugar en red, porque la partida se decide en un solo lugar y con reglas que no dependen del equipo. Hoy no hay multijugador en red; lo que está en desarrollo figura en la Hoja de ruta.
Cómo se comprueba#
En cada prueba de tu juego#
greengames probar corre el juego en cada dificultad y con cada semilla. Además, una vez por juego, repite la primera corrida con la misma semilla y compara el puntaje y una huella del estado final (el cuadro en que terminó, cuántas piezas quedaron y la posición de cada jugador con cuatro decimales). Si algo difiere, la prueba falla con este mensaje:
el juego no es reproducible: con la misma semilla dio <puntaje> puntos en <segundos> s y despues <puntaje> en <segundos> s. Usa azar y reloj en vez de DateEn las salidas de probar se ve la reproducibilidad a simple vista. Esta es real, del juego de ejemplo patio-de-monedas: las dos semillas dan el mismo resultado porque ese juego no usa azar.
OK facil semilla 1: victoria, puntaje 2, 3.0 s de partida en 72 ms; script 0.022 ms/cuadro (max 0.71), 12 piezas
OK facil semilla 2: victoria, puntaje 2, 3.0 s de partida en 71 ms; script 0.019 ms/cuadro (max 0.69), 12 piezasLos "s de partida" y el puntaje se repiten siempre. Los "ms" reales y los tiempos de script cambian de una corrida a otra: miden tu equipo, no la partida.
Entre Node y el navegador#
El repositorio de Green Games trae una prueba, pruebas/determinismo.sh, que hace lo siguiente con cada uno de los siete juegos del catálogo:
- Corre el juego en Node, en cada dificultad, con las semillas 1 y 2, y anota resultado, puntaje, duración y huella del estado final.
- Repite las mismas corridas en un navegador sin pantalla, dentro de la sala aislada.
- Exige que las cuatro cosas sean idénticas en cada corrida. Con una sola distinta, falla.
Son 42 corridas: siete juegos, tres dificultades, dos semillas.
Si el juego no es reproducible#
Cuando greengames probar dice "el juego no es reproducible", revisa en este orden:
- Lee los avisos de
greengames validar. Avisa porDate,performance.nowyMath.random. Empieza por ahí. - Cambia el tiempo real por el de la partida. Todo
Date.now()pasa a serreloj.ahora. - Cambia
Math.randomporazar. - Busca recorridos sin orden definido. Si decides algo recorriendo las claves de un objeto, ordénalas tú antes.
- Vuelve a probar con más semillas:
greengames probar mi-juego --semillas 10.
Otra cosa es que el bot gane con una semilla y pierda con otra. Eso no es falta de determinismo: es un juego con azar y un guion que no lo resiste. Se arregla mejorando el guion, o declarando en él cuántas corridas deben aprobar. Ver Pruebas con el bot.
Sigue con#
- Pruebas con el bot: el guion que aprovecha todo esto.
- azar y reloj: las dos fuentes que mantienen la partida reproducible.
- Rendimiento: cuánto tarda el script en cada cuadro.