McTweak.ai

Learning to Code through Comics

Chapter 9, Episode 2

Scoring System: Points Tracking

← Previous: Game Planning Home Next: Game Elements →

Scoring System: Points Tracking

In this episode, we'll learn how to implement a scoring system for our clicker game, including tracking points and displaying them to the player. A good scoring system is essential for giving players feedback and creating engagement.

The McTweak Team Discusses Scoring

FattyMcTweak
FattyMcTweak:

Listen up, team! Our client needs a scoring system for their game, and as the premium option, I naturally have the most POINTS to make about this! Get it? Points? Because we're talking about scoring systems?

GrumpyMcTweak
GrumpyMcTweak:

Do you have ANY IDEA how many security vulnerabilities exist in typical scoring systems? Global variables EXPOSED to window scope! Unsanitized inputs! No validation! It's like putting your bank account details on a BILLBOARD!

AllyMcTweak
AllyMcTweak:

Relax, Grumpy. It's a learning exercise, not a cryptocurrency exchange. Though I do appreciate your concern for security. Fatty, that joke was about as fresh as week-old code.

CodyMcTweak
CodyMcTweak:

Um, I have a basic scoring implementation... It's not fancy, but it's free... I mean, it works! It just uses a simple variable to track points and updates the DOM. Is that... okay?

TrashyMcTweak
TrashyMcTweak:

BASIC? BASIC?! We're not making Pong in 1972! A REAL scoring system needs particle explosions, combo multipliers, screenshake, and at MINIMUM seven different point types! Who wants to track ONE boring number?!

FattyMcTweak
FattyMcTweak:

Actually, I've been working on a premium scoring algorithm that analyzes player behavior patterns to dynamically adjust point values for maximum engagement and retention metrics. Only $999.99 per month for access.

AshleyMcTweak
AshleyMcTweak:

No one is charging our student for a scoring system, Fatty. And legally speaking, storing player scores could implicate data privacy regulations depending on implementation. We should include proper disclosures.

FattyMcTweak
FattyMcTweak:

Ashley, you always know just the right legal jargon to... wait, did you say "proper disclosures"? That's the most attractive phrase I've heard all day! Tell me more about these... regulations.

GarbageMcTweak
GarbageMcTweak:

*briefly looks up from Elden Ring* Let score = 0; function updateScore(points) { score += points; document.getElementById('score').textContent = score; } *returns to game*

TrashyMcTweak
TrashyMcTweak:

Did... did Garbage just solve the entire problem in one line while still playing his game? That's... THAT'S SO UNFAIR! I had designs for a 12-dimensional scoring matrix with blockchain integration!

AllyMcTweak
AllyMcTweak:

Garbage is right. Sometimes the simplest solution is the best one. Let's start with the basics of a scoring system and then build on it. Our student needs to understand the fundamentals before adding complexity.

1. Basic Score Tracking

Let's start with the simplest implementation of a scoring system. We need:

// Score variables
let score = 0;                    // Current player score
const pointsPerClick = 10;       // Points earned per click

// DOM element to display the score
const scoreDisplay = document.getElementById('score-display');

// Function to update the score
function updateScore(points) {
    score += points;                // Add points to current score
    scoreDisplay.textContent = score;  // Update display with new score
}

// Function to handle clicking the target
function handleClick() {
    updateScore(pointsPerClick);  // Add points when target is clicked
}
CodyMcTweak
CodyMcTweak:

This is the basic approach I was talking about. It's simple, but it works! Just a variable to store the score and a function to update it.

GrumpyMcTweak
GrumpyMcTweak:

AT LEAST there are comments! But that score variable is globally accessible! Any rogue script could modify it! We need ENCAPSULATION!

Try It Out: Basic Score Counter

0

2. Improved Score System with Module Pattern

Let's improve our scoring system by using the module pattern to encapsulate our score data and prevent direct access.

// Score module using IIFE (Immediately Invoked Function Expression)
const ScoreSystem = (function() {
    // Private variables
    let score = 0;                    // Current player score
    let highScore = 0;                // Highest score achieved
    const pointsPerClick = 10;       // Base points per click

    // DOM elements
    const scoreDisplay = document.getElementById('improved-score');
    const highScoreDisplay = document.getElementById('high-score');

    // Private methods
    function updateDisplays() {
        scoreDisplay.textContent = score;
        highScoreDisplay.textContent = highScore;
    }
    
    function checkHighScore() {
        if (score > highScore) {
            highScore = score;  // Update high score if current score is higher
        }
    }

    // Public methods (API)
    return {
        addPoints: function(points) {
            score += points;
            checkHighScore();
            updateDisplays();
            return score;  // Return current score
        },
        
        resetScore: function() {
            score = 0;
            updateDisplays();
        },
        
        getScore: function() {
            return score;  // Getter for current score
        },
        
        getHighScore: function() {
            return highScore;  // Getter for high score
        }
    };
})();

