Guardar el récord sin base de datos
Guardar un número en localStorage es una línea. Lo que hace este ejercicio útil es todo lo que pasa alrededor de esa línea, que es donde viven los bugs que llegan hasta producción.
Qué tienes que construir
Un juego mínimo —recoger cosas contra reloj sirve— con tres escenas:
- Menú, con el récord actual y un botón de jugar.
- Juego, 30 segundos y un marcador.
- Fin, con tu puntaje, el récord, y un aviso distinto si lo rompiste.
El récord tiene que sobrevivir a recargar la página y a cerrar el navegador.
Criterios de aceptación
- Hacer 15 puntos, recargar la página: el menú muestra 15.
- Hacer 10 puntos después: el récord sigue siendo 15, y la pantalla de fin no dice que rompiste el récord.
- Un botón de "borrar récord" lo regresa a 0 y eso también persiste.
- Con
localStoragebloqueado —abre una ventana en modo incógnito con las cookies apagadas— el juego se juega igual, solo que sin guardar. No hay pantalla en blanco ni error en consola. - Si metes basura a mano en
localStoragedesde la consola del navegador (localStorage.setItem('record', 'perro')), el juego arranca con récord 0 en lugar de romperse. - Salir de la escena de juego a media partida y volver a entrar empieza limpio: 30 segundos, marcador en 0.
Los criterios 4 y 5 son el ejercicio. Los otros cuatro son el pretexto.
Pistas
Encapsula el guardado en un módulo, aunque sean quince líneas. Que ninguna escena hable con localStorage directamente:
export const guardado = {
leerRecord() {
try {
const n = parseInt(localStorage.getItem('record'), 10)
return Number.isFinite(n) && n >= 0 ? n : 0
} catch {
return 0
}
},
guardarRecord(n) {
try { localStorage.setItem('record', String(n)) } catch { /* sin guardado */ }
},
}
Ese try/catch es el criterio 4: en modo incógnito estricto, localStorage existe pero lanza excepción al escribir, cosa que sorprende a mucha gente. Y el Number.isFinite es el criterio 5: parseInt('perro') te da NaN, y NaN comparado con cualquier cosa es false, así que tu récord se vuelve imposible de romper y no hay error en ningún lado. Ese bug es silencioso y por eso es peligroso.
El criterio 6 te va a morder si guardas el estado de la partida en propiedades de la escena y las inicializas en create(). Phaser reutiliza la instancia de la escena: lo que declares fuera de create() sobrevive entre partidas. Todo lo que sea estado de una partida se inicializa en create(), sin excepciones.
Cuando ya te salió
Guarda las cinco mejores puntuaciones con la fecha de cada una, y muéstralas en el menú.
Ahora ya no guardas un número sino una estructura, y necesitas versionarla: { v: 1, tabla: [...] }. El día que cambies el formato, la gente que ya jugó tiene el viejo en el navegador. Decide desde ahora si lo migras o lo tiras — es la misma decisión que toma cualquier juego con partidas guardadas, a otra escala.
Sigue con estos
El cronómetro que no miente
Un contador en pantalla que marque exactamente los mismos segundos en un monitor de 60 Hz y en uno de 144 Hz. Suena trivial hasta que lo mides.
- Game loop
Un sprite que camina y se detiene
Un personaje que corre cuando lo mueves y se queda quieto cuando lo sueltas, sin que la animación se reinicie sola ni se quede pegada en medio del paso.
- Sprites
- Input
Monedas que no se cuentan dos veces
Veinte monedas, un marcador y un bug que aparece solo cuando el jugador es lento: la moneda que suma tres veces porque tardaste tres frames en salir de ella.
- Colisiones
- UI y estado