in

The Ultimate Guide to Coding Snake in JavaScript

As a seasoned JavaScript developer and avid gamer, I‘m thrilled to walk you through building the iconic Snake game from scratch. This comprehensive tutorial will level up your coding skills and teach you how to create a fully playable Snake game in pure JavaScript and HTML, no frameworks required!

By the end, you‘ll be able to code your own browser-based games. Let‘s get started!

Why Learn to Code Snake?

Coding Snake is a rite of passage for many developers. Why? A few reasons:

  • It‘s a classic game that‘s fun and rewarding to recreate
  • It teaches core programming concepts like state management, collision detection, input handling, and more
  • The simple 2D graphics make it approachable for beginners
  • You get to flex your development skills and build something entertaining!

Recreating Snake is a microcosm of web development – you need HTML, CSS, and JavaScript working together to build an interactive experience. Along the way, you‘ll gain transferrable skills that apply to any coding project.

Let‘s look at some key skills this tutorial will teach:

JavaScript

  • Drawing to a canvas element
  • Tracking state with variables and arrays
  • Modular coding with functions
  • Handling keyboard input
  • Creating a game loop with setInterval
  • Dynamically generating content

Programming Concepts

  • State management
  • Collision detection algorithms
  • Input handling and validation
  • Executing code on a timed loop
  • Rendering graphics based on program state

You‘ll also problem-solve and debug along the way, which will improve your overall code competency.

In short, coding Snake in JS is a blast and you‘ll learn a ton. Let‘s get into it!

Overview: What We‘re Building

Here‘s a high-level overview of the Snake game mechanics we‘ll be coding:

  • The game board will be a 400×400 pixel canvas element

  • The snake will start at size 3 and move every 200ms in the direction it‘s facing

  • Arrow keys change the snake‘s direction, but illegal 180° turns are ignored

  • Food spawns randomly. Eating food makes the snake grow +1 segment

  • Colliding with walls or the snake‘s own body triggers game over

By the end, we‘ll have a working Snake game like this:

Let‘s break it down step-by-step!

Project Setup

Any coding journey starts by setting up your project workspace…

HTML

We‘ll need an HTML page to display the game. Here‘s the starting HTML code:

<!-- index.html -->

<html>
<head>
  <title>JavaScript Snake</title>
</head>

<body>

  <div id="game-over">

  </div>

  <canvas id="game-board"></canvas>

  <script src="game.js"></script>

</body>
</html> 

The key elements are:

  • <div id="game-over"> – Will display when the game ends
  • <canvas id="game-board"> – The canvas element that serves as our game board
  • <script src="game.js> – Loads our JavaScript code

Next we‘ll build out game.js to add the game functionality.

CSS

Let‘s add some quick CSS to hide the game over screen and size the canvas:

#game-over {
  display: none;
}

#game-board {
  border: 1px solid black;
  width: 400px;
  height: 400px; 
}

This hides the game over <div> initially and sets the canvas to a square 400×400 pixels.

Importing Assets

We could enhance our Snake game further by importing image assets, fonts, or sounds.

For simplicity, we‘ll just use code to draw basic shapes and text for now. But for your own games, consider sprucing up the visuals by importing assets!

Okay, project setup complete – let‘s start coding Snake!

Drawing the Game Board

The canvas element lets us draw 2D graphics dynamically via JavaScript.

First, we need to select the canvas element from the DOM and initialize the drawing context:

// Get canvas element 
const canvas = document.getElementById(‘game-board‘);

// Initialize 2d drawing context
const ctx = canvas.getContext(‘2d‘);

Next we can draw a border around the edges by calling methods on the ctx context:

// Draw canvas border
ctx.fillStyle = ‘black‘;
ctx.fillRect(0, 0, canvas.width, 10); // top 
ctx.fillRect(0, 0, 10, canvas.height); // left
ctx.fillRect(canvas.width - 10, 0, 10, canvas.height); // right 
ctx.fillRect(0, canvas.height - 10, canvas.width, 10); // bottom

The fillRect(x, y, width, height) method draws a rectangle filled with the currently set fillStyle.

