Skip to main content

Building a Snake Game in Vanilla JavaScript: A Step-by-Step Guide

Creating a simple Snake game is a fantastic project to deepen your understanding of JavaScript, HTML, and CSS. In this blog, we’ll guide you through building a Snake game from scratch using vanilla JavaScript. By the end, you'll have a fully functional game where the snake grows as it eats food and ends when it collides with itself or the walls.


What Will You Learn?

  • Setting up an HTML canvas for game rendering.
  • Handling keyboard input to control the snake.
  • Implementing basic game mechanics: movement, food generation, and collision detection.
  • Using JavaScript’s setInterval function for game loops.

Step 1: Setting Up the Project

HTML Structure

Start by creating a basic HTML file with a canvas element. The canvas will serve as the game board.

<!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: 2px solid black;
      display: block;
      margin: 20px auto;
    }
    body {
      text-align: center;
      font-family: Arial, sans-serif;
    }
  </style>
</head>
<body>
  <h1>Snake Game</h1>
  <canvas id="gameCanvas" width="400" height="400"></canvas>
  <script src="snake.js"></script>
</body>
</html>

Step 2: Initializing the Canvas

In the snake.js file, we’ll initialize the canvas and context for rendering.

// Select the canvas and set up the 2D drawing context
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

// Set the size of each grid cell
const gridSize = 20;

// Initialize the game variables
let snake = [{ x: gridSize * 5, y: gridSize * 5 }];
let direction = { x: 0, y: 0 };
let food = generateFood();
let score = 0;

Step 3: Rendering the Snake and Food

Define functions to draw the snake and the food on the canvas.

Drawing the Snake

We’ll loop through the snake array and draw each segment as a square.

function drawSnake() {
  ctx.fillStyle = 'green';
  snake.forEach(segment => {
    ctx.fillRect(segment.x, segment.y, gridSize, gridSize);
  });
}

Drawing the Food

The food is a red square randomly placed on the grid.

function drawFood() {
  ctx.fillStyle = 'red';
  ctx.fillRect(food.x, food.y, gridSize, gridSize);
}

Step 4: Game Mechanics

Snake Movement

The snake moves by adding a new head in the direction of movement and removing the tail.

function moveSnake() {
  const newHead = {
    x: snake[0].x + direction.x * gridSize,
    y: snake[0].y + direction.y * gridSize,
  };

  snake.unshift(newHead); // Add the new head

  // If the snake eats food, keep the tail (do not pop)
  if (newHead.x === food.x && newHead.y === food.y) {
    score++;
    food = generateFood(); // Generate new food
  } else {
    snake.pop(); // Remove the tail
  }
}

Food Generation

Ensure the food doesn’t appear inside the snake.

function generateFood() {
  let foodX, foodY;

  do {
    foodX = Math.floor(Math.random() * (canvas.width / gridSize)) * gridSize;
    foodY = Math.floor(Math.random() * (canvas.height / gridSize)) * gridSize;
  } while (snake.some(segment => segment.x === foodX && segment.y === foodY));

  return { x: foodX, y: foodY };
}

Step 5: Collision Detection

Wall Collision

Check if the snake’s head is out of bounds.

function checkWallCollision() {
  const head = snake[0];
  return (
    head.x < 0 || head.x >= canvas.width || head.y < 0 || head.y >= canvas.height
  );
}

Self-Collision

Check if the snake’s head touches any part of its body.

function checkSelfCollision() {
  const head = snake[0];
  return snake.slice(1).some(segment => segment.x === head.x && segment.y === head.y);
}

Step 6: Handling User Input

Add an event listener to track the arrow keys and update the snake's direction.

document.addEventListener('keydown', event => {
  const { key } = event;

  if (key === 'ArrowUp' && direction.y === 0) {
    direction = { x: 0, y: -1 };
  } else if (key === 'ArrowDown' && direction.y === 0) {
    direction = { x: 0, y: 1 };
  } else if (key === 'ArrowLeft' && direction.x === 0) {
    direction = { x: -1, y: 0 };
  } else if (key === 'ArrowRight' && direction.x === 0) {
    direction = { x: 1, y: 0 };
  }
});

Step 7: Game Loop

The game loop updates the game state and re-renders everything.

function gameLoop() {
  if (checkWallCollision() || checkSelfCollision()) {
    alert(`Game Over! Your score: ${score}`);
    document.location.reload(); // Reload the page to restart the game
  }

  ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
  drawSnake();
  drawFood();
  moveSnake();
}

// Run the game loop every 100ms
setInterval(gameLoop, 100);

Final Code

Here’s the complete code in one place for easy reference:

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const gridSize = 20;
let snake = [{ x: gridSize * 5, y: gridSize * 5 }];
let direction = { x: 0, y: 0 };
let food = generateFood();
let score = 0;

function drawSnake() {
  ctx.fillStyle = 'green';
  snake.forEach(segment => ctx.fillRect(segment.x, segment.y, gridSize, gridSize));
}

