Пишем игру змейка с помощью JavaScript + Canvas
Доброго времени суток, друзья. Сейчас я постараюсь вам показать как можно написать игру Змейка. Конечно, не самым быстрым способом и не самым маленьким в плане количества строк кода, но по-моему самым понятным для начинающих разработчиков, как я. Статья написана для людей, желающих чуть-чуть познакомиться с элементом canvas и его простыми методами для работы с 2D графикой.
Напишем змейку в «старом» виде, без особо красивой графики — в виде кубиков. Но это только упростит понимание разработки. Ну что же, поехали!
Подготовка
Пожалуй, стоит вообще начать с подготовки к созданию игры и написанию кода. Мы будем использовать простой редактор Sublime Text. Впрочем, это не важно. Все делаем в одном документе, чтобы было быстрее.
Первым делом, напишем сам код для встраивания canvas в документ. Напомню, что canvas поддерживается только в HTML5.
Подготовка завершена, теперь мы можем приступать к созданию самой игры.
Начинаем
Для начала, я хотел бы вам вообще объяснить как будет работать змейка, так будет гораздо понятнее. Наша змейка — это массив. Массив элементов, элементы — это ее части, на которые она делиться. Это всего лишь квадратики, которые имеют координаты X и Y. Как вы знаете, X — горизонталь, Y — вертикаль. В обычном виде мы представляем себе координатную плоскость вот так:

Она абсолютно правильная, в этом нет сомнения, но на мониторе компьютера (в частности, canvas) она выглядит по-другому, вот так:

Это нужно знать, если вы вдруг в первый раз столкнулись с canvas. Я, когда столкнулся с этим, сначала вообще не понял где точка (0,0), благо я быстро разобрался. Надеюсь и у вас проблем не возникло.
Вернемся к элементам змейки, ее частям. Представим, что каждый элемент имеет свои координаты, но одинаковую высоту и одинаковую ширину. Это квадратики, не более. А теперь представим, что мы вот нарисовали змейку, все квадратики, они идут друг за другом, одинаковые такие. И тут мы решили, что нам нужно подвинуть змейку вправо. Как бы вы поступили?
Некоторые люди ответили бы, что нам нужно первый элемент подвинуть вправо, затем второй, затем третий и так далее. Но для меня этот вариант не является правильным, так как в случае, если вдруг змейка огромная, а компьютер слабый, мы можем заметить, что змейка иногда разрывается, что вообще не должно быть. Да и вообще, данный способ требует слишком много команд, когда можно обойтись гораздо меньшим количеством, не потеряв качество. А теперь мой способ: Мы берем последний элемент змейки, и ставим его в начало, изменяя его координаты так, чтобы он был после головы. Теперь этот элемент — голова. Всего-то! И да, эффект движения будет присутствовать, а на компьютере вообще не будет заметно, как мы спрятали хвост, а потом его поставили в начало. именно так мы и будем поступать во время создания движения змейки.
Вот тут точно начинаем
Сначала нам нужно объявить некоторые переменные, которые мы в дальнейшем будем использовать.
И так, вообще, нужно чуть-чуть пояснить зачем нам нужно в функции возвращения случайного числа, умножать и делить на переменную s, которая хранит в себе ни что иное, как ширину, по совместительству и высоту элементов змейки. На самом деле, это нужно, чтобы не было смещений во время движения, так как у нас ширина элемента — 30, то если мы хотим двигать ее без разрывов, то все координаты должны делиться на 30 без остатка. Именно поэтому я делю на число, округляю, а потом умножаю. Таким образом, число возвращается таким, что его можно разделить без остатка на 30.
Вы могли бы возразить, сказав, что ты мог бы просто холсту сделать ширину и высоту, кратную 30. Но на самом деле, это не лучший вариант. Так как я лично привык использовать всю ширину экрана. И в случае, если ширина = 320, то мне пришлось бы аж целых 20 пикселей забирать у пользователя, что могло бы доставить дискомфорт. Именно поэтому в нашей змейки все координаты объектов делятся на 30, чтобы не было никаких неожиданных моментов. Было бы даже правильнее вынести это как отдельную функцию, так как она достаточно часто используется в коде. Но к этому выводу я пришел поздно. (Но возможно это даже не нужно).
Теперь сделаем так, чтобы картинка имела четкое качество отображения. По ходу статьи, я буду дописывать код, в некоторых местах возвращаться к началу, так что при себе имейте полную картину кода.
Если что, то переменные innerWidth и innerHeight хранятся в глобальном пространстве имен.
Поэтому к ним можно обратиться именно так. Правда, я не знаю правильно ли так делать.
Ну что же, теперь начинаем писать код змейки.
Чтобы было движение, нам нужна анимация, мы будем использовать функцию setInterval, вторым параметром которой будет число 60. Можно чуть больше, 75 на пример, но мне нравится 60. Функция всего на всего каждые 60 мс. рисует змейку «заново». Дальнейшее написание кода — это только этот интервал.
Покажу вообще простую отрисовку нашей змейки, пока что без движения.
Чтобы проверить, что наша змейка не сталкивается сама с собой, нам нужно сделать некоторую проверку для каждого элемента, кроме последнего. Мы будем проверять, не равны ли координаты последнего элемента (головы) змейки любым из… То есть проще говоря: не произошло ли столкновение. Эта строчка кода была единой строкой, но вам сделал ее понятной. Напоминаю, что все это добавляется в функцию интервала.
А теперь, вы наверное заметили, что во время того, как мы изменяем координаты, мы вечно что-то «сохраняем», сначала поделив, а потом округлив и умножив на число s. Это все тот же самый способ выравнивания змейки относительно яблока. Движение в данном случае строгое, простое, поэтому и есть змейка яблоко может строго по определенным правилам, которые задан в самом начале интервала. И если бы координаты головы змейки хоть на 1px сместились бы, то яблоко нельзя было бы съесть. И да, это простой вариант, поэтому все так сильно ограничено.
Ну а нам же осталось что сделать? Правильно, удалить из массива хвост (первый элемент), добавить новый элемент в самый конец и отрисовать всю змейку. Сделаем это, добавив в конец интервала вот такие строчки кода.
В добавок к отрисовке змейки, я добавил код, который делает ощущение, что конец экрана — это его начало. И если змейка выходит за границы, то она потом выходит из начала, на погибая.
Вы можете заменить обнуление координат, на пример, на сбрасывание игры, если у вас все очень жестко. Но мне нравится больше так. Ну а теперь, осталось только по нажатию кнопок изменять направление змейки. Делает за считанные секунды. Нужно лишь написать этот код сразу после setInterval. Примерно так:
Тут мы сделали так, что если змейка двигается вправо, то она не может изменить направление налево. Это логично, на самом деле.
Вот и все, друзья. Моя первая статья, написанная новичком для новичков. Надеюсь, все было понятно и кому-то это пригодилось. Змейку можно усовершенствовать, добавив на пример, счетчик очков, рекорды, дополнительные фишки, но это все уже дополнения, которые вы можете сделать сами. На этом все, всем удачи!
Змейка
Для начала нам нужно создать игровое поле при помощи Canvas. Мы зададим размеры холста, а в стиле пропишем его настройки: цвет фона, тень и т.д.
Сценарий игры разместим в отдельном файле snake.js.
Подготовим холст к игре. Проведём его инициализацию в сценарии. И сразу создадим функцию, которая будет заниматься отрисовкой игры.
Закрасим игровое поле чёрным цветом через функцию clearScreen().
На данном этапе мы получили игровое поле чёрного цвета, на котором будет выводить игровые объекты.

