Инструкции по выполнению шага
Следующей полезной функции игры будет "запись матчей". Нам бы хотелось сохранять историю траекторий мячика, ракетки и нажатий на клавиши управления, чтобы в дальнейшем можно было бы использовать это в качестве данных для обучения искусственного интеллекта. В этом шаге мы займёмся предварительными приготовлениями: созданием HTML-элементов, настройкой стилей, написанием логики сбора данных в виде JavaScript скриптов. А на следующем шаге мы создадим кнопку, с помощью которой можно эти данные будет выгрузить на жесткий диск в виде файла. Итак, приступим.
Для начала создадим два элемента в index.html
перед элементом с доской рейтинга из прошлого шага:
<!-- Начало места, которое мы изменяем -->
<img src="assets/background.jpg" id="background"></img>
<h5 class="logging"></h5>
<h1 class="datasetSize">0</h1>
<h1 class="score">
Current: <a>0</a><br />
Best score: <a>0</a><br />
Average: <a>0</a>
</h1>
<!-- Конец редактирования -->
В первом элементе будем отображать текущее значение системы: координаты мячика и ракеток, а так же флаги нажатых клавиш. Во втором элементе будем вести подсчёт числа сохранённых состояний игры.
Аналогично прошлому шагу, эти элементы нужно выровнять на главной странице. Поэтому добавим в файл style.css
следующие настройки:
Следующая часть добавляется в конец файла style.css
.
.logging {
color: white;
position: fixed;
left: 100px;
top: 50px;
}
.datasetSize {
color: white;
position: fixed;
left: 400px;
top: 35px;
}
Теперь опишем логику сохранения состояний игры. Для этого создадим в папке js
новый скрипт logging.js
. Не забудем подгрузить его в index.html
перед скриптом scores.js
:
<!-- Начало места, которое мы изменяем -->
<script src="js/render.js"></script>
<script src="js/pause.js"></script>
<script src="js/controls.js"></script>
<script src="js/logging.js"></script>
<script src="js/scores.js"></script>
<script src="js/engine.js"></script>
</body>
</html>
<!-- Конец редактирования -->
Вот так будет выглядеть наша папка проекта после создания файла logging.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
└── logging.js
В файле logging.js
напишем следующим код:
const logText = document.getElementsByClassName("logging")[0];
const datasetSize = document.getElementsByClassName("datasetSize")[0];
let lastGoodMove = 1;
const header = "ball_x,ball_y,paddle_y,up,down,nothing";
let recordData = [header];
const updateDataset = () => {
logText.innerHTML = [
ball.x,
ball.y,
rightPaddle.y,
keyPresses.up,
keyPresses.down,
keyPresses.nothing
];
recordData.push(logText.innerHTML);
datasetSize.textContent = recordData.length - 1;
}
Тут мы получаем доступ к ранее созданным HTML элементам. Создаём переменную номер состояния игры последнего отбитого мяча. Она позволит нам автоматически отбрасывать плохие куски партий, когда игрок пропустил мячик. Переменная header
содержит названия со стояний системы, которые мы будем сохранять:
ball_x
– горизонтальная координата мячика;ball_y
– вертикальная координата мячика;paddle_y
– вертикальная координата ракетки;up
– флаг зажатой стрелки вверх;down
– флаг зажатой стрелки вниз;nothing
– флаг отсутствия нажатий на клавиши.
Помимо этого, заведём массив recordData
, в который и будем сохранять вышеупомянутые состояния системы.
Наконец, создадим функцию updateDataset
. При каждом её вызове она последовательно запишем в HTML элемент logging
текущее состояние системы, затем добавит его в конец массива recordData при помощи метода push и наконец обновит HTML элемент datasetSize
, записав в него текущее число хранящихся состояний игры за вычетом первого элемента с описанием признаков.
Финальным этапом внедряем написанную логику в движок игры в скрипте engine.js
.
В начале цикла игры:
Следующая часть отображает изменения файла engine.js
.
// Главный цикл игры
const loop = () => {
// Сохраняем текущие значения объектов
updateDataset();
// Если платформы на предыдущем шаге куда-то двигались — пусть продолжают двигаться
rightPaddle.y += rightPaddle.dy;
Помимо этого добавим в функцию обратного вызова метода setTimeout
п осле проигрыша следующий код:
Следующие части отображают изменения файла engine.js
.
rightPaddle.y = canvas.height / 2 - paddleHeight / 2;
// Вырезаем плохой фрагмент из датасета
recordData = recordData.slice(0, lastGoodMove);
// Обновляем доску с рекордами
updateStatus(true);
}, 1000);
В обработчике столкновений мячика с ракеткой обновляем значение переменной последнего удачного момента:
ball.x = rightPaddle.x - ball.width;
// Обновляем доску с рекордами
updateStatus(false);
// Сохраняем временную позицию последнего удачно отбитого мячика
lastGoodMove = recordData.length;
}
Итоговый результат выполнения шага можно скачать тут.