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>
Semántico sayk'uy
Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.
3 qillqakuna · +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