«Случайный выбор» на с. 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-код закралась ошибка. Сведения об ошиб-
ках отображаются в консоли — посмотрите, нет ли
там каких-нибудь сообщений. В случае, если выяс-
нить, в чем проблема, так и не получилось, внима-
тельно, строка за строкой сверьте ваш код с кодом,