Create a Snake Game Using HTML, CSS, and JavaScript

JavaScript allows us to create fun and interactive projects, one of which is the Snake game. It is a very popular, beginner-friendly game project that can be built using simple and basic HTML, CSS, and JavaScript. A snake game is a type of action video game where the player controls a growing line that looks like a snake. The goal is to avoid hitting obstacles and the snake’s own body as it gets longer.

Understanding the Rules of Snake Game

Let’s understand the basic rules and mechanics of the Snake game:

  • The player controls the snake with the goal of eating as much food as possible without hitting the walls, obstacles, or the snake’s own body.
  • The game begins with a small snake that can move in different directions.
  • A player sets the direction for this snake using arrow keys: up, down, left, right. The snake just keeps moving in the direction of the last arrow key pressed.
  • Food items appear randomly on the screen. The player controls the snake in order to take it to the food.
  • The player must ensure the snake doesn’t hit the walls of the game area. The snake must also avoid collision with its own body while growing longer.
  • The game is over when the snake hits the wall, an obstacle, or its body. The score is usually based on how much food the snake has eaten.
  • Normally, there is no major “win” in the Snake game, the object is to attain the highest score possible before the game is over. If the game is over, the player can restart the game in an attempt to have a higher score.

To build one, you need to play one! So click here to play the snake game once so that you understand the stages better.

Creating the Snake Game

Three main components required for our snake game are: 1.HTML, 2. CSS, 3. JavaScript.

1. HTML

We will create an index.html file in our project folder. This file sets up the structure of our game on the webpage.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Snake Game</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="Body">
    <div id="scoreBox">Score: 0</div>
    <div id="hiscoreBox">HiScore: 0</div>
    <div id="Board"></div>
  </div>  
  <script src="index.js"></script>
</body>
</html>

Inside <body>, we have a <div class=”Body”> which acts as a container for our game elements:

  • div id=”scoreBox”>Score: 0</div>: This displays the player’s current score.
  • <div id=”hiscoreBox”>HiScore: 0</div>: This shows the highest score achieved.
  • <div id=”Board”></div>: This is where the actual game (the snake and food) will appear.
  • <script src=”index.js”></script>: This connects our JavaScript file, which controls the game logic.

Output:

If we run this code using the live server, the webpage will look like this:

HTML output

I know it does not look like a game from any angle right now, but let’s transform this lifeless skeleton into a live game using some logic and style.

2. CSS

Now we will create a style.css file in the same project folder. This file styles the appearance of our game, making it look nice.

* {
    padding: 0;
    margin: 0;
}

.Body {
    background: url("bg2.jpg");
    min-height: 100vh;
    background-size: 100vw 100vh;
    display: flex;
    justify-content: center;
    align-items: center; 
}

#scoreBox {
    position: absolute;
    top: 9px;
    right: 200px;
    font-size: 39px;
    color: aliceblue;
    font-weight: bold;
    font-family: 'New Tegomin', serif;
    text-shadow: 2px 2px 0 #000000;
}

#hiscoreBox {
    position: absolute;
    top: 59px;
    right: 140px;
    color: aliceblue;
    font-size: 39px;
    font-weight: bold;
    font-family: 'New Tegomin', serif;
    text-shadow: 2px 2px 0 #000000;
}

#Board {
    background: linear-gradient(rgb(114, 255, 114), rgb(252, 252, 132));
    width: 60vmin;
    height: 60vmin;
    border: 2px solid rgb(1, 61, 26);
    display: grid;
    grid-template-rows: repeat(12, 1fr);
    grid-template-columns: repeat(12, 1fr);
}

.head {
    background-color: red;
}

.snake {
    background-color: green;
}

.tail {
    background-color: rgb(12, 12, 121);
}

Here:

  • * { padding: 0; margin: 0; }: It removes default padding and margin from all elements to give us a clean slate.
  • .body: This class will centre the game on the screen, give it a background image (bg2.jpg), and make sure that it takes up the full height of the screen.
  • #scoreBox and #hiscoreBox: These styles will position the score and high score at the top right of the screen, with a specific font, size, colour, and a little shadow to make the text stand out.
  • #Board: It is for the game area where the snake moves. We will set it up as a grid with 12 rows and 12 columns with a nice gradient background and a border.
  • .head, .snake, .tail: These classes colour the snake’s head, body, and food respectively. The head is red, the body is green, and the food is dark blue.

Output:

CSS output

Do you see the difference? The website looks so much better now, with a real gaming vibe. But the game board is still empty and lifeless, right? To bring it to life and make our snake move, the final step is to add the logic using JavaScript.

3. JavaScript

Let us create an index.js file in the same project folder. This is where the magic happens—it controls how the game works. This code is going to be a little complex as it’s the brain of the game, so let us break it into steps for clarity.

Step 1: First, we will set up the initials for the game:

