From d8efaa53a60458af66e51c57c3d7d2b87fccdb87 Mon Sep 17 00:00:00 2001 From: george Date: Wed, 17 Jun 2026 17:07:42 +0300 Subject: [PATCH] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=203:=20=D1=81=D0=BE?= =?UTF-8?q?=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B5?= =?UTF-8?q?=D0=BA=D0=BE=D1=80=D0=B4=D0=B0=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7?= =?UTF-8?q?=20DataStore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Рекорд теперь сохраняется между запусками приложения. - 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) --- README.md | 7 ++-- app/build.gradle.kts | 2 ++ .../main/java/com/example/snake/ScoreStore.kt | 34 +++++++++++++++++++ .../main/java/com/example/snake/SnakeGame.kt | 19 ++++++++++- 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/example/snake/ScoreStore.kt diff --git a/README.md b/README.md index 41eab42..dfbe84a 100644 --- a/README.md +++ b/README.md @@ -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`, -экранные кнопки-стрелки, анимации, звук. +Идеи для следующих этапов: экранные кнопки-стрелки, анимации, звук, паузу. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 690f24a..5778849 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") } diff --git a/app/src/main/java/com/example/snake/ScoreStore.kt b/app/src/main/java/com/example/snake/ScoreStore.kt new file mode 100644 index 0000000..4aa36c4 --- /dev/null +++ b/app/src/main/java/com/example/snake/ScoreStore.kt @@ -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 by preferencesDataStore(name = "snake_prefs") + +/** + * Хранилище рекорда между запусками приложения. + * + * DataStore работает асинхронно (на корутинах): чтение — это [Flow], + * запись — suspend-функция. Так UI никогда не блокируется обращением к диску. + */ +object ScoreStore { + + private val BEST_KEY = intPreferencesKey("best_score") + + /** Поток рекорда. Если ещё ничего не сохраняли — отдаёт 0. */ + fun bestFlow(context: Context): Flow = + context.dataStore.data.map { prefs -> prefs[BEST_KEY] ?: 0 } + + /** Сохранить новый рекорд. */ + suspend fun saveBest(context: Context, value: Int) { + context.dataStore.edit { prefs -> prefs[BEST_KEY] = value } + } +} diff --git a/app/src/main/java/com/example/snake/SnakeGame.kt b/app/src/main/java/com/example/snake/SnakeGame.kt index 7dc61fb..26e6c6e 100644 --- a/app/src/main/java/com/example/snake/SnakeGame.kt +++ b/app/src/main/java/com/example/snake/SnakeGame.kt @@ -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) } }