With that, we‘ve programmed a canvas element to display graphics!

Initializing the Snake

Now let‘s add a snake that can move around the board.

We need variables to track:

  • The position of each segment
  • The current direction of movement
  • The length of the snake

Here‘s how we can initialize those values:

// Snake settings
const snakeSize = 10; // Snake segments are 10x10 pixels 
const startSegments = 3; // Starting snake length

// Starting position
let snakeX = canvas.width / 2;
let snakeY = canvas.height / 2; 

// Horizontal and vertical velocity
let velocityX = 0;
let velocityY = 0;

// Track snake segments 
let snake = []; 

// Add segments to start
for (let i = 0; i < startSegments; i++) {

  // Push segment positions  
  snake.push({x: snakeX, y: snakeY});

  // Update snake x position  
  snakeX -= snakeSize; 

}

Breaking this down:

  • snakeSize sets the dimensions of each segment
  • startSegments defines the starting length
  • We initialize the head position in the center
  • velocityX/velocityY will control movement direction
  • snake array tracks each segment‘s {x, y} coordinates
  • We loop to generate the starting segments

Next we can render the starting snake by looping through the snake array and drawing rectangles:

// Draw snake
function drawSnake() {

  // Loop through snake segments
  snake.forEach(segment => {

    // Draw rectangle for segment
    ctx.fillStyle = ‘limegreen‘;
    ctx.fillRect(segment.x, segment.y, snakeSize, snakeSize);

  });

} 

// Draw snake for first time
drawSnake();

Awesome! Our snake is initialized and ready to be moved around.

Moving the Snake

To animate the snake, we need a tick() function that runs in a loop to:

  1. Update the snake positions
  2. Render the snake‘s new position

We can achieve this with setInterval(), like so:

function tick() {

  // Update head position  
  snakeX += velocityX;
  snakeY += velocityY;

  // Add new head segment
  const newHead = {
    x: snakeX, 
    y: snakeY
  };

  // Add to beginning, remove last segment  
  snake.unshift(newHead);
  if (snake.length > startSegments) {
    snake.pop();
  }

  // Draw snake at new position
  drawSnake();

}

// Start game loop
setInterval(tick, 200); 

Here‘s what‘s happening on each tick():

  • Update snakeX/snakeY by adding velocities
  • Add a new head segment with the new position
  • Shift segments down by unshifting the new head
  • Remove the last segment if we‘ve grown past the starting length
  • Draw the snake in its new position

This will make the snake move, growing longer each tick.

But right now the velocity is (0, 0) – we need input handling to change the snake‘s direction.

Handling Inputs

We can update the velocity variables when arrow keys are pressed to change the snake‘s movement:

document.addEventListener(‘keydown‘, function(event) {

  // Prevent page scrolling
  event.preventDefault();

  switch(event.key) {
    case ‘ArrowUp‘: 
      velocityX = 0;
      velocityY = -snakeSize; // Up 
      break;

    case ‘ArrowDown‘:
      velocityX = 0; 
      velocityY = snakeSize; // Down
      break;

    case ‘ArrowLeft‘:
      velocityX = -snakeSize; // Left
      velocityY = 0;
      break;

    case ‘ArrowRight‘:
      velocityX = snakeSize; // Right
      velocityY = 0;
      break;
  }

});

We listen for the keydown event and update velocityX/velocityY based on which arrow key was pressed.

This lets us steer the snake around with arrow keys!

Preventing Illegal Moves

One gameplay challenge is preventing the snake from instantly reversing – e.g. going left then immediately right.

To address this, we validate direction changes before applying new velocities:

switch(event.key) {

  case ‘ArrowUp‘:
    if (velocityY != snakeSize) { 
      // Only allow up if not going down
      velocityX = 0;
      velocityY = -snakeSize;
    } 
    break;

  case ‘ArrowDown‘:
    if (velocityY != -snakeSize) {
      // Only allow down if not going up  
      velocityX = 0;
      velocityY = snakeSize;
    }
    break;

  // And so on...

} 

