Cupcake Clicker
A JavaScript game tutorial by She Codes Australia
Cupcakes are popping up all over the screen — click them before they disappear! In this tutorial you're going to write all the JavaScript that makes this game work, step by step.
By the end you'll have a fully working game and you'll have used variables, functions, events, timers, and the DOM — the core building blocks of JavaScript.
Open your CodePen starter ↗Your CodePen starter already has the HTML and CSS set up — the screens and buttons are there and waiting. Your only job today is to write the JavaScript in the JS panel.
Your Variables
Every game needs to keep track of information — like the player's score and how much time is left. In JavaScript, we use variables to store that information.
A variable is like a labelled box. You give it a name and store a value inside. Later you can read that value, or replace it with something new.
We also need to grab the elements from our HTML page so JavaScript can talk to them. We do this using document.getElementById() — it finds an element by its id and hands it back to us.
Add the following to the top of your JS panel:
// Game state — these values change as the game is played
let score = 0;
let timeLeft = 30;
// Timers — we'll use these to start and stop things later
let gameTimerInterval = null;
let spawnTimerInterval = null;
// Grab the elements we need from the HTML
const screenStart = document.getElementById("screen-start");
const screenGame = document.getElementById("screen-game");
const screenEnd = document.getElementById("screen-end");
const scoreDisplay = document.getElementById("score-display");
const timerDisplay = document.getElementById("timer-display");
const finalScore = document.getElementById("final-score");
const endMessage = document.getElementById("end-message");
const gameArea = document.getElementById("game-area");
const btnStart = document.getElementById("btn-start");
const btnRestart = document.getElementById("btn-restart");
To make sure everything is connected, add this line at the very bottom and check your console:
console.log("Score is:", score);
console.log line.
let score = 0;
let timeLeft = 30;
let gameTimerInterval = null;
let spawnTimerInterval = null;
const screenStart = document.getElementById("screen-start");
const screenGame = document.getElementById("screen-game");
const screenEnd = document.getElementById("screen-end");
const scoreDisplay = document.getElementById("score-display");
const timerDisplay = document.getElementById("timer-display");
const finalScore = document.getElementById("final-score");
const endMessage = document.getElementById("end-message");
const gameArea = document.getElementById("game-area");
const btnStart = document.getElementById("btn-start");
const btnRestart = document.getElementById("btn-restart");
Let's Get Started
Right now the Start button just sits there looking pretty. Let's bring it to life! We're going to write two functions — one to switch between screens, and one to kick off the game when the button is clicked.
A function is a reusable block of code that does one job. You give it a name, and whenever you want that job done, you call it by writing its name followed by ().
Add these two functions below your variables:
// Hide all screens, then show the one we want
function showScreen(screenToShow) {
screenStart.classList.add("hidden");
screenGame.classList.add("hidden");
screenEnd.classList.add("hidden");
screenToShow.classList.remove("hidden");
}
// Start the game — we'll build this up over the next few steps
function startGame() {
score = 0;
timeLeft = 30;
gameArea.innerHTML = "";
showScreen(screenGame);
}
Now we need to listen for the Start button being clicked. Add this at the very bottom of your code:
btnStart.addEventListener("click", startGame);
An event listener watches an element and waits for something to happen — like a click. When it does, it runs a function. Think of it like a doorbell: press the button (the event), and the bell rings (the function runs).
// ... variables from Step 1 ...
function showScreen(screenToShow) {
screenStart.classList.add("hidden");
screenGame.classList.add("hidden");
screenEnd.classList.add("hidden");
screenToShow.classList.remove("hidden");
}
function startGame() {
score = 0;
timeLeft = 30;
gameArea.innerHTML = "";
showScreen(screenGame);
}
btnStart.addEventListener("click", startGame);
Show the Score on Screen
We're in the game area now — but the score and timer in the HUD are still empty. Let's write a function that pushes our variable values onto the screen.
Add this function below showScreen:
function updateHUD() {
scoreDisplay.textContent = score;
timerDisplay.textContent = timeLeft;
}
Then call it inside startGame, right after showScreen(screenGame):
function startGame() {
score = 0;
timeLeft = 30;
gameArea.innerHTML = "";
showScreen(screenGame);
updateHUD(); // ← add this line
}
// ... variables from Step 1 ...
function showScreen(screenToShow) {
screenStart.classList.add("hidden");
screenGame.classList.add("hidden");
screenEnd.classList.add("hidden");
screenToShow.classList.remove("hidden");
}
function updateHUD() {
scoreDisplay.textContent = score;
timerDisplay.textContent = timeLeft;
}
function startGame() {
score = 0;
timeLeft = 30;
gameArea.innerHTML = "";
showScreen(screenGame);
updateHUD();
}
btnStart.addEventListener("click", startGame);
Summon a Cupcake!
Now for the fun part — let's make a cupcake appear on screen! We're going to write a function that creates a new cupcake element and drops it somewhere random in the game area.
Add this function below updateHUD:
function spawnCupcake() {
const cupcakes = ["🧁", "🍰", "🎂", "🍩", "🍪"];
// Pick a random cupcake emoji from the list
const randomIndex = Math.floor(Math.random() * cupcakes.length);
const emoji = cupcakes[randomIndex];
// Create a new <div> and give it the "cupcake" class
const cupcake = document.createElement("div");
cupcake.classList.add("cupcake");
cupcake.textContent = emoji;
// Place it at a random position inside the game area
const gameWidth = gameArea.offsetWidth;
const gameHeight = gameArea.offsetHeight;
cupcake.style.left = Math.floor(Math.random() * (gameWidth - 80)) + "px";
cupcake.style.top = Math.floor(Math.random() * (gameHeight - 80)) + "px";
// Add it to the page so it actually appears
gameArea.appendChild(cupcake);
}
Math.random() gives you a random decimal number between 0 and 1 (like 0.472). Multiply it by a larger number and use Math.floor() to round it down, and you get a random whole number in whatever range you need.
Now call it at the end of startGame so a cupcake appears as soon as the game loads:
function startGame() {
score = 0;
timeLeft = 30;
gameArea.innerHTML = "";
showScreen(screenGame);
updateHUD();
spawnCupcake(); // ← add this line
}
// ... variables from Step 1 ...
function showScreen(screenToShow) { ... }
function updateHUD() {
scoreDisplay.textContent = score;
timerDisplay.textContent = timeLeft;
}
function spawnCupcake() {
const cupcakes = ["🧁", "🍰", "🎂", "🍩", "🍪"];
const randomIndex = Math.floor(Math.random() * cupcakes.length);
const emoji = cupcakes[randomIndex];
const cupcake = document.createElement("div");
cupcake.classList.add("cupcake");
cupcake.textContent = emoji;
const gameWidth = gameArea.offsetWidth;
const gameHeight = gameArea.offsetHeight;
cupcake.style.left = Math.floor(Math.random() * (gameWidth - 80)) + "px";
cupcake.style.top = Math.floor(Math.random() * (gameHeight - 80)) + "px";
gameArea.appendChild(cupcake);
}
function startGame() {
score = 0;
timeLeft = 30;
gameArea.innerHTML = "";
showScreen(screenGame);
updateHUD();
spawnCupcake();
}
btnStart.addEventListener("click", startGame);
Click to Score!
Cupcakes are appearing — but clicking them does nothing yet. Let's add an event listener to each cupcake so that clicking it scores a point and makes it disappear.
Inside your spawnCupcake function, add the following after the gameArea.appendChild(cupcake) line:
cupcake.addEventListener("click", function () {
score = score + 1;
updateHUD();
cupcake.remove();
});
Your spawnCupcake function should now look like this:
function spawnCupcake() {
const cupcakes = ["🧁", "🍰", "🎂", "🍩", "🍪"];
const randomIndex = Math.floor(Math.random() * cupcakes.length);
const emoji = cupcakes[randomIndex];
const cupcake = document.createElement("div");
cupcake.classList.add("cupcake");
cupcake.textContent = emoji;
const gameWidth = gameArea.offsetWidth;
const gameHeight = gameArea.offsetHeight;
cupcake.style.left = Math.floor(Math.random() * (gameWidth - 80)) + "px";
cupcake.style.top = Math.floor(Math.random() * (gameHeight - 80)) + "px";
gameArea.appendChild(cupcake);
cupcake.addEventListener("click", function () {
score = score + 1;
updateHUD();
cupcake.remove();
});
}
They Won't Last Forever
Right now cupcakes sit there patiently waiting forever. That's way too easy! Let's make them disappear after 1.5 seconds if the player doesn't click them in time.
We'll use setTimeout — a built-in JavaScript function that runs some code after a set delay.
setTimeout(function, delay) waits for delay milliseconds, then runs the function once. There are 1000 milliseconds in a second — so 1500ms is 1.5 seconds.
Still inside spawnCupcake, add this block after your click event listener:
setTimeout(function () {
if (cupcake.isConnected) {
cupcake.remove();
}
}, 1500);
isConnected do?
When the timer fires, we check if the cupcake is still on the page. If the player already clicked it, it was already removed — so isConnected will be false and we skip the remove.
function spawnCupcake() {
const cupcakes = ["🧁", "🍰", "🎂", "🍩", "🍪"];
const randomIndex = Math.floor(Math.random() * cupcakes.length);
const emoji = cupcakes[randomIndex];
const cupcake = document.createElement("div");
cupcake.classList.add("cupcake");
cupcake.textContent = emoji;
const gameWidth = gameArea.offsetWidth;
const gameHeight = gameArea.offsetHeight;
cupcake.style.left = Math.floor(Math.random() * (gameWidth - 80)) + "px";
cupcake.style.top = Math.floor(Math.random() * (gameHeight - 80)) + "px";
gameArea.appendChild(cupcake);
cupcake.addEventListener("click", function () {
score = score + 1;
updateHUD();
cupcake.remove();
});
setTimeout(function () {
if (cupcake.isConnected) {
cupcake.remove();
}
}, 1500);
}
Let's Play!
Right now we can only spawn one cupcake at a time. Let's upgrade startGame to keep spawning cupcakes continuously and count down the clock — using setInterval.
setTimeout runs once after a delay. setInterval keeps running over and over on a repeating schedule — until you tell it to stop with clearInterval.
Update your startGame function to look like this — the new lines are the two setInterval blocks:
function startGame() {
score = 0;
timeLeft = 30;
gameArea.innerHTML = "";
showScreen(screenGame);
updateHUD();
// Countdown — ticks every second
gameTimerInterval = setInterval(function () {
timeLeft = timeLeft - 1;
updateHUD();
if (timeLeft <= 0) {
endGame();
}
}, 1000);
// Spawner — drops a new cupcake every 800ms
spawnTimerInterval = setInterval(function () {
spawnCupcake();
}, 800);
}
You'll notice we're calling endGame() when the timer hits zero — we'll write that function in the next step!
Game Over!
The timer hits zero… and nothing happens. Let's fix that! We need an endGame function that stops the timers, shows the end screen, and gives the player a message based on how they did.
Add the following two functions to your code, above startGame:
function getEndMessage(finalScoreValue) {
if (finalScoreValue >= 20) {
return "Amazing! You're a cupcake-catching champion! 🏆";
} else if (finalScoreValue >= 10) {
return "Great job! You've got quick hands! 👏";
} else {
return "Keep practising — those cupcakes are sneaky! 😄";
}
}
function endGame() {
clearInterval(gameTimerInterval);
clearInterval(spawnTimerInterval);
finalScore.textContent = score;
endMessage.textContent = getEndMessage(score);
showScreen(screenEnd);
}
A conditional lets your code make decisions. JavaScript checks the first condition — if it's true, it runs that block and stops. If not, it tries the next one. The final else is the fallback if nothing else matched.
Finally, add the Play Again button listener at the bottom alongside your Start button listener:
btnRestart.addEventListener("click", startGame);
// ── Variables ──────────────────────────────────────────────
let score = 0;
let timeLeft = 30;
let gameTimerInterval = null;
let spawnTimerInterval = null;
// ── Get elements from the page ──────────────────────────────
const screenStart = document.getElementById("screen-start");
const screenGame = document.getElementById("screen-game");
const screenEnd = document.getElementById("screen-end");
const scoreDisplay = document.getElementById("score-display");
const timerDisplay = document.getElementById("timer-display");
const finalScore = document.getElementById("final-score");
const endMessage = document.getElementById("end-message");
const gameArea = document.getElementById("game-area");
const btnStart = document.getElementById("btn-start");
const btnRestart = document.getElementById("btn-restart");
// ── Functions ───────────────────────────────────────────────
function showScreen(screenToShow) {
screenStart.classList.add("hidden");
screenGame.classList.add("hidden");
screenEnd.classList.add("hidden");
screenToShow.classList.remove("hidden");
}
function updateHUD() {
scoreDisplay.textContent = score;
timerDisplay.textContent = timeLeft;
}
function spawnCupcake() {
const cupcakes = ["🧁", "🍰", "🎂", "🍩", "🍪"];
const randomIndex = Math.floor(Math.random() * cupcakes.length);
const emoji = cupcakes[randomIndex];
const cupcake = document.createElement("div");
cupcake.classList.add("cupcake");
cupcake.textContent = emoji;
const gameWidth = gameArea.offsetWidth;
const gameHeight = gameArea.offsetHeight;
cupcake.style.left = Math.floor(Math.random() * (gameWidth - 80)) + "px";
cupcake.style.top = Math.floor(Math.random() * (gameHeight - 80)) + "px";
gameArea.appendChild(cupcake);
cupcake.addEventListener("click", function () {
score = score + 1;
updateHUD();
cupcake.remove();
});
setTimeout(function () {
if (cupcake.isConnected) {
cupcake.remove();
}
}, 1500);
}
function getEndMessage(finalScoreValue) {
if (finalScoreValue >= 20) {
return "Amazing! You're a cupcake-catching champion! 🏆";
} else if (finalScoreValue >= 10) {
return "Great job! You've got quick hands! 👏";
} else {
return "Keep practising — those cupcakes are sneaky! 😄";
}
}
function startGame() {
score = 0;
timeLeft = 30;
gameArea.innerHTML = "";
showScreen(screenGame);
updateHUD();
gameTimerInterval = setInterval(function () {
timeLeft = timeLeft - 1;
updateHUD();
if (timeLeft <= 0) {
endGame();
}
}, 1000);
spawnTimerInterval = setInterval(function () {
spawnCupcake();
}, 800);
}
function endGame() {
clearInterval(gameTimerInterval);
clearInterval(spawnTimerInterval);
finalScore.textContent = score;
endMessage.textContent = getEndMessage(score);
showScreen(screenEnd);
}
// ── Event listeners ─────────────────────────────────────────
btnStart.addEventListener("click", startGame);
btnRestart.addEventListener("click", startGame);