Creating A Snake Game Tutorial With HTML5

This tutorial shows you how to create a Snake Game using JavaScript and HTML5. A Snake Game is an action game that consists of a snake that is constantly moving inside a level. The player controls the direction of the snake, but the snake always keeps moving. When the snake eats something, it grows in size. If the snake touches the boundaries of the level, or if it tries to eat its own tail, the game is over. The goal of the game is to keep the snake alive as long as possible.

We are creating a Snake Game step by step using JavaScript and the HTML5 Canvas element. We implement the game logic and add some sprite graphics. At the end of this tutorial, you can play the finished game.

Creating A Snake Game Tutorial With HTML5

Creating A Snake Game Tutorial With HTML5

Click here to go directly to the end of this article and play the game.

Defining The Level

The snake in our game needs a place to live, a level. We define a two-dimensional grid on which the snake is allowed to move. The grid has certain dimensions defined by columns and rows properties. Each cell of the grid is occupied by a certain tile. Two basic tiles are the empty tile and the wall tile. The snake can move on empty tiles without any problem, but when it tries to move into a wall tile, the game is over.

We define a Level class below. The level has a two-dimensional grid, an array, of a certain size. The tiles, the cells of the grid, have a certain tilewidth and tileheight. We initialize the array of tiles to 0, which means that all of the tiles are the empty tile.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    // Level properties
    var Level = function (columns, rows, tilewidth, tileheight) {
        this.columns = columns;
        this.rows = rows;
        this.tilewidth = tilewidth;
        this.tileheight = tileheight;
 
        // Initialize tiles array
        this.tiles = [];
        for (var i=0; i<this.columns; i++) {
            this.tiles[i] = [];
            for (var j=0; j<this.rows; j++) {
                this.tiles[i][j] = 0;
            }
        }
    };

We need to generate an actual level by adding the generate() function to the Level class. The level we generate below will simply have an open area of empty tiles, while the borders of the level are wall tiles. A wall tile is defined by the number 1, while the empty spaces are defined by the number 0. To create a more interesting level, you could hand-craft the level array and load it when the game starts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    // Generate a default level with walls
    Level.prototype.generate = function() {
        for (var i=0; i<this.columns; i++) {
            for (var j=0; j<this.rows; j++) {
                if (i == 0 || i == this.columns-1 ||
                    j == 0 || j == this.rows-1) {
                    // Add walls at the edges of the level
                    this.tiles[i][j] = 1;
                } else {
                    // Add empty space
                    this.tiles[i][j] = 0;
                }
            }
        }
    };

Creating The Snake

The snake needs to be able to move in a certain direction. When the snake eats something, the snake needs to grow. We create an init function that initializes the properties of the snake. The body of the snake is represented by an array of positions. Check out the comments in the code below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    // Snake
    var Snake = function() {
        this.init(0, 0, 1, 10, 1);
    }
 
    // Direction table: Up, Right, Down, Left
    Snake.prototype.directions = [[0, -1], [1, 0], [0, 1], [-1, 0]];
 
    // Initialize the snake at a location
    Snake.prototype.init = function(x, y, direction, speed, numsegments) {
        this.x = x;
        this.y = y;
        this.direction = direction; // Up, Right, Down, Left
        this.speed = speed;         // Movement speed in blocks per second
        this.movedelay = 0;
 
        // Reset the segments and add new ones
        this.segments = [];
        this.growsegments = 0;
        for (var i=0; i<numsegments; i++) {
            this.segments.push({x:this.x - i*this.directions[direction][0],
                                y:this.y - i*this.directions[direction][1]});
        }
    }
 
    // Increase the segment count
    Snake.prototype.grow = function() {
        this.growsegments++;
    };
 
    // Check we are allowed to move
    Snake.prototype.tryMove = function(dt) {
        this.movedelay += dt;
        var maxmovedelay = 1 / this.speed;
        if (this.movedelay > maxmovedelay) {
            return true;
        }
        return false;
    };
 
    // Get the position of the next move
    Snake.prototype.nextMove = function() {
        var nextx = this.x + this.directions[this.direction][0];
        var nexty = this.y + this.directions[this.direction][1];
        return {x:nextx, y:nexty};
    }

