JavaScript Snake Game

Developing a game using JavaScript can be a rewarding experience that enhances your programming abilities and offers an engaging, interactive project to showcase. The Snake Game is a popular and classic choice for a beginner's project. This game involves controlling a snake to consume food, causing the snake to increase in length, all while avoiding collisions with walls or its own body. In this tutorial, we will explore the principles involved in creating a Snake Game using JavaScript.

The Basics of Game Development

Before delving into the intricacies of the Snake Game, it is crucial to grasp certain fundamental concepts of game development:

Game Loop: In every game, there exists a continuous loop that refreshes the game's status and displays it visually. This loop operates consistently, ensuring the game can respond to user actions and various alterations.

The HTML5 Canvas API is frequently utilized for rendering visuals in programming. It provides a drawable area within the application where various graphics like shapes, images, and other visual elements can be created for games.

The game state encompasses the details that describe the current status of the game, such as the position of the snake, distribution of food, and the score of the player.

Handling user input is fundamental in creating interactive games. Capturing the arrow key presses in the Snake Game enables you to manage the snake's motion.

Organizing the Snake Game

1. Initializing the Game Environment

Begin by creating an HTML file that defines the canvas element where the game graphics will be displayed. The JavaScript code will make use of this canvas to draw the game. In this HTML document, a canvas is defined with specific dimensions to serve as the game area.

Example:

Example

<!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>
    <style>
        canvas {
            border: 1px solid black;
            display: block;
            margin: auto;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="400" height="400"></canvas>
    <script src="https://placehold.co/400x300/1abc9c/ffffff?text=Sample+Image"></script>
</body>
</html>

2. Setting Up the Game Loop

The game loop plays a crucial role in updating and displaying the game continuously. In JavaScript, you can accomplish this by making use of either the setInterval or requestAnimationFrame APIs.

Example:

Example

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

let gameInterval = setInterval(gameLoop, 100); // Update every 100 milliseconds

function gameLoop() {
    update();
    draw();
}

The function GameLoop is invoked every 100 milliseconds to update the game state and redraw the canvas.

3. Characterizing the Snake and Food

Both the snake and the food play crucial roles in the game's components. The snake is represented as a series of movements, with each part being a square. On the other hand, the food is positioned at a single location on the canvas.

Example:

Example

let snake = [{ x: 10, y: 10 }];
let food = { x: 15, y: 15 };
let direction = { x: 1, y: 0 }; // Initially moving to the right

4. Dealing with User Input

To manage the snake's movements, you need to capture console events. The arrow keys are used to direct the snake's motion.

Example:

Example

document.addEventListener('keydown', changeDirection);

function changeDirection(event) {
    const keyPressed = event.keyCode;
    if (keyPressed === 37 && direction.x === 0) { // Left arrow
        direction = { x: -1, y: 0 };
    } else if (keyPressed === 38 && direction.y === 0) { // Up arrow
        direction = { x: 0, y: -1 };
    } else if (keyPressed === 39 && direction.x === 0) { // Right arrow
        direction = { x: 1, y: 0 };
    } else if (keyPressed === 40 && direction.y === 0) { // Down arrow
        direction = { x: 0, y: 1 };
    }
}

5. Updating the Game State

The update operation involves advancing the snake by adding a new head in the moving direction while removing the tail. If the snake consumes the food, the food position changes randomly, and the tail remains intact, causing the snake to increase in length.

Example:

Example

function update() {
    const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };

    // Check for collisions
    if (head.x < 0 || head.x >= canvas.width / 10 || head.y < 0 || head.y >= canvas.height / 10 || snakeCollision(head)) {
        clearInterval(gameInterval); // End the game
        alert('Game Over!');
    } else {
        snake.unshift(head); // Add new head

        if (head.x === food.x && head.y === food.y) {
            repositionFood();
        } else {
            snake.pop(); // Remove the tail
        }
    }
}

function snakeCollision(head) {
    for (let segment of snake) {
        if (segment.x === head.x && segment.y === head.y) {
            return true;
        }
    }
    return false;
}

function repositionFood() {
    food.x = Math.floor(Math.random() * canvas.width / 10);
    food.y = Math.floor(Math.random() * canvas.height / 10);
}

6. Drawing the Game

The draw function clears the canvas and then proceeds to redraw both the snake and the food items.

Example:

Example

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas

    ctx.fillStyle = 'green';
    for (let segment of snake) {
        ctx.fillRect(segment.x * 10, segment.y * 10, 10, 10);
    }

    ctx.fillStyle = 'red';
    ctx.fillRect(food.x * 10, food.y * 10, 10, 10);
}

Final Code - Snake Game in JavaScript

Let's examine the HTML, CSS, and JavaScript files required to develop a simple yet visually appealing snake game.

HTML Code:

Example

