How to Make a Snake Game in JavaScript
A complete snake game in about 80 lines of plain JavaScript and HTML canvas, with no libraries. Copy it, run it, then make it your own.
In short: store the snake as an array of grid cells, head first. On every tick, add a new head in the current direction and remove the last cell, unless the head reaches the food, in which case keep it so the snake grows. The game ends when the head hits a wall or the snake's own body.
The complete code
Save this as snake.html and open it in your browser. Use the arrow keys to play.
<!DOCTYPE html>
<html>
<body style="background:#222;color:#eee;font-family:sans-serif;text-align:center">
<canvas id="game" width="400" height="400"></canvas>
<p>Score: <span id="score">0</span></p>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const GRID = 20; // 20 x 20 cells
const CELL = canvas.width / GRID; // 20 px per cell
let snake, dir, nextDir, food, score, timer;
function reset() {
snake = [{ x: 10, y: 10 }, { x: 9, y: 10 }, { x: 8, y: 10 }]; // head first
dir = nextDir = { x: 1, y: 0 }; // moving right
score = 0;
scoreEl.textContent = score;
placeFood();
clearInterval(timer);
timer = setInterval(tick, 120); // one step every 120 ms
}
function placeFood() {
do {
food = { x: Math.floor(Math.random() * GRID), y: Math.floor(Math.random() * GRID) };
} while (snake.some(p => p.x === food.x && p.y === food.y)); // never on the snake
}
function tick() {
dir = nextDir;
const head = { x: snake[0].x + dir.x, y: snake[0].y + dir.y };
const eating = head.x === food.x && head.y === food.y;
const body = eating ? snake : snake.slice(0, -1); // the tail moves away unless we eat
const hitWall = head.x < 0 || head.y < 0 || head.x >= GRID || head.y >= GRID;
const hitSelf = body.some(p => p.x === head.x && p.y === head.y);
if (hitWall || hitSelf) return reset(); // game over: start again
snake.unshift(head); // move: add a new head...
if (eating) {
score += 10;
scoreEl.textContent = score;
placeFood();
} else {
snake.pop(); // ...and drop the tail
}
draw();
}
function draw() {
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#e33'; // apple
ctx.fillRect(food.x * CELL, food.y * CELL, CELL, CELL);
ctx.fillStyle = '#3c3'; // snake
snake.forEach(p => ctx.fillRect(p.x * CELL + 1, p.y * CELL + 1, CELL - 2, CELL - 2));
}
const KEYS = {
ArrowUp: { x: 0, y: -1 }, ArrowDown: { x: 0, y: 1 },
ArrowLeft: { x: -1, y: 0 }, ArrowRight: { x: 1, y: 0 }
};
document.addEventListener('keydown', e => {
const d = KEYS[e.key];
if (!d) return;
e.preventDefault(); // stop the page scrolling
if (d.x === -dir.x && d.y === -dir.y) return; // can't reverse into yourself
nextDir = d;
});
reset();
</script>
</body>
</html>
How it works
1. The grid and the snake
The 400 × 400 canvas is split into a 20 × 20 grid. The snake is an array of {x, y} cells, with the head at index 0. Working in grid cells instead of pixels makes collisions a simple equality check.
2. Moving = new head, drop tail
Every 120 ms, tick() computes the new head from the current direction, unshifts it onto the array and pops the tail. The snake looks like it slides, but really only two cells change each step.
3. Growing
When the new head is on the food, skip the pop(). The snake keeps its tail and becomes one cell longer. Then placeFood() picks a random cell that isn't on the snake.
4. Collisions
The head dies if it leaves the grid or lands on the body. Notice the check uses snake.slice(0, -1) when not eating, because the tail moves out of the way on the same step. That lets you chase your own tail, as in the real game.
5. Input without reversing
Key presses only set nextDir, and a direction exactly opposite the current one is ignored. Otherwise pressing ← while moving → would crash the snake into its own neck.
Ideas to extend it
- No walls: wrap the head with
(x + GRID) % GRID. - Speed levels: every 5 apples,
clearIntervaland restart with a shorter delay. - High score: save it with
localStorage.setItem('best', score). - Mobile: listen for
pointerdown/pointermoveand turn when the finger moves more than ~20 px. - Smooth timing: swap
setIntervalforrequestAnimationFramewith a time accumulator. - An AI player: breadth-first search from the head to the food. That's how our 🤖 Watch AI play mode works.
The PlaySnake game is built on exactly these ideas, plus a buffered input queue, pause, swipe controls, 2-player mode and high-DPI rendering.
Snake game in Python (pygame)
The same logic in Python. Install pygame with pip install pygame, save as snake.py and run python snake.py.
import random
import pygame
GRID, CELL = 20, 20
pygame.init()
screen = pygame.display.set_mode((GRID * CELL, GRID * CELL))
pygame.display.set_caption("Snake")
clock = pygame.time.Clock()
def new_food(snake):
while True:
f = (random.randrange(GRID), random.randrange(GRID))
if f not in snake:
return f
snake = [(10, 10), (9, 10), (8, 10)] # head first
direction = next_dir = (1, 0) # moving right
food = new_food(snake)
score = 0
KEYS = {pygame.K_UP: (0, -1), pygame.K_DOWN: (0, 1),
pygame.K_LEFT: (-1, 0), pygame.K_RIGHT: (1, 0)}
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN and event.key in KEYS:
d = KEYS[event.key]
if d != (-direction[0], -direction[1]): # no reversing
next_dir = d
direction = next_dir
head = (snake[0][0] + direction[0], snake[0][1] + direction[1])
eating = head == food
body = snake if eating else snake[:-1]
if not (0 <= head[0] < GRID and 0 <= head[1] < GRID) or head in body:
print("Game over! Score:", score)
break
snake.insert(0, head)
if eating:
score += 10
food = new_food(snake)
else:
snake.pop()
screen.fill((17, 17, 17))
pygame.draw.rect(screen, (230, 50, 50), (food[0] * CELL, food[1] * CELL, CELL, CELL))
for x, y in snake:
pygame.draw.rect(screen, (60, 200, 60), (x * CELL + 1, y * CELL + 1, CELL - 2, CELL - 2))
pygame.display.flip()
clock.tick(8) # 8 moves per second
pygame.quit()
FAQ
How do you make a snake game in JavaScript?
Draw a grid on an HTML canvas and store the snake as an array of cells, head first. On a timer, add a new head in the current direction and remove the tail, unless the head lands on the food. End the game if the head leaves the grid or hits the body. The complete code above is about 80 lines.
Do I need a library or game engine?
No. The JavaScript version uses only the built-in Canvas API and runs in any browser. The Python version uses the free pygame library (pip install pygame).
How do I make the snake go through walls?
Wrap the head position instead of ending the game: head.x = (head.x + GRID) % GRID, and the same for y, then remove the hitWall check.