function drawFood() {
  ctx.fillStyle = 'red';
  ctx.fillRect(food.x, food.y, gridSize, gridSize);
}

function moveSnake() {
  const newHead = {
    x: snake[0].x + direction.x * gridSize,
    y: snake[0].y + direction.y * gridSize,
  };

  snake.unshift(newHead);
  if (newHead.x === food.x && newHead.y === food.y) {
    score++;
    food = generateFood();
  } else {
    snake.pop();
  }
}

function generateFood() {
  let foodX, foodY;
  do {
    foodX = Math.floor(Math.random() * (canvas.width / gridSize)) * gridSize;
    foodY = Math.floor(Math.random() * (canvas.height / gridSize)) * gridSize;
  } while (snake.some(segment => segment.x === foodX && segment.y === foodY));
  return { x: foodX, y: foodY };
}

function checkWallCollision() {
  const head = snake[0];
  return head.x < 0 || head.x >= canvas.width || head.y < 0 || head.y >= canvas.height;
}

function checkSelfCollision() {
  const head = snake[0];
  return snake.slice(1).some(segment => segment.x === head.x && segment.y === head.y);
}

document.addEventListener('keydown', event => {
  const { key } = event;
  if (key === 'ArrowUp' && direction.y === 0) direction = { x: 0, y: -1 };
  else if (key === 'ArrowDown' && direction.y === 0) direction = { x: 0, y: 1 };
  else if (key === 'ArrowLeft' && direction.x === 0) direction = { x: -1, y: 0 };
  else if (key === 'ArrowRight' && direction.x === 0) direction = { x: 1, y: 0 };
});

function gameLoop() {
  if (checkWallCollision() || checkSelfCollision()) {
    alert(`Game Over! Your score: ${score}`);
    document.location.reload();
  }
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawSnake();
  drawFood();
  moveSnake();
}

setInterval(gameLoop, 100);

Conclusion

Congratulations! You’ve built a fully functional Snake game in vanilla JavaScript. You can now experiment further by adding features such as:

  • Increasing the game’s speed as the snake grows.
  • Keeping a high-score record.
  • Adding sound effects or themes.

Happy coding!

Comments

Popular posts from this blog

From Message Queues to Distributed Streams: A Comprehensive Introduction to Apache Kafka (Part 3)

In Part 1 and Part 2, we covered the basics of Kafka, its core concepts, and optimization techniques. We learned how to scale Kafka, secure it, govern data formats, monitor its health, and integrate with other systems. Now, in this final installment, we’re going to push deeper into advanced scenarios and look at how you can implement practical, production-ready solutions—especially with Java, the language of Kafka’s native client library. We’ll explore cross-data center replication, multi-cloud strategies, architectural patterns, advanced security, and more. We’ll highlight how to implement Kafka producers, consumers, and streaming logic in Java. By the end, you’ll have a solid understanding of complex Kafka deployments and the technical know-how to bring these ideas to life in code. Advanced Deployment Scenarios: Multi-Data Center and Hybrid Cloud As organizations grow, they may need Kafka clusters spanning multiple data centers or cloud regions. This can ensure higher availabilit...

From Message Queues to Distributed Streams: A Comprehensive Introduction to Apache Kafka (Part 2)

In the first part, we explored Kafka’s core concepts—topics, partitions, offsets—and discovered how it evolved from a LinkedIn project to a globally adored distributed streaming platform. We saw how Kafka transforms the idea of a distributed log into a powerful backbone for modern data infrastructures and event-driven systems. Now, in Part 2, we’ll step deeper into the world of Kafka. We’ll talk about how to optimize your Kafka setup, tune producers and consumers for maximum throughput, refine pub/sub patterns for scale, and use Kafka’s ecosystem tools to build robust pipelines. We’ll also introduce strategies to handle complex operational challenges like cluster sizing, managing topic growth, ensuring data quality, and monitoring system health. Get ready for a hands-on journey filled with insights, best practices, and practical tips. We’ll keep the paragraphs shorter, crisper, and more visually engaging. Let’s dive in! Scaling Kafka: Building a Data Highway Rather Than a Country ...

From Message Queues to Distributed Streams: A Comprehensive Introduction to Apache Kafka (Part 1)

In today’s data-driven world, the ability to effectively process, store, and analyze massive volumes of data in real-time is no longer a luxury reserved for a handful of tech giants; it’s a core requirement for businesses of all sizes and across diverse industries. Whether it’s handling clickstream data from millions of users, ingesting event logs from thousands of servers, or tracking IoT telemetry data from smart sensors deployed worldwide, the modern enterprise faces a formidable challenge: how to build reliable, scalable, and low-latency systems that can continuously move and transform data streams. Standing at the forefront of this revolution is Apache Kafka, an open-source distributed event streaming platform that has fundamentally redefined how organizations think about messaging, event processing, and data integration. Kafka’s rise to popularity is no accident. Originally developed at LinkedIn and later open-sourced in 2011, Kafka was conceived to address some of the toughest...