<!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="styles.css">
</head>
<body>
    <div class="container">
        <canvas id="gameCanvas" width="400" height="400"></canvas>
        <div class="score">Score: <span id="score">0</span></div>
        <button id="startButton">Start Game</button>
    </div>
    <script src="https://placehold.co/400x300/3498db/ffffff?text=Sample+Image"></script>
</body>
</html>

CSS Code:

Example

body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
    background: linear-gradient(to right, #ff7e5f, #feb47b);
    font-family: 'Arial', sans-serif;
    color: #fff;
}

.container {
    display: flex;
    flex-direction: column;
    align-items: center;
    background: rgba(0, 0, 0, 0.5);
    padding: 20px;
    border-radius: 15px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

canvas {
    border: 2px solid #000;
    background-color: #2c3e50;
    border-radius: 10px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}

.score {
    margin-top: 20px;
    font-size: 24px;
}

button {
    margin-top: 20px;
    padding: 10px 20px;
    font-size: 16px;
    background: linear-gradient(to right, #43cea2, #185a9d);
    color: white;
    border: none;
    cursor: pointer;
    border-radius: 5px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
    transition: background 0.3s ease;
}

button:hover {
    background: linear-gradient(to right, #185a9d, #43cea2);
}

.hidden {
    display: none;
}

JavaScript Code:

Example

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreDisplay = document.getElementById('score');
const startButton = document.getElementById('startButton');

const gridSize = 20; // Size of each grid cell
const canvasSize = canvas.width / gridSize; // Number of cells in one dimension

let snake = [{ x: 10, y: 10 }];
let direction = { x: 1, y: 0 }; // Start with a right direction
let food = { x: 15, y: 15 };
let score = 0;
let gameInterval;

document.addEventListener('keydown', changeDirection);
startButton.addEventListener('click', startGame);

function startGame() {
    // Reset game state
    snake = [{ x: 10, y: 10 }];
    direction = { x: 1, y: 0 }; // Reset direction to right
    food = { x: 15, y: 15 };
    score = 0;
    scoreDisplay.textContent = score;
    startButton.classList.add('hidden');
    
    gameInterval = setInterval(gameLoop, 100);
}

function gameLoop() {
    update();
    draw();
    if (isGameOver()) {
        clearInterval(gameInterval);
        startButton.textContent = 'Start Again';
        startButton.classList.remove('hidden');
        alert('Game Over! Your score: ' + score);
    }
}

function update() {
    const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };

    // Check if the snake eats the food
    if (head.x === food.x && head.y === food.y) {
        score++;
        scoreDisplay.textContent = score;
        repositionFood();
    } else {
        snake.pop(); // Remove the tail
    }

    snake.unshift(head); // Add new head to the snake
}

function draw() {
    // Clear the canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Draw the snake
    ctx.fillStyle = 'green';
    snake.forEach(segment => {
        ctx.fillRect(segment.x * gridSize, segment.y * gridSize, gridSize, gridSize);
    });

    // Draw the food
    ctx.fillStyle = 'red';
    ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);
}

function changeDirection(event) {
    const { keyCode } = event;

    // Prevent the snake from reversing
    if (keyCode === 37 && direction.x === 0) { // Left arrow
        direction = { x: -1, y: 0 };
    } else if (keyCode === 38 && direction.y === 0) { // Up arrow
        direction = { x: 0, y: -1 };
    } else if (keyCode === 39 && direction.x === 0) { // Right arrow
        direction = { x: 1, y: 0 };
    } else if (keyCode === 40 && direction.y === 0) { // Down arrow
        direction = { x: 0, y: 1 };
    }
}

function repositionFood() {
    food = {
        x: Math.floor(Math.random() * canvasSize),
        y: Math.floor(Math.random() * canvasSize)
    };

    // Ensure the food does not appear on the snake
    while (snake.some(segment => segment.x === food.x && segment.y === food.y)) {
        food = {
            x: Math.floor(Math.random() * canvasSize),
            y: Math.floor(Math.random() * canvasSize)
        };
    }
}

function isGameOver() {
    const head = snake[0];

    // Check for wall collisions
    if (head.x < 0 || head.x >= canvasSize || head.y < 0 || head.y >= canvasSize) {
        return true;
    }

    // Check for self collisions
    for (let i = 1; i < snake.length; i++) {
        if (head.x === snake[i].x && head.y === snake[i].y) {
            return true;
        }
    }

    return false;
}

// Hide the start button initially
startButton.classList.remove('hidden');

Output:

Below are the images depicting the appearance of the game before and after it concludes. Feel free to give it a try yourself.

Conclusion

Developing a Snake Game using JavaScript involves understanding and implementing core game development concepts such as the game loop, managing user input, and updating and rendering the game state. By breaking the game into smaller, more manageable parts, you can create a valuable and engaging game. Once you have mastered these fundamentals, you can enhance the game with features like levels, obstacles, and more advanced graphics. This project serves as an excellent introduction to game programming and also helps improve your JavaScript proficiency, setting the groundwork for more intricate projects.

Input Required

This code uses input(). Please provide values below: