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

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

Следующей полезной функции игры будет "запись матчей". Нам бы хотелось сохранять историю траекторий мячика, ракетки и нажатий на клавиши управления, чтобы в дальнейшем можно было бы использовать это в качестве данных для обучения искусственного интеллекта. В этом шаге мы займёмся предварительными приготовлениями: созданием 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;

}
к сведению

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