diff --git a/README.md b/README.md index 15bb270..7dc49c9 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,5 @@ - [Умный дом](smart-house/README.md) - [Умный дом WEB](smart-house-web/README.md) - [Игра "Змейка"](snake-game/README.md) -- проектная работа + +[Сертификат](https://otus.ru/certificate/aa2140d27fdd4561aa8890bfdc26eb87/) - файл `otus_cert.pdf` diff --git a/otus_cert.pdf b/otus_cert.pdf new file mode 100644 index 0000000..67ee034 Binary files /dev/null and b/otus_cert.pdf differ diff --git a/snake-game/README.md b/snake-game/README.md index 90541e6..c24abad 100644 --- a/snake-game/README.md +++ b/snake-game/README.md @@ -2,6 +2,8 @@ Проектная работа по курсу **Rust Developer. Professional** образовательной платформы [OTUS](https://otus.ru/). +[Код-ревью проекта «Змейка»](REVIEW.md) от преподавателя Кирилла Федченко + ## Запуск Сразу после запуска игра будет на паузе. Чтобы начать игру нужно снять её с паузы клавишей "Пробел". diff --git a/snake-game/REVIEW.md b/snake-game/REVIEW.md new file mode 100644 index 0000000..b7e29cc --- /dev/null +++ b/snake-game/REVIEW.md @@ -0,0 +1,112 @@ +# Код-ревью проекта «Змейка» + +## Общая оценка + +Крепкая, аккуратно организованная работа. Автор хорошо владеет идиомами Bevy ECS: +разделение на модули по расписаниям (`startup` / `update` / `fixed_update` / `post_update`), +использование событий и обзёрверов, разнесение доменной модели в `domain/`. Логика движения +по сетке с разбиением шага на границе (`split_by_grid`) — нетривиальная и покрыта хорошим +юнит-тестом. Документирующие комментарии присутствуют почти везде. + +Есть один реальный функциональный баг (таймер спавна еды) и несколько игровых/стилистических +замечаний. + +--- + +## Критичные замечания + +### 1. Таймер спавна еды тикается общим временем вместо дельты кадра +`src/fixed_update.rs:92` + +```rust +if meal_timer.tick(time.elapsed()).just_finished() && query.count() < MAX_MEALS_ON_FIELD as usize { +``` + +`Timer::tick` принимает **дельту** времени за кадр, а `time.elapsed()` возвращает **суммарное** +время с момента старта (постоянно растущее). В результате: + +- Первые ~2 секунды таймер копит `elapsed`, затем на каждом fixed-шаге тикается на всё + накопленное время (2.0s, 2.03s, 2.06s …). Для `Repeating`-таймера это значит `just_finished()` + становится `true` практически **каждый кадр** после первых 2 секунд. +- Как следствие поле мгновенно заполняется до `MAX_MEALS_ON_FIELD` (5 штук), а после поедания + еды тут же добивается обратно — заявленной механики «одна еда раз в 2 секунды» нет. + +**Исправление:** +```rust +if meal_timer.tick(time.delta()).just_finished() && query.count() < MAX_MEALS_ON_FIELD as usize { +``` + +--- + +## Средние замечания + +### 2. Еда может появиться прямо под змеёй +`src/fixed_update.rs:93-103` + +`used_positions` собирается **только из других объектов еды** — позиции головы и сегментов хвоста +не учитываются. Поэтому еда легко спавнится под телом змеи, что портит игровой процесс +(съедается «бесплатно» либо появляется в недосягаемом месте). Стоит добавить в исключаемые +позиции клетки, занятые `Head`/`Tail`. + +### 3. Столкновение с границей триггерится каждый кадр +`src/post_update.rs:6-16` + +После выхода за границу скорость обнуляется, но координаты головы остаются за пределами поля, +поэтому `Collision::Bounds` генерируется на **каждом** `PostUpdate`, и `handle_collisions` +бесконечно переустанавливает скорость/цвет. У столкновения с хвостом такой проблемы нет +(там есть отсечка `velocity.0 == 0.0`). Логичнее ввести явное состояние «игра окончена» +(например, компонент-маркер `GameOver` или `State`), которое остановит проверки и позволит +сделать рестарт. Сейчас после проигрыша игру можно только перезапустить процессом. + +--- + +## Мелкие замечания и стиль + +### 4. Диапазон случайной позиции еды меньше поля и смещён +`src/fixed_update.rs:96-99` + +```rust +const ABS: i8 = (FIELD_SIZE as f32 / 2.0) as i8; // = 10 +let rand_x = rng.random::() % ABS; // диапазон -9..=9 +``` + +При `FIELD_SIZE = 20` поле по логике занимает клетки `-10..=10`, но еда никогда не появляется +в крайних клетках `±10`. Также остаток от деления даёт чуть неравномерное распределение и `0` +встречается вдвое чаще краёв. Для равномерного распределения удобнее +`rng.random_range(-ABS..=ABS)`. + +### 5. Неограниченное ускорение/замедление времени +`src/update.rs:30-40` + +`Equal`/`Minus` умножают `relative_speed` без верхней/нижней границы — можно довести скорость до +неиграбельных значений (или почти до нуля). Стоит ограничить, например, диапазоном `[0.125, 8.0]`. + +### 6. Опечатки в именах (для читаемости кода) +- `check_bounds_colisions`, `check_tail_colisions` → `collisions` (`src/post_update.rs`, `src/game.rs`) +- `GridBarier` → `GridBarrier` (`src/domain/events.rs` и все использования) +- `splited` → `split` (`src/fixed_update.rs:23`) + +Не влияет на работу, но публичные имена типов/систем лучше писать без ошибок. + +### 8. Учебный «мусор» в репозитории поставки +- `tests/rust_learning.rs` — это личные эксперименты (`dbg!` по `saturating_sub`, `ceil`, + `floor` и т.п.), а не тесты проекта. В сдаваемой работе их лучше убрать или вынести за пределы + `tests/`. +- `examples/` содержит туториалы по Bevy (`bevy_learning.rs`, `paddle_demo.rs` …), не относящиеся + к игре. Для учебного проекта допустимо, но стоит хотя бы упомянуть их статус в README. + +--- + +## Что сделано хорошо + +- **Чистая модульная структура**: доменная модель отделена от систем, системы сгруппированы + по расписаниям Bevy. +- **Идиоматичный ECS**: события (`Collision`, `GrowUp`, `GridBarier`), обзёрверы, `Single`-запросы, + ресурсы (`SnakePath`, `MealRespawnTimer`). +- **Аккуратная механика поворотов**: змея не может развернуться в себя (только `rotate_left`/ + `rotate_right`), а первый сегмент за головой исключён из проверки столкновений — грамотное + решение классических проблем «Змейки». +- **`split_by_grid`** — сложная логика движения через узлы сетки покрыта развёрнутым + юнит-тестом со множеством сценариев. +- Настроен `rustfmt.toml`, продуманный релизный профиль (`opt-level="z"`, `lto`, `strip`), + ведётся логирование в файл и stdout.