JavaScript для детей — страница 44 из 48

В строке  мы создаем переменную

leftCollision и присваиваем

ей значение выражения head.col

=== 0. Таким образом, эта перемен-

ная примет значение true, если

змейка столкнется с левой стенкой,

иначе говоря, если голова окажется

в столбце 0. Аналогично перемен-

ная topCollision используется

для проверки строки, в которой находится змеиная голова, чтобы выяс-

нить, не столкнулась ли она с верхней стенкой.

Далее мы проверяем столкновение с правой стенкой, сравнивая стол-

бец, где находится голова со значением widthInBlocks – 1. Поскольку

widthInBlocks равняется 40, по сути, это проверка, не находится

ли голова в столбце 39, который занят правой стенкой, как показано

на рис. 17.1. Затем мы выполняем похожую проверку для нижней стенки

(переменная bottomCollision), проверяя, не равно ли свойство головы

row значению heightInBlocks – 1.

В строке  мы определяем, столкнулась ли змейка с какой-

нибудь из стенок: с помощью операции || (ИЛИ) мы проверяем, не соот-

ветствует ли переменная leftCollision, или topCollision, или

rightCollision, или bottomCollision значению true. Полученный

булев результат мы сохраняем в переменной wallCollision.

17. Пишем игру «Змейка»: часть 2 263

Проверка столкновения с собственным телом

Чтобы проверить, не столкнулась ли змейка с собственным телом,

в строке  мы создаем переменную selfCollision, сначала присвоив

ей значение false. Затем в строке  мы используем цикл for для пере-

бора всех сегментов змеиного тела и проверки, не находится ли какой-

нибудь из сегментов в той же позиции, что и новая голова змейки, — для

этого служит выражение head.equal(this.segments[i]). И голова,

и другие сегменты тела — это объекты-ячейки, поэтому мы можем

использовать для проверки совпадения позиций метод объектов-ячеек

equal. Если обнаружится, что какой-либо из сегментов тела находится

там же, где голова, значит змейка столкнулась сама с собой, и тогда мы

присваиваем selfCollision значение true .

И наконец, в строке  мы возвращаем из метода wallCollision ||

selfCollision — это выражение даст true, если змейка столкнулась

либо со стенкой, либо сама с собой.

Управляем змейкой с клавиатуры

Теперь мы напишем код, позволяющий игроку задавать направление

движения змейки с клавиатуры. Мы добавим в программу обработ-

чик событий клавиатуры, который будет определять нажатия кла-

виш-стрелок и менять направление движения в соответствии с нажа-

той клавишей.

Обработчик события keydown

Этот код обрабатывает события клавиатуры:

 var directions = {

37: "left",

38: "up",

39: "right",

40: "down"

};

 $("body").keydown(function (event) {

var newDirection = directions[event.keyCode];

 if (newDirection !== undefined) {

snake.setDirection(newDirection);

}

});

В строке  мы создаем объект для преобразования кодов кла-

виш-стрелок в строки, обозначающие направления движения (этот

объект аналогичен объекту keyActions, который мы использовали

264 Часть III. Графика

в разделе «Реакция на нажатия клавиш» на с. 231). В строке  мы задаем

обработчик для событий keydown внутри элемента body. Этот обработ-

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

(если сначала он кликнет мышкой по странице браузера).

Первым делом этот обработчик преобразовывает полученный

из объекта-события код клавиши в строку с названием направления

и сохраняет эту строку в переменной newDirection. Если же получен-

ный код клавиши не равен 37, 38, 39 или 40 (это коды клавиш-стрелок),

выражение directions[event.keyCode] вернет undefi ned.

В строке  мы сравниваем переменную newDirection с undefi ned

и, если она не равна undefi ned, вызываем метод объекта-змейки

setDirection, передавая ему строку newDirection. (Поскольку в этой

конструкции if нет ветви else, в случае если newDirection равняется

undefi ned, мы просто проигнорируем нажатие клавиши.)

Сейчас этот код не будет работать, поскольку мы еще не определили

метод объекта-змейки setDirection. Давайте это исправим.

Создаем метод setDirection

Метод setDirection принимает от обработчика клавиатуры, код кото-

рого мы только что разбирали, строку с направлением и использует ее,

чтобы установить новое направление движения змейки. Кроме того,

этот метод не дает игроку менять направление такими способами, после

которых змейка сразу же врезается сама в себя. Например, если змейка,

двинувшись вправо, тут же начнет двигаться влево, не повернув перед

этим вверх или вниз, чтобы освободить себе путь, она мгновенно вре-

