Cookie Clicker con HTML+CSS+Javascript (parte 3): Guardando el progreso

En esta parte aprenderemos a guardar el progreso del juego usando la memoria del navegador, para que el jugador no pierda su avance cuando cierre la página. Veremos cómo utilizar el almacenamiento local para almacenar cualquier tipo de información, de forma que podamos recuperarla después de actualizar o cerrar nuestra página, o incluso después de haber cerrado el navegador.

¿Qué es localStorage?

localStorage es una pequeña «libreta virtual» donde podemos guardar información directamente en el navegador del usuario. A diferencia de la memoria temporal (que se pierde al cerrar la página), los datos guardados en localStorage persisten incluso después de cerrar el navegador o apagar el ordenador.

¿Por qué usar localStorage?

En un juego como Cookie Clicker, queremos que el progreso del usuario (por ejemplo, cuántas galletas ha acumulado o cuántos ayudantes tiene) se mantenga aunque cierre el navegador. localStorage nos permite almacenar este tipo de información de manera sencilla.

¿Cómo funciona localStorage?

localStorage solo guarda datos en formato de texto. Para almacenar datos complejos (como un objeto con el estado del juego), usamos el formato JSON, convirtiendo nuestros datos a texto y viceversa con dos sencillas funciones:

  • JSON.stringify(): Convierte datos complejos a texto.
  • JSON.parse(): Convierte ese texto de vuelta a datos.

¿Qué es es JSON?

JSON es un formato utilizado para almacenar y enviar datos. Su estructura es muy sencilla y está basada en pares clave-valor:

  • Claves: Son los nombres de los datos (por ejemplo, «galletas»).
  • Valores: Son los datos asociados a cada clave (por ejemplo, 200).

Usamos JSON para convertir objetos complejos en cadenas de texto que pueden ser almacenadas en localStorage. Luego, podemos convertirlos de vuelta a objetos que podamos utilizar dentro del código.

Modificar el archivo «script.js»

Utilizando las siguientes funciones podremos guardar y recuperar la cantidad de galletas acumuladas y las mejoras o logros conseguidos durante el juego:

  • Función guardarProgreso(): Esta función guarda los datos del juego en el almacenamiento local del navegador usando localStorage. Los datos se convierten en formato JSON para que el navegador pueda almacenarlos como texto.
  • Función cargarProgreso(): Al iniciar el juego, esta función carga los datos guardados desde localStorage, conviertiendo la cadena JSON en un objeto y verificando que los datos sean correctos antes de usarlos.
...

// Función para comprar una mejora
// "mejora" es el objeto que representa la mejora que el jugador intenta comprar
function comprarMejora(nombreMejora) {
    const mejora = juego.mejoras[nombreMejora];
    if (juego.galletas >= mejora.precio) {  // Verifica si el jugador tiene suficientes galletas
        ...
        guardarProgreso();  // Guarda el progreso después de la compra
    } 
    ...
}

// Función para incrementar el contador de galletas cuando el jugador hace clic
function incrementarContador() {
    ...
    guardarProgreso();  // Guarda el progreso en la memoria del navegador para no perder las galletas conseguidas
}

// Función para guardar el progreso del juego
function guardarProgreso() {
    // Genera una cadena de texto cont todos los datos del juego y lo guarda en el navegador.
    localStorage.setItem('juego', JSON.stringify(juego));

    // Después de guardar, actualiza el progreso visualmente
    mostrarProgreso();  
}

// Función para cargar el progreso guardado
function cargarProgreso() {
    // Recupera los datos del juego guardados previamente
    let juegoGuardado = localStorage.getItem('juego');

    // Si el jugador ha guardado su progreso previamente, lo recuperamos y lo cargamos
    if (juegoGuardado) {
        juegoGuardado = JSON.parse(juegoGuardado);  // Convierte la cadena JSON a un objeto

        // Comprueba si los datos guardados previamente son correctos
        if (datosCorrectos(juegoGuardado)) {
            juego = juegoGuardado;  // Recupera los datos guardados y actualiza el estado del juego
        } else {
            guardarProgreso();  // Si los datos son incorrectos, guarda los datos actuales
        }
        mostrarProgreso();  // Actualiza visualmente el progreso
    }
}

// Función para inicializar el juego
function inicializar() {
    cargarProgreso();  // Carga el progreso guardado al iniciar el juego
}

inicializar();  // Llama a la función para iniciar el juego

Añadir la función de verificación en «utils.js»

A medida que vayamos actualizando la funcionalidad del juego, podrían haber algunas inconsistencias entre los datos guardados y los que tenga la nueva versión de nuestro juego. Por ello necesitaremos una función que compruebe si podemos recuperar la información guardada para utilizarla directamente:

  • Función datosCorrectos(datos): Esta función verifica que los datos guardados tienen las mismas propiedades que el objeto juego. De esta forma, si hay diferencias o los datos están corruptos, podremos evitar cargar datos incorrectos.
...

