Polish and Finishing Touches
The game is fully playable, but let’s add two finishing touches: progressive difficulty (the game speeds up as you score) and a persistent high score using localStorage. Both features show a benefit of the event-driven architecture. We can add them without changing any of the existing logic.
Progressive Difficulty
Right now the game runs at a fixed tick rate. Let’s make it speed up every time the player scores 50 points.
In src/engine.js, replace the single TICK_RATE constant with three:
- const TICK_RATE = 150;
+ const BASE_TICK_RATE = 150;
+ const MIN_TICK_RATE = 75;
+ const SPEED_INCREMENT = 5;
Add a helper function that computes the current tick rate based on the score:
function currentTickRate() {
const speedUps = Math.floor(score / 50);
return Math.max(MIN_TICK_RATE, BASE_TICK_RATE - speedUps * SPEED_INCREMENT);
}
Every 50 points, the interval drops by 5ms. Math.max keeps it from ever going below 75ms, which is twice the starting speed.
Add a helper to restart the interval at the new speed:
function restartInterval() {
clearInterval(intervalId);
intervalId = setInterval(tick, currentTickRate());
}
Call restartInterval() when the snake eats food, inside the update function:
if (newHead.x === food.x && newHead.y === food.y) {
score += 10;
food = placeFood();
emit("snake:eat", { score });
+ // Speed up the game
+ restartInterval();
} else {
Also update start and togglePause to use currentTickRate() instead of the old constant:
export function start() {
reset();
running = true;
emit("snake:start");
emitState();
- intervalId = setInterval(tick, TICK_RATE);
+ intervalId = setInterval(tick, currentTickRate());
}
export function togglePause() {
if (gameOver || !running) return;
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
emit("snake:pause");
} else {
- intervalId = setInterval(tick, TICK_RATE);
+ intervalId = setInterval(tick, currentTickRate());
emit("snake:resume");
}
}
High Score with localStorage
Now let’s add a persistent high score display. This is a UI feature, so we will add it entirely through a new event listener.
Add a <span> for the high score in index.html:
<div id="hud">
<span id="score">Score: 0</span>
+ <span id="high-score">Best: 0</span>
<button id="start-btn">Start</button>
</div>
Style it in src/style.css. Combine it with the score rule and add a muted color:
- #score {
+ #score,
+ #high-score {
color: #eee;
font-family: monospace;
font-size: 1.2rem;
}
+ #high-score {
+ color: #aaa;
+ }
In src/ui.js, grab the element and load the saved high score:
const scoreDisplay = document.getElementById("score");
+ const highScoreDisplay = document.getElementById("high-score");
const startBtn = document.getElementById("start-btn");
+ // Load high score from localStorage
+ let highScore = Number(localStorage.getItem("snake-high-score")) || 0;
+ highScoreDisplay.textContent = `Best: ${highScore}`;
Then update the snake:die listener to check and save the high score:
document.addEventListener("snake:die", (e) => {
board.classList.add("game-over");
startBtn.textContent = "Play Again";
+
+ // Update high score
+ if (e.detail.score > highScore) {
+ highScore = e.detail.score;
+ localStorage.setItem("snake-high-score", highScore);
+ highScoreDisplay.textContent = `Best: ${highScore}`;
+ }
});
Notice that adding the high score feature required:
- 0 changes to
engine.js(it already emitssnake:diewith the score) - 0 changes to
main.js - Only a new listener in
ui.jsand a new element in HTML
Run the dev server and play a few rounds. The game should speed up as you score, and your best score should persist across page refreshes:

Checkpoint: Commit your progress.
git add .
git commit -m "snake-11: Add progressive speed and persistent high score"
git push