Перейти к основному содержимому

Инструкции по выполнению шага

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

  • Current – текущее число успешно отбитых столкновений с мячиком;
  • Best score – максимальное число успешно отбитых столкновений с мячиком без проигрышей за всё время;
  • Average – среднее число успешно отбитых столкновений с мячиком за все игры.

Данные характеристики игры можно добавить на главную страницу с помощью HTML элементов, которые впоследствии нужно будет расположить в нужном месте при помощи CSS. Саму логику же реализуем при помощи скрипта на JavaScript.

Для начала давайте добавим в главный файл index.html в элементе <body> перед добавлением скриптов следующие строчки:

<!-- Начало места, которое мы изменяем -->
<img src="assets/background.jpg" id="background"></img>
<h1 class="score">
Current: <a>0</a><br />
Best score: <a>0</a><br />
Average: <a>0</a>
</h1>
<script src="js/render.js"></script>
<!-- Конец редактирования -->

Элемент <h1> это элемент заголовка – текст будет отображаться большим. Существуют также и заголовки <h2>, <h3>, <h4>, <h5>. Каждый из них как в матрёшке имеет все меньший размер текста. Для разнообразия добавим вместо уникального идентификатора, как мы уже делали ранее на Шаге 0, имя класса при помощи атрибута class. Имя класса, в отличие от значения уникального идентификатора может повторяться внутри одного HTML файла. Зачастую рекомендуется использовать именно атрибуты классов, чтобы обращаться к конкретным элементам в CSS или JavaScript. Внутри так же используются элементы <a> и <br>. Последний позволяет сделать перенос строки при отображении текста в браузере. А первый чаще всего используется для добавления гиперссылок, однако в данном конкретном случае мы будем его использовать в качестве маркера. При написании JavaScript кода это позволит изменять конкретное значение содержимого ссылки.

После добавления элемента с текстом рекордов, нужно его правильно расположить на главной странице. Для этого добавим следующие строчки в конец нашего CSS файла style.css:

к сведению

Следующая часть добавляется в конец файла style.css.

.score {
color: white;
position: fixed;
left: 100px;
bottom: 35px;
}

Тут происходит обращение через селектор класса к HTML элементам с таким именем класса (score). К ним применяется настройки цвета текста, фиксированного положения. После чего элементам задаются координаты от левого и нижнего краёв страницы.

Если вы теперь откроете главную страницу игры, то увидите следующую картину:

Добавленный элемент доски рекордов

Доска рекордов теперь отображается на экране, но в процессе игры ничего не происходит. Всё потому, что у нас нет логики изменения чисел в элементе с оценками. Поэтому создадим в папке js новый скрипт scores.js и не забудем его подгрузить в index.html перед скриптом с движком:

<!-- Начало места, которое мы изменяем -->
<img src="assets/background.jpg" id="background"></img>
<script src="js/render.js"></script>
<script src="js/pause.js"></script>
<script src="js/controls.js"></script>
<script src="js/scores.js"></script>
<script src="js/engine.js"></script>
</body>

</html>
<!-- Конец редактирования -->

Вот так будет выглядеть наша папка проекта после создания файла scores.js:

ping-pong
├── index.html
├── assets
│ ├── ball.png
│ ├── paddle.png
│ ├── background.jpg
│ └── style.css
└── js
├── render.js
├── engine.js
├── controls.js
├── pause.js
└── scores.js

В самом только что созданном файле создадим переменную-словарик scoreStats аналогичную переменным ball и rightPaddle из render.js:

к сведению

Следующая часть кода добавляется в начало файла scores.js.

var scoreStats = {
currentCombo: 0,
bestCombo: 0,
totalTries: 0,
mean: 0
};

Тут мы задали поля с начальными значениями в виде нулей. Текущее число отбитых столкновений хранится в поле currentCombo, лучшее число отбитых столкновений – в поле bestCombo. Для подсчёта среднего значения по всем играм нам нужно минимум два поля, это число сыгранных партий и собственно сама средняя оценка. В дальнейшем мы реализуем алгоритм, который позволит обновлять значение среднего по следующей рекуррентной формуле:

Average(n)=Average(n1)+ValueAverage(n1)n,(1)Average(n)= Average(n-1)+ \cfrac{Value-Average(n-1)}{n} \text{,}\quad \text{(1)}