Moving the snake is the most interesting part. When the snake makes a move, all of the segments of the snake body need to be updated. The basic concept is that every tail segment is moved to the position of the previous tail segment. Finally, the head is moved to the position of the next move. The order of the segments is important here: segments[0] represents the head of the snake while the next segments represent the body and the tail.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    // Move the snake in the direction
    Snake.prototype.move = function() {
        // Get the next move and modify the position
        var nextmove = this.nextMove();
        this.x = nextmove.x;
        this.y = nextmove.y;
 
        // Get the position of the last segment
        var lastseg = this.segments[this.segments.length-1];
        var growx = lastseg.x;
        var growy = lastseg.y;
 
        // Move segments to the position of the previous segment
        for (var i=this.segments.length-1; i>=1; i--) {
            this.segments[i].x = this.segments[i-1].x;
            this.segments[i].y = this.segments[i-1].y;
        }
 
        // Grow a segment if needed
        if (this.growsegments > 0) {
            this.segments.push({x:growx, y:growy});
            this.growsegments--;
        }
 
        // Move the first segment
        this.segments[0].x = this.x;
        this.segments[0].y = this.y;
 
        // Reset movedelay
        this.movedelay = 0;
    }

Drawing The Snake Using A Texture Atlas

We draw the snake by first creating and loading a sprite sheet, also called a texture atlas. The sprite sheet is a single image that consists of multiple smaller images, that represent parts of the snake that get composited later on. In my article How To Load And Draw Images With HTML5 Canvas you can see how you can load the image. The sprite sheet that you can see below shows all of the sprites that we will be using. You can see The different snake parts in all of the different directions. Finally, you can see the sprite for the apple. All of these sprite images have a resolution of 64 by 64 pixels and have a transparent background. When we draw them to the screen, we will scale them to fit our game.

Snake Game Graphics Sprite Sheet

Snake Game Graphics Sprite Sheet

To draw the snake, we have to connect the right sprites to each other. We want to draw a head, than parts of the body and finally a tail. They have to be drawn in the right direction as well. What we have to do, is create a big conditional statement and check all of the cases. The code below calculates the snake parts that need to be drawn to the screen. The tx and ty variables that are determined, are column and row indices in the sprite sheet. We use drawImage to select a part of the sprite sheet and draw it to the correct position on the screen. The result of the drawSnake function is a seamless snake on the screen that is composited of multiple sub-images from the sprite sheet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    // Draw the snake
    function drawSnake() {
        // Loop over every snake segment
        for (var i=0; i<snake.segments.length; i++) {
            var segment = snake.segments[i];
            var segx = segment.x;
            var segy = segment.y;
            var tilex = segx*level.tilewidth;
            var tiley = segy*level.tileheight;
 
            // Sprite column and row that gets calculated
            var tx = 0;
            var ty = 0;
 
            if (i == 0) {
                // Head; Determine the correct image
                var nseg = snake.segments[i+1]; // Next segment
                if (segy < nseg.y) {
                    // Up
                    tx = 3; ty = 0;
                } else if (segx > nseg.x) {
                    // Right
                    tx = 4; ty = 0;
                } else if (segy > nseg.y) {
                    // Down
                    tx = 4; ty = 1;
                } else if (segx < nseg.x) {
                    // Left
                    tx = 3; ty = 1;
                }
            } else if (i == snake.segments.length-1) {
                // Tail; Determine the correct image
                var pseg = snake.segments[i-1]; // Prev segment
                if (pseg.y < segy) {
                    // Up
                    tx = 3; ty = 2;
                } else if (pseg.x > segx) {
                    // Right
                    tx = 4; ty = 2;
                } else if (pseg.y > segy) {
                    // Down
                    tx = 4; ty = 3;
                } else if (pseg.x < segx) {
                    // Left
                    tx = 3; ty = 3;
                }
            } else {
                // Body; Determine the correct image
                var pseg = snake.segments[i-1]; // Previous segment
                var nseg = snake.segments[i+1]; // Next segment
                if (pseg.x < segx && nseg.x > segx || nseg.x < segx && pseg.x > segx) {
                    // Horizontal Left-Right
                    tx = 1; ty = 0;
                } else if (pseg.x < segx && nseg.y > segy || nseg.x < segx && pseg.y > segy) {
                    // Angle Left-Down
                    tx = 2; ty = 0;
                } else if (pseg.y < segy && nseg.y > segy || nseg.y < segy && pseg.y > segy) {
                    // Vertical Up-Down
                    tx = 2; ty = 1;
                } else if (pseg.y < segy && nseg.x < segx || nseg.y < segy && pseg.x < segx) {
                    // Angle Top-Left
                    tx = 2; ty = 2;
                } else if (pseg.x > segx && nseg.y < segy || nseg.x > segx && pseg.y < segy) {
                    // Angle Right-Up
                    tx = 0; ty = 1;
                } else if (pseg.y > segy && nseg.x > segx || nseg.y > segy && pseg.x > segx) {
                    // Angle Down-Right
                    tx = 0; ty = 0;
                }
            }
 
            // Draw the image of the snake part
            context.drawImage(tileimage, tx*64, ty*64, 64, 64, tilex, tiley,
                              level.tilewidth, level.tileheight);
        }
    }