жется сама в себя. Мы будем называть такие смены направления недопу-

стимыми, поскольку мы не хотим давать игроку возможность их совер-

шать. Например, на рис. 17.6 для движения змейки вправо показано два

допустимых направления и одно недопустимое.

Текущее

Допустимые

направление

новые

направления

Текущее

Недопустимое

направление

новое

направление

Рис. 17.6. Допустимые направления относительно текущего

17. Пишем игру «Змейка»: часть 2 265

Метод setDirection проверяет, не пытается ли игрок выбрать недо-

пустимое направление. Когда это так, метод использует команду return

для досрочного выхода. В противном случае он обновляет свойство объ-

екта-змейки nextDirection.

Вот код метода setDirection:

Snake.prototype.setDirection = function (newDirection) {

 if (this.direction === "up" && newDirection === "down") {

return;

} else if (this.direction === "right" && newDirection === "left") {

return;

} else if (this.direction === "down" && newDirection === "up") {

return;

} else if (this.direction === "left" && newDirection === "right") {

return;

}

 this.nextDirection = newDirection;

};

Конструкция if... else в строке  состоит из четырех частей — для

обработки четырех недопустимых смен направления, которые мы хотим

предотвратить. В первой части говорится, что, если змейка движется

вверх (this.direction равняется "up"), а игрок нажал стрелку вниз

(newDirection равняется "down"), мы должны совершить досрочный

выход из метода с помощью return. Остальные части конструкции обра-

батывают другие недопустимые смены направления тем же образом.

Последняя строка метода setDirection будет выполнена, только

если в newDirection находится допустимое новое направление —

иначе до нее дело не дойдет, потому что одна из команд return в преды-

дущих строках завершит выполнение метода.

Если направление newDirection является допустимым, мы присва-

иваем его свойству nextDirection в строке .

Создаем яблоко

В этой игре яблоко представляет собой объект из трех компонентов: это

свойство position, которое хранит позицию яблока в виде объекта-

ячейки, метод draw, с помощью которого мы будем рисовать яблоко

на экране, и метод move, который нужен, чтобы задать яблоку новую

позицию после того, как змейка его съела.

Пишем конструктор Apple

Все, что делает конструктор, — это задает свойство position, присваи-

вая ему новый объект-ячейку.

266 Часть III. Графика

var Apple = function () {

this.position = new Block(10, 10);

};

Этот код создает новый объект-ячейку в столбце 10, строке 10 и при-

сваивает его свойству объекта-яблока position. Мы будем использовать

этот конструктор для создания объекта-яблока в самом начале игры.

Рисуем яблоко

Чтобы нарисовать яблоко, создадим метод draw:

Apple.prototype.draw = function () {

this.position.drawCircle("LimeGreen");

};

Этот метод очень прост, всю работу за него выполняет метод drawCircle

(созданный в разделе «Добавляем метод drawCircle» на с. 255). Чтобы

нарисовать яблоко, мы просто вызываем для хранящего объект-ячейку

свойства position метод drawCircle, передавая ему название цвета

"LimeGreen" (темно-зеленый) для отображения в заданной ячейке

зеленого кружка.

Чтобы проверить работу этого метода, выполните такой код:

var apple = new Apple();

apple.draw();

Перемещаем яблоко

Метод move перемещает яблоко в случайную новую позицию на игро-

вом поле (то есть в любую ячейку на «холсте», кроме области рамки).

Мы будем вызывать этот метод всякий раз, когда змейка съест яблоко,

чтобы оно появилось снова в другой позиции.

Apple.prototype.move = function () {

 var randomCol = Math.floor(Math.random() * (widthInBlocks - 2)) + 1;

Random col —

var randomRow = Math.floor(Math.random() * (heightInBlocks - 2)) + 1;

случайный

 this.position = new Block(randomCol, randomRow);

столбец

};

Random row —

случайная

В строке  и следующей строке мы создаем переменные randomCol

строка

и randomRow, которые примут значения, соответствующие случайному

столбцу и случайной строке на игровом поле. Как показано на рис. 17.1

на с. 253, столбцы и строки игрового поля имеют номера от 1 до 38, сле-

довательно, нам нужно выбрать случайные числа из этого диапазона.

17. Пишем игру «Змейка»: часть 2 267

Для получения этих чисел мы воспользуемся выражением Math.

fl oor(Math.random() * 38), которое возвращает случайное число

от 0 до 37, и затем прибавим к нему 1, чтобы получить число от 1 до 38

(как работают методы Math.fl oor и Math.random, см. в разделе