Tower Defence. Часть 5
Tower Defence. Часть 5: Финализация логики и прогресса
Финальная часть закрывает продуктовую сторону: как аккуратно считать награды, состояние волны, прогресс игрока и итоговый счет.
Технологии именно этого урока (lesson-14-5)
- Защита от повторного начисления награды (idempotent-логика).
- Централизация формул скейлинга (
scaleEnemyByWave). - Сборка UI-строки статистики (
buildStatsLine). - Проверка условий старта волны (
canStartWave). - Безопасный оркестратор действий (
startWaveSafe,simulateTick). - Снимок прогресса в объект (
buildProgressSnapshot). - Итоговая формула очков (
computeFinalScore).
Сквозной прогресс в Practice Preview
Финальный модуль progression закрывает сквозной Tower-проект:
- Все функции из частей 1-5 живут в одном workspace и переживают refresh/login/logout.
- Во вкладке
Previewможно проверить итоговую петлю (simulateTick, snapshot, score) до финального milestone. - Если миграция старого кода сработала с fallback, это помечается warning в preview, чтобы студент мог перепроверить поведение.
Фундамент: почему нужна защита от дублей
В game.js есть проверка rewardedWave !== wave, чтобы бонус за завершение волны не начислялся дважды.
Это стандартный паттерн в любом приложении: действие можно вызывать много раз, но результат должен примениться один раз.
Смотри, что важно: в задачах бонус выдаем только когда волна действительно завершена:
waveQueue === 0(в очереди спавна никого не осталось),enemies.length === 0(на поле никого не осталось),rewardedWave !== wave(за эту волну еще не выдавали бонус),- и обычно имеет смысл дополнительно проверять, что
wave > 0(чтобы не начислить награду "до старта первой волны").
Фундамент: единая формула лучше копипаста
Если формулы врагов (hp/speed/reward) и счета размазаны по коду, их сложно менять и легко сломать.
Поэтому:
scaleEnemyByWave(wave)хранит все формулы врага в одном месте;computeFinalScore(state)хранит формулу результата в одном месте.
Что именно ты реализуешь в задачах
rewardWaveCompletion.scaleEnemyByWave.buildStatsLine.canStartWave.startWaveSafe.registerKill.applyRestart.computeFinalScore.buildProgressSnapshot.simulateTick.
Пример безопасного старта
function startWaveSafe(state) {
if (!canStartWave(state)) return state;
state.wave += 1;
state.waveQueue = 5 + state.wave * 2;
state.spawnTimer = 0;
return state;
}
Смотри, что важно: в задачах startWaveSafe возвращает state (а не boolean), чтобы можно было выстраивать одинаковый стиль "функция обновляет state и возвращает его". При недопуске важно не менять state вообще.
Пример агрегатора состояния
function simulateTick(state) {
rewardWaveCompletion(state);
const mode = state.gameOver ? 'Игра окончена' : state.waveQueue > 0 ? 'Волна идет' : 'Ожидание';
return {
mode,
stats: buildStatsLine(state, mode),
snapshot: buildProgressSnapshot(state),
};
}
Зачем это новичку
Ты изучаешь не только «как чтобы работало», но и «как чтобы не ломалось при развитии проекта». Это и есть практический переход от учебного скрипта к поддерживаемому продукту.
Результат после Part 5
Линия Tower Defence завершена: есть целостная игровая петля, прозрачная статистика, корректный прогресс и понятная структура для дальнейших улучшений.