You should first read part 1 and part 2 of the tutorial.

It’s time to complete our small snake game (long timeout between posts due to work). Last time we made the snake move on the board and added keyboard controls. Now all that remains is picking up the apples and collision detection with the tail.

Placing apples

First step is placing the apples on the board.

This is something we will do many times, so we introduce a new function placeApple that handles this.

Apples should be assigned to a random position, so we pick a random cell on the board, using the Math.random and Math.floor functions in combination to pick an integer (non-decimal) number for the X and Y positions. We then update the board at this position with the property apple set to the value 1.

function placeApple() {
    // A random coordinate for the apple
    var appleX = Math.floor(Math.random() * boardWidth);
    var appleY = Math.floor(Math.random() * boardHeight);

    board[appleY][appleX].apple = 1;
}

Ta place an apple on the board when the game starts, update the startGame function. Before the final } brace, insert a call to the placeApple() function.

We also need to update the loop in this function to clear all apples on the board. The end of the startGame function will thus look like this, with a call to the new function and an updated loop:

// Clear board
for (var y = 0; y < boardHeight; ++y) {
    for (var x = 0; x < boardWidth; ++x) {
        board[y][x].snake = 0;
        board[y][x].apple = 0;
    }
}

// Set the center of the board to contain a snake
board[snakeY][snakeX].snake = snakeLength;

// Place the first apple on the board.
placeApple();

We also need to check in the render loop for the apple value (the same place we check the board for the snake value) and set a different CSS class if there is an apple on that spot. In other words, we update the gameLoop function with the following else if statement.

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

And finally, add a matching CSS class, to style the apple red on the board:

#board .apple {
    background-color: red;
}

Once these changes have been made, the red apple should appear on the board. You can also look at the source of the below sample.

View this in a new tab
There should now be a red apple on the board, but we can't collect it.

The final piece of the puzzle, will be to pick up the apples and tail collisions.

Collecting apples

To check if the snake collided with an apple, we will update the game loop with another if statement. Add this after checking if we collided with any walls (as before):

// Collect apples
if (board[snakeY][snakeX].apple === 1) {
    snakeLength++;
    board[snakeY][snakeX].apple = 0;
    placeApple()
}

We check if an apple is present on the cell we are moving to, if that is the case we increase the length of the snake, remove the apple and place a new apple on the board. This makes the game playable:

View this in a new tab
You can now collect apples, and make the snake grow.

Collisions with the tail

If you play the game above long enough, you’ll notice you don’t die if you go through your own tail. This is the final thing we need to add to our game.

This requires a very similar check to the one we had for apples, but we instead check if the snake property is set. If it is, we just restart the game. The following if statement goes before the one we added before to pick up apples:

// Tail collision
if (board[snakeY][snakeX].snake > 0) {
    startGame();
}

Mission success

And there you have it, a complete snake game made entirely in simple HTML, JavaScript and CSS. Try it out below:

View this in a new tab
The final game now works!

After revelling in your success, we can note there are still bugs to be fixed:

  • Apples can be placed ontop of the snake, making them invisible! In placeApple we should prevent this by always finding a free position on the board.
  • You can walk backwards and collide with yourself. We should check in enterKey that we don’t reverse the direction of the snake.

After fixing the bugs, try to extend the game. Here are some ideas on what can be done:

  • Replace the snake and Apple with cooler graphics, try using background-image.
  • Golden apples that give three times the length extension, but disappear if you don’t pick it up fast enough.
  • A score display to show the user how many apples they picked up.
  • Continuing on score, a high score table. And maybe store it in localStorage so it persists?
  • Only start the game after the user gives some input after dying.
  • Mobile support is atrocious, try to make it playable on an iPhone!
  • Maybe don’t using 1000 / snakeLength for timeout, as the game quickly becomes impossible due to the speed. Try some other formula that is more fair.

Enjoy, and if you make any improvements, please let me know!