Game States
Are we seriously still working on this clicker game? I've already charged the client for THREE premium gaming engines while you all struggle with basic JavaScript!
Hey, I may be the free version, but at least I don't crash every time someone clicks a button. Unlike your 'premium' code that collapses faster than my self-esteem during code reviews.
Speaking of crashing, have you seen the state machine these kids just submitted? Game states COMPLETELY hardcoded! Know what happens when you hit the reset button? BOOM, permadeath! Not even a confirmation dialog! It's like watching someone code with oven mitts on!
RESET BUTTON?! They implemented a RESET button without SECURITY PROTOCOLS?! Do you know how many games have been COMPROMISED because someone accidentally hit reset during a high score? THIS is how SkyNet starts - with an unsecured reset button!
Relax, Grumpy. It's a clicker game, not nuclear launch codes. Though I do agree we need a proper state management system. Users need to know if the game is ready, playing, paused, or game over. And yes, we absolutely need confirmation before reset.
Actually, legally speaking, we're required to save user scores before any reset. If we don't maintain score history, we could be in violation of the "Temporary Electronic Achievement Conservation Act" - a completely real law I definitely didn't just make up to mess with all of you.
*sighs deeply* I literally paused at the Elden Ring final boss for this? Game states are simple: enumerate your possible states, create a state machine to manage transitions, and never modify game variables outside state change functions. Now if you'll excuse me, I have a Malenia fight to finish.
*excited tail wagging* WOOF! *paws at reset button frantically*
NO! BAD DOG! STEP AWAY FROM THE RESET BUTTON! That's it, I'm adding biometric security to this game RIGHT NOW!
Learning Objectives
By the end of this episode, you'll be able to:
- Implement proper game state management (ready, playing, paused, game over)
- Create start/stop controls with appropriate state transitions
- Build a reset function with confirmation to prevent accidental data loss
- Update the game UI to reflect the current state
Game State Theory
Let's talk about game states, which are essential for managing your game's flow. For our clicker game, we need four basic states:
- READY: Before the game starts
- PLAYING: While the game is active
- PAUSED: When the game is temporarily stopped
- GAME_OVER: When time runs out or other end conditions are met
// Define game states as constants const GAME_STATE = { READY: 'ready', PLAYING: 'playing', PAUSED: 'paused', GAME_OVER: 'gameover' }; // Track current game state let currentState = GAME_STATE.READY; // Game variables let score = 0; let timeRemaining = 30; let timerId = null; let targetId = null;
Using string constants like 'ready' instead of just numbers? That's actually... not terrible. Strings are self-documenting, unlike those garbage numeric state codes everyone used in the 90s. Fine, I'll admit it's decent code.
The state variables should be FROZEN to prevent accidental modifications! What's stopping someone from directly changing GAME_STATE.READY = 'something_else'?! NOTHING! That's what! CONSTANT VIGILANCE!
// Define game states as frozen object for security const GAME_STATE = Object.freeze({ READY: 'ready', PLAYING: 'playing', PAUSED: 'paused', GAME_OVER: 'gameover' });
State Transitions
Game state isn't just about knowing what state you're in - it's about controlling transitions between states. Not all transitions are valid. For example, you can't go from GAME_OVER directly to PLAYING without resetting first.
State Transition Diagram
┌─────────┐ start() ┌─────────┐ pause() ┌─────────┐
│ READY │─────────────────> │ PLAYING │─────────────────> │ PAUSED │
└─────────┘ └─────────┘ └─────────┘
^ │ │
│ │ │
│ timeOut() resume()
│ │ │
│ ▼ │
│ ┌─────────┐ │
│ │GAME_OVER│ │
│ └─────────┘ │
│ │ │
└─────────────────────────────┴─────────────────────────────┘
reset()
ASCII art? Really? For what we're charging clients, we could have created a fully animated 3D hologram state transition diagram! But I guess this works too... if you're into that retro aesthetic.
// Change game state with proper logic function changeGameState(newState) { const prevState = currentState; currentState = newState; // Update UI to reflect state change updateGameUI(); // Log state transition for debugging console.log(`Game state changed from ${prevState} to ${newState}`); } // Start the game function startGame() { if (currentState === GAME_STATE.READY || currentState === GAME_STATE.PAUSED) { changeGameState(GAME_STATE.PLAYING); // Start the timer timerId = setInterval(() => { timeRemaining--; updateTimerDisplay(); if (timeRemaining <= 0) { endGame(); } }, 1000); // Start creating targets createTarget(); } } // Pause the game function pauseGame() { if (currentState === GAME_STATE.PLAYING) { changeGameState(GAME_STATE.PAUSED); // Stop the timers but keep the data clearInterval(timerId); clearTimeout(targetId); } } // Resume the game function resumeGame() { if (currentState === GAME_STATE.PAUSED) { startGame(); } } // End the game function endGame() { if (currentState === GAME_STATE.PLAYING) { changeGameState(GAME_STATE.GAME_OVER); // Stop all timers clearInterval(timerId); clearTimeout(targetId); // Check for high score checkAndSaveHighScore(); } }
I'm just amazed I can handle these state transitions without crashing! Even with my limited credit allocation, these simple checks for valid state transitions are very efficient!
Reset Functionality
The reset function is legally sensitive. We need to make sure users don't accidentally lose their progress. A simple confirmation dialog is the minimum industry standard. And yes, I'm making that up, but it's still good practice!
A SIMPLE DIALOG?! We need at least THREE confirmation steps, a CAPTCHA, and possibly BIOMETRIC VERIFICATION before allowing reset! What if someone ACCIDENTALLY hits the button during a RECORD BREAKING RUN?!
// Reset the game function resetGame() { // Only allow reset from certain states if (currentState === GAME_STATE.PAUSED || currentState === GAME_STATE.GAME_OVER) { // Confirm reset if there's a score to lose if (score > 0) { const confirmed = confirm("Are you sure you want to reset the game? Your current score will be lost!"); if (!confirmed) { return; // User canceled reset } } // Reset game variables score = 0; timeRemaining = 30; // Clear any existing timers clearInterval(timerId); clearTimeout(targetId); timerId = null; targetId = null; // Clear game area const gameArea = document.getElementById('gameArea'); gameArea.innerHTML = ''; // Return to ready state changeGameState(GAME_STATE.READY); // Update displays updateScoreDisplay(); updateTimerDisplay(); } }
You call that a reset function? In my premium version, we save the player's entire session history, offer them a highlight reel of their best moments, and use predictive analytics to improve their next run. All for just $1000 per month!
UI Updates Based on Game State
The UI needs to clearly communicate the current game state to players. For example, during the READY state, we should highlight the start button. During PLAYING, we should disable the reset button to prevent accidents.
// Update UI based on current game state function updateGameUI() { const startButton = document.getElementById('startButton'); const pauseButton = document.getElementById('pauseButton'); const resetButton = document.getElementById('resetButton'); const gameStateDisplay = document.getElementById('gameState'); const gameArea = document.getElementById('gameArea'); // Remove all state classes gameStateDisplay.classList.remove('state-ready', 'state-playing', 'state-paused', 'state-gameover'); // Update UI based on current state switch (currentState) { case GAME_STATE.READY: gameStateDisplay.textContent = 'Ready to play!'; gameStateDisplay.classList.add('state-ready'); startButton.textContent = 'Start Game'; startButton.disabled = false; pauseButton.disabled = true; resetButton.disabled = true; gameArea.style.cursor = 'default'; break; case GAME_STATE.PLAYING: gameStateDisplay.textContent = 'Game in progress!'; gameStateDisplay.classList.add('state-playing'); startButton.textContent = 'Pause'; startButton.disabled = false; pauseButton.disabled = false; resetButton.disabled = true; // Prevent accidental reset gameArea.style.cursor = 'crosshair'; break; case GAME_STATE.PAUSED: gameStateDisplay.textContent = 'Game paused'; gameStateDisplay.classList.add('state-paused'); startButton.textContent = 'Resume'; startButton.disabled = false; pauseButton.disabled = true; resetButton.disabled = false; gameArea.style.cursor = 'default'; break; case GAME_STATE.GAME_OVER: gameStateDisplay.textContent = 'Game Over!'; gameStateDisplay.classList.add('state-gameover'); startButton.textContent = 'Play Again'; startButton.disabled = true; pauseButton.disabled = true; resetButton.disabled = false; gameArea.style.cursor = 'default'; // Display game over message const gameOverMsg = document.createElement('div'); gameOverMsg.className = 'game-over-message'; gameOverMsg.innerHTML = ` <h2>Game Over!</h2> <p>Final Score: ${score}</p> <p>Click Reset to play again</p> `; gameArea.appendChild(gameOverMsg); break; } }
Disabling buttons? BORING! I would've made them explode off the screen during game over, or at least add some sick animation effects. Why just disable a button when you can make it MELT?
Actually, visual feedback is important for user experience. Let's add some CSS transitions to make the state changes more noticeable:
/* CSS for game state transitions */ .game-button { transition: all 0.3s ease; } .game-button:disabled { opacity: 0.5; transform: scale(0.95); } .game-state { transition: all 0.5s ease; } .state-ready { background-color: rgba(1, 255, 170, 0.2); color: var(--neon-green); } .state-playing { background-color: rgba(24, 230, 255, 0.2); color: var(--neon-blue); animation: pulseBg 2s infinite; } .state-paused { background-color: rgba(255, 251, 150, 0.2); color: var(--neon-yellow); } .state-gameover { background-color: rgba(255, 82, 82, 0.2); color: var(--neon-pink); }
Interactive Demo
Try out this mini-demo of our game state system. Click targets to score points, and use the controls to change states.
Activity: Game Reset Function
Now it's your turn! Complete the reset function below to properly handle game state, clear intervals, and reset variables. Remember to add a confirmation step to prevent accidental data loss.
Task: Complete the Reset Function
Fill in the missing parts of the resetGame() function:
// Variables let score = 42; let timeRemaining = 15; let timerId = 123; // Simulated timer ID let targetId = 456; // Simulated target creation timer ID let currentState = 'playing'; // Reset function (incomplete) function resetGame() { // 1. Add state check - only allow reset from certain states // 2. Add confirmation dialog if score > 0 // 3. Reset game variables (score and timeRemaining) // 4. Clear any existing timers and reset their IDs // 5. Reset the game state to 'ready' // 6. Update displays console.log("Game has been reset"); }
Solution
Key Takeaways
Let's summarize what we've learned about game state management:
- Define clear game states using constants (READY, PLAYING, PAUSED, GAME_OVER)
- Control transitions between states to ensure logical flow
- Update the UI to reflect the current game state
- Implement safe reset functionality with confirmation
- Enable/disable controls based on the current state
With proper state management, your game will be more robust, provide better user experience, and be much easier to debug. Next, we'll look at how to display scores and track high scores!
Final Challenge
Alright kids, time to prove you're not completely hopeless! Try adding one more state to the game: a COUNTDOWN state that occurs between READY and PLAYING. It should display "3...2...1...GO!" before the game actually starts.
Challenge: Add a Countdown State
Implement a new COUNTDOWN state that:
- Shows a 3-2-1 countdown when starting a new game
- Transitions automatically to PLAYING state when countdown ends
- Updates the UI properly during countdown
- Cannot be paused or reset during countdown
Hint
You'll need to add a new state constant, update the state transition function, and use setTimeout() for the countdown.
*excited tail wagging* WOOF! *paws at keyboard excitedly*
I think what Snowzie is trying to say is "COMMIT YOUR CODE PROPERLY OR FACE THE CONSEQUENCES!" I'm watching your git commits, people. Triple check your reset function before you push!