По нажатию кнопки игра уже начисляет очки и расходует энергию. Теперь вынесем расчёт награды в отдельную функцию, чтобы проверять его без многократных нажатий. Сегодня функции получат входные данные через параметры и вернут ответ через return. Правила готовой игры сохранятся.

Сыграй одну партию прямо здесь: собирай кристаллы, отдыхай и при желании покупай улучшение. Правила остались такими же, как в предыдущем уроке; теперь разберём, как функции с параметрами и return делают правила понятнее.

Возьми final/ урока об условиях и победе, он же start/ в комплекте. Ты уже умеешь передавать обработчик события и использовать пустой return; для выхода.

Открыть эту версию в отдельной вкладке

Исходники урока

Выбирай стартовую версию, промежуточный этап или финал в просмотрщике. Во вкладке «Инструкция» — как запустить проект и чем отличаются версии. Решения самостоятельных заданий открывай после своей попытки.

Скачать ZIP

Распакуй архив целиком. У каждой версии своя папка с index.html, style.css и game.js. Открывай HTML внутри выбранной папки двойным щелчком; после правки сохраняй файл и обновляй браузер. Установка Node.js, аккаунт и сервер здесь не нужны. CSS уже знаком по первому модулю и дан как готовое оформление.

Считаем награду по силе и количеству кристаллов

В game.js после поиска элементов, перед render, добавь:

function calculateReward(power, amount) {
  const reward = power * amount;
  return reward;
}

power и amount — параметры, имена для значений, которые поступят при вызове. В скобках вызова calculateReward(2, 3) стоят аргументы: конкретные 2 и 3. Первый попадёт в power, второй — в amount. Формула умножает силу на число собранных порций. В нашей кнопке за действие собирается одна порция, но сам расчёт умеет работать и с другим количеством.

return reward завершает функцию и передаёт вычисленное число к месту вызова. Можно сохранить его в переменную, вывести или прибавить к счёту. Код после return в этом вызове не выполняется. На входе здесь ожидаем числа; функция не превращает произвольный пользовательский текст в корректные данные.

console.log(calculateReward(1, 1)); // 1
console.log(calculateReward(2, 3)); // 6
console.log(calculateReward(2, 0)); // 0

Эти вызовы введи в Console после загрузки страницы. Они ничего не меняют в игре. Функция читает лишь параметры и возвращает ответ, а не трогает DOM или общий score. Так удобно проверять формулу с разными значениями параметров.

Используем новую функцию при сборе кристалла

Внутри handleGather замени одну строку:

score = score + calculateReward(clickPower, 1);

Сначала вызов возвращает награду, затем она складывается со старым счётом, после чего результат присваивается score. При силе 1 это прибавка 1; после покупки силы 2 — прибавка 2. Остальные строки расхода энергии, победы и render оставь на месте. Это step-01/: ты изменил устройство расчёта, но не правила игры.

Проверка: сравни первый клик и клик после покупки с предыдущей версией. Если появляется NaN (нечисловой результат), проверь, что функция возвращает число и вызвана с двумя аргументами. Если пропустить amount, значение параметра будет undefined, а умножение не даст нужной награды.

Где доступны переменные функции

Имя reward, объявленное через const внутри фигурных скобок функции, доступно только в этой области. Оно не является новой переменной всей игры. После calculateReward(2, 3) выражение reward в Console снаружи вызова даст ошибку имени. Получить результат снаружи можно так:

const previewReward = calculateReward(2, 3);
console.log(previewReward);

Названия параметров тоже локальны. Они не обязаны совпадать с именем clickPower, которое мы передаём. Вызов передаёт его текущее числовое значение, а не название переменной. Здесь функция вообще не меняет power; изменение локального числового параметра также не переписало бы clickPower.

Не путай три действия: return отдаёт значение вызывающему коду, console.log показывает диагностическое сообщение, textContent обновляет страницу. Если заменить return на console.log, в Console число появится, но результат вызова окажется undefined. Если убрать вызов render из действия, состояние изменится, а надпись отстанет.

Возвращаем ответ «можно или нельзя»

Рядом с функцией награды добавь:

function canBuyUpgrade(points, price) {
  return points >= price;
}

Сравнение уже даёт true или false — логическое значение типа boolean. Поэтому дополнительный if здесь не нужен. Эта функция проверяет только достаточность очков. А обработчик отдельно проверит, что улучшение ещё не куплено и игра не закончена.

В handleUpgrade замени условие выхода на:

if (won || upgraded || !canBuyUpgrade(score, upgradePrice)) {
  return;
}

В render замени строку доступности покупки:

upgradeButton.disabled = won || upgraded || !canBuyUpgrade(score, upgradePrice);

Теперь и обработчик, и render проверяют наличие очков через одну функцию. ! по-прежнему меняет разрешение на запрет. Это final урока. Сначала проверь вызовы ниже, затем весь сценарий покупки:

console.log(canBuyUpgrade(9, 10));  // false
console.log(canBuyUpgrade(10, 10)); // true
console.log(canBuyUpgrade(11, 10)); // true

Самостоятельно: остаток после покупки

В отдельной копии напиши remainingAfterPurchase(points, price). Вызывай эту функцию только тогда, когда очков хватает на покупку. Она должна вычесть цену из количества очков и вернуть остаток. Она не меняет общий score, не выводит сообщение и не ищет кнопку. Проверь случаи (10,10) → 0, (13,10) → 3, (20,5) → 15. Перед каждым вызовом запиши ожидаемое значение.

После проверки попробуй использовать её в своём обработчике покупки вместо прямого вычитания. Условие, запрещающее покупку при недостатке, оставь до присваивания. Для следующего урока сохрани базовый final без этой дополнительной функции.

Подсказка 1

Параметры перечисляются в объявлении. Ответ должен попасть на место вызова, поэтому console.log внутри не заменяет нужную инструкцию.

Подсказка 2

Возвращай разность параметров; снаружи можно присвоить результат переменной. Самостоятельно реши, какая сторона вычитания соответствует остатку.

Готово, когда: игра ведёт себя как до изменения, ты различаешь параметры и аргументы, можешь получить число из функции и проверить формулу с тремя разными наборами чисел. Объясни, почему canBuyUpgrade не должна списывать очки: render тоже вызывает её.

Разбор самостоятельного задания

Сначала попробуй решить задание сам. Затем сравни с примером ту функцию или условие, которые вызвали затруднение. Разбор самостоятельной задачи доступен во вкладке «Решение» в просмотрщике исходников.