Tower Defence. Часть 2

Tower Defence. Часть 2: Постановка башен

В этой части появляется первый осознанный выбор игрока: куда поставить башню и почему это место подходит или не подходит.

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

  1. Валидация входных данных через набор маленьких функций.
  2. Булева логика и guard-условия.
  3. Работа с коллекциями (Array.some, Set.has).
  4. Перевод пикселей курсора в координаты клетки (Math.floor).
  5. Создание объекта башни с предсказуемой структурой.
  6. Разделение ответственности: отдельные функции для проверки и для изменения state.

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

С этого этапа линия Tower Defence работает как единый проект:

  1. После успешной проверки задачи её функция синхронизируется в общий workspace.
  2. Во вкладке Preview в practice можно сразу увидеть, как изменения влияют на поле.
  3. Для части placement доступен milestone-checkpoint: цель модуля считается выполненной, когда все ключевые функции постановки башен проходят checkpoint-assertions.

Фундамент: как игрок кликом выбирает клетку

Клик мыши приходит в пикселях, но игра работает в клетках.

Нужно:

  1. Взять позицию курсора внутри canvas.
  2. Разделить на размер клетки (cellSize).
  3. Округлить вниз через Math.floor.
const cellX = Math.floor(px / cellSize);
const cellY = Math.floor(py / cellSize);

Так мы получаем адрес клетки, например { x: 4, y: 2 }.

Смотри, что важно: если клик пришел «вне поля» и px/py отрицательные, Math.floor даст отрицательный индекс. Это нормально: дальше это отфильтруется проверкой isInsideGrid.

console.log(Math.floor(-1 / 64)); // -1

Фундамент: почему проверки разбиваем на функции

Вместо одной огромной функции делаем простые правила:

  • canAffordTower — хватает ли денег.
  • isInsideGrid — внутри ли поле.
  • isPathCell — не занята ли клетка маршрутом.
  • hasTowerAt — нет ли уже башни в этой клетке.

Потом объединяем в canPlaceTower.

Каждая такая функция должна быть простой и предсказуемой: вход -> true/false, без побочных эффектов и без мутации state.

Так код легче читать, тестировать и отлаживать.

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

  1. canAffordTower(state, towerCost).
  2. isInsideGrid(cellX, cellY, cols, rows).
  3. isPathCell(cellX, cellY, pathCells).
  4. hasTowerAt(towers, cellX, cellY).
  5. createTower(cellX, cellY, cellSize).
  6. getCellFromPixels(px, py, cellSize).
  7. canPlaceTower(...) как общий валидатор.
  8. placeTower(...) со списанием денег и push в state.towers.
  9. placeTowerFromClick(...) как связка клика и постановки.
  10. describeTowerProgress(state, towerCost) для понятной строки прогресса.

Пример композиции проверок

function canPlaceTower(state, cellX, cellY, cols, rows, pathCells, towerCost) {
  if (!isInsideGrid(cellX, cellY, cols, rows)) return false;
  if (isPathCell(cellX, cellY, pathCells)) return false;
  if (hasTowerAt(state.towers, cellX, cellY)) return false;
  if (!canAffordTower(state, towerCost)) return false;
  return true;
}

Зачем это новичку

Ты учишься проектировать поведение как «набор правил», а не как хаотичный код. Это базовый навык для любой бизнес-логики: от игр до форм, корзин и CRM.

Результат после Part 2

Игрок ставит башни только в корректные клетки, экономика не ломается, а поведение системы остается предсказуемым.