Tower Defence. Часть 5

Tower Defence. Часть 5: Финализация логики и прогресса

Финальная часть закрывает продуктовую сторону: как аккуратно считать награды, состояние волны, прогресс игрока и итоговый счет.

Технологии именно этого урока (lesson-14-5)

  1. Защита от повторного начисления награды (idempotent-логика).
  2. Централизация формул скейлинга (scaleEnemyByWave).
  3. Сборка UI-строки статистики (buildStatsLine).
  4. Проверка условий старта волны (canStartWave).
  5. Безопасный оркестратор действий (startWaveSafe, simulateTick).
  6. Снимок прогресса в объект (buildProgressSnapshot).
  7. Итоговая формула очков (computeFinalScore).

Сквозной прогресс в Practice Preview

Финальный модуль progression закрывает сквозной Tower-проект:

  1. Все функции из частей 1-5 живут в одном workspace и переживают refresh/login/logout.
  2. Во вкладке Preview можно проверить итоговую петлю (simulateTick, snapshot, score) до финального milestone.
  3. Если миграция старого кода сработала с fallback, это помечается warning в preview, чтобы студент мог перепроверить поведение.

Фундамент: почему нужна защита от дублей

В game.js есть проверка rewardedWave !== wave, чтобы бонус за завершение волны не начислялся дважды.

Это стандартный паттерн в любом приложении: действие можно вызывать много раз, но результат должен примениться один раз.

Смотри, что важно: в задачах бонус выдаем только когда волна действительно завершена:

  • waveQueue === 0 (в очереди спавна никого не осталось),
  • enemies.length === 0 (на поле никого не осталось),
  • rewardedWave !== wave (за эту волну еще не выдавали бонус),
  • и обычно имеет смысл дополнительно проверять, что wave > 0 (чтобы не начислить награду "до старта первой волны").

Фундамент: единая формула лучше копипаста

Если формулы врагов (hp/speed/reward) и счета размазаны по коду, их сложно менять и легко сломать.

Поэтому:

  • scaleEnemyByWave(wave) хранит все формулы врага в одном месте;
  • computeFinalScore(state) хранит формулу результата в одном месте.

Что именно ты реализуешь в задачах

  1. rewardWaveCompletion.
  2. scaleEnemyByWave.
  3. buildStatsLine.
  4. canStartWave.
  5. startWaveSafe.
  6. registerKill.
  7. applyRestart.
  8. computeFinalScore.
  9. buildProgressSnapshot.
  10. 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 завершена: есть целостная игровая петля, прозрачная статистика, корректный прогресс и понятная структура для дальнейших улучшений.