Змейка проходит сквозь край поля и появляется с противоположной стороны. - координаты головы считаются по модулю: (x+dx+COLS)%COLS, (y+dy+ROWS)%ROWS - столкновение с краем больше не убивает; смерть только от себя/препятствий Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
113 lines
6.9 KiB
Markdown
113 lines
6.9 KiB
Markdown
# 🐍 Змейка на Jetpack Compose
|
||
|
||
Учебный пример простого Android-приложения для студентов: классическая игра
|
||
«Змейка» на одном экране. Цель примера — показать, как устроено реальное
|
||
Android-приложение на современном стеке (Kotlin + Jetpack Compose) без лишней
|
||
сложности.
|
||
|
||
## Что внутри
|
||
|
||
- Игровое поле-сетка 20×30
|
||
- Управление **свайпами** (вверх/вниз/влево/вправо)
|
||
- Змейка растёт, когда съедает еду; ведётся счёт и рекорд за сессию
|
||
- Столкновение со стеной или с собой = Game Over, **тап** — рестарт
|
||
- Вся отрисовка — через 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 }`.
|
||
|
||
## Управление
|
||
|
||
| Действие | Жест |
|
||
|----------|------|
|
||
| Начать игру | свайп или тап |
|
||
| Повернуть | свайп ↑ ↓ ← → |
|
||
| Рестарт после проигрыша | тап по экрану |
|
||
|
||
## Этапы (ветки)
|
||
|
||
Проект задуман как серия этапов — каждый в своей ветке, чтобы можно было
|
||
сравнивать версии:
|
||
|
||
- **`snake-stage-1`** — базовая рабочая игра.
|
||
- **`snake-stage-2`** — ускорение: змейка ускоряется с каждой съеденной едой
|
||
(задержка шага 150 → 60 мс), на экране индикатор «Скорость». Смотри
|
||
функции `stepDelay()` / `speedLevel()` и игровой цикл в `SnakeGame.kt`.
|
||
- **`snake-stage-3`** — рекорд сохраняется между запусками через **Jetpack
|
||
DataStore** (`ScoreStore.kt`): чтение — `Flow`, запись — suspend-функция,
|
||
диск не блокирует UI. Смотри `LaunchedEffect` для загрузки рекорда и
|
||
`scope.launch { ScoreStore.saveBest(...) }` при новом рекорде.
|
||
|
||
- **`snake-stage-4`** — звук при поедании еды через **SoundPool**: короткий
|
||
WAV в `res/raw/eat.wav` грузится один раз, играется при каждом съедании
|
||
(низкая задержка). Смотри настройку `SoundPool` и `soundPool.play(...)`
|
||
в `step()`; освобождение ресурса — в `DisposableEffect`.
|
||
|
||
- **`snake-stage-5`** — анимация поедания: в момент укуса на месте еды
|
||
расходится затухающее кольцо. Смотри `Animatable` (`popProgress` 0→1),
|
||
запуск анимации в `step()` и отрисовку кольца в `drawBoard()`.
|
||
|
||
- **`snake-stage-6`** — пауза: тап по полю во время игры ставит/снимает
|
||
паузу (свайпы по-прежнему рулят направлением). Смотри флаг `paused`,
|
||
условие в игровом цикле и оверлей «ПАУЗА».
|
||
|
||
- **`snake-stage-7`** — бонус-еда с таймером: иногда (1 из 4 при поедании
|
||
обычной еды) появляется золотая еда на ~36 шагов и даёт +3 очка; вокруг
|
||
неё рисуется дуга-таймер, по истечении она исчезает. Время считается в
|
||
шагах игры, поэтому пауза учитывается автоматически. Смотри `bonusFood` /
|
||
`bonusStepsLeft`, логику в `step()` и отрисовку (`drawArc`) в `drawBoard()`.
|
||
|
||
- **`snake-stage-8`** — препятствия и уровни: каждые 5 очков — новый уровень,
|
||
на поле добавляется порция серых стен; врезался в стену — Game Over. Еда и
|
||
бонус не спавнятся в препятствиях, новые стены не ставятся вплотную к голове.
|
||
В шапке — «Уровень». Смотри `obstacles`/`level`, `addObstacles()`, проверку
|
||
коллизий и повышение уровня в `step()`.
|
||
|
||
- **`snake-stage-9`** — сквозь границы (тор): змейка проходит сквозь край
|
||
поля и появляется с противоположной стороны; смерть остаётся только от
|
||
самой себя и от препятствий. Реализовано %-арифметикой по координатам
|
||
головы в `step()`.
|
||
|
||
Идеи для следующих этапов: два игрока, выбор размера поля, экран настроек.
|