Запустим игровой цикл.
Разобьём игровое поле на условные квадратики, по которым будет двигаться змейка. Размеры квадратов вычисляются динамически. В каждом ряду будет 20 квадратов.
Нарисуем змейку, точнее, пока только голову в центре игрового поля.

Добавим управление змейкой через клавиши-стрелки.
Создадим переменные для направления движения головы змейки и создадим функцию, которая будет задавать направление движения змейки после нажатий клавиш со стрелками. На основе этой информации мы можем вычислить позицию змейки
На данный момент змейка может двигаться в любую сторону, не обращая внимания на границы поля и на себя. В реальной игре такое недопустимо. Змейка не может идти в обратную сторону по себе и выходить за пределы поля (тут могут быть варианты, мы берём стандартный вариант).
Давайте ограничим движение змейки в обратную сторону на себя.
Яблоко
Нарисуем яблоко. Оно представляет собой квадратик поля красного цвета.

Столкновения
Теперь нужно определять моменты столкновения головы змейки с яблоком, сравнивая их позиции.
Тело змейки
После каждого съеденного яблока змейка увеличивается. Нам понадобится массив, который будет содержать части змейки и переменная, которая будет отвечать за размер змейки. После каждого столкновения (змейка съела яблоко) увеличиваем размер змейки.
Нам понадобится класс, который будет содержать информацию о частях змейки.
В функции drawSnake() добавим новый код, который будет рисовать хвост змейки к её голове.
Змейка ожила, она есть яблоки и растёт в размерах.

За каждое яблоко начисляется очко. Будем выводить счёт в углу игрового поля.

