Originally published on the Sorceress blog.
TL;DR: a fixed-tick grid, a growing body, food on empty cells, death on wall or self-collision. Under ~350 lines of vanilla JS.
Scope it first
"Snake" covers three builds: a Python turtle classroom demo, a multiplayer .io arena (weeks of backend), or a single-player browser snake — fixed grid, fixed tick, arrow/WASD turns, respawning food, growth on eat, death on wall or self. The last is the weekend build.
The grid loop in one minute
-
Input — read arrow/WASD presses into a one-slot
nextDirectionbuffer; reject reverses. - Tick — every N ms, take direction from the buffer, compute the next head cell, advance.
- Eat — if the next cell holds food, push a new head without dropping the tail, then respawn food on a random empty cell. Otherwise push head and shift tail.
- Collide — if the next cell is out of bounds or in the body, stop and show Game Over.
-
Score — increment on eat; optionally shrink
tickMsevery few points so tension ramps.
Portals, wrap-around walls and power-ups are polish added after one apple feels fair.
Two details that bite beginners
- One-slot direction buffer. Writing direction on keydown lets a fast double-tap reverse the snake into itself. Queue one turn per tick, validated against the current direction.
- Food respawn. Pick from the set of empty cells, not a random cell retried until free — the retry loop degenerates as the board fills.
Picking an engine
-
Vanilla JS + canvas — the default. Fill rects per segment, fixed tick, paint with
requestAnimationFrame. - DOM grid with CSS cells — accessible labels per tile; heavy at larger boards.
- Phaser 4 — Scene lifecycle and tweens; you still write the tick and reverse-guard.
Full guide: sorceress.games
Top comments (1)
The one-slot
nextDirectionbuffer is the small detail that keeps fast double-taps from turning a fair game into a mysterious self-collision. Building food placement from the set of empty cells is similarly important once the board gets crowded, where retrying random coordinates can become unpredictable. One edge case worth defining explicitly is whether the next head may enter the current tail cell when the snake is not eating: allowing it requires collision detection to account for the tail moving on that tick, but it also keeps tight endgame paths fair.