revew and cert
This commit is contained in:
@@ -6,3 +6,5 @@
|
|||||||
- [Умный дом](smart-house/README.md)
|
- [Умный дом](smart-house/README.md)
|
||||||
- [Умный дом WEB](smart-house-web/README.md)
|
- [Умный дом WEB](smart-house-web/README.md)
|
||||||
- [Игра "Змейка"](snake-game/README.md) -- проектная работа
|
- [Игра "Змейка"](snake-game/README.md) -- проектная работа
|
||||||
|
|
||||||
|
[Сертификат](https://otus.ru/certificate/aa2140d27fdd4561aa8890bfdc26eb87/) - файл `otus_cert.pdf`
|
||||||
|
|||||||
Binary file not shown.
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Проектная работа по курсу **Rust Developer. Professional** образовательной платформы [OTUS](https://otus.ru/).
|
Проектная работа по курсу **Rust Developer. Professional** образовательной платформы [OTUS](https://otus.ru/).
|
||||||
|
|
||||||
|
[Код-ревью проекта «Змейка»](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::<i8>() % 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.
|
||||||
Reference in New Issue
Block a user