How it works
The game state lives on the server, not in your browser. One background loop plays the game at a fixed 30 moves per second, measured against the real clock, so it runs at the same speed no matter how many people watch. Each move is pushed to every open page over server-sent events. A new viewer receives the full board once; after that a message only carries the last few moves, and a complete snapshot follows every two seconds so a page that missed something catches up by itself. The loop and the model exist only while someone is watching and stop about 20 seconds after the last viewer leaves. A new game starts only when the snake dies or fills the whole board.
The network learned from a teacher, a hand-written strategy that I built to look good as well as play well. It takes the shortest path to the apple, and among equally short paths it prefers the one closest to the straight line from where the apple appeared. In open space that produces diagonal staircases instead of long straight runs. Before committing, the teacher simulates walking the path and eating the apple, and only accepts it if the snake could still reach its own tail afterwards. If not, it follows its tail through open space until a safe path appears. This rule is the reason the early game never loops: the snake always heads straight for the apple.
The network is a plain multilayer perceptron: 105 numbers in, two hidden layers of 256 units, three outputs (turn left, go straight, turn right). The inputs describe, for each of the three moves, whether it is blocked, how much free space it leaves, whether the tail stays reachable, how far the apple is, and how far the move is from the ideal diagonal line. On top come a few global values (apple direction, snake length, steps since the last apple), a 7×7 window of cells around the head, and a coarse 5×5 map of where the body is. None of it depends on the board size, so one network plays 20×20 up to 50×50.
Training used imitation learning with DAgger. First the teacher plays and every position is labeled with its move. Then the network plays, the teacher labels each position the network ends up in, and the network is retrained on everything collected so far. This repeats, and it matters because a network that only ever saw the teacher's perfect positions has never learned how to recover from its own mistakes. In total about six million labeled positions from boards of four sizes were used, many of them started from late-game snapshots so that long snakes are not underrepresented. All of it ran on an ordinary CPU, with the game simulation written in Go and only the weight fitting done in Python.
The trained weights are exported as a JSON file and the forward pass is a few dozen lines of plain Go, so the server needs no machine learning runtime. A test compares the Go output with an independent implementation on 40 recorded positions to make sure both compute the same numbers.
There is one honest caveat. The network copies the teacher's move about 99% of the time, but a single wrong move in a tight spot is enough to trap the snake. On its own it lost 6 of 64 test games before the board was a quarter full. So the server adds a small safety check: the network ranks its three moves, and the best-ranked one that does not block the way back to the tail is played. The network still makes every decision in which more than one move is safe; the check only vetoes a move that would trap it. With the check, 2 of 128 test games ended before 25% fill, against none for the teacher itself.
Against the teacher on 128 fresh 50×50 games of 60,000 moves each, the network needs 36.5 moves per apple for the first 150 apples, the teacher 36.3. Its median score at the move limit is 711, the teacher's 720. As the snake gets long it plays more cautiously and slows down, because taking risks near the end would mostly end the game. It can still die, and you will occasionally see that happen live; then the game restarts and the best score so far stays on the scoreboard.
Ideas for later: let the network search a few moves ahead instead of relying on the check, or train it against the cautious late-game behavior so it finishes boards faster.