tawasuyu

sergio/humanoid

HTML 38Markdown 1TOML 10 Definitions

Cubo: persistir la partida en curso — reanuda el tablero al reabrir

El servicio ya no hace reset() ciego: restaura el tablero guardado (celdas +
score + won) si encaja con el tamaño del cubo, guarda tras cada jugada y borra
al perder (el récord ya queda sellado aparte).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

76f95ed72e3c82eea461f275ae12110838cf1e3c · by Sergio · (4 months ago) · parent 5c80ec5d4 · browse the tree at this commit

Semantic delta

No definition changed: this change is text, documentation or data.

3 files · +85 −5

diff --git a/cubo/src/main/java/com/rizoma/cubo/CuboViewModel.kt b/cubo/src/main/java/com/rizoma/cubo/CuboViewModel.kt
index 383e942..388dd81 100644
--- a/cubo/src/main/java/com/rizoma/cubo/CuboViewModel.kt
+++ b/cubo/src/main/java/com/rizoma/cubo/CuboViewModel.kt
@@ -35,8 +35,18 @@ class CuboViewModel(app: Application) : AndroidViewModel(app) {
     init {
         viewModelScope.launch { progreso.mejorScore.collect { mejorScore = it } }
         viewModelScope.launch { progreso.mejorBaldosa.collect { mejorTile = it } }
-        juego.reset()
-        sync()
+        // Reanuda la partida a medias si la hay (y encaja con el tamaño del
+        // cubo); si no, empieza una nueva. El tablero arranca en blanco y esta
+        // carga es asíncrona, así que la UI verá el tablero apenas resuelva.
+        viewModelScope.launch {
+            val saved = progreso.cargarPartida()
+            if (saved != null && saved.cells.size == juego.cells.size) {
+                juego.restore(saved.cells, saved.score, saved.won)
+            } else {
+                juego.reset()
+            }
+            sync()
+        }
     }
 
     /** Instantánea barata del tablero para el renderer (copia defensiva). */
@@ -61,8 +71,16 @@ class CuboViewModel(app: Application) : AndroidViewModel(app) {
         won = juego.won
         over = juego.over
         version++
-        if (over || won) {
-            viewModelScope.launch { progreso.registrar(juego.score, juego.maxValue) }
+        // Captura el estado para persistir fuera del hilo (la partida es mutable).
+        val cells = juego.cells.copyOf()
+        val s = juego.score
+        val mt = juego.maxValue
+        val w = juego.won
+        val finished = juego.over
+        viewModelScope.launch {
+            if (finished || w) progreso.registrar(s, mt)
+            // Guarda mientras se juega; al terminar, borra (nada que reanudar).
+            if (finished) progreso.limpiarPartida() else progreso.guardarPartida(cells, s, w)
         }
     }
 }
diff --git a/cubo/src/main/java/com/rizoma/cubo/data/Progreso.kt b/cubo/src/main/java/com/rizoma/cubo/data/Progreso.kt
index 738e9a8..ae470ba 100644
--- a/cubo/src/main/java/com/rizoma/cubo/data/Progreso.kt
+++ b/cubo/src/main/java/com/rizoma/cubo/data/Progreso.kt
@@ -3,15 +3,34 @@ package com.rizoma.cubo.data
 import android.content.Context
 import androidx.datastore.core.DataStore
 import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
 import androidx.datastore.preferences.core.edit
 import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.core.stringPreferencesKey
 import androidx.datastore.preferences.preferencesDataStore
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
 
 private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "cubo")
 
-/** Récords del cubo, persistidos en DataStore: mejor puntuación y mejor baldosa. */
+/** Una partida a medias: el tablero tal cual, con su puntuación y si ya ganó. */
+data class PartidaCubo(val cells: IntArray, val score: Int, val won: Boolean) {
+    // data class con IntArray: equals/hashCode a mano para comparar por contenido.
+    override fun equals(other: Any?): Boolean =
+        other is PartidaCubo && score == other.score && won == other.won &&
+            cells.contentEquals(other.cells)
+
+    override fun hashCode(): Int =
+        (cells.contentHashCode() * 31 + score) * 31 + won.hashCode()
+}
+
+/**
+ * Persistencia del cubo en DataStore. Dos cosas: los **récords** (mejor
+ * puntuación y mejor baldosa, sobreviven a cualquier partida) y la **partida
+ * en curso** (el tablero a medias, para reanudar al reabrir la app). La
+ * partida se borra al terminar (game over): ya no hay nada que reanudar.
+ */
 class Progreso(context: Context) {
     private val store = context.applicationContext.dataStore
 
@@ -25,8 +44,39 @@ class Progreso(context: Context) {
         }
     }
 
+    /** Lee la partida a medias guardada, o null si no hay ninguna. */
+    suspend fun cargarPartida(): PartidaCubo? {
+        val p = store.data.first()
+        val csv = p[KEY_CELLS] ?: return null
+        val cells = csv.split(',').mapNotNull { it.toIntOrNull() }.toIntArray()
+        if (cells.isEmpty()) return null
+        return PartidaCubo(cells, p[KEY_PSCORE] ?: 0, p[KEY_PWON] ?: false)
+    }
+
+    /** Guarda el tablero en curso (se llama tras cada jugada). */
+    suspend fun guardarPartida(cells: IntArray, score: Int, won: Boolean) {
+        store.edit { p ->
+            p[KEY_CELLS] = cells.joinToString(",")
+            p[KEY_PSCORE] = score
+            p[KEY_PWON] = won
+        }
+    }
+
+    /** Borra la partida en curso (game over: ya no hay nada que reanudar). */
+    suspend fun limpiarPartida() {
+        store.edit { p ->
+            p.remove(KEY_CELLS)
+            p.remove(KEY_PSCORE)
+            p.remove(KEY_PWON)
+        }
+    }
+
     private companion object {
         val KEY_SCORE = intPreferencesKey("mejor_score")
         val KEY_TILE = intPreferencesKey("mejor_baldosa")
+        // Partida en curso.
+        val KEY_CELLS = stringPreferencesKey("partida_cells")
+        val KEY_PSCORE = intPreferencesKey("partida_score")
+        val KEY_PWON = booleanPreferencesKey("partida_won")
     }
 }
diff --git a/cubo/src/main/java/com/rizoma/cubo/game/Cubo.kt b/cubo/src/main/java/com/rizoma/cubo/game/Cubo.kt
index 54157cd..1899ce7 100644
--- a/cubo/src/main/java/com/rizoma/cubo/game/Cubo.kt
+++ b/cubo/src/main/java/com/rizoma/cubo/game/Cubo.kt
@@ -34,6 +34,18 @@ class Cubo(val n: Int = 3, seed: Long = 0x43_55_42_4FL /* "CUBO" */) {
         spawn()
     }
 
+    /**
+     * Restaura una partida a medias guardada. [saved] debe tener el mismo
+     * tamaño que el tablero (n³); el llamador lo valida. No reaparece nada: el
+     * tablero queda exactamente como estaba al guardarse.
+     */
+    fun restore(saved: IntArray, score: Int, won: Boolean) {
+        saved.copyInto(cells)
+        this.score = score
+        this.won = won
+        lastSpawn = -1
+    }
+
     /**
      * Deja caer todo hacia la cara dada y, si algo se movió, aparece una baldosa
      * nueva. Devuelve si el tablero cambió (un movimiento nulo no cuenta ni hace