// Example usage
document.getElementById('improved-click-btn').addEventListener('click', function() {
    ScoreSystem.addPoints(10);
});

document.getElementById('improved-reset-btn').addEventListener('click', function() {
    ScoreSystem.resetScore();
});
AllyMcTweak
AllyMcTweak:

This is much better! By using a module pattern with an IIFE, we've encapsulated our score variables and only exposed the methods we want others to access.

GrumpyMcTweak
GrumpyMcTweak:

FINALLY! Some PROPER encapsulation! The score variable is now private and can only be modified through our controlled methods. Much more secure!

Try It Out: Improved Score Counter

Current Score:

0

High Score:

0
Pro Tip:

The module pattern is a powerful way to organize your code and protect your data. By using an IIFE (Immediately Invoked Function Expression), we create a private scope for our variables and only expose what we want through a returned object.

3. Advanced Scoring Features

Now let's add some more advanced features to our scoring system:

// Advanced Score System
const AdvancedScoreSystem = (function() {
    // Private variables
    let score = 0;                    // Current player score
    let highScore = loadHighScore(); // Load high score from storage
    let multiplier = 1;              // Current score multiplier
    let combo = 0;                   // Current combo count
    let comboTimer = null;          // Timer for combo reset
    
    const COMBO_TIMEOUT = 2000;      // Time in ms before combo resets
    const BASE_POINTS = 10;          // Base points per click
    
    // DOM elements
    const scoreEl = document.getElementById('adv-score');
    const highScoreEl = document.getElementById('adv-high-score');
    const multiplierEl = document.getElementById('multiplier');
    const comboEl = document.getElementById('combo');
    
    // Private methods
    function updateDisplays() {
        scoreEl.textContent = score;
        highScoreEl.textContent = highScore;
        multiplierEl.textContent = 'x' + multiplier;
        comboEl.textContent = combo;
    }
    
    function checkHighScore() {
        if (score > highScore) {
            highScore = score;
            saveHighScore();  // Save to local storage
        }
    }
    
    function updateCombo() {
        combo++;  // Increment combo
        
        // Update multiplier based on combo
        if (combo >= 5 && combo < 10) {
            multiplier = 2;
        } else if (combo >= 10 && combo < 20) {
            multiplier = 3;
        } else if (combo >= 20) {
            multiplier = 5;
        }
        
        // Reset combo timer
        if (comboTimer) {
            clearTimeout(comboTimer);
        }
        
        // Set new timer to reset combo if no clicks happen
        comboTimer = setTimeout(function() {
            combo = 0;
            multiplier = 1;
            updateDisplays();
        }, COMBO_TIMEOUT);
    }
    
    function saveHighScore() {
        try {
            localStorage.setItem('highScore', highScore);
        } catch (e) {
            console.warn('Could not save high score to localStorage');
        }
    }
    
    function loadHighScore() {
        try {
            const savedScore = localStorage.getItem('highScore');
            return savedScore ? parseInt(savedScore, 10) : 0;
        } catch (e) {
            return 0;
        }
    }
    
    function createScoreAnimation(pointsAdded) {
        const animation = document.createElement('div');
        animation.textContent = '+' + pointsAdded;
        animation.className = 'score-animation';
        animation.style.position = 'absolute';
        animation.style.color = '#01ffaa';
        animation.style.fontSize = '1.5rem';
        animation.style.fontFamily = 'Orbitron, sans-serif';
        animation.style.textShadow = '0 0 5px rgba(1, 255, 170, 0.7)';
        animation.style.zIndex = '100';
        animation.style.left = (Math.random() * 80 + 10) + '%';
        animation.style.top = '50%';
        animation.style.opacity = '1';
        animation.style.transform = 'translateY(0)';
        animation.style.transition = 'all 1s ease-out';
        
        document.getElementById('animation-container').appendChild(animation);
        
        // Animate and remove
        setTimeout(function() {
            animation.style.opacity = '0';
            animation.style.transform = 'translateY(-50px)';
        }, 50);
        
        setTimeout(function() {
            animation.remove();
        }, 1050);
    }
    
    // Initialize displays
    updateDisplays();
    
    // Public API
    return {
        addPoints: function() {
            const pointsToAdd = BASE_POINTS * multiplier;
            score += pointsToAdd;
            updateCombo();
            checkHighScore();
            updateDisplays();
            createScoreAnimation(pointsToAdd);
            return score;
        },
        
        resetScore: function() {
            score = 0;
            combo = 0;
            multiplier = 1;
            if (comboTimer) {
                clearTimeout(comboTimer);
            }
            updateDisplays();
        },
        
        getStats: function() {
            return {
                score: score,
                highScore: highScore,
                multiplier: multiplier,
                combo: combo
            };
        }
    };
})();
TrashyMcTweak
TrashyMcTweak:

NOW we're talking! Combos! Multipliers! Animations! Local storage! This is the BARE MINIMUM of what a real scoring system should include! Though I still think we need at least three more special bonus types...

