Find the movement clock
Download Neon Snake and open index.html in an editor. Search for MOVE_INTERVAL_MS. The original value is 145: the snake moves one cell roughly every 145 milliseconds. The canvas draws more often, but drawing a frame does not automatically advance the snake. This separation keeps the rules on a grid while the browser handles repainting.
For a first experiment, change 145 to 190, reload, and play a full round. The larger interval slows the snake. Leave the grid alone for now. The starter draws a 400-pixel board using 20-pixel cells, and several drawing values assume that geometry. Changing only GRID would leave the rendered board out of step with the rules.
Understand one movement step
The first item in snake is its head. Every step builds a new head from the current direction. If that cell contains food, the new head is inserted and the tail stays in place. Otherwise the tail is removed. A food pickup adds 10 points. The body collision check deliberately treats these two cases differently.
const eat = head.x === food.x && head.y === food.y;
const body = eat ? snake : snake.slice(0, -1);
const hitsBody = body.some(p => p.x === head.x && p.y === head.y);This is an explanation of the existing check, rather than an extra block to paste in. A non-growing snake may enter the square its tail is leaving on that same step. Including that tail in every collision test would reject a legal move. When eating, the tail stays, so the whole body must be checked.
Add a gradual speed increase
For a second experiment, restore the original interval and replace the whole update function with the version below. It calculates an interval from the score. Each food pickup removes five milliseconds, down to a floor of 75. Keeping the minimum prevents later rounds from becoming arbitrarily fast.
function update(dt) {
acc += dt * 1000;
let interval = Math.max(75, MOVE_INTERVAL_MS - score / 2);
while (acc >= interval && running) {
acc -= interval;
step();
interval = Math.max(75, MOVE_INTERVAL_MS - score / 2);
}
}The accumulator carries leftover time into the next frame. Subtract the interval instead of setting the accumulator to zero, which would discard that remainder. Recalculate after a step because eating may have changed the score. Restart already resets the score and accumulator, so it also returns this progression to its opening speed.
Keep turns predictable
The steer function blocks a reversal into the snake's neck and accepts only one direction change per movement step. That second check matters when a player presses two keys between steps. Removing it can allow a right-moving snake to receive up, then left, before it moves; the resulting left turn runs into its own body.
Try holding a direction key and then pressing its opposite. The snake should keep moving in its legal direction. Try rapid corner inputs next. A buffered queue is a possible future feature, but it needs explicit rules about how many turns are stored and when they are consumed.
Test food and the end of a round
placeFood constructs a list of free cells, then chooses one. This avoids placing food inside the trail and also handles a completely full board. Replacing it with repeated random guesses needs a stopping condition, otherwise a full board can trap the program in a loop.
- Reach each wall and confirm the round ends once.
- Eat food at a corner and verify the score increases by 10.
- Restart after a fast round and confirm the opening speed returns.
- Use both WASD and the direction buttons to check identical turn rules.
For your next change, add one fixed obstacle. Store its grid coordinate, exclude it from food placement, include it in head collision, and draw it from the same data. That exercise touches all parts of a new rule without changing the board geometry.