Snake Game Logic

Creating the basic game loop is already explained in my tutorial How To Make An HTML5 Canvas Game. Our game logic builds upon this basic game structure. Let’s start by creating the snake and level objects and starting a new game. We initialize the snake at position 10, 10, facing to the right, at 10 blocks per second with a length of 4 body segments. The level is generated and an apple is added to the level. The apple is a food item that the snake can eat to grow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    // Create objects
    var snake = new Snake();
    var level = new Level(20, 15, 32, 32);
 
    // Variables
    var score = 0;              // Score
    var gameover = true;        // Game is over
    var gameovertime = 1;       // How long we have been game over
    var gameoverdelay = 0.5;    // Waiting time after game over
 
    // (...)
 
    function newGame() {
        // Initialize the snake
        snake.init(10, 10, 1, 10, 4);
 
        // Generate the default level
        level.generate();
 
        // Add an apple
        addApple();
 
        // Initialize the score
        score = 0;
 
        // Initialize variables
        gameover = false;
    }

We have to create the addApple function. We need to add an apple to the level, but we only want to add the apple at an empty position. We don’t want to add the apple at a position where the body of the snake is or where there is a wall in the level. The code below finds a random valid position. If it can’t find a position right away, it keeps trying. For the code to work efficiently, there should be enough valid empty spaces left.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    // Add an apple to the level at an empty position
    function addApple() {
        // Loop until we have a valid apple
        var valid = false;
        while (!valid) {
            // Get a random position
            var ax = randRange(0, level.columns-1);
            var ay = randRange(0, level.rows-1);
 
            // Make sure the snake doesn't overlap the new apple
            var overlap = false;
            for (var i=0; i<snake.segments.length; i++) {
                // Get the position of the current snake segment
                var sx = snake.segments[i].x;
                var sy = snake.segments[i].y;
 
                // Check overlap
                if (ax == sx && ay == sy) {
                    overlap = true;
                    break;
                }
            }
 
            // Tile must be empty
            if (!overlap && level.tiles[ax][ay] == 0) {
                // Add an apple at the tile position
                level.tiles[ax][ay] = 2;
                valid = true;
            }
        }
    }

Moving the snake inside the level and checking collisions is done in the code below. When the snake collides with an apple, the snake grows and a new apple is added to the level. Check out the comments below for more information.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    function updateGame(dt) {
        // Move the snake
        if (snake.tryMove(dt)) {
            // Check snake collisions
 
            // Get the coordinates of the next move
            var nextmove = snake.nextMove();
            var nx = nextmove.x;
            var ny = nextmove.y;
 
            if (nx >= 0 && nx < level.columns && ny >= 0 && ny < level.rows) {
                if (level.tiles[nx][ny] == 1) {
                    // Collision with a wall
                    gameover = true;
                }
 
                // Collisions with the snake itself
                for (var i=0; i<snake.segments.length; i++) {
                    var sx = snake.segments[i].x;
                    var sy = snake.segments[i].y;
 
                    if (nx == sx && ny == sy) {
                        // Found a snake part
                        gameover = true;
                        break;
                    }
                }
 
                if (!gameover) {
                    // The snake is allowed to move
 
                    // Move the snake
                    snake.move();
 
                    // Check collision with an apple
                    if (level.tiles[nx][ny] == 2) {
                        // Remove the apple
                        level.tiles[nx][ny] = 0;
 
                        // Add a new apple
                        addApple();
 
                        // Grow the snake
                        snake.grow();
 
                        // Add a point to the score
                        score++;
                    }
                }
            } else {
                // Out of bounds
                gameover = true;
            }
 
            if (gameover) {
                gameovertime = 0;
            }
        }
    }

Snake Game Example

Here you can play the finished Snake Game that was created in this tutorial. The full source code is available on GitHub, licensed under GPLv3. Move the snake by pressing the arrow keys, wasd keys or by using the mouse button. As a bonus debug feature, you can grow the snake by pressing the spacebar key.