Welcome to Chapter 9, Episode 8 of our mini-game development series! Today, we're learning about score display and high score tracking to add that competitive edge to our clicker game. Join the McTweak crew as they tackle the challenges of persistent data storage with localStorage!
Hey everyone! I just beat my high score in "Extreme Office Chair Racing." Seven complete rotations and only two minor injuries!
That's not a real game. You're just spinning in that chair until you get dizzy and fall off.
Excuse you! It has scoring, injuries, AND a leaderboard. I'm currently competing with myself for first place.
Speaking of scores... I'm trying to implement this high score tracking system for our clicker game, but it's not working. The score displays initially but then it just... disappears when the game restarts.
Here's my current code for tracking scores. What's wrong with it?
// Game variables let score = 0; let highScores = []; // Update score function function updateScore() { score++; const scoreDisplay = document.getElementById('score-display'); scoreDisplay.textContent = `Score: ${score}`; } // Save high score function function saveHighScore() { // Create score object with current score and timestamp const scoreData = { score: score, date: new Date().toISOString() }; // Add to high scores array highScores.push(scoreData); // Sort high scores (highest first) highScores.sort((a, b) => b.score - a.score); // Keep only top 5 scores highScores = highScores.slice(0, 5); // Save to localStorage localStorage.highScores = highScores; displayHighScores(); } // Display high scores function function displayHighScores() { const highScoresList = document.getElementById('high-scores-list'); highScoresList.innerHTML = ''; // Load from localStorage if (localStorage.highScores) { highScores = localStorage.highScores; // Create list items for each high score highScores.forEach((scoreData, index) => { const li = document.createElement('li'); const date = new Date(scoreData.date); li.textContent = `${index + 1}. Score: ${scoreData.score} - ${date.toLocaleDateString()}`; highScoresList.appendChild(li); }); } } // Initialize the game function initGame() { score = 0; const scoreDisplay = document.getElementById('score-display'); scoreDisplay.textContent = `Score: ${score}`; // Try to load high scores if (localStorage.highScores) { highScores = localStorage.highScores; } displayHighScores(); }
Did you try throwing more resources at it? That's my solution to everything. More memory, more processing power, more CAKE!
WHO TOUCHED MY CODE? I SPECIFICALLY LABELED EVERYTHING 'DO NOT TOUCH' AND YET SOMEONE TOUCHED IT!
Define "touch." If you mean "completely rewrote your overly-complicated scoring function with something that actually works in this century," then yeah, I touched it.
YOU INCOMPETENT CHAOS GENERATOR! My scoring system was BULLETPROOF!
Your scoring system was bulletproof because it was written in COBOL and no one could understand it enough to break it. It's the security through obscurity approach.
Before we fix the code, let's understand what localStorage is and how it works:
The key issue in Cody's code: When storing objects or arrays in localStorage, you must convert them to strings using JSON.stringify(), and when retrieving them, convert them back using JSON.parse().
If I'm paying $1000 a month for a service, the scores better not just "disappear." That's like ordering a cake and getting... well, half a cake. Which is still fine, actually, as long as it's the good half.
The client specifically requested score persistence across game sessions in section 7, paragraph 4, subclause B of the contract. If we don't implement this correctly, we're in breach.
I'm trying! But localStorage isn't working and I can't figure out why!
Did someone say localStorage?
[Everyone suddenly goes silent]
Let me guess... you're trying to store a complex object without stringifying it first.
Wait, you have to... stringify it?
Oh my god, he's trying to store objects directly in localStorage! What's next, trying to teach a fish to ride a bicycle?
Says the guy who once tried to use CSS to implement encryption.
IT WAS INNOVATIVE!
Show me what you've got.
Here's how to fix the localStorage handling in our high score tracking system:
// Game variables let score = 0; let highScores = []; // Update score function function updateScore() { score++; const scoreDisplay = document.getElementById('score-display'); scoreDisplay.textContent = `Score: ${score}`; } // Save high score function function saveHighScore() { // Create score object with current score and timestamp const scoreData = { score: score, date: new Date().toISOString() }; // Add to high scores array highScores.push(scoreData); // Sort high scores (highest first) highScores.sort((a, b) => b.score - a.score); // Keep only top 5 scores highScores = highScores.slice(0, 5); // Save to localStorage - FIXED: Stringify the object first localStorage.setItem('highScores', JSON.stringify(highScores)); displayHighScores(); } // Display high scores function function displayHighScores() { const highScoresList = document.getElementById('high-scores-list'); highScoresList.innerHTML = ''; // Load from localStorage - FIXED: Parse the stored string back to object const storedScores = localStorage.getItem('highScores'); if (storedScores) { highScores = JSON.parse(storedScores); // Create list items for each high score highScores.forEach((scoreData, index) => { const li = document.createElement('li'); const date = new Date(scoreData.date); li.textContent = `${index + 1}. Score: ${scoreData.score} - ${date.toLocaleDateString()}`; highScoresList.appendChild(li); }); } } // Initialize the game function initGame() { score = 0; const scoreDisplay = document.getElementById('score-display'); scoreDisplay.textContent = `Score: ${score}`; // Try to load high scores - FIXED: Proper localStorage handling const storedScores = localStorage.getItem('highScores'); if (storedScores) { highScores = JSON.parse(storedScores); } displayHighScores(); }
localStorage.setItem(key, value) and localStorage.getItem(key) methods instead of direct property accessJSON.stringify() to convert objects/arrays to strings before storageJSON.parse() to convert stored strings back to objects/arrays when retrieving
Woof! Woof!
[EVERYONE: SNOWZIE!]
The client! Quick, make it look like we know what we're doing!
I've been working non-stop on this project! Worth every penny of my premium pricing!
What do you think? Should we add JSON.stringify() here and JSON.parse() when we retrieve it?
Bark! [paws at keyboard]
Exactly what I was thinking.
Let's see our high score tracking system in action with this simple demo:
// Storing data localStorage.setItem('username', 'player1'); // Retrieving data const username = localStorage.getItem('username'); // Removing data localStorage.removeItem('username'); // Clearing all data localStorage.clear();
// Storing an object const playerData = { name: 'Player1', level: 5, items: ['sword', 'shield'] }; // Convert to string and store localStorage.setItem('playerData', JSON.stringify(playerData)); // Retrieve and convert back to object const storedData = localStorage.getItem('playerData'); const retrievedPlayer = storedData ? JSON.parse(storedData) : null;
// Safe retrieval with error handling function getStoredObject(key) { try { const data = localStorage.getItem(key); return data ? JSON.parse(data) : null; } catch (error) { console.error(`Error retrieving ${key} from localStorage:`, error); return null; } }
Let's add a feature to our game that remembers player settings between game sessions:
// Game settings object let gameSettings = { playerName: 'Player', difficulty: 'medium', soundEnabled: true }; // TODO: Write function to save settings to localStorage function saveSettings() { // Your code here - Store gameSettings in localStorage } // TODO: Write function to load settings from localStorage function loadSettings() { // Your code here - Load settings from localStorage } // Function to update settings when form changes function updateSettings() { const nameInput = document.getElementById('player-name'); const difficultySelect = document.getElementById('difficulty'); const soundToggle = document.getElementById('sound-toggle'); gameSettings.playerName = nameInput.value; gameSettings.difficulty = difficultySelect.value; gameSettings.soundEnabled = soundToggle.checked; saveSettings(); displayCurrentSettings(); } // Show current settings in UI function displayCurrentSettings() { const settingsDisplay = document.getElementById('current-settings'); settingsDisplay.innerHTML = ` Player: ${gameSettings.playerName}
Difficulty: ${gameSettings.difficulty}
Sound: ${gameSettings.soundEnabled ? 'On' : 'Off'} `; } // Initialize settings on page load function initSettings() { loadSettings(); const nameInput = document.getElementById('player-name'); const difficultySelect = document.getElementById('difficulty'); const soundToggle = document.getElementById('sound-toggle'); nameInput.value = gameSettings.playerName; difficultySelect.value = gameSettings.difficulty; soundToggle.checked = gameSettings.soundEnabled; displayCurrentSettings(); } // HINT: Use JSON.stringify() and JSON.parse() with localStorage window.onload = initSettings;
For the saveSettings() function, you need to use localStorage.setItem('gameSettings', JSON.stringify(gameSettings));
For the loadSettings() function, you need to:
JSON.parse() to retrieve them
It... it works! How did you know?
I was just about to suggest that. Obviously.
Sure you were, right after you finished implementing blockchain in your chair racing game.
Well... I suppose this passes the bare minimum security standards. BUT I'M ADDING EIGHT MORE VALIDATION CHECKS LATER!
This satisfies the client requirements. Snowzie, would you like to approve the implementation?
[excited spinning and happy barks]
The code is committed!
And that's how you implement high score tracking. Remember to stringify your objects before storing them, unlike SOME people...
Hey, I'm the free version! What do you expect?
[jumps up, licks CODY's face, tail wagging furiously]
Up next: Adding sound effects to our game. Let's hope Trashy doesn't try to implement them using only CSS again.
IT COULD WORK!
Continue building your mini-game by:
"Always stringify your objects before storing them in localStorage, unless you want to debug this same issue again next week!"
— GarbageMcTweak