About this game
Serpentine is the classic snake game rebuilt in 3D. Your snake glides smoothly across a board rather than jumping from square to square, and it steers towards your mouse pointer. Eat the glowing food to grow and score; hit a wall or your own body and the run is over. Each piece of food makes you a little longer and a little faster.
It runs in your browser using WebGL through the Three.js library. The sections below cover the controls, the three difficulty levels in exact numbers, and how the game's movement and collisions work.
| Input | What it does |
|---|---|
| Move the mouse | The snake turns towards the pointer |
| WASD or arrow keys | Steer with the keyboard instead |
| Space | Pause and resume |
| R | Restart |
| T | Switch between walls and wrap-around |
| M | Change the control mode |
| Esc | Open the settings |
On a phone or tablet the game starts in joystick mode (drag the on-screen stick). The control button in the corner cycles through joystick, swipe (the snake follows your finger) and mouse control, and the game remembers your choice.
In the settings you can lower the graphics Quality and the number of particles if the game stutters on an older device, and adjust how smoothly the snake turns.
| Easy | Medium | Hard | |
|---|---|---|---|
| Edges | Wrap around | Walls | Walls |
| Board (from the centre) | ยฑ22 | ยฑ20 | ยฑ18 |
| Starting speed | 7 | 10 | 15 |
| Speed added per food | 0.04 | 0.1 | 0.2 |
| Points per food | 10 | 15 | 25 |
| Segments added per food | 3 | 3 | 4 |
After 20 pieces of food, an Easy snake has 200 points and a speed of 7.8; a Medium snake has 300 points and a speed of 12.0; a Hard snake has 500 points, a speed of 19.0, and - once it has finished growing - a length of 81. The speed-up is what makes Hard hard: you are both faster and longer on a smaller board.
In wrap mode, leaving one edge brings you back on the opposite edge, so only your own body can end the run.
A grid snake jumps one square per tick. Serpentine instead keeps the head at an exact position with a direction, and moves it forward in small, equal steps. The game works out its physics exactly 60 times a second, whatever your screen's refresh rate, by collecting the real time that has passed and spending it in fixed slices of 1/60 of a second:
// from the game's source, with its comments trimmed
const FIXED_STEP_SEC = 1 / 60; // 0.016667s baseline
...
if (frameTime > 0.1) frameTime = 0.1;
s_accumulator += frameTime;
...
let maxSubSteps = 8;
while (s_accumulator >= FIXED_STEP_SEC && maxSubSteps > 0) {
update(FIXED_STEP_SEC);
s_accumulator -= FIXED_STEP_SEC;
maxSubSteps--;
}
Inside each step the head moves by speed ร step time:
headPos.addScaledVector(headDir, currentSpeed * dt);
With a step of 1/60 s, a Medium snake at speed 10 moves 0.167 units per step - 10 units a second - and crosses its 40-unit board in 4.0 seconds. On Hard, at speed 15, it is 0.25 units per step and 2.4 seconds from wall to wall. Because the step never changes, the game plays the same on a 60 Hz laptop and a 144 Hz monitor. The 0.1-second cap and the limit of 8 steps a frame stop it racing to catch up after you switch back from another tab.
Turning is smoothed too. The direction is blended a little towards the pointer each step with a
linear interpolation (lerp) and then rescaled to length 1, so the snake curves instead of
snapping.
The body is a list of points. After the head moves, each segment looks at the one in front of it; if it has fallen more than one spacing behind, it is pulled forward along the line between them:
const dist = _segDiff.length();
if (dist > spacing) { _segDiff.normalize().multiplyScalar(dist - spacing); curr.sub(_segDiff); }
Nothing tells the body where to go. Each segment only chases its neighbour, and the whole snake traces the path of the head - the same trick used for ropes, chains and trailing tails in many games.
Two details make the self-collision fair. First, the segments right behind the head are skipped (the first 12 on Easy, 8 on Medium, 5 on Hard); they are always touching the head, and would end every game at once. Second, the test is swept: it checks the whole line the head travelled this frame, not just where it ended up. Without that, a fast snake on a slow frame could jump straight over its own body.
The graphics use WebGL, the browser's interface to your graphics card, through the Three.js library. Three.js provides the scene, the lights, the meshes for the head, body and food, and a perspective camera. Each frame the game moves those objects to match the positions worked out above, and Three.js renders the picture.
The camera does not lock onto the head. Every frame it moves a small fraction of the way towards a point above and behind the snake, which gives the gentle, trailing follow you see. If WebGL is not available, the game shows a message instead of a blank screen.
Your best score for each difficulty, your recent scores and your settings are saved in this browser
with localStorage, so they survive a page reload on the same device.
More: Balloon Blaster in the same arcade. A game keeps track of which mode it is in - menu, playing, paused, game over - the same way a state machine does in hardware.