Lesson: Object-Oriented Game Development – Whack-a-Mole

In this lesson we will study how Object-Oriented Programming (OOP) principles can be applied to build a fun game: Whack-a-Mole.
We will not just play the game, but learn the design concepts behind it, how the code is organized, and why OOP is powerful for games and larger software projects.

This lesson combines:

  • Teaching text to explain programming ideas step by step.
  • Code examples in JavaScript.
  • FRQ-style practice scattered throughout to simulate AP CSP exam thinking.
  • Connections to Big Ideas (abstraction, data, algorithms, impact).

Learning Objectives

By the end of this lesson, you should be able to:

  • Define and identify OOP principles (encapsulation, inheritance, polymorphism, composition).
  • Explain how OOP helps organize complex programs like games.
  • Understand how local storage can persist data beyond runtime.
  • Analyze and modify code to meet new requirements (FRQs).

The Whack-a-Mole Concept

In Whack-a-Mole, the computer manages a grid of holes. Randomly, a mole (or sometimes a power-up) appears in a hole. The player must click or tap the mole before it disappears.

Key Rules:

  • Moles give points when clicked.
  • Power-ups may give bonus points or extra time.
  • The game tracks score and saves the high score using local storage.

This simple idea is perfect for studying OOP: each part of the game (holes, moles, power-ups, score) can be represented as an object.

OOP Structure

We will design the game using classes:

  • Game → manages the entire program (composition).
  • Hole → represents a place where something can appear.
  • Entity (abstract base class) → shared logic for anything that appears in a hole.
  • Mole → a specific type of Entity.
  • PowerUp → another type of Entity.

👉 This is an example of inheritance: both Mole and PowerUp inherit from Entity.

👉 This is also composition: the Game is composed of multiple Hole objects.

Spawning Entities

The game must randomly choose where to put new moles or power-ups. This is handled inside the Game class.

Here is the method that chooses an empty hole and places something in it:

spawnEntity() {
    let emptyHoles = this.holes.filter(h => !h.entity);
    if (emptyHoles.length > 0) {
        let hole = emptyHoles[Math.floor(Math.random() * emptyHoles.length)];
        if (Math.random() < 0.8) {
            hole.entity = new Mole(hole, "normal");
        } else {
            hole.entity = new PowerUp(hole, "bonus");
        }
    }
}

Teaching Explanation

  • filter() → removes all holes that already have something inside.
  • Math.random() → makes the game unpredictable.
  • If chance < 0.8, spawn a mole (80% chance). Otherwise, spawn a power-up (20% chance).

👉 Notice how the Game class does not directly create pixels on screen. Instead, it uses the object system: Hole manages its own location, Entity manages its own lifespan, etc.

📝 FRQ Checkpoint 1

Prompt:

  1. Explain why it is better for the Game class to delegate work to the Hole and Entity classes instead of doing everything itself.
  2. Modify the logic so that GoldenMole appears 5% of the time, worth double points.
  3. Justify how this change demonstrates polymorphism.

Handling Player Input

When a player clicks, the game checks whether the click intersects with a mole or power-up. If so, it calls the object’s onHit() method.

handleClick(mx, my) {
    this.holes.forEach(hole => {
        if (hole.entity &&
            mx >= hole.x - hole.size/2 && 
            mx <= hole.x + hole.size/2 &&
            my >= hole.y - hole.size/2 &&
            my <= hole.y + hole.size/2) {
            hole.entity.onHit(this);
        }
    });
}

Teaching Explanation

  • Each Entity object controls what happens when hit (encapsulation).
  • The Game just forwards the event.
  • Polymorphism: onHit() means something different for a Mole (gain points) versus a PowerUp (bonus effects).

Debugging

Debugging is a critical part of learning to code games. Below are step-by-step debugging tips for different parts of the Whack-a-Mole lesson. Use these to find and fix problems as you build.


🟢 Game Setup Issues

  • Problem: Nothing appears on the canvas.
    Check: Did you correctly reference the <canvas id="gameCanvas"> in your HTML?
    Check: Did you create the Game object at the bottom with new Game("gameCanvas")?
    Tool: Use console.log(this.canvas) inside the Game constructor to confirm the canvas is found.

🟡 Entity (Mole/PowerUp) Bugs

  • Problem: Moles don’t disappear.
    Check: Is Entity.update() being called each frame?
    Check: Does the code set this.hole.entity = null when the mole times out or gets hit?
    Tool: Add console.log("Mole expired") inside the update to verify.

📝 FRQ Checkpoint 2

Prompt:

  1. Suppose the player clicks slightly outside the hole’s boundary. How does the code prevent counting that as a hit?
  2. Write pseudocode for a new entity Bomb that subtracts points when hit.
  3. Explain how this uses inheritance and overriding methods.

Persisting High Scores with Local Storage

Without local storage, scores vanish when you refresh the page. Local storage lets us keep the high score.

Example implementation:

this.highScore = localStorage.getItem("highScore") || 0;

addScore(points) {
    this.score += points;
    if (this.score > this.highScore) {
        this.highScore = this.score;
        localStorage.setItem("highScore", this.highScore);
    }
}

Debugging

🔵 Score & Storage

  • Problem: High score never saves.
    Check: Is localStorage.setItem("whackAMoleHighScore", this.highScore); called at game over?
    Check: Is this.highScore = localStorage.getItem("whackAMoleHighScore") || 0; in the constructor?
    Tool: Open DevTools → Application → Local Storage to see if the key is set.

  • Problem: Multiplier doesn’t reset.
    Check: In updateGame(), does the code correctly check if (Date.now() > this.multiplierEnd)?

Teaching Explanation

  • localStorage.setItem(key, value) → saves data to browser storage.
  • localStorage.getItem(key) → retrieves saved data.
  • This persists across sessions (until the user clears it).

👉 This is an example of data abstraction: we don’t worry about how the browser stores data, we just use a simple API.

📝 FRQ Checkpoint 3

Prompt:

  1. Explain why local storage is useful in a game but not always appropriate in secure applications.
  2. Modify the game so it also saves the last score in addition to high score.
  3. How does this relate to Big Idea 5: Impact of Computing?

Big Ideas Connections

  • Big Idea 1: Creative Development → Using OOP to design reusable code.
  • Big Idea 2: Data → High score stored in local storage.
  • Big Idea 4: Algorithms → Random entity spawning and event handling.
  • Big Idea 5: Impact → Ethical considerations of storing persistent data.

This project demonstrates how classroom theory applies to real-world interactive applications.

Reflection Questions

  • How does encapsulation make debugging easier?
  • How could inheritance reduce duplicated code in larger games?
  • Why is polymorphism important when designing new entity types?
  • What are some limitations of local storage for more advanced games?

👉 Answer these questions in your notebook to solidify your learning.

Hack Homework

Try these hacks to extend your Whack-a-Mole game and deepen your understanding of OOP:

  1. Add a New Mole Type
    • Create a blue mole worth +50 points but also speeds up the game.
  2. Change the Grid Size
    • Modify the game to support a 4×4 grid instead of 3×3.
  3. Add Sound Effects
    • Play a hit sound when a mole is clicked and a game over sound when lives reach 0.
  4. Track Combos
    • Add a combo counter that rewards extra points if you hit 3 or more moles in a row without missing.
  5. FRQ-style Reflection
    • Explain in 2–3 sentences how the game uses inheritance and polymorphism.
  6. Local Storage Challenge
    • Store the player’s last 5 game scores in localStorage and display them on the game over screen.