6-space-game/6-end-condition/README.md
journey
title Your Game Completion Journey
section End Conditions
Define win/lose states: 3: Student
Implement condition checking: 4: Student
Handle state transitions: 4: Student
section Player Experience
Design feedback systems: 4: Student
Create restart mechanics: 5: Student
Polish user interface: 5: Student
section System Integration
Manage game lifecycle: 5: Student
Handle memory cleanup: 5: Student
Create complete experience: 5: Student
Every great game needs clear end conditions and a smooth restart mechanism. You've built an impressive space game with movement, combat, and scoring - now it's time to add the final pieces that make it feel complete.
Your game currently runs indefinitely, like the Voyager probes that NASA launched in 1977 - still traveling through space decades later. While that's fine for space exploration, games need defined endpoints to create satisfying experiences.
Today, we'll implement proper win/lose conditions and a restart system. By the end of this lesson, you'll have a polished game that players can complete and replay, just like the classic arcade games that defined the medium.
mindmap
root((Game Completion))
End Conditions
Victory States
Defeat Conditions
Progress Tracking
State Validation
Player Feedback
Visual Messages
Color Psychology
Clear Communication
Emotional Response
State Management
Game Loop Control
Memory Cleanup
Object Lifecycle
Event Handling
Restart Systems
Input Handling
State Reset
Fresh Initialization
User Experience
Polish Elements
Message Display
Smooth Transitions
Error Prevention
Accessibility
When should your game end? This fundamental question has shaped game design since the early arcade era. Pac-Man ends when you're caught by ghosts or clear all dots, while Space Invaders ends when aliens reach the bottom or you destroy them all.
As the game creator, you define the victory and defeat conditions. For our space game, here are proven approaches that create engaging gameplay:
flowchart TD
A["š® Game Start"] --> B{"Check Conditions"}
B --> C["Enemy Count"]
B --> D["Hero Lives"]
B --> E["Score Threshold"]
B --> F["Level Progress"]
C --> C1{"Enemies = 0?"}
D --> D1{"Lives = 0?"}
E --> E1{"Score ā„ Target?"}
F --> F1{"Objectives Complete?"}
C1 -->|Yes| G["š Victory"]
D1 -->|Yes| H["š Defeat"]
E1 -->|Yes| G
F1 -->|Yes| G
C1 -->|No| B
D1 -->|No| B
E1 -->|No| B
F1 -->|No| B
G --> I["š Restart Option"]
H --> I
style G fill:#e8f5e8
style H fill:#ffebee
style I fill:#e3f2fd
N Enemy ships have been destroyed: It's quite common if you divide up a game into different levels that you need to destroy N Enemy ships to complete a levelN points: Another common end condition is for you to collect points. How you get points is up to you but it's quite common to assign points to various activities like destroying an enemy ship or maybe collect items that items drop when they are destroyed.X enemy ships destroyed, Y points collected or maybe that a specific item has been collected.Good games encourage replayability through smooth restart mechanisms. When players complete a game (or meet defeat), they often want to try again immediately - whether to beat their score or improve their performance.
stateDiagram-v2
[*] --> Playing: Game Start
Playing --> Victory: All enemies destroyed
Playing --> Defeat: Lives = 0
Victory --> MessageDisplay: Show win message
Defeat --> MessageDisplay: Show lose message
MessageDisplay --> WaitingRestart: Press Enter prompt
WaitingRestart --> Resetting: Enter key pressed
Resetting --> CleanupMemory: Clear intervals
CleanupMemory --> ClearEvents: Remove listeners
ClearEvents --> InitializeGame: Fresh start
InitializeGame --> Playing: New game begins
note right of MessageDisplay
Color-coded feedback:
Green = Victory
Red = Defeat
end note
note right of Resetting
Complete state reset
prevents memory leaks
end note
Tetris exemplifies this perfectly: when your blocks reach the top, you can instantly start a new game without navigating complex menus. We'll build a similar restart system that cleanly resets the game state and gets players back into action quickly.
ā Reflection: Think about the games you've played. Under what conditions do they end, and how are you prompted to restart? What makes a restart experience feel smooth versus frustrating?
You'll implement the final features that transform your project into a complete game experience. These elements distinguish polished games from basic prototypes.
Here's what we're adding today:
Let's prepare your development environment. You should have all your space game files from the previous lessons ready.
Your project should look something like this:
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| life.png
-| index.html
-| app.js
-| package.json
Start your development server:
cd your-work
npm start
This command:
http://localhost:5000Open http://localhost:5000 in your browser and verify your game is running. You should be able to move, shoot, and interact with enemies. Once confirmed, we can proceed with the implementation.
š” Pro Tip: To avoid warnings in Visual Studio Code, declare
gameLoopIdat the top of your file aslet gameLoopId;instead of declaring it inside thewindow.onloadfunction. This follows modern JavaScript variable declaration best practices.
flowchart TD
A["1. Condition Tracking"] --> B["2. Event Handlers"]
B --> C["3. Message Constants"]
C --> D["4. Restart Controls"]
D --> E["5. Message Display"]
E --> F["6. Reset System"]
G["isHeroDead()\nisEnemiesDead()"] --> A
H["Collision Events\nEnd Game Events"] --> B
I["GAME_END_WIN\nGAME_END_LOSS"] --> C
J["Enter Key\nRestart Trigger"] --> D
K["Victory/Defeat\nColor-coded Text"] --> E
L["State Cleanup\nFresh Initialization"] --> F
F --> M["š® Complete Game"]
style A fill:#e3f2fd
style B fill:#e8f5e8
style C fill:#fff3e0
style D fill:#f3e5f5
style E fill:#e0f2f1
style F fill:#fce4ec
style M fill:#e1f5fe
We need functions to monitor when the game should end. Like sensors on the International Space Station that constantly monitor critical systems, these functions will continuously check the game state.
function isHeroDead() {
return hero.life <= 0;
}
function isEnemiesDead() {
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
return enemies.length === 0;
}
Here's what's happening under the hood:
true when the battlefield is clear of enemiesNow we'll connect these condition checks to the game's event system. Every time a collision occurs, the game will evaluate whether it triggers an end condition. This creates immediate feedback for critical game events.
sequenceDiagram
participant Collision
participant GameLogic
participant Conditions
participant EventSystem
participant Display
Collision->>GameLogic: Laser hits enemy
GameLogic->>GameLogic: Destroy objects
GameLogic->>Conditions: Check isEnemiesDead()
alt All enemies defeated
Conditions->>EventSystem: Emit GAME_END_WIN
EventSystem->>Display: Show victory message
else Enemies remain
Conditions->>GameLogic: Continue game
end
Collision->>GameLogic: Enemy hits hero
GameLogic->>GameLogic: Decrease lives
GameLogic->>Conditions: Check isHeroDead()
alt Lives = 0
Conditions->>EventSystem: Emit GAME_END_LOSS
EventSystem->>Display: Show defeat message
else Lives remain
GameLogic->>Conditions: Check isEnemiesDead()
alt All enemies defeated
Conditions->>EventSystem: Emit GAME_END_WIN
end
end
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
if (isHeroDead()) {
eventEmitter.emit(Messages.GAME_END_LOSS);
return; // loss before victory
}
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.GAME_END_WIN, () => {
endGame(true);
});
eventEmitter.on(Messages.GAME_END_LOSS, () => {
endGame(false);
});
What's going on here:
You'll need to add new message types to your Messages constant object. These constants help maintain consistency and prevent typos in your event system.
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
In the above, we've:
Now you'll add keyboard controls that allow players to restart the game. The Enter key is a natural choice since it's commonly associated with confirming actions and starting new games.
Add Enter key detection to your existing keydown event listener:
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
Add the new message constant:
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
What you need to know:
Your game needs to communicate results clearly to players. We'll create a message system that displays victory and defeat states using color-coded text, similar to the terminal interfaces of early computer systems where green indicated success and red signaled errors.
Create the displayMessage() function:
function displayMessage(message, color = "red") {
ctx.font = "30px Arial";
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
Step by step, here's what's happening:
Create the endGame() function:
function endGame(win) {
clearInterval(gameLoopId);
// Set a delay to ensure any pending renders complete
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (win) {
displayMessage(
"Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
"green"
);
} else {
displayMessage(
"You died !!! Press [Enter] to start a new game Captain Pew Pew"
);
}
}, 200)
}
What this function does:
Game State Management: Before implementing reset functionality, ensure you understand:
Quick Self-Test: What would happen if you didn't clear event listeners during reset? Answer: Memory leaks and duplicate event handlers causing unpredictable behavior
Game Design Principles: You're now implementing:
The reset system needs to completely clean up the current game state and initialize a fresh game session. This ensures players get a clean start without any leftover data from the previous game.
Create the resetGame() function:
function resetGame() {
if (gameLoopId) {
clearInterval(gameLoopId);
eventEmitter.clear();
initGame();
gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawPoints();
drawLife();
updateGameObjects();
drawGameObjects(ctx);
}, 100);
}
}
Let's understand each part:
Add the Enter key event handler to your initGame() function:
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
Add the clear() method to your EventEmitter class:
clear() {
this.listeners = {};
}
Key points to remember:
š½ š„ š You've successfully built a complete game from the ground up. Like the programmers who created the first video games in the 1970s, you've transformed lines of code into an interactive experience with proper game mechanics and user feedback. š š„ š½
You've accomplished:
Complete Game Development System: Celebrate your mastery of the full game development cycle:
System Mastery: Your complete game demonstrates:
Industry-Ready Skills: You've implemented:
timeline
title Complete Game Development Learning Progression
section Foundation (Lessons 1-2)
Game Architecture: Project structure
: Asset management
: Canvas basics
: Event systems
section Interaction Systems (Lessons 3-4)
Player Control: Input handling
: Movement mechanics
: Collision detection
: Physics simulation
section Game Mechanics (Lesson 5)
Feedback Systems: Scoring mechanisms
: Life management
: Visual communication
: Player motivation
section Game Completion (Lesson 6)
Polish & Flow: End conditions
: State management
: Restart systems
: User experience
section Advanced Features (1 week)
Enhancement Skills: Audio integration
: Visual effects
: Level progression
: Performance optimization
section Professional Development (1 month)
Industry Readiness: Framework mastery
: Team collaboration
: Portfolio development
: Community engagement
section Career Advancement (3 months)
Specialization: Advanced game engines
: Platform deployment
: Monetization strategies
: Industry networking
After completing this entire space game series, you now have mastered:
Real-World Applications: Your game development skills apply directly to:
Professional Skills Gained: You can now:
Game Development Concepts Mastered:
Next Level: You're ready to explore advanced game frameworks, 3D graphics, multiplayer systems, or transition into professional game development roles!
š Achievement Unlocked: You've completed a full game development journey and built a professional-quality interactive experience from scratch!
Welcome to the game development community! š®āØ
Use the Agent mode to complete the following challenge:
Description: Enhance the space game by implementing a level progression system with increasing difficulty and bonus features.
Prompt: Create a multi-level space game system where each level has more enemy ships with increased speed and health. Add a scoring multiplier that increases with each level, and implement power-ups (like rapid fire or shield) that randomly appear when enemies are destroyed. Include a level completion bonus and display the current level on screen alongside the existing score and lives.
Learn more about agent mode here.
Add Audio to Your Game: Enhance your gameplay experience by implementing sound effects! Consider adding audio for:
Audio implementation example:
// Create audio objects
const laserSound = new Audio('assets/laser.wav');
const explosionSound = new Audio('assets/explosion.wav');
// Play sounds during game events
function playLaserSound() {
laserSound.currentTime = 0; // Reset to beginning
laserSound.play();
}
What you need to know:
currentTime to allow rapid-fire sound effectsš” Learning Resource: Explore this audio sandbox to learn more about implementing audio in JavaScript games.
Your assignment is to create a fresh sample game, so explore some of the interesting games out there to see what type of game you might build.