Snake game using HTML5 Canvas and KineticJS

Snake game. In the beginning, before start making a new game, you need to prepare the concept (logic) of the game. You need to have a clear idea about it. You need to develop a high level of understanding, without going into the fine details. In the first phase, we find the answer "what" we want to create, and we reserve the answers to "how to do" for the upcoming stages. Let me illustrate this method with an example. Let’s develop the popular game "Snake". This game was popular long ago, even on consoles and old cell phones with text user interfaces.

Concept of the game

Initially a small sized snake appears on the screen which keeps running for the rest of the game like endless runner. Player can change the direction only. Speed of the snake increases with time. Length of the snake also increases after eating randomly appearing food. Increasing length and speed of the snake adds difficulty to the game over time.

We can use Storyboard technique to graphically represent our idea.

According to Wikipedia: "Storyboards are graphic organizers in the form of illustrations or images displayed in sequence for the purpose of pre-visualizing a motion picture, animation, motion graphic or interactive media sequence."

Here is our Storyboard:

For your convenience I am adding the description of each Storyboard screen:

Screen 3: Snake can re-enter the playing area from the opposite edge after leaving it from an edge.

Screen 4: Snake dies after biting itself :(

Here, UML Statechart diagram may also help to understand different "states" of the snake during a game.

According to Wikipedia: "A state diagram is a type of diagram used in computer science and related fields to describe the behavior of systems. State diagrams require that the system described is composed of a finite number of states; sometimes, this is indeed the case, while at other times this is a reasonable abstraction. Many forms of state diagrams exist, which differ slightly and have different semantics."

Here is our Statechart diagram:

In the diagram, edges represent the actions and the ovals represent the states a snake is in after the specific action. Game starts with an idle snake. You can move snake in all four directions using arrow keys. While moving in any direction when a key is pressed, the snake goes to "Deciding" state to determine which key was pressed and then again goes to the respective direction. Snake eats food when encountered while moving. There is also a "dead" state if snake hits itself while moving.

You may also want to add more state diagrams for prominent objects to clarify. There are other UML diagrams which may help you describe your game project. These diagrams are not only helpful for yourself but also helps if you are working in a team making team communication easy and unambiguous.

Structure of the game

The play area in virtually divided into a 200?200-pixel grid having 10?10-pixel cells. The initGrid() function prepares the grid. As you can guess from the code that the snake’s width and height is also 10?10 pixels. So as a programming trick, I used the height and width of the snake to represent the dimensions of a single cell.

You are right if you are thinking about the usage of this virtual grid. In fact, during the initialization of the game structure, this grid helps us to identify the places (cells…to be precise) where we can put the snake and food randomly. A random number generator function randomCell(cells) gives us a random number which we use as an index to the grid array and get the coordinates stored against that specific index. Here is the random number generator function…

Kinetic.Rect constructor constructs the initial single rectangle to represent the snake. Later, when the snake would grow after eating the food, we would be adding more rectangles. Each rectangle is assigned a number to represent its actual position in the snake array. As there is only one rectangle at the moment, we assign it position 1.

snakePart.position = snakeParts;

snakeParts is a counter which keeps counting the number of parts in the snake array. You might be wondering that we have not created any array of snakePart objects but we are talking about array? In fact if you keep the value of name: property same for all the snakePart objects, KineticJS would return all those objects as an array if you ask like this…

var snakePartsArray = stage.get('.snake');

You will see the usage of this feature in action later in the code.

snakePart.position shows how you can add custom properties to Kinetic.Rect object dynamically, or to any other object.

Why we need the position when KineticJS can return indexed array? Please don’t bother yourself with this question at the moment, you will find the answer if you keep reading. :)

Two more identifications are required to make the job more easy to manage the snake actions and movements, snake head and tail. There is only one snake-part (rectangle) to begin with therefore both head and tail pointers point to the same rectangle.

container property of Stage needs to know the id of the div where we want to show our HTML5 Canvas.

Initial screen of the game looks like this once our structure is complete…

After setting up the environment/structure let’s deal with the user stories / use cases one by one.

The main game loop executing after a set interval

var gameInterval = self.setInterval(function(){gameLoop()},70);

setInterval is a Javascript function which makes a function called asynchronously that is passed as an argument, after the set intervals. In our case gameLoop() is the function which drives the whole game. Have a look at it…

Well, the behaviour of gameLoop() is pretty obvious. It moves the snake according to the arrow key pressed. And if snake hits himself then display a Game Over message to the player and also stop the asynchronous calls by calling a Javascript clearInterval() method.

To capture the arrow keys I have used the jQuery’s keydown event handler to respond to the keys pressed. It sets a variable ‘where’ that is eventually used by gameLoop() to pass the code to actual move() function.

As you might have guessed already that move() function is actual brain of this game and Kinetic.Animation handles the actual movement of the objects. All you have to do is to set new locations for your desired objects and then call start() method. To prevent the animation from running infinitely call stop() method immediately after start(). Try removing the stop() method and see what happens yourself.

Move the snake

Snake is divided into small 10?10-pixels squares. The square on the front is head and the back most square is tail. The technique to move the snake is simple. We pick the tail and put it before the head except when there is only one snake part. The position number assigned to each part of the snake is out of sequence now. Head and tail pointers are pointing towards wrong parts. We need to reposition the pointers and position numbers. It is done by calling rePosition(). It works as shown in the diagram below…

Grow the snake when it eats food

Snake eats food when snake’s head is on the food. Once this condition is met, food is relocated to some other cell of the grid. The decision is made inside snakeEatsFood() function. The algorithm used is commonly known as Bounding Box Collision Detection for 2D objects.

To grow the snake, head moves one step ahead by leaving an empty cell behind. A new rectangle is created at that empty cell to give the impression of the growth of the snake.

resetPositions() is almost identical to rePosition(), see the detils under "Move the snake" heading.

Assign the food a new location

Once the snake eats the food, the food is assigned a new location on the grid. relocateFood() performs this function. It prepares a new grid skipping all the positions occupied by the snake. After creating a new grid array, random number generator function generates a number which is used as an index to the grid and eventually we get the coordinates where we can place the food without overlapping the snake.

Re-enter the snake from the opposite edge when it meets and passes an edge

This is really simple. We let snake finish its move, then we check if the head is out of boundary. If it is, we assign it new coordinates to make it appear from the opposite end. The code given below works when snake is moving down, for example…

if(snakeHead.getY() == stageHeight)
snakeHead.setY(0);

End the game when snake hits himself or it occupies the whole ground

After each move, checkGameStatus() is called by gameLoop() to check if snake has hit himself or not. Logic is fairly simple. The same Bounding Box Collision Detection method for 2D objects is used here. If coordinates of head matches the coordinates of any other part of the snake, the snake is dead – GAME END!

Hello there! My name is Andrew and I have been doing web development for years. Frankly, not only web-development, and system-development too. This site is the place where I get to teach and share my experience for the web. Read more