TinyGames · how it works

Serpent

Eat, grow, and run out of room. The whole game is the tension between the space you have and the length you have earned.

Draft. It is built, compiled and tested like every other game, and it is not on the hub: "draft": true in build/games.json keeps it off the cards, out of the sitemap and without a readme page until the rules are real. Take the flag off when it is worth playing.

How it is put together

snake.ts is the rules and touches nothing - no canvas, no engine, no storage. serpent.ts is everything that draws or listens. The split is what makes the hard half testable: node test-snake.js runs against the shipped, minified JavaScript rather than the source, which is how a minification fault gets caught.

Every screen except the run itself belongs to the shared shell, so this game has one scene of its own, play. The menu, the pause card, the game-over screen and the high-score table come from src/shared/scenes.ts and look and behave the same here as in the other twenty.

The three faults a snake hides

It looks like the simplest game here. It is not, and all three of its classic faults are invisible while the snake is short - which is to say, invisible exactly when nobody is watching.

The tail is not in the way. On a step that does not grow, the tail vacates its cell at the same moment the head arrives. Testing the head against the whole body kills the player for moving into a square that is empty by the time they reach it, and it only happens when the snake is curled tightly - when the run was going well. The test builds a 2x2 ring and steps the head onto the tail; it must live. The same move while growing must kill, because then nothing vacates.

Food is not placed by guessing. "Pick a cell, try again if it is inside the snake" is fine at length 5 and unusable at length 380: the expected number of tries is the reciprocal of the free fraction, so the game stutters precisely as the player approaches their best run, and on a full board it never returns. Free cells are collected and one is chosen - one pass, always. The test fills a board to its last cell and asks; it must answer immediately, and report -1 rather than loop when nothing is free. Uniformity is checked separately over forty thousand placements, because a placement that merely avoids the snake can still favour the top-left corner.

A reversal is not a turn. Pressing left while travelling right walks into your own neck. Refusing the input is right; treating it as death punishes a slip of the thumb. Turns queue rather than apply, and each is judged against the direction actually travelled - otherwise up-then-left from a snake going right composes into a reversal out of two legal-looking presses.

Playing it

Swipe anywhere, or use the arrow keys or WASD. The whole canvas is the control because a drawn d-pad would cover the board; a flick resolves on release along the larger axis, and anything shorter than the threshold is a tap and goes to the shell instead, which is how the menu still works on a phone.

The snake keeps its own clock rather than stepping once per frame, so a 120Hz phone plays the same game as a 60Hz laptop. Each meal closes a fraction of the gap to the fastest step rather than subtracting a fixed amount - subtracting reaches zero eventually, and a snake that steps every frame is not difficult, it is broken.

Still to do