Конец игры
Создадим функцию, которая будет отслеживать конец игры. Во-первых, это происходит во время столкновения со стеной, во-вторых, когда змейка кусает себя.
Игра готова. Она ещё требует различных доработок, но в целом мы получили полноценную игру.
Creating a Snake Game in JavaScript
The objective is to create a snake like game with the following requirements and constraints:
- The is a 200×200 pixel game board.
- The player starts at a random safe location on the game board.
- Eating food increases the length of the snake.
- The game will be written using standard javascript web apis.
Drawing on the canvas
Given that we have a root <div> node to work with we will create and attach a child <canvas> element to act as our target container for the game. https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
The game loop
I decided to make two primary functions for this game ready and update .
- ready — serves as a function to initialize the game and set up event listeners
- update — the game loop. Every iteration will update the state of the game and draw the results on the canvas
Drawing the player
Our first iteration will be to draw a representation of the player on the board. To keep things simple we will represent the player as a green 10×10 pixel box.
Since we eventaully would like to be able to move the player on the game board we can create a player variable that will hold the current state of where the player is located and the vector ( dx , dy ) which will tell us which direction the player is traveling each frame.
If we were to play the game at this point we might notice green square fly off the screen to the right. We can slow this down by waiting a given amount of time by using setTimeout before requesting another frame.
Receiving Input
Now we want the player to be able to control the green snake friend.
Collecting Items
We need an goal for the player to achieve. In this type of game it involves collecting or “eating” some food.
First we will start by just drawing something to represent the food.
Next we should detect when the player occupies the same space to eat the food. We can accomplish this by adding some logic to detect when the player’s x and y coordinate are the same.
Keeping score
We also want to keep score and have the tail of the snake’s grow as it eats food.
First we will add another variable score
We will increment this number when we eat the food by 10.
Displaying the score is important for the player to know. We can use the fillText function on the canvas object.
Adding Randomness
Currently we have the same start point for our food and snake. We also have a known location as to where the food will end up. Having the same expected state makes for a boring game. We can add some randomness by leveraging Math.random();
Because of the contraints in our game we should place our objects in a visible location on the board. It should also be a number divisible by 10. Doing a little bit of math, out board is 200×200 pixels, divide by 10 and we get 20. We can find a random integer between 0 and 19 (inclusive) to find a random location on the board.
When the game starts we should choose a safe location for the player and the first food item. We can do this in our ready function.
Then when the player eats the food we should call the random integer function to place the food
Growing the tail
I chose to keep the track of how long to draw the tail by having an array of tail coordinates that I can push and pop off depending on the state.
First we start off with an empty array for the tail
Every frame I push the current player’s coordinates onto the tail array. This should be done before updating the player’s coordinates to make a proper trail.
If the length of the tail array exceeds the player’s size then I remove the last element in the array via shift .
Finally I draw all of the tail elements on the screen.
End State
In this game the end state is when the player runs into a section of its own tail.
First lets make a varible gameover to detect if the game is over.
We will check the value of the gameover flag every update tick. If the value is true then we will draw the game over screen.
Before we start drawing the tail we will check to see if the head of the snake occupies the same space. We will then force a frame update so that we show the game over screen immediatley.
JavaScript Snake tutorial
JavaScript Snake tutorial shows how to create a Snake game in JavaScript. The images and sources are available at the author’s Github JavaScript-Snake-Game repository.
Advertisements Snake game
Snake is an older classic video game which was first created in late 70s. Later it was brought to PCs. In this game the player controls a snake. The objective is to eat as many apples as possible. Each time the snake eats an apple, its body grows. The snake must avoid the walls and its own body. This game is sometimes called Nibbles.
HTML5 Canvas
HTML5 canvas element provides a resolution-dependent bitmap area, which can be used for rendering graphs, game graphics, art, or other visual images on the fly. In simple terms, canvas is a new element in HTML5, which allows you to draw graphics using JavaScript. Canvas brings animations to web pages without the need of plugins like Flash, Silverlight, or Java.
Advertisements JavaScript Snake code example
The size of each of the joints of a snake is 10 px. The snake is controlled with the cursor keys. Initially, the snake has three joints. If the game is finished, the «Game Over» message is displayed in the middle of the canvas.
This is the HTML source. We put the JavaScript source in the snake.js file.
We create a canvas object. It is a rendering area for our game.
This is the JavaScript Snake source.
DOT_SIZE is the size of the apple and the dot of the snake. ALL_DOTS constant defines the maximum number of possible dots on the canvas (900 = 300*300/10*10). MAX_RAND constant is used to calculate a random position for an apple. DELAY constant determines the speed of the game. C_HEIGHT and C_WIDTH constants store the size of the canvas.
These constants store the values of arrow keys. They are used for better readability.
These two arrays store the x and y coordinates of all joints of a snake.
The init function gets the reference to the canvas object and its context. The loadImages , createSnake , and locateApple functions are called to perform specific tasks. The setTimeout starts the animation.
In the loadImages function we load three images for the game.
In the createSnake function we create the snake object. At the start, it has three joints.
If the head collides with the apple, we increase the number of joints of the snake. We call the locateApple method which randomly positions a new apple object.
In the move method we have the key algorithm of the game. In order to understand it, look at how the snake is moving. We control the head of the snake. We can change its direction with the cursor keys. The rest of the joints move one position up the chain. The second joint moves where the first was, the third joint where the second was etc.
The for loop moves the joints of a snake up the chain.
This line moves the head to the left.
In the checkCollision method, we determine if the snake has hit itself or one of the borders.
If the snake hits one of its joints with its head, the game is over.
The game is finished if the snake hits the bottom of the canvas.
The locateApple randomly selects x and y coordinates for the apple object. The apple_x and apple_y are the coordinates of the uppler-left point of the apple image.
The gameCycle function forms a game cycle. Provided that the game has not finished, we perform collision detection, do movement and drawing. The setTimeout function calls recursively the gameCycle function.
If we hit the left cursor key, we set the leftDirection variable to true. This variable is used in the move function to change the coordinates of the snake object. Notice also that when the snake is heading to the right, we cannot immediately turn to the left. Figure: Snake game