You should first read part 1 of the tutorial.

This is the second part of our quest to make a simple Snake game in the browser. In this part, we will code the game loop, rendering and controls for the game.

We left off with an empty game board in part 1, with the game state set up. Now it’s time to start implementing the game logic. You can view the result from part 1 here, right click → View Source to see the code.

The game loop

First thing we’ll do is to code the game loop. The game loop is a generic term for the code that constantly runs in the background inside games. In this way, games are different from most other programs in that they require no input from the user to do things. Think about it, most computer programs do not do anything unless you click a key, move the mouse or tap a button. But games do something even when the user is just looking at the screen. The code that runs constantly in the background, is called the game loop.

To run the game loop, we will introduce a new function, that we call gameLoop. This function will run repeatedly during the duration of the game. It will handle rendering and moving the snake around the board.

    function gameLoop() {
        // This function calls itself, with a timeout of 1000 milliseconds
        setTimeout(gameLoop, 1000);
    }

The setTimeout call will the the function again, recursively, every second so that any code inside will run repeatedly in the background.

What we need to do next, is to iterate over the entire board, and update the DOM (ie. the HTML div elements we created in part 1) with a different CSS class if the corresponding cell contains the snake. We do this by looping over the Y and X axes, and checking if the board has a snake, if so, we set the element’s class to "snake". Else we remove any class set.

function gameLoop() {
    // Loop over the entire board, and update every cell
    for (var y = 0; y < boardHeight; ++y) {
        for (var x = 0; x < boardWidth; ++x) {
            var cell = board[y][x];

            if (cell.snake) {
                cell.element.className = 'snake';
            }
            else {
                cell.element.className = '';
            }
        }
    }

    // This function calls itself, with a timeout of 1000 milliseconds
    setTimeout(gameLoop, 1000);
}

We also need to style the snake class, so it looks like a snake (green). Add the following inside the <style> tag to do that:

#board .snake {
    background-color: green;
}

Now when we refresh the page, the center element on the board should be green. It should look like the example below.

View this in a new tab

Now that the initial snake is visible, we need to make it to move around the board.

Moving the snake

To make the snake move, we need to extend the game loop.

Before we update the board, we should look at what direction the snake is heading. Using the direction, we update the position of the snake (the snakeX and snakeY global variables) accordingly. Then we also need to update the board (the board[y][x].snake variable). When the board is updated, it will automatically update div element with the snake class, because the code that does that will follow immediately after our check.

So what does this look like in code? We have the snakeDirection global variable to define the direction the snake is heading, right now it always has the value "Up", but we also need to check for "Left", "Right", "Down". For each direction, we change the value of either snakeX or snakeY by 1. Changing snakeY will move the snake either up or down, changing snakeX will move the snake left or right.

Remember that snakeX = 0 and snakeY = 0 is the upper left of the board. So to make the snake move right, we increase snakeX. To make it go down, we increase snakeY. And the reverse for left and up. In code, it looks like this:

    // Update position depending on which direction the snake is moving.
    switch (snakeDirection) {
        case 'Up':    snakeY--; break;
        case 'Down':  snakeY++; break;
        case 'Left':  snakeX--; break;
        case 'Right': snakeX++; break;
    }

    // Update the board at the new snake position
    board[snakeY][snakeX].snake = 1;

After updating the snake position, we also update the board at the new position to set the snake as present. This code should be inserted inside gameLoop before the double for-loops.

When you run the code, this should be the result:

View this in a new tab
Most likely this appears as a green line from the center and up. Refresh the page to see the snake slowly move up.

As you can see, the snake moves up the board slowly, since the direction is always "Up", the next step will be to make it possible to change the direction of the snake.

Handling input

Next step is handling input. Input will be done using the keyboard (sorry, mobile readers). To capture this we need to bind the onKeyDown event. We check what key was pressed and update the snakeDirection accordingly.

We create a new function to do this, called enterKey

    function enterKey(event) {
        // Update direction depending on key hit
    }

We also need to add the onkeydown event handler to the body element. Here we already have one event handler to initializes the game, so add another one that handles the key presses:

<body onload="initGame()" onkeydown="enterKey(event)">

Inside the function, we will use the another switch statement to check what key was pressed — in this case we check for all the arrow keys — and update the direction of the snake. This will then automatically get picked up by the game loop and the we will be able to control the snake!

    function enterKey(event) {
        // Update direction depending on key hit
        switch (event.key) {
            case 'ArrowUp': snakeDirection = 'Up'; break;
            case 'ArrowDown': snakeDirection = 'Down'; break;
            case 'ArrowLeft': snakeDirection = 'Left'; break;
            case 'ArrowRight': snakeDirection = 'Right'; break;
            default: return;
        }

        // This prevents the arrow keys from scrolling the window
        event.preventDefault();
    }

The call to preventDefault is necessary, so that pressing the arrow keys do not perform their default action (that scrolls the page up/down/left/right).

Try the link below to try it out, note that if you collide with the walls, the game will crash and you can’t play any more. If that happens, refresh to restart the game.

Try out controlling the snake in a new tab

The walls

Let’s fix that bug with the walls that makes the game crash, and we’ll have a contrallable snake game!

To check for the walls, we simply need to add an if statement inside the game loop after updating the snake position. This will check if the new position is outside the board by comparing against 0 (snake can’t be on a negative position) and boardWidth/boardHeight.

If we detect that the snake is outside the board, we restart the game by calling startGame, this will reset both the position, length and direction of the snake.

// Check for walls, and restart if we collide with any
if (snakeX < 0 || snakeY < 0 || snakeX >= boardWidth || snakeY >= boardHeight) {
    startGame()
}

// Update the board at the new snake position
board[snakeY][snakeX].snake = 1;

This version of the game is now playable, without crashing. Try it out by clicking below and steering using the arrow keys:

View this in a new tab
Click the frame to fcosu. You can now steer the snake around using the arrow keys. The game will reset if you collide with the walls.

Of course, the snake still does not ‘disappear’ from the board as we move around. So one thing left on the todo list.

Making the snake disappear

To remove the snake, we need to keep track of the tail.

We can use the board to do this, rather than trying to keep track of where different snake segments are on the board. We store how many more iterations of the game loops the Snake should be visible in every cell.

We then simply decrease this as we iterate over the board.

We need to make a few small changes to accomodate this. First, we need to replace the assignments board[snakeY][snakeX].snake = 1; to assign the snakeLength instead. So find the two places with that code and replace with: board[snakeY][snakeX].snake = snakeLength;

Now, instead of just storing if there is a snake on the board, we store the remaining length of the snake on that cell.

Next part is changing the loop over the entire board. Instead of checking for cell.snake, we want to check if the value is greater than zero. If so, there is a snake on that cell, so we still set the snake className, but we also decrease the value of cell.snake by 1.

The update code inside the game loop will look like this:

var cell = board[y][x];

if (cell.snake > 0) {
    cell.element.className = 'snake';
    cell.snake -= 1;
}
else {
    cell.element.className = '';
}

Try out the result below, I also decreased the setTimeout call to 1000 / snakeLength so the game is faster:

View this in a new tab
You can now steer the snake around using the arrow keys, and it disappears as you move.

Now we have an almost playable version of the game. All that remains is collecting those delicious apples (and not being able to move through yourself). We’ll write this code in part 3.