# 🐍 Змейка на Jetpack Compose Учебный пример простого Android-приложения для студентов: классическая игра «Змейка» на одном экране. Цель примера — показать, как устроено реальное Android-приложение на современном стеке (Kotlin + Jetpack Compose) без лишней сложности. ## Что внутри (итоговая версия на `main`) - Игровое поле-сетка 20×30, управление пальцем как **джойстиком** (ведёшь — змейка поворачивает) - Змейка растёт и **ускоряется** с ростом счёта; ведётся счёт, **уровень** и **рекорд** (сохраняется между запусками) - **Бонус-еда** с таймером даёт больше очков; **препятствия** добавляются с каждым уровнем - Границы поля **телепортируют** на противоположную сторону (тор); смерть — от самой себя и от препятствий - **Звук** и **анимация-вспышка** при поедании, **пауза** по тапу - Вся отрисовка — через Compose `Canvas`, игровой цикл — на корутине > Каждая фича добавлялась отдельным этапом-веткой — см. раздел [Этапы](#этапы-ветки) > ниже: там видно, что именно меняется на каждом шаге. ## Стек | Что | Версия | |-----|--------| | Язык | Kotlin 2.0.20 | | UI | Jetpack Compose (BOM 2024.10.01, Material 3) | | Android Gradle Plugin | 8.13.2 | | Gradle | 8.13 | | compileSdk / minSdk | 36 / 24 | ## Как запустить ### Вариант 1: Android Studio (рекомендуется) 1. Открыть папку проекта в Android Studio (**File → Open**). 2. Дождаться Gradle Sync. 3. Подключить телефон по USB (с включённой *отладкой по USB*) или запустить эмулятор. 4. Нажать ▶ **Run**. ### Вариант 2: командная строка ```bash # нужен JDK 17+ и установленный Android SDK ./gradlew installDebug # соберёт и поставит приложение на подключённое устройство ``` > Файл `local.properties` с путём к SDK создаётся автоматически Android Studio > и **не** хранится в репозитории — у каждого свой путь. ## Структура проекта ``` app/src/main/ ├── java/com/example/snake/ │ ├── MainActivity.kt # точка входа: ComponentActivity + setContent { SnakeGame() } │ └── SnakeGame.kt # вся игра: состояние, игровой цикл, отрисовка, управление ├── res/values/themes.xml # тема приложения └── AndroidManifest.xml ``` 👉 **Главный файл для изучения — [`SnakeGame.kt`](app/src/main/java/com/example/snake/SnakeGame.kt).** Там по шагам видно идеи Compose: - состояние через `remember { mutableStateOf(...) }` — UI сам перерисовывается при его изменении; - игровой цикл через `LaunchedEffect { while(true) { delay(); step() } }`; - ввод через `Modifier.pointerInput { detectTapGestures / detectDragGestures }`; - рисование через `Canvas { drawRoundRect / drawLine }`. ## Управление | Действие | Жест | |----------|------| | Начать игру | касание/движение или тап | | Повернуть | **веди пальцем** как джойстиком (быстрый флик тоже работает) | | Пауза / продолжить | тап по полю во время игры | | Рестарт после проигрыша | тап по экрану | ## Этапы (ветки) Проект построен как серия этапов: каждый — отдельная **ветка**, добавляющая **одну** фичу поверх предыдущего. Между соседними ветками — маленький diff, поэтому легко увидеть, что именно меняется и какой Android/Kotlin-приём за это отвечает. `main` = последний этап (вся игра целиком). | Этап (ветка) | Что меняется | Ключевой приём | Где в коде | |---|---|---|---| | **`snake-stage-1`** | Базовая игра: поле 20×30, рост змейки, счёт, рекорд за сессию, свайпы, Game Over об стену/себя | Compose `Canvas`, игровой цикл на корутине, состояние в `remember` | весь `SnakeGame.kt` | | **`snake-stage-2`** | Змейка ускоряется с ростом счёта (150→60 мс), индикатор «Скорость» | задержка шага пересчитывается из состояния | `stepDelay()`, `speedLevel()` | | **`snake-stage-3`** | Рекорд **сохраняется между запусками** | Jetpack **DataStore** (`Flow` на чтение, `suspend` на запись) | `ScoreStore.kt`, загрузка в `LaunchedEffect` | | **`snake-stage-4`** | **Звук** при поедании еды | **SoundPool** + `res/raw/eat.wav`, очистка в `DisposableEffect` | `soundPool.play(...)` в `step()` | | **`snake-stage-5`** | **Анимация-вспышка** (кольцо) в момент укуса | **`Animatable`** (0→1) + `drawCircle` | `popProgress`, `drawBoard()` | | **`snake-stage-6`** | **Пауза** по тапу во время игры | флаг `paused` в условии игрового цикла | `detectTapGestures`, оверлей «ПАУЗА» | | **`snake-stage-7`** | **Бонус-еда** с таймером (1 из 4), +3 очка, золотая, исчезает по времени | таймер в «шагах» (учитывает паузу) + `drawArc` | `bonusFood`/`bonusStepsLeft`, `step()`/`drawBoard()` | | **`snake-stage-8`** | **Препятствия и уровни**: каждые 5 очков +порция стен; врезался — Game Over | генерация на `Set`, коллизии, прогрессия | `obstacles`/`level`, `addObstacles()` | | **`snake-stage-9`** | **Сквозь границы (тор)**: уходишь за край — появляешься с другой стороны | модульная арифметика координат `% COLS/ROWS` | `step()` | | **`snake-stage-10`** | **Джойстик**: ведёшь пальцем — змейка непрерывно поворачивает в сторону смещения от точки касания | `detectDragGestures` с обработкой в `onDrag` (а не на отпускании) + мёртвая зона | `turn()`, блок `pointerInput` | Посмотреть, что именно поменялось на этапе N, удобно diff'ом между ветками, например для этапа 7: ```bash git diff snake-stage-6 snake-stage-7 ``` Идеи для следующих этапов: два игрока на одном экране, выбор размера поля, экран настроек.