// Game constants
let inputDir = {x: 0, y: 0};
let speed = 2;
let lastPaintTime = 0;
let snakeArr = [{x: 5, y: 6}];
let tail = {x: 3, y: 4};
let score = 0;
  • let inputDir = {x: 0, y: 0};: This keeps track of the snake’s direction. Initially, the snake isn’t moving.
  • let speed = 2;: Controls how fast the snake moves.
  • let snakeArr = [{x: 5, y: 6}];: This is an array that keeps track of the snake’s body parts. It starts with one segment.
  • let tail = {x: 3, y: 4};: It represents the food’s position on the grid.
  • let score = 0;: It will track the player’s score.

Step 2: Next, we will connect our JavaScript to the HTML elements using getElementById for the game board, score box, and high score box.

const Board = document.getElementById('Board');
const scoreBox = document.getElementById('scoreBox');
const hiscoreBox = document.getElementById('hiscoreBox');

Step 3: Now we will build the main game loop.

function main(ctime) {
    window.requestAnimationFrame(main);
    if ((ctime - lastPaintTime) / 1000 < 1 / speed) {
        return;
    }
    lastPaintTime = ctime;
    gameEngine();
}
  • This function will keep the game running by repeatedly calling itself (window.requestAnimationFrame(main);).
  • It will check the time to control the snake’s speed and then run the gameEngine() function, which will update the game.

Step 4: Next we will write the logic for collision detection.

function isCollide(snake) {
    // If you bump into yourself
    for (let i = 1; i < snakeArr.length; i++) {
        if (snake[i].x === snake[0].x && snake[i].y === snake[0].y) {
            return true;
        }
    }
    // If you bump into the wall
    if (snake[0].x >= 12 || snake[0].x <= 0 || snake[0].y >= 12 || snake[0].y <= 0) {
        return true;
    }
    return false;
}

This function will check if the snake has collided with itself or the walls. If it has, the game ends.

Step 5: Now we will design the heart of the game—that is, the game engine.

function gameEngine() {
    // Updating the snake array and tail
    if (isCollide(snakeArr)) {
        inputDir = {x: 0, y: 0};
        alert("Game Over. Press any key to play again!");
        snakeArr = [{x: 5, y: 6}];
        score = 0;
        scoreBox.innerHTML = "Score: " + score;
    }

    // If you have eaten the food, increment the score and regenerate the food
    if (snakeArr[0].y === tail.y && snakeArr[0].x === tail.x) {
        score += 1;
        scoreBox.innerHTML = "Score: " + score;
        if (score > hiscoreval) {
            hiscoreval = score;
            localStorage.setItem("hiscore", JSON.stringify(hiscoreval));
            hiscoreBox.innerHTML = "HiScore: " + hiscoreval;
        }
        snakeArr.unshift({x: snakeArr[0].x + inputDir.x, y: snakeArr[0].y + inputDir.y});
        let a = 2;
        let b = 10;
        tail = {x: Math.round(a + (b-a) * Math.random()), y: Math.round(a + (b-a) * Math.random())};
    }

    // Moving the snake
    for (let i = snakeArr.length - 2; i >= 0; i--) { 
        snakeArr[i+1] = {...snakeArr[i]};
    }

    snakeArr[0].x += inputDir.x;
    snakeArr[0].y += inputDir.y;

    // Display snake
    Board.innerHTML = "";
    snakeArr.forEach((e, index) => {
        const snakeElement = document.createElement('div');
        snakeElement.style.gridRowStart = e.y;
        snakeElement.style.gridColumnStart = e.x;
        if (index === 0) {
            snakeElement.classList.add('head');
        } else {
            snakeElement.classList.add('snake');
        }
        Board.appendChild(snakeElement);
    });

    // Display food
    const tailElement = document.createElement('div');
    tailElement.style.gridRowStart = tail.y;
    tailElement.style.gridColumnStart = tail.x;
    tailElement.classList.add('tail');
    Board.appendChild(tailElement);
}
// Game logic
let hiscore = localStorage.getItem("hiscore");
let hiscoreval;
if (hiscore === null) {
    hiscoreval = 0;
    localStorage.setItem("hiscore", JSON.stringify(hiscoreval));
} else {
    hiscoreval = JSON.parse(hiscore);
    hiscoreBox.innerHTML = "HiScore: " + hiscoreval;
}
  • It first checks for collisions.
  • If the snake eats the food, the score increases, the snake grows longer, and the food is moved to a new random position.
  • The snake’s position is updated based on the input direction.
  • Finally, it displays the updated snake and food on the game board.

Step 6: Next we will write logic to control the snake.

window.requestAnimationFrame(main);
window.addEventListener('keydown', e => {
    inputDir = {x: 0, y: 1}; // Start game
    switch (e.key) {
        case "ArrowUp":
            inputDir.x = 0;
            inputDir.y = -1;
            break;
        case "ArrowDown":
            inputDir.x = 0;
            inputDir.y = 1;
            break;
        case "ArrowLeft":
            inputDir.x = -1;
            inputDir.y = 0;
            break;
        case "ArrowRight":
            inputDir.x = 1;
            inputDir.y = 0;
            break;
        default:
            break;
    }
});

window.addEventListener(‘keydown’, e => { … });: This listens for arrow key presses and changes the snake’s direction based on which key is pressed. The game saves and displays the highest score using localStorage, so it persists even after the page is refreshed.

Here is the complete JavaScript code:

