Опубликовано 27.12.2022
Пишем игру на чистом Javascript
В этой статье я пошагово опишу разработку простой браузерной игры - "Пятнашки". Я старался реализовать проект простым, понятным и уникальным методом, с минимумом динамических элементов. Что из этого получилось - смотрите ниже.
Скелет игры в HTML
Для начала напишем в HTML таблицу, в которой будут происходить действие игры.
<div class="wrap15">
<table class="table-bordered" onselectstart="return false" onmousedown="return false">
<tbody>
<tr>
<td class="p-3" id="0"></td>
<td class="p-3" id="1"></td>
<td class="p-3" id="2"></td>
<td class="p-3" id="3"></td>
</tr>
<tr>
<td class="p-3" id="4"></td>
<td class="p-3" id="5"></td>
<td class="p-3" id="6"></td>
<td class="p-3" id="7"></td>
</tr>
<tr>
<td class="p-3" id="8"></td>
<td class="p-3" id="9"></td>
<td class="p-3" id="10"></td>
<td class="p-3" id="11"></td>
</tr>
<tr>
<td class="p-3" id="12"></td>
<td class="p-3" id="13"></td>
<td class="p-3" id="14"></td>
<td class="p-3" id="15"></td>
</tr>
</tbody>
</table>
</div>
Думаю, проблем с пониманием вышеуказанного кода не возникнет: создаем таблицу с четырьмя рядами, по четыре строки в каждом. В аттрибутах таблицы прописываем запрет на выделение области нажатия мышки: ведь неприятно, когда при двойном клике на приложение срабатывает встроенная функция выделения текста синим цветом. Каждой клетке присваеваем свой id, начиная с 0 (нумерация массивов в JS начинается с 0, поэтому нам будет удобно под нее подстроиться).
Мы могли бы сформировать таблицу динамически с помьщью скрипта, тогда проще было бы присваивать id каждой ячейке, но я считаю, что использование html-кода выглядит более наглядно и понятно.
Сразу добавим кнопку сброса игры, она будет просто перезагружать страницу.
<div class="refresh">
<a onclick="window.location.reload();" id="asBut" data-title="Сброс"></a>
</div>
Теперь перейдем непосредственно к скрипту. Создаем папку scripts и файл game15.js, после чего подключаем скрипт к html страничке.
<script src= "scripts/game15.js" type="text/javascript"></script>
Теперь начинаем работу над игрой. Нам нужен массив чисел от 1 до 15: писать его вручную мы, конечно, не будем. Воспользуемся циклом.
function fift(){
const result = [];
for (let i = 1; i <= 15; i++){
result.push(i);
}
return result;
}
const fifteen = fift();
Вы скажете: "Неужели это проще, чем написать 1, 2... 15? Может, в данном случае и не проще, но это скорее исключение, чем правило. В JS нет встроенного аналога функции range, формирующей массив чисел и символов в заданном диапазоне. Можно, конечно, ее НАПИСАТЬ или СКАЧАТЬ, но это уж точно дольше, чем написать цикл вручную.
Функция тасования костяшек
Теперь напишем функцию перемешивания номеров. Скажу честно, сначала я написал эту функцию как чистый рандом: наполнение клеток номерами от 1 до 15 в случайном порядке.
Через несколько проигранных в такую игру сессий я искренне решил, что пятнашки раскладывать разучился. Ходил, сокрушался, жаловался жене и только спустя время подумал: может быть, дело в неправильной расстановке костяшек?
Может быть, перемешивать номера нужно не случайно, а по правилам игры?
Семен Семеныч!
В общем, функция должна рандомно менять пустое поле с соседними костяшками. Начинаем с объявления пустого массива, который будет сохранять промежуточные значения комбинации перемешанных номеров.
После - откроем цикл с 2000 итераций (думаю, должно хватить) и объявим несколько буферных переменных, в которые будет сохранено значение перед заменой на ноль.
//функция перемешивания номеров
function shuffle(arr){
const newArr = [];
arr.push(0);
for(i = 0; i < 2000; i++){
let rand = Math.ceil(Math.random()* 4);
const idx = arr.indexOf(0);
//сюда сохраняется число перед заменой на ноль
let bufferR = arr[idx + 1];
let bufferL = arr[idx - 1];
let bufferD = arr[idx + 4];
let bufferU = arr[idx - 4];
Здесь я прервал функцию, чтобы коротко описать, что произошло выше и что будет происходить дальше. Выше мы создали алгоритм, который случайным образом выбирает одну из 4 цифр. Каждая цифра соответствует стороне, в которую будет помещена пустая ячейка. Если же пустая ячейка стоит на краю, то в сторону границы она "пойти" не может - в этом случае мы шлем ее в противоположную сторону.
Воспользуемся связкой switch/case, потому что она удобно группирует условия.
//если ноль в углу, мешаем в другую сторону
switch(rand){
case 1:
switch(arr.indexOf(0)){
case 0:
case 4:
case 8:
case 12:
arr[idx] = bufferR;
arr[idx + 1] = 0;
break;
default:
arr[idx - 1] = 0;
arr[idx] = bufferL;
break;
}
break;
case 2:
switch(idx){
case 0:
case 1:
case 2:
case 3:
arr[idx] = bufferD;
arr[idx + 4] = 0;
break;
default:
arr[idx] = bufferU;
arr[idx - 4] = 0;
break;
}
break;
case 3:
switch(idx){
case 3:
case 7:
case 11:
case 15:
arr[idx - 1] = 0;
arr[idx] = bufferL;
arr[idx - 1] = 0;
break;
default:
arr[idx] = bufferR;
arr[idx + 1] = 0;
break;
}
break;
case 4:
switch(idx){
case 12:
case 13:
case 14:
case 15:
arr[idx] = bufferU;
arr[idx - 4] = 0;
break;
default:
arr[idx] = bufferD;
arr[idx + 4] = 0;
break;
}
break;
}
}
//удаляем ноль из массива и заменяем его пустой строкой
arr[arr.indexOf(0)] = '';
return arr;
}
Мы перемешали массив, получив случайную комбинацию костяшек, но при этом, её все еще можно разложить, чтобы пройти игру. Теперь надо наполнить таблицу полученными значениями. Для этого воспользуемся циклом.
let shuf = shuffle(fifteen);
for(i = 0; i < shuf.length; i++){
document.getElementById(i).innerHTML = shuf[i];
}
Главная функция игры
Последний этап разработки игры - создание функции, которая будет реагировать на клик мышкой, перемещать костяшки и проверять не завершена ли игра. Назовем функцию changeItem, зададим в качестве аргумента событие event - от него нам понадобится цель, на которой сработал обработчик.
function changeItem(event){
let neighbour = [], empty;
//ищем соседа пустой клетки
function findN(){
for(i = 0; i < shuf.length; i++){
document.getElementById(i).innerHTML = shuf[i];
if(shuf[i] === ''){
empty = document.getElementById(i);
if(document.getElementById((i - 1)) && i !== 4 && i !== 8 && i !== 12){
neighbour.push(document.getElementById((i - 1)));
}
if (document.getElementById((i + 1)) && i !== 3 && i !== 7 && i !== 11){
neighbour.push(document.getElementById((i + 1)));
}
if (document.getElementById((i - 4))){
neighbour.push(document.getElementById((i - 4)));
}
if (document.getElementById((i + 4)))
neighbour.push(document.getElementById((i + 4)));
}
}
}
findN();
let empId = Number(empty.id);
let target = event.target;
Остановимся, чтобы разобраться, что здесь произошло. Мы объявили функцию "найди соседа" пустой ячейки. Соседи нам будут нужны для того, чтобы на них срабатывал обработчик, все остальные элементы приложения должны оставаться неактивными.
После обнаружения, соседи пустой ячейки собираются в массив neighbour. Этот массив будет меняться с каждым ходом - в нем собраны элементы страницы в виде объектов.
Поскольку не всем людям удобно играть постоянно кликая мышкой, добавим возможность управлять кнопками клавиатуры. Здесь все просто: определяем код нажатой клавиши, и, если такой ход разрешен, указываем соответствующую ячейку как цель, на которой сработало событие.
//добавляем функцию управления стрелками клавиатуры
if(event.code === "ArrowUp" && empId < 12){
target = document.getElementById(empId + 4);
} else if(event.code === "ArrowDown" && empId > 3){
target = document.getElementById(empId - 4);
} else if(event.code === "ArrowLeft" && empId !== 3 && empId !== 7 && empId !== 11 && empId !== 15){
target = document.getElementById(empId + 1);
} else if(event.code === "ArrowRight" && empId !== 0 && empId !== 4 && empId !== 8 && empId !== 12){
target = document.getElementById(empId - 1);
}
Теперь напишем участок, отвечающий за замену пустой ячейки на выбранного соседа
//если кликнули мышкой по соседу, выполняем замену
if(neighbour.includes(target)){
let buffer = target.innerHTML;
let idx = shuf.indexOf(Number(target.innerHTML));
target.innerHTML = '';
empty.innerHTML = buffer;
let numb = [];
for(let i = 0; i < shuf.length; i++){
numb.push(Number(document.getElementById(i.toString()).innerHTML));
}
numb[numb.indexOf(0)] = '';
shuf = numb;
Мы объявили переменную "буфер", в которую сохранили значение нажатой костяшки, перед ее заменой на пустую ячейку. Для преобразования строки в число мы используем объект Number, но можно также воспользоваться стандартной функцией parseInt().
Мы, в общем-то закончили. Теперь проверяем не пройдена ли игра на текущем ходу.
let newFift = fift();
newFift.push('');
if(shuf.toString() === newFift.toString()){
//если игра пройдена - добавляем нужый обработчик
На этом все: поиграть в игру можно здесь, исходный код скрипта - здесь.