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

«Случайный выбор» на с. 65).

Именно это мы и делаем в строке  для получения случайного

столбца, однако вместо 38 мы пишем (widthInBlocks - 2). Это нужно

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

без необходимости редактировать код программы. И таким же образом

мы получаем случайное значение строки с помощью Math.fl oor(Math.

random() * (heightInBlocks - 2)) + 1.

Наконец, в строке  мы создаем новый объект-ячейку, позиция кото-

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

сохраняя его в this.position. Таким образом, позиция яблока изме-

нится на новую случайную позицию в пределах игрового поля.

Вы можете проверить работу метода move таким образом:

var apple = new Apple();

apple.move();

apple.draw();

Код игры

Наша игра состоит примерно из 200 строк JavaScript-кода! Если соеди-

нить все рассмотренные ранее фрагменты воедино, программа будет

выглядеть так:

// Настройка «холста»

 var canvas = document.getElementById("canvas");

var ctx = canvas.getContext("2d");

// Получаем ширину и высоту элемента canvas

var width = canvas.width;

var height = canvas.height;

// Вычисляем ширину и высоту в ячейках

var blockSize = 10;

var widthInBlocks = width / blockSize;

var heightInBlocks = height / blockSize;

// Устанавливаем счет 0

var score = 0;

// Рисуем рамку

 var drawBorder = function () {

ctx.fillStyle = "Gray";

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

ctx.fillRect(0, 0, width, blockSize);

ctx.fillRect(0, height - blockSize, width, blockSize);

ctx.fillRect(0, 0, blockSize, height);

ctx.fillRect(width - blockSize, 0, blockSize, height);

};

// Выводим счет игры в левом верхнем углу

var drawScore = function () {

ctx.font = "20px Courier";

ctx.fillStyle = "Black";

ctx.textAlign = "left";

ctx.textBaseline = "top";

ctx.fillText("Счет: " + score, blockSize, blockSize);

};

// Отменяем действие setInterval и печатаем сообщение «Конец игры»

var gameOver = function () {

clearInterval(intervalId);

ctx.font = "60px Courier";

ctx.fillStyle = "Black";

ctx.textAlign = "center";

ctx.textBaseline = "middle";

ctx.fillText("Конец игры", width / 2, height / 2);

};

// Рисуем окружность (используя функцию из главы 14)

var circle = function (x, y, radius, fillCircle) {

ctx.beginPath();

ctx.arc(x, y, radius, 0, Math.PI * 2, false);

if (fillCircle) {

ctx.fill();

} else {

ctx.stroke();

}

};

// Задаем конструктор Block (ячейка)

 var Block = function (col, row) {

this.col = col;

this.row = row;

};

// Рисуем квадрат в позиции ячейки

Block.prototype.drawSquare = function (color) {

var x = this.col * blockSize;

var y = this.row * blockSize;

ctx.fillStyle = color;

ctx.fillRect(x, y, blockSize, blockSize);

};

// Рисуем круг в позиции ячейки

Block.prototype.drawCircle = function (color) {

var centerX = this.col * blockSize + blockSize / 2;

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

var centerY = this.row * blockSize + blockSize / 2;

ctx.fillStyle = color;

circle(centerX, centerY, blockSize / 2, true);

};

// Проверяем, находится ли эта ячейка в той же позиции, что и ячейка

// otherBlock

Block.prototype.equal = function (otherBlock) {

return this.col === otherBlock.col && this.row === otherBlock.row;

};

// Задаем конструктор Snake (змейка)

 var Snake = function () {

this.segments = [

new Block(7, 5),

new Block(6, 5),

new Block(5, 5)

];

this.direction = "right";

this.nextDirection = "right";

};

// Рисуем квадратик для каждого сегмента тела змейки

Snake.prototype.draw = function () {

for (var i = 0; i < this.segments.length; i++) {

this.segments[i].drawSquare("Blue");

}

};

// Создаем новую голову и добавляем ее к началу змейки,

// чтобы передвинуть змейку в текущем направлении

