How to Build a Snake Game Using Javascript?

By Prajwal Haniya

Techletter #41 | August 12, 2023

Building a snake game is definitely not an easy thing. If you build it, will definitely understand a lot of JavaScript concepts. So, in this techletter, I try to explain how you can build a simple snake game using javascript.

Overview

Before moving ahead with coding, we need to know what things are required to build a snake game.

You can improve a lot on top of it. But, the core logic remains the same.

Let us first start writing the JavaScript code with all the logic

const board = document.getElementById('board'); // from html file

let inputCoordinates = { x: 0, y: 0 };
let snakesSpeed = 10;
let renderTime = 0;
let snake = [{ x: 13, y: 15}];
let lastRenderTime = 0;
const boardSize = 18;

let food = { x: 4, y:4 };

In the above code, you can see all the variables that I have declared. Here, I am using boardSize as const because it won’t change at any time.

At first, the food is placed at (4, 4) position on the board. The x and y axis is not the same as in the coordinate geometry. It is slightly different.

Here, the X-axis goes from left to right, and the Y-axis goes from top to bottom. The origin point (0, 0) is at the top left corner of the web page.

Now let’s write a function to generate food randomly at a place on the board

const generateFood = () => {
    const x = Math.floor(Math.random() * boardSize) + 1;
    const y = Math.floor(Math.random() * boardSize) + 1;
    return { x, y };
}

So the above function returns the position of the food.

Now we will write a function that helps the snake to move. It’s the main function that calls other functions to perform their actions.

const renderView = (viewTime) => {
    window.requestAnimationFrame(renderView);
    if ((viewTime - lastRenderTime) / 1000 < 1 / snakesSpeed) {
        return;
    }
    lastRenderTime = viewTime;
    gameLoop();
}

window.requestAnimationFrame(renderView);: This line requests the browser to call the renderView function on the next available frame repaint. This creates a loop that continually renders the view as the browser refreshes the screen.

(viewTime - lastRenderTime) / 1000 < 1 / snakesSpeed: This condition checks if the time elapsed since the last render is less than the time interval required for the snake to move.

Let’s check for collision

const  checkCollision = (positions) => {
    for (let i = 1; i < snake.length; i++) {
        if (positions[i].x === positions[0].x && positions[i].y === positions[0].y) {
            return true;
        }

        if (positions[i].x > 18 || positions[0].x <=0 || positions[i].y > 18 || positions[0].y <= 0) {
            return true;
        }

        return false;
    }
}

If the snake collides with itself or with the boundaries the above function returns as true. That means the collision occurred. Else the function returns false.

Now, let’s write the gameLoop function

const gameLoop = () => {
    // checks collision 
    if (checkCollision(snake)) {
        inputCoordinates = { x: 0, y: 0 };
        alert('Game over');
        snake = [{ x: 13, y:15 }];
    }
    
    // if snake eats food then increase the size of snake & call generateFood() 
    if (snake[0].y === food.y && snake[0].x === food.x) {
        snake.unshift({ x: snake[0].x + inputCoordinates.x, y: snake[0].y + inputCoordinates.y });
        food = generateFood();
    }
    
    for (let i = snake.length - 2; i >= 0; i-- ) {
        snake[i+1] = { ...snake[i]};
    }
    
    snake[0].x += inputCoordinates.x;
    snake[0].y += inputCoordinates.y;
    
    // everytime the game loop function is called the board innerHTML is set to ""
    board.innerHTML = "";

    snake.forEach((e,i) => {
        snakeElement = document.createElement('div');
        snakeElement.style.gridRowStart = e.y;
        snakeElement.style.gridColumnStart = e.x;
        snakeElement.classList.add('snake');
        board.appendChild(snakeElement);
    });

    foodElement = document.createElement('div');
    foodElement.style.gridRowStart = food.y;
    foodElement.style.gridColumnStart = food.x;
    foodElement.classList.add('food');
    board.appendChild(foodElement);
}

You need to add event listeners in order to know in which direction the snake needs to move and increase or decrease the coordinates accordingly.

// initial call
window.requestAnimationFrame(renderView);

// event listener
window.addEventListener('keydown', (e) => {
    inputCoordinates = { x: 0, y: 1 };
    switch(e.key) {
        case 'ArrowUp':
            inputCoordinates.x = 0;
            inputCoordinates.y = -1;
            break;
        case 'ArrowDown':
            inputCoordinates.x = 0;
            inputCoordinates.y = 1;
            break;
        case 'ArrowLeft':
            inputCoordinates.x = -1;
            inputCoordinates.y = 0;
            break;
        case 'ArrowRight':
            inputCoordinates.x = 1;
            inputCoordinates.y = 0;
            break;
        default:
            break;
    }
});

Now, after completing the javascript code you can write HTML and CSS code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Snake Game</title>
    <link rel="stylesheet" href="styles.css" /> 
    <script src="script.js" defer></script>
</head>
<body>
    <div id="board">
    </div>
</body>
</html>

To add the styles

*{
    padding: 0;
    margin: 0;
}

body {
    min-height: 100vh;
    background-size: 100vw 100vh;
    background-repeat: no-repeat;
    display: flex;
    justify-content: center;
    align-items: center;
}

#board {
    display: grid;
    grid-template-columns: repeat(18, 1fr);
     grid-template-rows: repeat(18, 1fr); 
    width: 90vmin;
    height: 92vmin;

    gap: 1px;
    border: 1px solid #ccc;
}

.snake {
    background-color: red;
    height: 12px;
    width: 12px;
}

.food {
    background-color: blue;
    border-radius: 50%;
    height: 10px;
    width: 10px;
}

This is a simple snake game. You can improve on top of it by adding scores, and improved UI with different colors as well as sounds.