CHAPTER 9: EDITION 8

Score Display: High Score Tracking

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!

THE SCENE: Score Display Dilemma

TrashyMcTweak
TrashyMcTweak:

Hey everyone! I just beat my high score in "Extreme Office Chair Racing." Seven complete rotations and only two minor injuries!

FattyMcTweak
FattyMcTweak: [mouth full of cake]

That's not a real game. You're just spinning in that chair until you get dizzy and fall off.

TrashyMcTweak
TrashyMcTweak:

Excuse you! It has scoring, injuries, AND a leaderboard. I'm currently competing with myself for first place.

CodyMcTweak
CodyMcTweak: [sighing]

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.

The Problematic Code

CodyMcTweak
CodyMcTweak:

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();
}
FattyMcTweak
FattyMcTweak:

Did you try throwing more resources at it? That's my solution to everything. More memory, more processing power, more CAKE!

GrumpyMcTweak
GrumpyMcTweak: [storms in with his cane]

WHO TOUCHED MY CODE? I SPECIFICALLY LABELED EVERYTHING 'DO NOT TOUCH' AND YET SOMEONE TOUCHED IT!

TrashyMcTweak
TrashyMcTweak: [spinning in chair]

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.

GrumpyMcTweak
GrumpyMcTweak: [face turning red]

YOU INCOMPETENT CHAOS GENERATOR! My scoring system was BULLETPROOF!

AllyMcTweak
AllyMcTweak:

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.

Understanding localStorage

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().

FattyMcTweak
FattyMcTweak:

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.

AshleyMcTweak
AshleyMcTweak:

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.

CodyMcTweak
CodyMcTweak: [desperately]

I'm trying! But localStorage isn't working and I can't figure out why!

GarbageMcTweak
GarbageMcTweak: [appears in the doorway, elkhound at his side]

Did someone say localStorage?

[Everyone suddenly goes silent]

GarbageMcTweak
GarbageMcTweak: [sighs deeply]

Let me guess... you're trying to store a complex object without stringifying it first.

CodyMcTweak
CodyMcTweak: [eyes widening]

Wait, you have to... stringify it?

TrashyMcTweak
TrashyMcTweak: [snorts]

Oh my god, he's trying to store objects directly in localStorage! What's next, trying to teach a fish to ride a bicycle?

AllyMcTweak
AllyMcTweak: [rolling eyes]

Says the guy who once tried to use CSS to implement encryption.

TrashyMcTweak
TrashyMcTweak:

IT WAS INNOVATIVE!

THE SOLUTION: Properly Using localStorage

GarbageMcTweak
GarbageMcTweak:

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();
}

Key Changes:

  1. Use localStorage.setItem(key, value) and localStorage.getItem(key) methods instead of direct property access
  2. Use JSON.stringify() to convert objects/arrays to strings before storage
  3. Use JSON.parse() to convert stored strings back to objects/arrays when retrieving
SnowzieMcTweak
SNOWZIE: [trots in, tail wagging excitedly]

Woof! Woof!

[EVERYONE: SNOWZIE!]

GrumpyMcTweak
GrumpyMcTweak: [suddenly nervous]

The client! Quick, make it look like we know what we're doing!

FattyMcTweak
FattyMcTweak: [hiding cake]

I've been working non-stop on this project! Worth every penny of my premium pricing!

GarbageMcTweak
GarbageMcTweak: [to Snowzie]

What do you think? Should we add JSON.stringify() here and JSON.parse() when we retrieve it?

SnowzieMcTweak
SNOWZIE:

Bark! [paws at keyboard]

GarbageMcTweak
GarbageMcTweak: [nodding]

Exactly what I was thinking.

DEMO: High Score Tracking

Let's see our high score tracking system in action with this simple demo:

TARGET CLICKER

Score: 0
High Scores:
  • No high scores yet... start playing!

Key Concepts: Working with localStorage

1. localStorage Basics:

// Storing data
localStorage.setItem('username', 'player1');

// Retrieving data
const username = localStorage.getItem('username');

// Removing data
localStorage.removeItem('username');

// Clearing all data
localStorage.clear();

2. Storing Objects and Arrays:

// 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;

3. Error Handling:

// 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;
  }
}

Common localStorage Errors:

  • Forgetting to stringify objects/arrays before storage
  • Forgetting to parse strings back to objects/arrays when retrieving
  • Not handling cases where localStorage might be disabled or full
  • Exceeding the storage limit (typically 5MB per domain)

Your Mission: Implement Settings Persistence

Let's add a feature to our game that remembers player settings between game sessions:

  1. Create a settings object with player preferences (name, difficulty level, sound on/off)
  2. Store these settings in localStorage when they change
  3. Retrieve and apply these settings when the game loads
// 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;

SOLUTION HINT:

For the saveSettings() function, you need to use localStorage.setItem('gameSettings', JSON.stringify(gameSettings));

For the loadSettings() function, you need to:

  1. Check if settings exist in localStorage
  2. If they do, use JSON.parse() to retrieve them
  3. Apply them to the current gameSettings object
CodyMcTweak
CodyMcTweak: [amazed]

It... it works! How did you know?

TrashyMcTweak
TrashyMcTweak: [feigning boredom]

I was just about to suggest that. Obviously.

AllyMcTweak
AllyMcTweak: [smirking]

Sure you were, right after you finished implementing blockchain in your chair racing game.

GrumpyMcTweak
GrumpyMcTweak: [inspecting the code]

Well... I suppose this passes the bare minimum security standards. BUT I'M ADDING EIGHT MORE VALIDATION CHECKS LATER!

AshleyMcTweak
AshleyMcTweak: [reviewing contract]

This satisfies the client requirements. Snowzie, would you like to approve the implementation?

SnowzieMcTweak
SNOWZIE:

[excited spinning and happy barks]

FattyMcTweak
FattyMcTweak: [ceremoniously]

The code is committed!

AllyMcTweak
AllyMcTweak: [to audience]

And that's how you implement high score tracking. Remember to stringify your objects before storing them, unlike SOME people...

CodyMcTweak
CodyMcTweak: [embarrassed]

Hey, I'm the free version! What do you expect?

SnowzieMcTweak
SNOWZIE:

[jumps up, licks CODY's face, tail wagging furiously]

GarbageMcTweak
GarbageMcTweak: [to audience]

Up next: Adding sound effects to our game. Let's hope Trashy doesn't try to implement them using only CSS again.

TrashyMcTweak
TrashyMcTweak:

IT COULD WORK!

SUMMARY: High Score Tracking

What We Learned:

  • localStorage allows you to save data between browser sessions
  • localStorage only stores strings, so you need to convert objects and arrays with JSON.stringify()
  • When retrieving data, use JSON.parse() to convert it back to usable JavaScript objects
  • Properly handling localStorage errors ensures your game won't crash if storage is unavailable
  • User settings and high scores are perfect candidates for localStorage persistence

Next Steps:

Continue building your mini-game by:

  1. Completing the settings persistence activity
  2. Adding a more sophisticated scoring system (combos, multipliers)
  3. Creating visual feedback for new high scores
  4. Implementing user profiles with avatars

Remember:

"Always stringify your objects before storing them in localStorage, unless you want to debug this same issue again next week!"

— GarbageMcTweak

Previous Lesson Home Next Lesson