1
0

Этап 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`** — ускорение: змейка ускоряется с каждой съеденной едой - **`snake-stage-2`** — ускорение: змейка ускоряется с каждой съеденной едой
(задержка шага 150 → 60 мс), на экране индикатор «Скорость». Смотри (задержка шага 150 → 60 мс), на экране индикатор «Скорость». Смотри
функции `stepDelay()` / `speedLevel()` и игровой цикл в `SnakeGame.kt`. функции `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.ui:ui")
implementation("androidx.compose.foundation:foundation") implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material3:material3") 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.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius 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.ui.unit.sp
import androidx.compose.material3.Text import androidx.compose.material3.Text
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlin.math.abs import kotlin.math.abs
import kotlin.random.Random import kotlin.random.Random
@ -73,6 +77,15 @@ fun SnakeGame() {
var started by remember { mutableStateOf(false) } var started by remember { mutableStateOf(false) }
var gameOver 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() { fun reset() {
snake = startSnake snake = startSnake
dir = Cell(0, -1) dir = Cell(0, -1)
@ -99,7 +112,11 @@ fun SnakeGame() {
snake = if (grew) listOf(nh) + snake else listOf(nh) + snake.dropLast(1) snake = if (grew) listOf(nh) + snake else listOf(nh) + snake.dropLast(1)
if (grew) { if (grew) {
score++ score++
if (score > best) best = score if (score > best) {
best = score
// новый рекорд — сохраняем на диск (асинхронно, UI не блокируется)
scope.launch { ScoreStore.saveBest(context, best) }
}
food = randomFreeCell(snake) food = randomFreeCell(snake)
} }
} }