Snake.prototype.move = function () {

var head = this.segments[0];

var newHead;

this.direction = this.nextDirection;

if (this.direction === "right") {

newHead = new Block(head.col + 1, head.row);

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

newHead = new Block(head.col, head.row + 1);

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

newHead = new Block(head.col - 1, head.row);

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

newHead = new Block(head.col, head.row - 1);

}

if (this.checkCollision(newHead)) {

gameOver();

return;

}

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

this.segments.unshift(newHead);

if (newHead.equal(apple.position)) {

score++;

apple.move();

} else {

this.segments.pop();

}

};

// Проверяем, не столкнулась ли змейка со стеной или собственным

// телом

Snake.prototype.checkCollision = function (head) {

var leftCollision = (head.col === 0);

var topCollision = (head.row === 0);

var rightCollision = (head.col === widthInBlocks - 1);

var bottomCollision = (head.row === heightInBlocks - 1);

var wallCollision = leftCollision || topCollision || 

rightCollision || bottomCollision;

var selfCollision = false;


for (var i = 0; i < this.segments.length; i++) {

if (head.equal(this.segments[i])) {

selfCollision = true;

}

}

return wallCollision || selfCollision;

};

// Задаем следующее направление движения змейки на основе нажатой

// клавиши

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;

};

// Задаем конструктор Apple (яблоко)

 var Apple = function () {

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

};

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

// Рисуем кружок в позиции яблока

Apple.prototype.draw = function () {

this.position.drawCircle("LimeGreen");

};

// Перемещаем яблоко в случайную позицию

Apple.prototype.move = function () {

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

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

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

};

// Создаем объект-змейку и объект-яблоко

 var snake = new Snake();

var apple = new Apple();

// Запускаем функцию анимации через setInterval

var intervalId = setInterval(function () {

ctx.clearRect(0, 0, width, height);

drawScore();

snake.move();

snake.draw();

apple.draw();

drawBorder();

}, 100);

// Преобразуем коды клавиш в направления

 var directions = {

37: "left",

38: "up",

39: "right",

40: "down"

};

// Задаем обработчик события keydown (клавиши-стрелки)

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

var newDirection = directions[event.keyCode];

if (newDirection !== undefined) {

snake.setDirection(newDirection);

}

});

Этот код состоит из нескольких частей. Первая часть начинается

со строки , в ней создаются и настраиваются необходимые в коде игры

переменные, включая «холст», контекст рисования, ширину и высоту

«холста» (об этом мы говорили в главе 16). Далее, со строки , идет код

отдельных функций: drawBorder, drawScore, gameOver и circle.

Со строки  начинается код конструктора Block, за которым сле-

дуют его методы drawSquare, drawCircle и equal. Затем, со строки ,

идет код конструктора Snake и все его методы. Далее, со строки , сле-

дует конструктор Apple и его методы draw и move.

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

Наконец, со строки  начинается код, который запускает игру и обес-

печивает ее выполнение. Сначала мы создаем объект-змейку snake

и объект-яблоко apple. Затем с помощью setInterval мы запускаем

анимацию игры. Обратите внимание, что при вызове setInterval мы

сохранили ID интервала в переменной intervalId, чтобы иметь воз-

можность отменить его выполнение в функции gameOver.

Функция, переданная setInterval в качестве аргумента, вызы-

вается на каждом шаге игры. Она отвечает за отображение на «холсте»

всей графики и за обновление состояния игры. Она очищает «холст»

и затем рисует змейку, яблоко и рамку. Также она вызывает метод объек-

та-змейки move, передвигающий змейку на один шаг в текущем направ-

лении. После вызова setInterval со строки  идет код для отслежи-

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

Как и прежде, вам следует поместить весь этот код внутрь элемента

script в HTML-документе. Чтобы запустить игру, просто загрузите файл

snake.html в браузер и управляйте змейкой с помощью клавиш-стрелок.

Если при нажатии на клавиши ничего не происходит, может понадо-

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

вать события клавиатуры.

Если игра не работает, возможно, в ваш

JavaScript-код закралась ошибка. Сведения об ошиб-

ках отображаются в консоли — посмотрите, нет ли

там каких-нибудь сообщений. В случае, если выяс-

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

тельно, строка за строкой сверьте ваш код с кодом,