Now direction changes will be ignored if they would cause an illegal 180° turn.

This improves the gameplay feel and difficulty!

Spawning Food

What‘s a snake without snacks to munch on? Let‘s add food for our snake:

// Food settings
const foodSize = 10; // 10x10 pixels

// Randomly generate food x/y
function generateFood() {

  foodX = Math.floor(Math.random() * canvas.width / 10) * 10; 
  foodY = Math.floor(Math.random() * canvas.height / 10) * 10;

}  

// Draw food on canvas
function drawFood() {

  ctx.fillStyle = ‘red‘;
  ctx.fillRect(foodX, foodY, foodSize, foodSize);

}

// Draw initial food
generateFood(); 
drawFood();

generateFood() picks a random position constrained to the 10×10 grid.

drawFood() renders the food in red at that position.

Now the snake has snacks to hunt!

Detecting Collisions

As the snake grows longer, we need to watch out for collisions with food, walls, and the snake‘s own body.

Let‘s walk through detecting and handling each collision case…

Snake / Food Collision

On each tick() we check if the snake‘s head is now in the same position as the food:

// Check for food collisions 
if (snakeX === foodX && snakeY === foodY) {

  // Increase snake length
  snake.push({x: snakeX, y: snakeY})  

  // Generate new food
  generateFood();

}

If so, we lengthen the snake and spawn food somewhere new.

Snake / Wall Collision

To check for wall collisions, we can see if the head position is now outside the canvas boundaries:

if (
  snakeX < 0 || snakeX >= canvas.width ||
  snakeY < 0 || snakeY >= canvas.height  
) {
  handleGameOver();
} 

If the head has moved outside the canvas, trigger game over.

Snake / Self Collision

Detecting a collision with the snake‘s own body is trickier.

We need to loop through all segments and check if any match the head‘s position:

for (let i = 0; i < snake.length; i++) {

  let segment = snake[i];

  // Self collision?
  if (segment.x === snakeX && segment.y === snakeY) {
    handleGameOver();
  }

}

If the head matches any other segment, that‘s game over!

Game Over Logic

Now that we can detect collisions, let‘s add logic to handle the game over state:

function handleGameOver() {

  // Stop snake movement
  clearInterval(interval); 

  // Display game over UI
  document.getElementById(‘game-over‘).style.display = ‘block‘; 

}

We clear the setInterval() loop to stop snake updates and show the "Game Over" screen we defined in HTML.

Our collision detection is complete!

Keeping Score

As one final enhancement, let‘s track points earned by eating food:

let score = 0;

function displayScore() {

  // Update HTML
  document.getElementById(‘score‘).innerText = score;

}

// When eating food
if (snakeAteFood) {

  // Increase score
  score += 10;

  // Show updated score 
  displayScore();

}

We increment score when eating, update the DOM, and reset on game over.

Adding a scoring system is a great way to increase engagement and replayability. It also shows how to update UI elements based on game state.

Summary: What We Built

After all that coding, we now have a fully playable Snake game!

Let‘s recap what we accomplished:

  • Rendered graphics on a canvas using the HTML canvas API
  • Defined variables to track game state like snake segments
  • Animated the snake by updating positions on a timed loop
  • Handled directional input from arrow keys
  • Added food for the snake to eat and growlonger
  • Detected collisions and made the snake die
  • Added a score and game over screen

We learned about key concepts like state management, input handling, collision detection, and more.

You now have all the skills to build your own browser-based games with JavaScript!

The full source code is available on GitHub. I encourage you to continue building on this project.

Here are some ideas for improvements:

  • Make difficulty increase over time
  • Add sound effects
  • Support touch inputs
  • Add animations and particle effects
  • Implement multiplayer

The possibilities are endless! I hope you enjoyed this project as much as I enjoyed creating it. Happy coding!

AlexisKestler

Written by Alexis Kestler

A female web designer and programmer - Now is a 36-year IT professional with over 15 years of experience living in NorCal. I enjoy keeping my feet wet in the world of technology through reading, working, and researching topics that pique my interest.