george f81a1a88a2 Этап 10: джойстик-управление пальцем
Змейка непрерывно поворачивает в сторону смещения пальца от точки касания,
пока ведёшь по экрану (а не один поворот на отпускание свайпа).

- обработка в onDrag (а не onDragEnd): turn() вызывается на каждое движение
- направление по доминирующей оси смещения от origin; мёртвая зона 28dp
- общий turn() с запретом разворота на 180°; быстрый флик тоже работает
- подсказка на старте обновлена

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:49:27 +03:00

🐍 Змейка на 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: командная строка

# нужен 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. Там по шагам видно идеи 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<Cell>, коллизии, прогрессия obstacles/level, addObstacles()
snake-stage-9 Сквозь границы (тор): уходишь за край — появляешься с другой стороны модульная арифметика координат % COLS/ROWS step()
snake-stage-10 Джойстик: ведёшь пальцем — змейка непрерывно поворачивает в сторону смещения от точки касания detectDragGestures с обработкой в onDrag (а не на отпускании) + мёртвая зона turn(), блок pointerInput

Посмотреть, что именно поменялось на этапе N, удобно diff'ом между ветками, например для этапа 7:

git diff snake-stage-6 snake-stage-7

Идеи для следующих этапов: два игрока на одном экране, выбор размера поля, экран настроек.

Description
Учебный пример: Змейка на Jetpack Compose (Android)
Readme 177 KiB
Languages
Kotlin 100%