// Función para comprobar si los datos recibidos como parámetro tienen las mismas propiedades que el objeto 'juego'
function datosCorrectos(datos) {
    // Si no es un objeto o es null, devuelve false.
    if (typeof datos !== 'object' || datos === null) return false;

    // Obtener las claves (propiedades) de 'datos' y 'juego'.
    const keys1 = Object.keys(datos);
    const keys2 = Object.keys(juego);

    // Si el número de propiedades es distinto, devuelve false.
    if (keys1.length !== keys2.length) return false;

    // Comprobar que todas las claves de 'datos' están también en 'juego'.
    return keys1.every(key => keys2.includes(key));
}

Cookie Clicker con HTML+CSS+Javascript (parte 2): Galletas extra por cada click

En esta unidad vamos a explicar cómo agregar una mejora al Cookie Clicker que permita ganar galletas extra con cada clic.

Imagen para activar la mejora

Al hacer click sobre esta imagen activarás la mejora de galletas extra. Puedes elegir cualquier imagen para tu juego, teniendo en cuenta simplemente que deberás colocarla dentro de la carpeta img. A continuación te proporcionamos una imagen de ejemplo:

Modificaciones del fichero «index.html»

Primero añadiremos una nueva imagen que represente una mejora que podremos comprar dentro de nuestro juego. Cuando el usuario haga clic en esta imagen, podrá comprar una mejora que le dará galletas extra por cada clic:

  • Para poder representar la mejora, utilizaremos una imagen adicional que meteremos dentro de un bloque div.
  • Al hacer clic sobre el bloque div, el jugador podrá adquirir la mejora si tiene suficientes galletas. Este proceso se llevará a cabo en la función comprarMejora('galletasExtra'), que crearemos a continuación en un fichero JavaScript.
...
<body>
    ...
    <div onclick="comprarMejora('galletasExtra')">
      <img src="img/muchas-galletas.png" width="125">
      <div id="textoGalletasExtra"></div>
    </div>

    <script src="variables.js"></script>
    <script src="utils.js"></script>
    <script src="script.js"></script>
</body>
</html>

Modificaciones del fichero «variables.js»

En este archivo, añadiremos una propiedad nueva dentro del objeto juego para almacenar las mejoras del juego, incluyendo la cantidad de galletas extra que el jugador ganará con cada clic y el precio de esta mejora:

  • La mejora tiene un precio (5 galletas) y un contador que muestra cuántas veces ha sido comprada.
  • Dentro de juego, ahora tenemos una sección llamada mejoras, que incluye la mejora de galletas extra.
// Objeto que almacena toda la información relativa al progreso y configuración del juego
let juego = {
    galletas: 0, // Cantidad total de galletas conseguidas
    mejoras: {
        galletasExtra: { cantidad: 0, precio: 100, descripcion: "Galletas extra por cada click" }, // Mejora de galletas extras por click
    }
}

Fichero «utils.js»

Este archivo contendrá funciones auxiliares, como una que muestra mensajes al jugador. Por ejemplo, cuando compra una mejora o cuando no tiene suficientes galletas:

  • mostrarMensaje(mensaje) muestra una ventana emergente con un mensaje. Es útil para informar al jugador si ha comprado una mejora o si no tiene suficientes galletas.
// Función para mostrar mensajes por pantalla, para avisar por ejemplo de las mejoras adquiridas
function mostrarMensaje(mensaje) {
    alert(mensaje);
}

Modificaciones del fichero «script.js»

Aquí vamos a modificar las funciones que actualizan el contador de galletas y que permiten comprar mejoras:

  • Función incrementarContador(): Ahora esta función no solo aumentará el contador de galletas en 1, sino que también sumará las galletas extra si el jugador ha comprado la mejora.
  • Función comprarMejora(nombreMejora): Esta función permite al jugador comprar mejoras. Verifica si tiene suficientes galletas y, si es así, descuenta el precio, aumenta la cantidad de la mejora y muestra un mensaje de confirmación.
...

// Función para incrementar el contador de galletas cuando el jugador hace clic en la galleta
function incrementarContador() {
    juego.galletas++;  // Aumenta la cantidad de galletas
    juego.galletas += juego.mejoras.galletasExtra.cantidad;  // Suma las galletas extra si se ha comprado la mejora    
    mostrarProgreso();  // Muestra la cantidad de galletas
}

// Función para comprar una mejora
// "mejora" es el objeto que representa la mejora que el jugador intenta comprar
function comprarMejora(nombreMejora) {
    const mejora = juego.mejoras[nombreMejora];
    if (juego.galletas >= mejora.precio) {  // Verifica si el jugador tiene suficientes galletas
        juego.galletas -= mejora.precio;  // Resta el precio de las galletas
        mejora.cantidad++;  // Aumenta la cantidad de la mejora
        mostrarMensaje(`¡Tienes ${mejora.cantidad} x ${mejora.descripcion}!`);  // Muestra un mensaje de confirmación
        mostrarProgreso();  // Muestra la cantidad de galletas después de pagar la mejora
    } else {
        mostrarMensaje("¡No tienes suficientes galletas!", 'warning');  // Si no tiene galletas suficientes, muestra una advertencia
    }
}