Snake
One of the most recognisable games ever made. A line grows longer every time it eats food, and the challenge is to keep steering it without hitting a wall or biting your own tail. Simple rules, surprisingly deep.
The Board: a 2D Grid
Every Snake implementation starts with the same question: how do I represent the playing field?
The answer is a 2D coordinate system. Each cell is addressed by (x, y) where
x increases to the right and y increases downward.
const COLS = 40 // columns
const ROWS = 20 // rows
const CELL = 20 // pixels per cell
Movement: Directions and the Game Loop
The snake jumps one full cell per tick. Each tick the game reads the queued direction, computes a new head position, prepends it to the snake array, and removes the last segment unless food was eaten.
const delta = {
RIGHT: { x: 1, y: 0 },
LEFT: { x: -1, y: 0 },
DOWN: { x: 0, y: 1 },
UP: { x: 0, y: -1 },
}
snake.unshift(newHead) // grow head
snake.pop() // shrink tail (unless food eaten)
Collision Detection
The game ends in two cases, both checked after computing the new head but before drawing.
// Wall collision
if (newHead.x < 0 || newHead.x >= COLS ||
newHead.y < 0 || newHead.y >= ROWS) gameOver()
// Self collision (tail excluded — it moves away this tick)
if (snake.slice(0, -1).some(s =>
s.x === newHead.x && s.y === newHead.y)) gameOver()
Food and Growing
Food is placed at a random cell not occupied by the snake. When the head lands on food, the tail is not popped — the snake stays one cell longer. Each food adds one point.
function randomFood(snake) {
let p
do {
p = { x: rand(COLS), y: rand(ROWS) }
} while (snake.some(s => s.x === p.x && s.y === p.y))
return p
}
Play
Arrow keys or WASD on desktop. D-pad on mobile. Hit Start to play.
Concepts at a Glance
| Concept | Implementation |
|---|---|
| Board | COLS × ROWS grid, each cell addressed by (x, y) |
| Game loop | setInterval at configurable ms, cleared on game over |
| Movement | Unshift new head, pop tail each tick |
| Input | keydown listener + queued nextDir per tick |
| Rendering | canvas.getContext("2d") draw calls each tick |
| Collision | Arithmetic bounds check + Array.some overlap check |
| Food | do-while random placement avoiding snake cells |