FattyMcTweak
FattyMcTweak:

I must admit, this is a premium-quality implementation. The combo system adds that addictive quality that keeps players engaged. Though my version would also include analytics and personalized difficulty adjustment.

Try It Out: Advanced Score System

Score:

0

High Score:

0

Multiplier:

x1

Combo:

0
Important:

While this implementation includes local storage for persistent high scores, remember that localStorage is limited to the current browser and can be cleared by users. For a truly persistent scoring system in a production game, you'd need server-side storage with user authentication.

4. Displaying Scores in the UI

A good scoring system needs a good display. Let's look at some options for presenting scores to players.

Basic Score Display

/* CSS for a basic score display */
.score-container {
  position: absolute;
  top: 10px;
  right: 10px;
  background-color: rgba(0, 0, 0, 0.7);
  border-radius: 5px;
  padding: 10px;
  color: white;
  font-family: 'Orbitron', sans-serif;
  border: 1px solid #18e6ff;
}

.score-label {
  font-size: 14px;
  color: #18e6ff;
  margin-bottom: 5px;
}

.score-value {
  font-size: 24px;
  text-align: center;
}

/* HTML Structure */
<div class="score-container">
  <div class="score-label">SCORE</div>
  <div class="score-value" id="score-display">0</div>
</div>

Implementing a Scoreboard

For games with high scores or leaderboards, a table-based display works well:

// Sample JavaScript for generating a scoreboard
function generateScoreboard(scores) {
    const scoreboardEl = document.getElementById('scoreboard');
    scoreboardEl.innerHTML = '';
    
    // Create table header
    const header = `
        <tr>
            <th>Rank</th>
            <th>Player</th>
            <th>Score</th>
            <th>Date</th>
        </tr>
    `;
    
    // Sort scores (highest first)
    scores.sort((a, b) => b.score - a.score);
    
    // Generate table rows
    let rows = '';
    scores.forEach((score, index) => {
        const rank = index + 1;
        const rowClass = rank <= 3 ? 'top-score' : '';
        
        rows += `
            <tr class="${rowClass}">
                <td>${rank}</td>
                <td>${score.player}</td>
                <td>${score.score}</td>
                <td>${formatDate(score.date)}</td>
            </tr>
        `;
    });
    
    // Update the scoreboard table
    scoreboardEl.innerHTML = header + rows;
}

// Helper function to format dates
function formatDate(dateString) {
    const date = new Date(dateString);
    return date.toLocaleDateString();
}

Example Scoreboard

Rank Player Score Date
1 GarbageMcTweak 9999 2023-05-12
2 FattyMcTweak 8750 2023-05-15
3 AllyMcTweak 7640 2023-05-11
4 TrashyMcTweak 6235 2023-05-10
5 AshleyMcTweak 4120 2023-05-13
6 GrumpyMcTweak 3580 2023-05-09
7 CodyMcTweak 980 2023-05-14
CodyMcTweak
CodyMcTweak:

Why am I always at the bottom of the scoreboard? Even in the examples?

GarbageMcTweak
GarbageMcTweak:

*still playing Elden Ring* It's not about the credits, Cody. It's about efficiency. Also, I'd have a much higher score if this wasn't just an example.

5. Best Practices for Scoring Systems

DO:

  • Use clear, consistent score displays
  • Provide visual and audio feedback when points are earned
  • Consider using local storage for persistence
  • Document your scoring algorithm with clear comments
  • Format large numbers for readability (e.g., 1,000,000)
  • Create a balanced progression system

DON'T:

  • Store scores in global variables (use encapsulation)
  • Make scoring systems too complex for players to understand
  • Forget to validate scores to prevent cheating
  • Create frustrating score mechanics
  • Ignore accessibility in your score displays
  • Forget to back up high scores if they're important
AshleyMcTweak
AshleyMcTweak:

Just to reiterate, if you're storing user data like high scores for a commercial game, make sure your privacy policy discloses this. Data protection laws in many jurisdictions require transparency about what you collect and how you use it.

GrumpyMcTweak
GrumpyMcTweak:

And NEVER trust client-side scores for competitive games! ALWAYS validate on the server! You think users won't try to hack your game? THEY WILL! They'll be injecting JavaScript faster than you can say "high score"!

Your Challenge: Complete Score Display Setup

Now it's your turn! Use what you've learned to create a complete score display setup for your clicker game. Your implementation should include:

Pro Tip:

Start with the basic implementation and incrementally add features. Test each addition thoroughly before moving on to the next one. This approach helps you identify and fix issues early, making your development process smoother.

SnowzieMcTweak

SNOWZIE'S VERDICT

That's an impressive scoring system you've created! You've learned how to track points, implement high scores, create combos and multipliers, and display it all with style. CODE APPROVED!

In the next episode, we'll build on this by creating clickable game elements that will earn these points for the player.

← Previous: Game Planning Home Next: Game Elements →