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, clearInterval and restart with a shorter delay.
  • High score: save it with localStorage.setItem('best', score).
  • Mobile: listen for pointerdown/pointermove and turn when the finger moves more than ~20 px.
  • Smooth timing: swap setInterval for requestAnimationFrame with 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.

Play the finished version

See how it feels with walls/no walls, three speeds, 2 players and an AI demo.