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:
snakeSizesets the dimensions of each segmentstartSegmentsdefines the starting length- We initialize the head position in the center
velocityX/velocityYwill control movement directionsnakearray 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:
- Update the snake positions
- 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/snakeYby 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!