How the Snake AI works: breadth-first search, a tail-safety check and a Hamiltonian cycle

Published September 18, 2026 · LK Forge · numbers from a re-runnable, seeded benchmark

100/100
games the Hamiltonian strategy filled all 400 cells
17%
of the board a greedy search fills before it traps itself
0
deaths for Safe search in 100 games (mean fill 99%)
52.5
moves per apple for the cycle with shortcuts, vs 16 for greedy

The autopilot in AI Snake is not a recording and not a neural network. It is a graph search that runs in your browser on every single move, and you can watch it think: the faint dots on the board are the route it just planned. This post explains the three strategies you can pick from, why the obvious one fails, why the careful one almost succeeds, and why a piece of 19th-century graph theory finishes the job. Every number here comes from a benchmark you can re-run.

The board is a graph

A 20×20 Snake board is a graph with 400 nodes, one per cell, and an edge between each pair of side-by-side cells. The snake's body removes nodes; the apple is a target node. So "which way should the snake turn?" becomes "what is the shortest path from the head to the apple through the free cells?" — a question breadth-first search answers exactly, in time proportional to the number of cells. The engine keeps it plain:

// BFS from the head to a target over free cells; returns the path or null
function bfs(g, from, to, blocked) {
  var prev = {}, seen = {}, q = [from];
  for (var i = 0; i < q.length; i++) {
    for (var d of ['up','down','left','right']) {
      var n = nextCell(g, q[i].x, q[i].y, d);      // null off a walled board
      if (!n || seen[key(n)] || blocked[key(n)]) continue;
      seen[key(n)] = 1; prev[key(n)] = q[i];
      if (n.x === to.x && n.y === to.y) return walkBack(prev, n);
      q.push(n);
    }
  }
  return null;
}

One detail matters more than it looks: when the snake plans, the tip of its tail is not counted as blocked, because by the time the head could arrive there the tail will have moved on. Forgetting that makes the snake refuse perfectly good moves.

Greedy search: right for the next move, wrong for the game

The obvious strategy is to follow that shortest path every time. It is fast — 16 moves per apple in the benchmark — and it is wrong, because it optimises the present and ignores the future. Every apple makes the body one cell longer, and the shortest route to an apple is very often a route into a pocket that the body has just sealed. In 100 games the greedy strategy died every time, at a mean length of 69.7 — about 17% of the board. Its best game reached 110.

Safe search: look before you eat

The fix is to ask a second question before taking the first step: if I follow this whole route and eat the apple, can I still reach my own tail afterwards? The engine answers it literally. It copies the game, plays the planned route forward — growth included — and runs a second BFS from the new head to the new tail.

// virtual walk + tail-reachability check
var v = cloneGame(g);
for (var i = 0; i < path.length; i++) step(v, dirTo(v.snake[0], path[i], v));
if (v.alive && tailReachable(v)) return firstStepOf(path);   // safe to commit
// otherwise: follow the tail along the longest path we can find

Why the tail? Because the tail is the one cell that is guaranteed to become free next move. If the head can reach the tail, the snake can keep circling indefinitely; it can never be boxed in. That single invariant — "always keep a route to your tail" — is the difference between dying and not dying. In 100 games Safe search never died. Its worst game still reached 386 cells; its mean was 395.8, a 99% fill.

When the apple route fails the check, the snake chases its tail — and here a small trick pays off. If it takes the shortest route to the tail it coils up tight and the board stays partitioned. So the engine takes the BFS path and stretches it: wherever two consecutive cells have free neighbours alongside, it replaces the direct step with a three-step detour. Repeating that until nothing can be extended gives a long, meandering route that spreads the body across the board and reopens routes to the apple. That is the "following the tail to open space" line you see under the board.

Where Safe search runs out of proof

Look closely at the benchmark and Safe search has a flaw the deaths hide: it finished only 4 games in 100. In the other 96 it reached the last handful of cells and then circled, for tens of thousands of moves, unable to satisfy its own safety condition. With three or four free cells left, almost no route to the apple leaves a route to the tail afterwards, so the check says "no" forever and the snake orbits. Not dying is not the same as winning. The mean of 198 moves per apple is that orbiting, averaged in.

The Hamiltonian cycle: never trapped, by construction

A Hamiltonian cycle is a closed loop that visits every node of a graph exactly once. If the snake only ever moves forward along such a loop, it can never touch itself, however long it is: every cell ahead of the head is a cell the tail has already vacated, or will vacate before the head arrives. The whole difficulty of Snake dissolves into "follow the loop".

Not every grid has one, but a 20×20 grid does, and it is easy to build. Sweep the rows in alternating directions using columns 1 to 19, and reserve column 0 as the lane back up to the start. Because the height is even, the last sweep ends next to column 0 and the loop closes. (Think of the checkerboard colouring: a cycle must alternate colours, so it needs an even number of cells — 400 is fine.)

// rows snake through columns 1..w-1; column 0 is the return lane (h must be even)
for (y = 0; y < h; y++)
  if (y % 2 === 0) for (x = 1; x < w; x++) order.push({x, y});
  else             for (x = w - 1; x >= 1; x--) order.push({x, y});
for (y = h - 1; y >= 0; y--) order.push({x: 0, y});

The pure cycle is safe but slow: on average the apple is half a loop away, about 200 moves. The engine therefore takes shortcuts, and this is the part with a proof in it. Number the cells along the loop from the head. Since the snake has only ever moved forward along the loop, every body cell has a smaller number than the head — the body lies entirely on the arc behind the head, ending at the tail. So every cell on the arc between the head and the tail is free, and the head may jump to any neighbour whose loop number is ahead of the head but short of the tail. The engine picks the biggest such jump that does not overshoot the apple, keeps one cell of margin for the growth tick, and stops taking shortcuts once the snake fills half the board. Result: 100 of 100 games filled all 400 cells, at 52.5 moves per apple — a quarter of the pure cycle's cost.

The benchmark

The numbers come from scripts/benchmark-snake.mjs, which loads the exact snake-core.js the game ships and self-plays 100 games per strategy on a 20×20 board with seeded apple placement, so the same 100 games are dealt every run. A game ends on death, on filling the board, or at an 80,000-move cap that only the orbiting Safe search ever reaches.

StrategyMean lengthBoard fillPerfect gamesDeathsMoves per apple
Greedy search69.717.4%010016.1
Safe search395.899.0%40198
Hamiltonian cycle400100%100052.5
0 100 200 300 400 69.7395.8400 Greedy searchSafe searchHamiltonian cycle Mean final length, 100 games, 20×20 board (max 400)

The honest summary: the greedy search is what most first Snake bots do, and it explains why they die young. The tail check is the single idea that stops the dying. The Hamiltonian cycle is the idea that finishes the board — and its shortcuts are safe not because they usually work, but because of where the body can and cannot be.

What this has in common with the other LK Forge games

Snake is the second game here built on breadth-first search — Color Lines uses it to find routes for its marbles — and, like 2048's expectimax solver or Connect 4's negamax, the engine is a plain JavaScript file that runs on your device with nothing sent to a server. The same file that plays the game is the one that ran this benchmark.

Watch it fill the board

Pick Hamiltonian cycle, set the speed to Fast, and press Auto.

Open AI Snake