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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5c80ec5d46
commit
76f95ed72e
@@ -35,8 +35,18 @@ class CuboViewModel(app: Application) : AndroidViewModel(app) {
|
|||||||
init {
|
init {
|
||||||
viewModelScope.launch { progreso.mejorScore.collect { mejorScore = it } }
|
viewModelScope.launch { progreso.mejorScore.collect { mejorScore = it } }
|
||||||
viewModelScope.launch { progreso.mejorBaldosa.collect { mejorTile = it } }
|
viewModelScope.launch { progreso.mejorBaldosa.collect { mejorTile = it } }
|
||||||
juego.reset()
|
// Reanuda la partida a medias si la hay (y encaja con el tamaño del
|
||||||
sync()
|
// 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). */
|
/** Instantánea barata del tablero para el renderer (copia defensiva). */
|
||||||
@@ -61,8 +71,16 @@ class CuboViewModel(app: Application) : AndroidViewModel(app) {
|
|||||||
won = juego.won
|
won = juego.won
|
||||||
over = juego.over
|
over = juego.over
|
||||||
version++
|
version++
|
||||||
if (over || won) {
|
// Captura el estado para persistir fuera del hilo (la partida es mutable).
|
||||||
viewModelScope.launch { progreso.registrar(juego.score, juego.maxValue) }
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,34 @@ package com.rizoma.cubo.data
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "cubo")
|
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) {
|
class Progreso(context: Context) {
|
||||||
private val store = context.applicationContext.dataStore
|
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 {
|
private companion object {
|
||||||
val KEY_SCORE = intPreferencesKey("mejor_score")
|
val KEY_SCORE = intPreferencesKey("mejor_score")
|
||||||
val KEY_TILE = intPreferencesKey("mejor_baldosa")
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,18 @@ class Cubo(val n: Int = 3, seed: Long = 0x43_55_42_4FL /* "CUBO" */) {
|
|||||||
spawn()
|
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
|
* 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
|
* nueva. Devuelve si el tablero cambió (un movimiento nulo no cuenta ni hace
|
||||||
|
|||||||
Reference in New Issue
Block a user