Этап 3: сохранение рекорда через DataStore

Рекорд теперь сохраняется между запусками приложения.

- ScoreStore.kt — обёртка над Preferences DataStore (Flow на чтение,
  suspend saveBest на запись; работа с диском вне UI-потока)
- загрузка рекорда при старте экрана через LaunchedEffect
- сохранение нового рекорда из step() через scope.launch
- зависимость androidx.datastore:datastore-preferences:1.1.1

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
george 2026-06-17 17:07:42 +03:00
parent 6a1719c148
commit d8efaa53a6
4 changed files with 59 additions and 3 deletions

View File

@ -74,6 +74,9 @@ app/src/main/
- **`snake-stage-2`** — ускорение: змейка ускоряется с каждой съеденной едой
(задержка шага 150 → 60 мс), на экране индикатор «Скорость». Смотри
функции `stepDelay()` / `speedLevel()` и игровой цикл в `SnakeGame.kt`.
- **`snake-stage-3`** — рекорд сохраняется между запусками через **Jetpack
DataStore** (`ScoreStore.kt`): чтение — `Flow`, запись — suspend-функция,
диск не блокирует UI. Смотри `LaunchedEffect` для загрузки рекорда и
`scope.launch { ScoreStore.saveBest(...) }` при новом рекорде.
Идеи для следующих этапов: сохранение рекорда через `DataStore`,
экранные кнопки-стрелки, анимации, звук.
Идеи для следующих этапов: экранные кнопки-стрелки, анимации, звук, паузу.

View File

@ -42,4 +42,6 @@ dependencies {
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material3:material3")
// Этап 3: сохранение рекорда между запусками
implementation("androidx.datastore:datastore-preferences:1.1.1")
}

View File

@ -0,0 +1,34 @@
package com.example.snake
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
// Один DataStore на всё приложение (файл snake_prefs.preferences_pb во
// внутреннем хранилище). Расширение Context — стандартный приём для DataStore.
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "snake_prefs")
/**
* Хранилище рекорда между запусками приложения.
*
* DataStore работает асинхронно (на корутинах): чтение это [Flow],
* запись suspend-функция. Так UI никогда не блокируется обращением к диску.
*/
object ScoreStore {
private val BEST_KEY = intPreferencesKey("best_score")
/** Поток рекорда. Если ещё ничего не сохраняли — отдаёт 0. */
fun bestFlow(context: Context): Flow<Int> =
context.dataStore.data.map { prefs -> prefs[BEST_KEY] ?: 0 }
/** Сохранить новый рекорд. */
suspend fun saveBest(context: Context, value: Int) {
context.dataStore.edit { prefs -> prefs[BEST_KEY] = value }
}
}

View File

@ -14,7 +14,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
@ -28,6 +30,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.material3.Text
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.random.Random
@ -73,6 +77,15 @@ fun SnakeGame() {
var started by remember { mutableStateOf(false) }
var gameOver by remember { mutableStateOf(false) }
// Этап 3: контекст и scope для работы с DataStore (рекорд хранится на диске)
val context = LocalContext.current
val scope = rememberCoroutineScope()
// Подгружаем сохранённый рекорд один раз при запуске экрана
LaunchedEffect(Unit) {
best = ScoreStore.bestFlow(context).first()
}
fun reset() {
snake = startSnake
dir = Cell(0, -1)
@ -99,7 +112,11 @@ fun SnakeGame() {
snake = if (grew) listOf(nh) + snake else listOf(nh) + snake.dropLast(1)
if (grew) {
score++
if (score > best) best = score
if (score > best) {
best = score
// новый рекорд — сохраняем на диск (асинхронно, UI не блокируется)
scope.launch { ScoreStore.saveBest(context, best) }
}
food = randomFreeCell(snake)
}
}