где nn – число сыгранных игр, Average(n1)Average(n-1) – среднее за прошлые игры, ValueValue – оценка за эту последнюю. Заметим, что логически у нас обновление доски рекордов происходит при трёх сценариях:

  • В результате столкновения ракетки с мячом;
  • В случае проигрыша;
  • В случае сброса игры.

Тогда напишем функцию updateStatus, которая будет соответствовать этой логике:

к сведению

Следующая часть добавляется в конец файла scores.js.

// вызывается при проигрыше
const updateStatus = (gameover = false, reset = false) => {
const scoreElements = Array.from(
document.getElementsByClassName("score")[0].getElementsByTagName("a")
);
if (!gameover) {
scoreStats.currentCombo++;
scoreStats.bestCombo = Math.max(
scoreStats.currentCombo,
scoreStats.bestCombo
);
} else {
scoreStats.totalTries++;
scoreStats.mean =
Math.floor(
(scoreStats.mean +
(scoreStats.currentCombo - scoreStats.mean) / scoreStats.totalTries) *
100
) / 100;
scoreStats.currentCombo = 0;
}
if (reset) {
scoreStats.totalTries = 0;
scoreStats.currentCombo = 0;
scoreStats.bestCombo = 0;
scoreStats.mean = 0;
}
// Current
scoreElements[0].textContent = scoreStats.currentCombo;
// Best
scoreElements[1].textContent = scoreStats.bestCombo;
// Average
scoreElements[2].textContent = scoreStats.mean;
};

Для начала получаем доступ к элементу с рейтингом при помощи метода document.getElementsByClassName. По умолчанию этот метод возвращает набор подходящих элементов. Поскольку у нас в HTML файле всего один такой элемент, то мы обращаемся к нему через индекс [0]. Т.к. внутри элемента с рейтингом <h1> лежат ещё и дочерние элементы ссылок <a> , обратимся к ним при помощи метода getElementsByTagName("a"). Этот метод так же возвращает набор элементов. В случае, когда игра не завершена, т.е. когда мячик столкнулся с ракеткой, мы увеличиваем значение текущей оценки на единичку при помощи оператора инкремента ++. В тот же момент нам нужно проверить установки нового рекорда. Чтобы не писать дополнительных условных операций, воспользуемся стандартной функцией Math.max. Она возвращает максимальный элемент из набора переданных аргументов. В случае проигрыша, необходимо увеличить счётчик сыгранных партий, а затем обновить значение среднего числа по ранее упомянутой формуле (1)(1). Среднее может иметь дробные разряды. Так, если было сыграно три игры с числом столкновений 1, 1 и 2, то среднее будет 1.(3). В JavaScript такие дробные числа представляются в виде чисел с плавающей точкой и будут выглядеть, как 1.33333333333333333333. Что выглядит не слишком аккуратно. Поэтому в коде выполняется огрубление значений через умножение на 100, округление до целых в меньшую сторону функцией Math.floor и последующее деление на 100. В результате таких махинаций предыдущий пример будет отображаться уже в виде 1.33. Наконец, после проигрыша надо не забыть обнулить текущее число оценок.

На последнем этапе мы обращаемся к элементам <a> и записываем в них текущие значения полей из словарика. Для этого используются индексация массива и метод textContent.

После реализации функции обновления оценок игры, их нужно добавить в игровой движок. То есть в основной цикл игры – функцию loop.

Найдите в engine.js следующие строчки и допишите вызов функции:

к сведению

Следующая часть кода модифицирует в конец файла engine.js, а именно добавляется вызов функции updateStatus() в нескольких местах .

            rightPaddle.y = canvas.height / 2 - paddleHeight / 2;
// Обновляем доску с рекордами
updateStatus(true);
}, 1000);
}
// Если мяч коснулся левой платформы,
if (collides(ball, leftPaddle)) {
// то отправляем его в обратном направлении
ball.dx *= -1;
// Увеличиваем координаты мяча на ширину платформы, чтобы не засчитался новый отскок
ball.x = leftPaddle.x + leftPaddle.width;
}
// Проверяем и делаем то же самое для правой платформы
else if (collides(ball, rightPaddle)) {
ball.dx *= -1;
ball.x = rightPaddle.x - ball.width;

// Обновляем доску с рекордами
updateStatus(false);
}

// Отрисовываем новый кадр
redraw();
};
к сведению

Итоговый результат выполнения шага можно скачать тут.