Snake game in JavaScript (110 lines of code)

In this article I will build a simple snake game in JavaScript from scratch. The player can control the snake by keyboard. Whenever the snake collides with food(a red circle), it gets 1 point and food moves to  a random position. 

You can view the finished application here. The requirements for this application are basic knowledge of html, JavaScript and some prior knowledge of html canvas.
The file structure of application looks something like this:
  1. index.html : contains the html code
  2. script.js : contains main logic of the application 
First of all let us start with the html file. We will need one canvas for drawing the snake, food and score. The html code is shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<html>
<head>
    <title> Snake </title>
</head>
<body>

    <!-- Canvas to draw snake, food and score -->
    <canvas id="canvas" height="500px" width="500px" style="border-style:solid; margin:0 auto;"></canvas>

    <!-- JavaScript file -->
    <script type="text/javascript" src="script.js"></script>
</body>
</html>


The canvas is given the height and width of ‘500px’ and border style ‘solid’ so we can view it clearly. It is given the id ‘canvas’ to reference it from script.js file That’s all for HTML. Open it in your browser and you will see an empty white box. That's our canvas.


Now let’s move on to scripting part. Initially we get reference to the canvas so that we can draw on it.


1
2
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),

In order to use canvas we get reference to the context of the canvas. The parameter ‘2d’ is passed, which specifies that we are working in 2D. Next, we declare some variables which will hold our snake, food, score, direction, speed etc.  Information of each variables are in the comments.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
var canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d'),
    length = 5, // length of the snake
    size = 5, // size of each circle
    snake = [], // array containing coordinate of each snake's circle
    direction = 'right', // direction of snake
    speed = size * 2, // speed with which snake moves
    startPos = {
        x: 100,
        y: 100
    }, // start position of snake
    timer, // timer which runs throughout the game
    food = {
        x: 12,
        y: 67,
        color: 'red'
    }, //position and color of food
    score = 0; // your game score

Now we have our variables ready, we initialize our snake. Initially snake array is empty. It’s supposed to contain the coordinates of the snake. Let’s do that now.


1
2
3
4
5
6
7
8
function init() {
    for (var i = 0; i < length; i++) {
        snake.push({
            x: startPos.x - (size * 2) * i,
            y: startPos.y
        });
    }
    timer = setInterval(draw, 100);

The for loop goes from 0 to length. Each time it adds a circle to the snake array so that the circle lies exactly to the left of the previous circle. For that, the x-coordinate of the circle is decreased each time by (size*2) and the y-coordinate is kept constant. After the snake is made ready we call the setInterval function which takes two parameter: a function to call each interval and a integer number which is the interval in milliseconds. In our case it’s 100. i.e the function draw is called once in every 100 milliseconds.



The ‘draw’ function is the main part of our program where the magic happens. In every 100 milliseconds the darw function is invoked which clears the canvas, updates the position of snake based on it’s direction, and redraws it. This happens so quickly that we don’t even notice. At the same time it checks collision of snake and food and updates the score too.


 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
function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    //draw snake
    for (var i = snake.length - 1; i >= 0; i--) {
        if (i != 0) {
            // if its not snake's head
            snake[i].x = snake[i - 1].x;
            snake[i].y = snake[i - 1].y;
        } else {
            // if its snake's head
            switch (direction) {
            case 'up':
                snake[0].y -= speed;
                break;
            case 'down':
                snake[0].y += speed;
                break;
            case 'right':
                snake[0].x += speed;
                break;
            case 'left':
                snake[0].x -= speed;
                break;
            default:
                break;
            }
        }

        ctx.beginPath();
        ctx.arc(snake[i].x, snake[i].y, size, 0, 2 * Math.PI);
        ctx.stroke();
    }

    //draw food
    ctx.fillStyle = food.color;
    ctx.beginPath();
    ctx.arc(food.x, food.y, size, 0, 2 * Math.PI);
    ctx.fill();

    // check collissin between food and snake
    if (checkCollission(snake[0], food)) {
        score++;
        food.x = random(0, canvas.width);
        food.y = random(0, canvas.height);
    }

    //draw score
    ctx.fillText('Score : ' + score, 10, 20);
}


The ctx.clearRect() method clears the canvas before redrawing. The successive for loop loops over the circles of the snake from its tail to head (from last index to first index).  If the current index is not the head, it sets it to its preceding circle. i.e. the last circle takes the position of second last circle, second last takes the position of third last and so on… so that the snake seems as if its moving.
If the current index is head, first it checks the direction(in switch case) and increases the coordinate of circle according to the direction.

Right : increase x-coordinate
Left : decrease x-coordinate
Up : decrease y-coordinate
Down : increase y-coordinate


After adding and subtracting the coordinate it should be drawn on the canvas so that the player can see the moving snake. The code below draws each circle of the snake with its updated coordinates.

1
2
3
ctx.beginPath();
ctx.arc(snake[i].x, snake[i].y, size, 0, 2 * Math.PI);
ctx.stroke();

Now our snake is drawn on the canvas. But it’s not the only thing to draw, We still need to draw the food and score. Drawing the food is similar to drawing the circles of the snake. In case of food we fill it with red color. Next  issue is checking the collision. The function checkCollission() check for collision and returns a Boolean value. It takes two circles as its parameter, in our case it’s snake’s head and the food.


1
2
3
4
5
function checkCollission(cir1, cir2) {
    var d = Math.sqrt(Math.pow(cir2.x - cir1.x, 2) + Math.pow(cir2.y - cir1.y, 2));
    var r = size * 2;
    return (d < r);
}


The logic for above function is quite simple. It what we studied in our school. First we take the distance between the central points of two circles and compare it with the sum of their radii. If it is greater : no collision else: they collided. The illustration will clear the concept.



If checkCollission() returns true, first the score is increased and the food is placed on any random position between 0 to width/height of the canvas. The random function takes two parameter min, max and  gives a random number between them.


1
2
3
function random(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

Now we have come to the end. The last piece of the puzzle is the keydown event handler. We need to control the direction of the snake according to the keyboard button pressed.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
document.onkeydown = function (e) {
    switch (e.keyCode) {
    case 38:
        direction = 'up';
        break;
    case 40:
        direction = 'down';
        break;
    case 39:
        direction = 'right';
        break;
    case 37:
        direction = 'left';
        break;
    default:
        break;
    }
}


The function is invoked whenever a key is pressed down. Then we check if the key is right, left, up or down arrow and assign the respective direction. 37, 38, 39 and 40 are the keycode (ASCII value) for left, up, right and down  arrows. Now save the file and open it in your browser.

The code doesn't work yet. It’s because, for the code to work the ‘timer’ should start, which we haven’t done yet. As you have noticed, the timer is set in init() function. So call the init function at last line of your code, save it and refresh the browser. You can see a moving snake which you can control with our keyboard arrow keys.

You can take the reference of the final code here  if it's not working . If you get stuck anywhere please do mention in the comments. I will be happy to answer.



Happy Coding (^_^)

0 comments:

Post a Comment