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 also 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:
You should have made a copy of the game to follow along and complete the hacks at the end of the lesson.
By the end of this lesson, you should and will be able to:
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.
This simple idea is perfect for studying OOP: each part of the game (holes, moles, power-ups, score) can be represented as an object.
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.
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");
}
}
}
Cell In[5], line 2
let emptyHoles = this.holes.filter(h => !h.entity);
^
SyntaxError: invalid syntax. Perhaps you forgot a comma?
filter() → removes all holes that already have something inside.Math.random() → makes the game unpredictable.👉 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.
Prompt:
Game class to delegate work to the Hole and Entity classes instead of doing everything itself.GoldenMole appears 5% of the time, worth double points.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);
}
});
}
Entity object controls what happens when hit (encapsulation).Game just forwards the event.onHit() means something different for a Mole (gain points) versus a PowerUp (bonus effects).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. There will be debugging steps all through the lesson.
<canvas id="gameCanvas"> in your HTML?Game object at the bottom with new Game("gameCanvas")?console.log(this.canvas) inside the Game constructor to confirm the canvas is found.Entity.update() being called each frame?this.hole.entity = null when the mole times out or gets hit?console.log("Mole expired") inside the update to verify.Prompt:
Bomb that subtracts points when hit.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);
}
}
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)?
localStorage.setItem(key, value) → saves data to browser storage.localStorage.getItem(key) → retrieves saved data.👉 This is an example of data abstraction: we don’t worry about how the browser stores data, we just use a simple API.
Prompt:
This project demonstrates how classroom theory applies to real-world interactive applications.
Try these hacks to extend your Whack-a-Mole game and deepen your understanding of OOP:
localStorage and display them on the game over screen.