diff --git a/README.md b/README.md index ab255ce..d7f08ab 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Android-приложение на современном стеке (Kotlin + Je ## Что внутри (итоговая версия на `main`) -- Игровое поле-сетка 20×30, управление **свайпами** +- Игровое поле-сетка 20×30, управление пальцем как **джойстиком** (ведёшь — змейка поворачивает) - Змейка растёт и **ускоряется** с ростом счёта; ведётся счёт, **уровень** и **рекорд** (сохраняется между запусками) - **Бонус-еда** с таймером даёт больше очков; **препятствия** добавляются с каждым уровнем - Границы поля **телепортируют** на противоположную сторону (тор); смерть — от самой себя и от препятствий @@ -65,8 +65,8 @@ app/src/main/ | Действие | Жест | |----------|------| -| Начать игру | свайп или тап | -| Повернуть | свайп ↑ ↓ ← → | +| Начать игру | касание/движение или тап | +| Повернуть | **веди пальцем** как джойстиком (быстрый флик тоже работает) | | Пауза / продолжить | тап по полю во время игры | | Рестарт после проигрыша | тап по экрану | @@ -88,6 +88,7 @@ app/src/main/ | **`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: diff --git a/app/src/main/java/com/example/snake/SnakeGame.kt b/app/src/main/java/com/example/snake/SnakeGame.kt index 6258bb2..7e9b752 100644 --- a/app/src/main/java/com/example/snake/SnakeGame.kt +++ b/app/src/main/java/com/example/snake/SnakeGame.kt @@ -146,6 +146,15 @@ fun SnakeGame() { obstacles = emptySet() } + // Сменить направление (для джойстика). Запрет разворота на 180°; первый ввод запускает игру. + fun turn(dx: Int, dy: Int) { + if (gameOver) return + if (!started) started = true + if (dx != 0 && dir.x == -dx) return + if (dy != 0 && dir.y == -dy) return + nextDir = Cell(dx, dy) + } + // Добавить порцию препятствий в свободные клетки (не у головы змейки). fun addObstacles(count: Int) { val head = snake.first() @@ -255,28 +264,24 @@ fun SnakeGame() { } } } - // Свайпы: смена направления (и старт первым свайпом) + // Джойстик: ведёшь пальцем — змейка непрерывно поворачивает в сторону + // смещения пальца от точки касания (доминирующая ось). Мёртвая зона + // у центра гасит дрожание. .pointerInput(Unit) { - var total = Offset.Zero + val deadZone = 28.dp.toPx() + var origin = Offset.Zero detectDragGestures( - onDragStart = { - total = Offset.Zero + onDragStart = { pos -> + origin = pos if (!started && !gameOver) started = true - }, - onDragEnd = { - if (started && !gameOver) { - if (abs(total.x) > abs(total.y)) { - if (total.x > 0 && dir.x != -1) nextDir = Cell(1, 0) - else if (total.x < 0 && dir.x != 1) nextDir = Cell(-1, 0) - } else { - if (total.y > 0 && dir.y != -1) nextDir = Cell(0, 1) - else if (total.y < 0 && dir.y != 1) nextDir = Cell(0, -1) - } - } } - ) { change, drag -> - total += drag + ) { change, _ -> change.consume() + val off = change.position - origin + if (off.getDistance() >= deadZone) { + if (abs(off.x) > abs(off.y)) turn(if (off.x > 0) 1 else -1, 0) + else turn(0, if (off.y > 0) 1 else -1) + } } } ) { @@ -299,7 +304,7 @@ fun SnakeGame() { // Оверлеи: старт, пауза и проигрыш if (!started) { - Overlay(title = "ЗМЕЙКА", subtitle = "Свайпни, чтобы начать · тап — пауза", modifier = Modifier.align(Alignment.Center)) + Overlay(title = "ЗМЕЙКА", subtitle = "Веди пальцем — поворот · тап — пауза", modifier = Modifier.align(Alignment.Center)) } else if (gameOver) { Overlay(title = "GAME OVER", subtitle = "Счёт: $score · тапни для рестарта", modifier = Modifier.align(Alignment.Center)) } else if (paused) {