// Game constants
let inputDir = {x: 0, y: 0};
let speed = 2;
let lastPaintTime = 0;
let snakeArr = [{x: 5, y: 6}];
let tail = {x: 3, y: 4};
let score = 0;

const Board = document.getElementById('Board');
const scoreBox = document.getElementById('scoreBox');
const hiscoreBox = document.getElementById('hiscoreBox');

// Game functions
function main(ctime) {
    window.requestAnimationFrame(main);
    if ((ctime - lastPaintTime) / 1000 < 1 / speed) {
        return;
    }
    lastPaintTime = ctime;
    gameEngine();
}

function isCollide(snake) {
    // If you bump into yourself
    for (let i = 1; i < snakeArr.length; i++) {
        if (snake[i].x === snake[0].x && snake[i].y === snake[0].y) {
            return true;
        }
    }
    // If you bump into the wall
    if (snake[0].x >= 12 || snake[0].x <= 0 || snake[0].y >= 12 || snake[0].y <= 0) {
        return true;
    }
    return false;
}

function gameEngine() {
    // Updating the snake array and tail
    if (isCollide(snakeArr)) {
        inputDir = {x: 0, y: 0};
        alert("Game Over. Press any key to play again!");
        snakeArr = [{x: 5, y: 6}];
        score = 0;
        scoreBox.innerHTML = "Score: " + score;
    }

    // If you have eaten the food, increment the score and regenerate the food
    if (snakeArr[0].y === tail.y && snakeArr[0].x === tail.x) {
        score += 1;
        scoreBox.innerHTML = "Score: " + score;
        if (score > hiscoreval) {
            hiscoreval = score;
            localStorage.setItem("hiscore", JSON.stringify(hiscoreval));
            hiscoreBox.innerHTML = "HiScore: " + hiscoreval;
        }
        snakeArr.unshift({x: snakeArr[0].x + inputDir.x, y: snakeArr[0].y + inputDir.y});
        let a = 2;
        let b = 10;
        tail = {x: Math.round(a + (b-a) * Math.random()), y: Math.round(a + (b-a) * Math.random())};
    }

    // Moving the snake
    for (let i = snakeArr.length - 2; i >= 0; i--) { 
        snakeArr[i+1] = {...snakeArr[i]};
    }

    snakeArr[0].x += inputDir.x;
    snakeArr[0].y += inputDir.y;

    // Display snake
    Board.innerHTML = "";
    snakeArr.forEach((e, index) => {
        const snakeElement = document.createElement('div');
        snakeElement.style.gridRowStart = e.y;
        snakeElement.style.gridColumnStart = e.x;
        if (index === 0) {
            snakeElement.classList.add('head');
        } else {
            snakeElement.classList.add('snake');
        }
        Board.appendChild(snakeElement);
    });

    // Display food
    const tailElement = document.createElement('div');
    tailElement.style.gridRowStart = tail.y;
    tailElement.style.gridColumnStart = tail.x;
    tailElement.classList.add('tail');
    Board.appendChild(tailElement);
}

// Game logic
let hiscore = localStorage.getItem("hiscore");
let hiscoreval;
if (hiscore === null) {
    hiscoreval = 0;
    localStorage.setItem("hiscore", JSON.stringify(hiscoreval));
} else {
    hiscoreval = JSON.parse(hiscore);
    hiscoreBox.innerHTML = "HiScore: " + hiscoreval;
}

window.requestAnimationFrame(main);
window.addEventListener('keydown', e => {
    inputDir = {x: 0, y: 1}; // Start game
    switch (e.key) {
        case "ArrowUp":
            inputDir.x = 0;
            inputDir.y = -1;
            break;
        case "ArrowDown":
            inputDir.x = 0;
            inputDir.y = 1;
            break;
        case "ArrowLeft":
            inputDir.x = -1;
            inputDir.y = 0;
            break;
        case "ArrowRight":
            inputDir.x = 1;
            inputDir.y = 0;
            break;
        default:
            break;
    }
});

Output:

Now as we run the code on the live server, we see that our snakehead and the food appear on the game board, this signals that our game is ready for play! 

Final output after adding the JavaScript

Playing the Snake Game

Now let’s test our game and see how it works. We need to just follow the rules we discussed at the start and try to beat the high score. Here is a short clip of its gameplay.

As we see here, when we move the arrow keys, the snakehead (red box) starts moving in that direction, and as soon as it touches the food (blue box), the size of the snake tail (number of green boxes) increases, and this cycle repeats each time the food is eaten by the snake. The score increases by one when the food is eaten. But as soon as it collides with the walls, the game ends.

Conclusion

And that’s it! We have successfully created a basic snake game of our own. Javascript allows us to create wonderful things like these. There are many projects that you can build using only vanilla JavaScript along with simple HTML and CSS. Building such projects boosts your confidence and puts your skills to the test, and making such interesting game projects not only provides you entertainment but also improves your tech.

Python is also a great tool for building such projects and it is very easy to learn. Here are some cool projects we made using Python:

Reference

https://stackoverflow.com/questions/19614329/snake-game-in-javascript

Snigdha Keshariya
Snigdha Keshariya
Articles: 101