updating part 6

softchris-patch-9
chris 2 months ago
parent f087f6d7ec
commit 17da96140c

@ -1,41 +1,49 @@
# Build a Space Game Part 1: Introduction
![video](../images/pewpew.gif)
![Space game animation showing gameplay](../images/pewpew.gif)
Welcome to an exciting journey where you'll build your very own space game using JavaScript! In this lesson, you'll discover the fundamental design patterns that power modern game development. These patterns aren't just for games they're essential architectural concepts that will make you a better programmer across all types of applications.
You'll explore two powerful approaches to organizing code: inheritance and composition, and learn when to use each one. We'll also dive into the pub/sub (publish-subscribe) pattern, a communication system that keeps different parts of your application loosely connected yet perfectly coordinated. Think of it as a sophisticated messaging system that allows game objects to communicate without directly knowing about each other.
By the end of this lesson, you'll understand how to structure larger applications, create flexible game architectures, and implement communication patterns that scale beautifully. These concepts will serve you well whether you're building games, web applications, or any complex software project. Let's begin building the foundation for your space game adventure!
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/29)
### Inheritance and Composition in game development
## Inheritance and Composition in Game Development
In earlier lessons, there was not much need to worry about the design architecture of the apps you built, as the projects were very small in scope. However, when your applications grow in size and scope, architectural decisions become a larger concern. There are two major approaches to creating larger applications in JavaScript: *composition* or *inheritance*. There are pros and cons to both but let's explain them from within the context of a game.
As your programming skills advance, you'll need to make important decisions about how to organize and structure your code. When applications grow beyond simple scripts into complex interactive experiences like games, architectural patterns become crucial for maintainability and scalability.
In this section, you'll learn about two fundamental approaches to building larger JavaScript applications: inheritance and composition. Each approach offers unique advantages, and understanding both will help you choose the right pattern for different situations. We'll explore these concepts through the lens of game development, where objects interact in complex ways and clear organization is essential for success.
✅ One of the most famous programming books ever written has to do with [design patterns](https://en.wikipedia.org/wiki/Design_Patterns).
In a game you have `game objects` which are objects that exist on a screen. This means they have a location on a cartesian coordinate system, characterized by having an `x` and `y` coordinate. As you develop a game you will notice that all your game objects have a standard property, common for every game you create, namely elements that are:
In a game, you have `game objects` entities that exist on screen and interact with players and each other. These objects live in a coordinate system with `x` and `y` positions, much like points on a map. As you develop games, you'll notice that most game objects share common behaviors and properties.
- **location-based** Most, if not all, game elements are location based. This means that they have a location, an `x` and `y`.
- **movable** These are objects that can move to a new location. This is typically a hero, a monster or an NPC (a non player character), but not for example, a static object like a tree.
- **self-destructing** These objects only exist for a set period of time before they set themselves up for deletion. Usually this is represented by a `dead` or `destroyed` boolean that signals to the game engine that this object should no longer be rendered.
- **cool-down** 'Cool-down' is a typical property among short-lived objects. A typical example is a piece of text or graphical effect like an explosion that should only be seen for a few milliseconds.
**Here's what most game objects have in common:**
- **Location-based positioning** **Uses** `x` and `y` coordinates to define where the object appears on screen
- **Movement capabilities** **Enables** objects like heroes, enemies, or NPCs (non-player characters) to change position over time
- **Lifecycle management** **Controls** how long objects exist before being removed from the game
- **Temporary effects** **Handles** short-lived elements like explosions, power-ups, or visual effects that appear briefly
✅ Think about a game like Pac-Man. Can you identify the four object types listed above in this game?
### Expressing behavior
### Expressing Behavior Through Code
All we described above are behavior that game objects can have. So how do we encode those? We can express this behavior as methods associated to either classes or objects.
Now that you understand the common behaviors game objects share, let's explore how to implement these behaviors in JavaScript. You can express object behavior through methods attached to either classes or individual objects, and there are several approaches to choose from.
**Classes**
**The Class-Based Approach**
The idea is to use `classes` in conjunction with `inheritance` to accomplish adding a certain behavior to a class.
One powerful way to organize behavior is through `classes` combined with `inheritance`. This approach allows you to define shared behaviors in parent classes and extend them with specialized functionality in child classes.
✅ Inheritance is an important concept to understand. Learn more on [MDN's article about inheritance](https://developer.mozilla.org/docs/Web/JavaScript/Inheritance_and_the_prototype_chain).
Expressed via code, a game object can typically look like this:
Here's how you can implement game objects using classes and inheritance:
```javascript
//set up the class GameObject
// Step 1: Create the base GameObject class
class GameObject {
constructor(x, y, type) {
this.x = x;
@ -43,167 +51,279 @@ class GameObject {
this.type = type;
}
}
```
//this class will extend the GameObject's inherent class properties
**Breaking down what happens here:**
- **Defines** a base class that all game objects will inherit from
- **Stores** position coordinates (`x`, `y`) and object type in the constructor
- **Establishes** the foundation for all objects in your game
```javascript
// Step 2: Add movement capability through inheritance
class Movable extends GameObject {
constructor(x,y, type) {
super(x,y, type)
constructor(x, y, type) {
super(x, y, type); // Call parent constructor
}
//this movable object can be moved on the screen
// Add the ability to move to a new position
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
```
**In the above, we've:**
- **Extended** the GameObject class to add movement functionality
- **Called** the parent constructor using `super()` to initialize inherited properties
- **Added** a `moveTo()` method that updates the object's position
//this is a specific class that extends the Movable class, so it can take advantage of all the properties that it inherits
```javascript
// Step 3: Create specific game object types
class Hero extends Movable {
constructor(x,y) {
super(x,y, 'Hero')
constructor(x, y) {
super(x, y, 'Hero'); // Set type automatically
}
}
//this class, on the other hand, only inherits the GameObject properties
class Tree extends GameObject {
constructor(x,y) {
super(x,y, 'Tree')
constructor(x, y) {
super(x, y, 'Tree'); // Trees don't need movement
}
}
//a hero can move...
const hero = new Hero();
hero.moveTo(5,5);
// Step 4: Use your game objects
const hero = new Hero(0, 0);
hero.moveTo(5, 5); // Hero can move!
//but a tree cannot
const tree = new Tree();
const tree = new Tree(10, 15);
// tree.moveTo() would cause an error - trees can't move
```
**Understanding these concepts:**
- **Creates** specialized object types that inherit appropriate behaviors
- **Demonstrates** how inheritance allows selective feature inclusion
- **Shows** that heroes can move while trees remain stationary
- **Illustrates** how the class hierarchy prevents inappropriate actions
✅ Take a few minutes to re-envision a Pac-Man hero (Inky, Pinky or Blinky, for example) and how it would be written in JavaScript.
**Composition**
**The Composition Approach**
A different way of handling object inheritance is by using *Composition*. Then, objects express their behavior like this:
Composition offers an alternative to inheritance by combining smaller, focused objects into larger ones. Instead of creating class hierarchies, you build objects by mixing and matching behaviors as needed.
```javascript
//create a constant gameObject
// Step 1: Create base behavior objects
const gameObject = {
x: 0,
y: 0,
type: ''
};
//...and a constant movable
const movable = {
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
//then the constant movableObject is composed of the gameObject and movable constants
const movableObject = {...gameObject, ...movable};
};
```
//then create a function to create a new Hero who inherits the movableObject properties
**Here's what this code does:**
- **Defines** a base `gameObject` with position and type properties
- **Creates** a separate `movable` behavior object with movement functionality
- **Separates** concerns by keeping position data and movement logic independent
```javascript
// Step 2: Compose objects by combining behaviors
const movableObject = { ...gameObject, ...movable };
// Step 3: Create factory functions for different object types
function createHero(x, y) {
return {
...movableObject,
x,
y,
type: 'Hero'
}
};
}
//...and a static object that inherits only the gameObject properties
function createStatic(x, y, type) {
return {
...gameObject
...gameObject,
x,
y,
type
}
};
}
//create the hero and move it
const hero = createHero(10,10);
hero.moveTo(5,5);
//and create a static tree which only stands around
const tree = createStatic(0,0, 'Tree');
```
**Which pattern should I use?**
**In the above, we've:**
- **Combined** base object properties with movement behavior using spread syntax
- **Created** factory functions that return customized objects
- **Enabled** flexible object creation without rigid class hierarchies
- **Allowed** objects to have exactly the behaviors they need
```javascript
// Step 4: Create and use your composed objects
const hero = createHero(10, 10);
hero.moveTo(5, 5); // Works perfectly!
const tree = createStatic(0, 0, 'Tree');
// tree.moveTo() is undefined - no movement behavior was composed
```
It's up to you which pattern you choose. JavaScript supports both these paradigms.
**Key points to remember:**
- **Composes** objects by mixing behaviors rather than inheriting them
- **Provides** more flexibility than rigid inheritance hierarchies
- **Allows** objects to have exactly the features they need
- **Uses** modern JavaScript spread syntax for clean object combination
```
--
**Which Pattern Should You Choose?**
Another pattern common in game development addresses the problem of handling the game's user experience and performance.
> 💡 **Pro Tip**: Both patterns have their place in modern JavaScript development. Classes work well for clearly defined hierarchies, while composition shines when you need maximum flexibility.
>
**Here's when to use each approach:**
- **Choose** inheritance when you have clear "is-a" relationships (a Hero *is-a* Movable object)
- **Select** composition when you need "has-a" relationships (a Hero *has* movement abilities)
- **Consider** your team's preferences and project requirements
- **Remember** that you can mix both approaches in the same application
## Pub/sub pattern
## Communication Patterns: The Pub/Sub System
✅ Pub/Sub stands for 'publish-subscribe'
As your game grows more complex, different parts need to communicate without becoming tightly coupled. The publish-subscribe (pub/sub) pattern solves this challenge by creating a messaging system that keeps components independent while enabling sophisticated interactions.
This pattern addresses the idea that the disparate parts of your application shouldn't know about one another. Why is that? It makes it a lot easier to see what's going on in general if various parts are separated. It also makes it easier to suddenly change behavior if you need to. How do we accomplish this? We do this by establishing some concepts:
This pattern is essential for game development because it allows objects to react to events without knowing exactly where those events come from. Imagine a game where the hero's health changes with pub/sub, the health bar, sound effects, and visual indicators can all respond automatically without the hero object needing to know about any of them.
- **message**: A message is usually a text string accompanied by an optional payload (a piece of data that clarifies what the message is about). A typical message in a game can be `KEY_PRESSED_ENTER`.
- **publisher**: This element *publishes* a message and sends it out to all subscribers.
- **subscriber**: This element *listens* to specific messages and carries out some task as the result of receiving this message, such as firing a laser.
**Pub/Sub** stands for 'publish-subscribe'
The implementation is quite small in size but it's a very powerful pattern. Here's how it can be implemented:
### Understanding the Pub/Sub Architecture
The pub/sub pattern keeps different parts of your application loosely coupled, meaning they can work together without being directly dependent on each other. This separation makes your code more maintainable, testable, and flexible to changes.
**Key concepts that make pub/sub work:**
- **Message** **Carries** information as a string identifier (like `'PLAYER_SCORED'`) with optional data
- **Publisher** **Broadcasts** messages when events occur, without knowing who will receive them
- **Subscriber** **Listens** for specific messages and responds accordingly
- **Event System** **Manages** the connection between publishers and subscribers
### Building an Event System
Let's create a simple but powerful event system that demonstrates these concepts:
```javascript
//set up an EventEmitter class that contains listeners
// Step 1: Create the EventEmitter class
class EventEmitter {
constructor() {
this.listeners = {};
this.listeners = {}; // Store all event listeners
}
//when a message is received, let the listener to handle its payload
// Register a listener for a specific message type
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
//when a message is sent, send it to a listener with some payload
// Send a message to all registered listeners
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach(l => l(message, payload))
this.listeners[message].forEach(listener => {
listener(message, payload);
});
}
}
}
```
To use the above code we can create a very small implementation:
**Breaking down what happens here:**
- **Creates** a central event management system using a simple class
- **Stores** listeners in an object organized by message type
- **Registers** new listeners using the `on()` method
- **Broadcasts** messages to all interested listeners using `emit()`
- **Supports** optional data payloads for passing relevant information
### Putting It All Together: A Practical Example
Let's see how the pub/sub pattern works in practice by creating a simple movement system:
```javascript
//set up a message structure
// Step 1: Define your message types
const Messages = {
HERO_MOVE_LEFT: 'HERO_MOVE_LEFT'
HERO_MOVE_LEFT: 'HERO_MOVE_LEFT',
HERO_MOVE_RIGHT: 'HERO_MOVE_RIGHT',
ENEMY_SPOTTED: 'ENEMY_SPOTTED'
};
//invoke the eventEmitter you set up above
// Step 2: Create your event system and game objects
const eventEmitter = new EventEmitter();
//set up a hero
const hero = createHero(0,0);
//let the eventEmitter know to watch for messages pertaining to the hero moving left, and act on it
const hero = createHero(0, 0);
```
**Here's what this code does:**
- **Defines** a constants object to prevent typos in message names
- **Creates** an event emitter instance to handle all communication
- **Initializes** a hero object at the starting position
```javascript
// Step 3: Set up event listeners (subscribers)
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
hero.moveTo(hero.x - 5, hero.y);
console.log(`Hero moved to position: ${hero.x}, ${hero.y}`);
});
//set up the window to listen for the keyup event, specifically if the left arrow is hit, emit a message to move the hero left
window.addEventListener('keyup', (evt) => {
if (evt.key === 'ArrowLeft') {
eventEmitter.emit(Messages.HERO_MOVE_LEFT)
}
eventEmitter.on(Messages.HERO_MOVE_RIGHT, () => {
hero.moveTo(hero.x + 5, hero.y);
console.log(`Hero moved to position: ${hero.x}, ${hero.y}`);
});
```
Above we connect a keyboard event, `ArrowLeft` and send the `HERO_MOVE_LEFT` message. We listen to that message and move the `hero` as a result. The strength with this pattern is that the event listener and the hero don't know about each other. You can remap the `ArrowLeft` to the `A` key. Additionally it would be possible to do something completely different on `ArrowLeft` by making a few edits to the eventEmitter's `on` function:
**In the above, we've:**
- **Registered** event listeners that respond to movement messages
- **Updated** the hero's position based on the movement direction
- **Added** console logging to track the hero's position changes
- **Separated** the movement logic from the input handling
```javascript
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
// Step 4: Connect keyboard input to events (publishers)
window.addEventListener('keydown', (event) => {
switch(event.key) {
case 'ArrowLeft':
eventEmitter.emit(Messages.HERO_MOVE_LEFT);
break;
case 'ArrowRight':
eventEmitter.emit(Messages.HERO_MOVE_RIGHT);
break;
}
});
```
As things gets more complicated when your game grows, this pattern stays the same in complexity and your code stays clean. It's really recommended to adopt this pattern.
**Understanding these concepts:**
- **Connects** keyboard input to game events without tight coupling
- **Enables** the input system to communicate with game objects indirectly
- **Allows** multiple systems to respond to the same keyboard events
- **Makes** it easy to change key bindings or add new input methods
> 💡 **Pro Tip**: The beauty of this pattern is flexibility! You can easily add sound effects, screen shake, or particle effects by simply adding more event listeners no need to modify the existing keyboard or movement code.
>
**Benefits you'll discover:**
- **Enables** easy addition of new features without modifying existing code
- **Allows** multiple systems to respond to the same events
- **Simplifies** testing by isolating different parts of your application
- **Makes** debugging easier by centralizing event flow
### Why Pub/Sub Scales Beautifully
As your game grows more complex with multiple enemies, power-ups, sound effects, and UI elements, the pub/sub pattern maintains its simplicity. You can add new features without touching existing code just create new publishers and subscribers as needed.
> ⚠️ **Common Mistake**: Don't create too many specific message types early on. Start with broad categories and refine them as your game's needs become clearer.
>
**Best practices to follow:**
- **Groups** related messages into logical categories
- **Uses** descriptive names that clearly indicate what happened
- **Keeps** message payloads simple and focused
- **Documents** your message types for team collaboration
---

@ -1,11 +1,86 @@
# Mock up a game
# Mock up a Game: Apply Design Patterns
## Assignment Overview
Put your newfound knowledge of design patterns to work by creating a simple game prototype! This assignment will help you practice both architectural patterns (inheritance or composition) and the pub/sub communication system you learned about in the lesson.
## Instructions
Using the code samples in the lesson, write a representation of a game you enjoy. It will have to be a simple game, but the goal is to use either the class or the composition pattern and the pub/sub pattern to show how a game might launch. Get creative!
Create a simple game representation that demonstrates the design patterns from this lesson. Your game should be functional but doesn't need complex graphics \u2013 focus on the underlying architecture and communication patterns.
### Requirements
**Choose Your Architecture Pattern:**
- **Option A**: Use class-based inheritance (like the `GameObject``Movable``Hero` example)
- **Option B**: Use composition (like the factory function approach with mixed behaviors)
**Implement Communication:**
- **Include** an `EventEmitter` class for pub/sub messaging
- **Set up** at least 2-3 different message types (like `PLAYER_MOVE`, `ENEMY_SPAWN`, `SCORE_UPDATE`)
- **Connect** user input (keyboard/mouse) to game events through the event system
**Game Elements to Include:**
- At least one player-controlled character
- At least one other game object (enemy, collectible, or environmental element)
- Basic interaction between objects (collision, collection, or communication)
### Suggested Game Ideas
**Simple Games to Consider:**
- **Snake Game** \u2013 Snake segments follow the head, food spawns randomly
- **Pong Variation** \u2013 Paddle responds to input, ball bounces off walls
- **Collector Game** \u2013 Player moves around collecting items while avoiding obstacles
- **Tower Defense Basics** \u2013 Towers detect and shoot at moving enemies
### Code Structure Guidelines
```javascript
// Example starting structure
const Messages = {
// Define your game messages here
};
class EventEmitter {
// Your event system implementation
}
// Choose either class-based OR composition approach
// Class-based example:
class GameObject { /* base properties */ }
class Player extends GameObject { /* player-specific behavior */ }
// OR Composition example:
const gameObject = { /* base properties */ };
const movable = { /* movement behavior */ };
function createPlayer() { /* combine behaviors */ }
```
### Testing Your Implementation
**Verify your code works by:**
- **Testing** that objects move or change when events are triggered
- **Confirming** that multiple objects can respond to the same event
- **Checking** that you can add new behaviors without modifying existing code
- **Ensuring** keyboard/mouse input properly triggers game events
## Submission Guidelines
**Your submission should include:**
1. **JavaScript file(s)** with your game implementation
2. **HTML file** to run and test your game (can be simple)
3. **Comments** explaining which pattern you chose and why
4. **Brief documentation** of your message types and what they do
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ------------------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------- |
| | Three elements are placed on the screen and manipulated | Two elements are placed on the screen and manipulated | One element is placed on the screen and manipulated |
| Criteria | Exemplary (3 points) | Adequate (2 points) | Needs Improvement (1 point) |
|----------|---------------------|---------------------|------------------------------|
| **Architecture Pattern** | Correctly implements either inheritance OR composition with clear class/object hierarchy | Uses chosen pattern with minor issues or inconsistencies | Attempts to use pattern but implementation has significant problems |
| **Pub/Sub Implementation** | EventEmitter works correctly with multiple message types and proper event flow | Basic pub/sub system works with some event handling | Event system present but doesn't work reliably |
| **Game Functionality** | Three or more interactive elements that communicate through events | Two interactive elements with basic event communication | One element responds to events or basic interaction |
| **Code Quality** | Clean, well-commented code with logical organization and modern JavaScript | Generally well-organized code with adequate comments | Code works but lacks organization or clear commenting |
**Bonus Points:**
- **Creative game mechanics** that showcase interesting uses of the patterns
- **Multiple input methods** (keyboard AND mouse events)
- **Scalable architecture** that would be easy to extend with new features

@ -1,12 +1,20 @@
# Build a Space Game Part 2: Draw Hero and Monsters to Canvas
The Canvas API is one of the most exciting and powerful features of modern web development, allowing you to create dynamic, interactive graphics directly in the browser. In this lesson, you'll discover how to transform a blank HTML `<canvas>` element into a vibrant game world filled with heroes, monsters, and visual effects. Think of the canvas as your digital art board where code becomes visual magic.
Building on your foundational knowledge from the previous lesson, you'll now learn to load and display game sprites, position elements precisely using coordinate systems, and create the visual foundation for your space game. This lesson bridges the gap between static web pages and dynamic, game-like experiences that respond to user interaction.
By the end of this lesson, you'll have created a complete game scene with a hero ship positioned strategically and enemy formations ready for action. You'll understand how modern games render graphics in browsers and possess the skills to create your own interactive visual experiences. Let's dive into the world of canvas graphics and bring your space game to life!
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/31)
## The Canvas
The canvas is an HTML element that by default has no content; it's a blank slate. You need to add to it by drawing on it.
The `<canvas>` element is HTML5's answer to dynamic graphics and animations in web browsers. Unlike images or videos, the canvas gives you pixel-level control over what appears on screen, making it perfect for games, data visualizations, and interactive art. Think of it as a programmable drawing surface where JavaScript becomes your paintbrush.
By default, a canvas element appears as a blank, transparent rectangle on your page. Its real power emerges when you use JavaScript to draw shapes, load images, create animations, and respond to user interactions.
✅ Read [more about the Canvas API](https://developer.mozilla.org/docs/Web/API/Canvas_API) on MDN.
@ -16,42 +24,46 @@ Here's how it's typically declared, as part of the page's body:
<canvas id="myCanvas" width="200" height="100"></canvas>
```
Above we are setting the `id`, `width` and `height`.
**Here's what this code does:**
- **Sets** the `id` attribute so you can reference this specific canvas element in JavaScript
- **Defines** the `width` in pixels to control the canvas's horizontal size
- **Establishes** the `height` in pixels to determine the canvas's vertical dimensions
- `id`: set this so you can obtain a reference when you need to interact with it.
- `width`: this is the width of the element.
- `height`: this is the height of the element.
## Drawing Simple Geometry
## Drawing simple geometry
Now that you understand what the canvas element is, let's explore how to actually draw on it. The canvas uses a coordinate system that might feel familiar from math class, but with one important twist that's specific to computer graphics.
The Canvas is using a cartesian coordinate system to draw things. Thus it uses an x-axis and y-axis to express where something is located. The location `0,0` is the top left position and the bottom right is what you said to be the WIDTH and HEIGHT of the canvas.
The canvas employs a Cartesian coordinate system with an x-axis (horizontal) and y-axis (vertical) to precisely position every element you draw. However, unlike the coordinate system you might remember from school, the origin point `(0,0)` starts at the top-left corner, with x-values increasing as you move right and y-values increasing as you move down.
![the canvas's grid](canvas_grid.png)
> Image from [MDN](https://developer.mozilla.org/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes)
To draw on the canvas element you will need to go through the following steps:
To draw on the canvas element, you'll follow a consistent three-step process that forms the foundation of all canvas graphics:
1. **Get a reference** to the Canvas element.
1. **Get a reference** on the Context element that sits on the canvas element.
1. **Perform a drawing operation** using the context element.
1. **Get a reference** to the Canvas element from the DOM
2. **Get a reference** to the 2D rendering context that provides the drawing methods
3. **Perform drawing operations** using the context's built-in methods
Code for the above steps usually looks like so:
Here's how this process looks in practice:
```javascript
// draws a red rectangle
//1. get the canvas reference
canvas = document.getElementById("myCanvas");
// Step 1: Get the canvas element
const canvas = document.getElementById("myCanvas");
//2. set the context to 2D to draw basic shapes
ctx = canvas.getContext("2d");
// Step 2: Get the 2D rendering context
const ctx = canvas.getContext("2d");
//3. fill it with the color red
// Step 3: Set fill color and draw a rectangle
ctx.fillStyle = 'red';
//4. and draw a rectangle with these parameters, setting location and size
ctx.fillRect(0,0, 200, 200) // x,y,width, height
ctx.fillRect(0, 0, 200, 200); // x, y, width, height
```
**Breaking down what happens here:**
- **Retrieves** the canvas element using its ID and stores it in a constant
- **Obtains** the 2D rendering context, which provides all the drawing methods
- **Sets** the fill color to red using the `fillStyle` property
- **Draws** a rectangle starting at coordinates (0,0) with 200px width and height
✅ The Canvas API mostly focuses on 2D shapes, but you can also draw 3D elements to a web site; for this, you might use the [WebGL API](https://developer.mozilla.org/docs/Web/API/WebGL_API).
You can draw all sorts of things with the Canvas API like:
@ -62,62 +74,98 @@ You can draw all sorts of things with the Canvas API like:
✅ Try it! You know how to draw a rectangle, can you draw a circle to a page? Take a look at some interesting Canvas drawings on CodePen. Here's a [particularly impressive example](https://codepen.io/dissimulate/pen/KrAwx).
## Load and draw an image asset
## Load and Draw an Image Asset
You load an image asset by creating an `Image` object and set its `src` property. Then you listen to the `load` event to know when it's ready to be used. The code looks like this:
While drawing basic shapes is useful for learning, most games and interactive applications rely on image assets like sprites, backgrounds, and textures. Loading and displaying images on the canvas requires a slightly different approach than drawing geometric shapes, but it's essential for creating visually appealing games.
### Load asset
The process involves creating an `Image` object, loading your image file asynchronously, and then drawing it to the canvas once it's fully loaded. This asynchronous approach ensures your images display properly without blocking your application.
### Basic Image Loading
```javascript
const img = new Image();
img.src = 'path/to/my/image.png';
img.onload = () => {
// image loaded and ready to be used
}
// Image loaded and ready to be used
console.log('Image loaded successfully!');
};
```
### Load asset pattern
**Here's what this code does:**
- **Creates** a new Image object to hold our sprite or texture
- **Sets** the source path to tell the browser which image file to load
- **Listens** for the load event to know when the image is ready for canvas operations
It's recommended to wrap the above in a construct like so, so it's easier to use and you only try to manipulate it when it's fully loaded:
### Recommended Async Pattern
For better code organization and error handling, it's recommended to wrap image loading in a Promise-based function:
```javascript
function loadAsset(path) {
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = path;
img.onload = () => {
// image loaded and ready to be used
resolve(img);
}
})
};
img.onerror = () => {
reject(new Error(`Failed to load image: ${path}`));
};
});
}
// use like so
async function run() {
const heroImg = await loadAsset('hero.png')
const monsterImg = await loadAsset('monster.png')
// Modern usage with async/await
async function initializeGame() {
try {
const heroImg = await loadAsset('hero.png');
const monsterImg = await loadAsset('monster.png');
// Images are now ready to use
} catch (error) {
console.error('Failed to load game assets:', error);
}
}
```
To draw game assets to a screen, your code would look like this:
**In the above, we've:**
- **Wrapped** the image loading logic in a Promise for better async handling
- **Added** error handling with `reject` to catch loading failures
- **Used** modern async/await syntax for cleaner, more readable code
- **Included** try/catch blocks to handle potential loading errors gracefully
Once your images are loaded, drawing them to the canvas is straightforward:
```javascript
async function run() {
const heroImg = await loadAsset('hero.png')
const monsterImg = await loadAsset('monster.png')
canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d");
ctx.drawImage(heroImg, canvas.width/2,canvas.height/2);
ctx.drawImage(monsterImg, 0,0);
async function renderGameScreen() {
try {
// Load game assets
const heroImg = await loadAsset('hero.png');
const monsterImg = await loadAsset('monster.png');
// Get canvas and context
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
// Draw images to specific positions
ctx.drawImage(heroImg, canvas.width / 2, canvas.height / 2);
ctx.drawImage(monsterImg, 0, 0);
} catch (error) {
console.error('Failed to render game screen:', error);
}
}
```
## Now it's time to start building your game
**Step by step, here's what's happening:**
- **Loads** both hero and monster images asynchronously using await
- **Obtains** the canvas element and its 2D rendering context
- **Positions** the hero image at the center of the canvas using calculated coordinates
- **Places** the monster image at the top-left corner (0,0) of the canvas
- **Handles** any loading or rendering errors with proper error catching
### What to build
## Now It's Time to Start Building Your Game
With a solid understanding of canvas fundamentals and image loading techniques, you're ready to apply these concepts to create the visual foundation of your space game. This hands-on section will guide you through building a complete game screen with properly positioned sprites and a professional-looking layout.
### What to Build
You will build a web page with a Canvas element. It should render a black screen `1024*768`. We've provided you with two images:
@ -131,61 +179,88 @@ You will build a web page with a Canvas element. It should render a black screen
### Recommended steps to start development
Locate the files that have been created for you in the `your-work` sub folder. It should contain the following:
Locate the starter files that have been created for you in the `your-work` sub folder. Your project structure should contain:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
your-work/
├── assets/
│ ├── enemyShip.png
│ └── player.png
├── index.html
├── app.js
└── package.json
```
Open the copy of this folder in Visual Studio Code. You need to have a local development environment setup, preferably with Visual Studio Code with NPM and Node installed. If you don't have `npm` set up on your computer, [here's how to do that](https://www.npmjs.com/get-npm).
**Understanding the project structure:**
- **Contains** game sprites in the `assets/` folder for organized resource management
- **Includes** the main HTML file that sets up your canvas element
- **Provides** a JavaScript file where you'll write your game rendering code
- **Features** a package.json with development server configuration for local testing
Open this folder in Visual Studio Code to begin development. You'll need a local development environment with Visual Studio Code, NPM, and Node.js installed. If you don't have `npm` set up on your computer, [here's how to install it](https://www.npmjs.com/get-npm).
Start your project by navigating to the `your_work` folder:
Start your development server by navigating to the `your-work` folder:
```bash
cd your-work
npm start
```
The above will start a HTTP Server on address `http://localhost:5000`. Open up a browser and input that address. It's a blank page right now, but that will change
**What this command accomplishes:**
- **Launches** a local HTTP server on `http://localhost:5000`
- **Serves** your HTML, CSS, and JavaScript files with proper MIME types
- **Enables** live development with automatic file watching
- **Provides** a professional development environment for testing your game
> Note: to see changes on your screen, refresh your browser.
> 💡 **Pro Tip**: Your browser will show a blank page initially that's expected! As you add code, refresh your browser to see your changes. Many developers use browser extensions like LiveReload for automatic refresh functionality.
### Add code
Add the needed code to `your-work/app.js` to solve the below
1. **Draw** a canvas with black background
> tip: add two lines under the appropriate TODO in `/app.js`, setting the `ctx` element to be black and the top/left coordinates to be at 0,0 and the height and width to equal that of the canvas.
2. **Load** textures
> tip: add the player and enemy images using `await loadTexture` and passing in the image path. You won't see them on the screen yet!
3. **Draw** hero in the center of the screen in the bottom half
> tip: use the `drawImage` API to draw heroImg to the screen, setting `canvas.width / 2 - 45` and `canvas.height - canvas.height / 4)`;
4. **Draw** 5*5 monsters
> tip: Now you can uncomment the code to draw enemies on the screen. Next, go to the `createEnemies` function and build it out.
First, set up some constants:
```javascript
const MONSTER_TOTAL = 5;
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
const STOP_X = START_X + MONSTER_WIDTH;
```
then, create a loop to draw the array of monsters onto the screen:
```javascript
for (let x = START_X; x < STOP_X; x += 98) {
for (let y = 0; y < 50 * 5; y += 50) {
ctx.drawImage(enemyImg, x, y);
}
}
```
Add the required code to `your-work/app.js` to complete the following tasks:
1. **Draw a canvas with black background**
> 💡 **Implementation Tip**: Add two lines under the appropriate TODO in `/app.js`. Set the `ctx.fillStyle` to black and use `ctx.fillRect()` with coordinates (0,0) and dimensions matching your canvas size.
2. **Load game textures**
> 💡 **Implementation Tip**: Use `await loadAsset()` to load both player and enemy images. Store them in variables for later use. The images won't appear on screen until you draw them!
3. **Draw hero ship in the center-bottom position**
> 💡 **Implementation Tip**: Use the `ctx.drawImage()` API to position your hero. Calculate the x-coordinate as `canvas.width / 2 - 45` to center it horizontally, and y-coordinate as `canvas.height - canvas.height / 4` to place it in the bottom quarter.
4. **Draw a 5×5 formation of enemy ships**
> 💡 **Implementation Tip**: Locate the `createEnemies` function and implement the nested loop structure. You'll need to calculate proper spacing and positioning for a professional formation appearance.
First, establish constants for proper enemy formation layout:
```javascript
const ENEMY_TOTAL = 5;
const ENEMY_SPACING = 98;
const FORMATION_WIDTH = ENEMY_TOTAL * ENEMY_SPACING;
const START_X = (canvas.width - FORMATION_WIDTH) / 2;
const STOP_X = START_X + FORMATION_WIDTH;
```
**Understanding these constants:**
- **Sets** the number of enemies per row and column to 5
- **Defines** spacing between enemies for visual clarity
- **Calculates** total formation width based on enemy count and spacing
- **Centers** the formation horizontally by calculating start and stop positions
Then, create nested loops to draw the enemy formation:
```javascript
for (let x = START_X; x < STOP_X; x += ENEMY_SPACING) {
for (let y = 0; y < 50 * 5; y += 50) {
ctx.drawImage(enemyImg, x, y);
}
}
```
**Step by step, here's what's happening:**
- **Iterates** through horizontal positions from left to right across the formation
- **Loops** through vertical positions to create rows of enemies
- **Draws** each enemy sprite at calculated x,y coordinates
- **Maintains** consistent spacing for a professional, organized appearance
## Result

@ -1,11 +1,67 @@
# Play with the Canvas API
# Assignment: Explore the Canvas API
## Learning Objectives
By completing this assignment, you will demonstrate your understanding of Canvas API fundamentals and apply creative problem-solving to build visual elements using JavaScript and HTML5 canvas.
## Instructions
Pick one element of the Canvas API and create something interesting around it. Can you create a little galaxy of repeated stars? Can you create an interesting texture of colored lines? You can look at CodePen for inspiration (but don't copy)
Choose one aspect of the Canvas API that interests you and create an engaging visual project around it. This assignment encourages you to experiment with the drawing capabilities you've learned while building something uniquely yours.
### Project Ideas to Inspire You
**Geometric Patterns:**
- **Create** a galaxy of animated twinkling stars using random positioning
- **Design** an interesting texture using repeated geometric shapes
- **Build** a kaleidoscope effect with rotating, colorful patterns
**Interactive Elements:**
- **Develop** a drawing tool that responds to mouse movements
- **Implement** shapes that change color when clicked
- **Design** a simple animation loop with moving elements
**Game-Related Graphics:**
- **Craft** a scrolling background for a space game
- **Build** particle effects like explosions or magic spells
- **Create** animated sprites with multiple frames
### Development Guidelines
**Research and Inspiration:**
- **Browse** CodePen for creative canvas examples (for inspiration, not copying)
- **Study** the [Canvas API documentation](https://developer.mozilla.org/docs/Web/API/Canvas_API) for additional methods
- **Experiment** with different drawing functions, colors, and animations
**Technical Requirements:**
- **Use** proper canvas setup with `getContext('2d')`
- **Include** meaningful comments explaining your approach
- **Test** your code thoroughly to ensure it runs without errors
- **Apply** modern JavaScript syntax (const/let, arrow functions)
**Creative Expression:**
- **Focus** on one Canvas API feature but explore it deeply
- **Add** your own creative twist to make the project personal
- **Consider** how your creation could be part of a larger application
### Submission Guidelines
Submit your completed project as a single HTML file with embedded CSS and JavaScript, or as separate files in a folder. Include a brief comment explaining your creative choices and the Canvas API features you explored.
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | --------------------------------------------------------- | ----------------------------------- | --------------------- |
| | Code is submitted showing an interesting texture or shape | Code is submitted, but does not run | Code is not submitted |
| Criteria | Exemplary | Adequate | Needs Improvement |
|----------|-----------|----------|-------------------|
| **Technical Implementation** | Canvas API used creatively with multiple features, code runs flawlessly, modern JavaScript syntax applied | Canvas API used correctly, code runs with minor issues, basic implementation | Canvas API attempted but code has errors or doesn't execute |
| **Creativity and Design** | Highly original concept with polished visual appeal, demonstrates deep exploration of chosen Canvas feature | Good use of Canvas features with some creative elements, solid visual result | Basic implementation with minimal creativity or visual appeal |
| **Code Quality** | Well-organized, commented code following best practices, efficient algorithms | Clean code with some comments, follows basic coding standards | Code lacks organization, minimal comments, inefficient implementation |
## Reflection Questions
After completing your project, consider these questions:
1. **What Canvas API feature did you choose and why?**
2. **What challenges did you encounter while building your project?**
3. **How could you extend this project into a larger application or game?**
4. **What other Canvas API features would you like to explore next?**
> 💡 **Pro Tip**: Start simple and gradually add complexity. A well-executed simple project is better than an overly ambitious project that doesn't work properly!

@ -1,9 +1,17 @@
# Build a Space Game Part 3: Adding Motion
Movement is the heart of any engaging game experience. Without motion, your space game would be nothing more than a static image interesting to look at, but hardly exciting to play! In this lesson, you'll breathe life into your game by learning how to create dynamic, interactive movement that responds to player input and creates autonomous behavior for game objects.
You'll discover the fundamental principles behind all game movement: coordinate manipulation, screen clearing, and redrawing. These concepts form the foundation of game animation and will serve you well whether you're building simple 2D games or complex interactive applications. Think of this as learning the core "engine" that powers visual interactivity.
By the end of this lesson, you'll have a fully interactive space game where players can control their hero spaceship and watch enemy ships move across the screen. You'll understand how keyboard events work, how to create smooth animations, and how to organize your code using modern JavaScript patterns. Let's transform your static game into an exciting interactive experience!
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/33)
## Understanding Game Movement
Games aren't much fun until you have aliens running around on screen! In this game, we will make use of two types of movements:
- **Keyboard/Mouse movement**: when the user interacts with the keyboard or mouse to move an object on the screen.
@ -17,42 +25,56 @@ Typically you need the following steps to accomplish *movement* on a screen:
2. **Clear the screen**, the screen needs to be cleared in between draws. We can clear it by drawing a rectangle that we fill with a background color.
3. **Redraw object** at new location. By doing this we finally accomplish moving the object from one location to the other.
Here's what it can look like in code:
Here's what it can look like in code:
```javascript
//set the hero's location
// Set the hero's location
hero.x += 5;
// clear the rectangle that hosts the hero
// Clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height)
// Redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
**Here's what this code does:**
- **Updates** the hero's x-coordinate by 5 pixels to move it horizontally
- **Clears** the entire canvas area to remove the previous frame
- **Fills** the canvas with a black background color
- **Redraws** the hero image at its new position
✅ Can you think of a reason why redrawing your hero many frames per second might accrue performance costs? Read about [alternatives to this pattern](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas).
## Handle keyboard events
You handle events by attaching specific events to code. Keyboard events are triggered on the whole window whereas mouse events like a `click` can be connected to clicking a specific element. We will use keyboard events throughout this project.
Interactive games respond to player input, and keyboard events are one of the most common ways players control their game characters. Understanding how to capture and respond to keyboard input is essential for creating engaging gameplay experiences.
To handle an event you need to use the window's `addEventListener()` method and provide it with two input parameters. The first parameter is the name of the event, for example `keyup`. The second parameter is the function that should be invoked as a result of the event taking place.
You handle events by attaching specific events to code. Keyboard events are triggered on the whole window whereas mouse events like a `click` can be connected to clicking a specific element. We will use keyboard events throughout this project.
To handle an event you need to use the window's `addEventListener()` method and provide it with two input parameters. The first parameter is the name of the event, for example `keyup`. The second parameter is the function that should be invoked as a result of the event taking place.
Here's an example:
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = string representation of the key
// evt.key = string representation of the key
if (evt.key === 'ArrowUp') {
// do something
}
})
});
```
**Breaking down what happens here:**
- **Listens** for keyboard events on the entire window
- **Captures** the event object which contains information about which key was pressed
- **Checks** if the pressed key matches a specific key (in this case, the up arrow)
- **Executes** code when the condition is met
For key events there are two properties on the event you can use to see what key was pressed:
- `key`, this is a string representation of the pressed key, for example `ArrowUp`
- `keyCode`, this is a number representation, for example `37`, corresponds to `ArrowLeft`.
- `key` - this is a string representation of the pressed key, for example `'ArrowUp'`
- `keyCode` - this is a number representation, for example `37`, corresponds to `ArrowLeft`
✅ Key event manipulation is useful outside of game development. What other uses can you think of for this technique?
@ -61,7 +83,7 @@ For key events there are two properties on the event you can use to see what key
There are some *special* keys that affect the window. That means that if you are listening to a `keyup` event and you use these special keys to move your hero it will also perform horizontal scrolling. For that reason you might want to *shut-off* this built-in browser behavior as you build out your game. You need code like this:
```javascript
let onKeyDown = function (e) {
const onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
@ -79,27 +101,41 @@ let onKeyDown = function (e) {
window.addEventListener('keydown', onKeyDown);
```
The above code will ensure that arrow-keys and the space key have their *default* behavior shut off. The *shut-off* mechanism happens when we call `e.preventDefault()`.
**Understanding this prevention code:**
- **Checks** for specific key codes that might cause unwanted browser behavior
- **Prevents** the default browser action for arrow keys and spacebar
- **Allows** other keys to function normally
- **Uses** `e.preventDefault()` to stop the browser's built-in behavior
## Game induced movement
Not all movement in games comes from player input. Many game objects need to move on their own think of enemies patrolling, bullets flying, or background elements scrolling. This type of automatic movement creates the dynamic environment that makes games feel alive.
We can make things move by themselves by using timers such as the `setTimeout()` or `setInterval()` function that update the location of the object on each tick, or time interval. Here's what that can look like:
```javascript
let id = setInterval(() => {
//move the enemy on the y axis
const id = setInterval(() => {
// Move the enemy on the y axis
enemy.y += 10;
})
}, 100);
```
**Here's what this movement code does:**
- **Creates** a timer that runs every 100 milliseconds
- **Updates** the enemy's y-coordinate by 10 pixels each time
- **Stores** the interval ID so we can stop it later if needed
- **Moves** the enemy downward on the screen automatically
## The game loop
The game loop is the heartbeat of your game it's what makes everything come together and creates the illusion of continuous motion. Think of it as the conductor of an orchestra, coordinating all the different elements to create a smooth, synchronized experience.
The game loop is a concept that is essentially a function that is invoked at regular intervals. It's called the game loop as everything that should be visible to the user is drawn into the loop. The game loop makes use of all the game objects that are part of the game, drawing all of them unless for some reason shouldn't be part of the game any more. For example if an object is an enemy that was hit by a laser and blows up, it's no longer part of the current game loop (you'll learn more on this in subsequent lessons).
Here's what a game loop can typically look like, expressed in code:
```javascript
let gameLoopId = setInterval(() =>
const gameLoopId = setInterval(() => {
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
@ -107,17 +143,27 @@ let gameLoopId = setInterval(() =>
drawHero();
drawEnemies();
drawStaticObjects();
}
gameLoop();
}, 200);
```
The above loop is invoked every `200` milliseconds to redraw the canvas. You have the ability to choose the best interval that makes sense for your game.
**Understanding the game loop structure:**
- **Clears** the entire canvas to remove the previous frame
- **Fills** the background with a solid color
- **Draws** all game objects in their current positions
- **Repeats** this process every 200 milliseconds to create smooth animation
- **Manages** the frame rate by controlling the interval timing
## Continuing the Space Game
You will take the existing code and extend it. Either start with the code that you completed during part I or use the code in [Part II- starter](your-work).
Now it's time to put theory into practice! You'll take the existing code and extend it with the movement concepts you've just learned. This is where your static space game transforms into an interactive experience.
You will take the existing code and extend it. Either start with the code that you completed during part I or use the code in [Part II- starter](your-work).
- **Moving the hero**: you will add code to ensure you can move the hero using the arrow keys.
- **Move enemies**: you will also need to add code to ensure the enemies move from top to bottom at a given rate.
**What you'll implement:**
- **Moving the hero**: you will add code to ensure you can move the hero using the arrow keys
- **Move enemies**: you will also need to add code to ensure the enemies move from top to bottom at a given rate
## Recommended steps
@ -132,25 +178,29 @@ Locate the files that have been created for you in the `your-work` sub folder. I
-| package.json
```
You start your project the `your_work` folder by typing:
You start your project in the `your-work` folder by typing:
```bash
cd your-work
npm start
```
**What this command does:**
- **Navigates** to your project directory
- **Starts** a HTTP Server on address `http://localhost:5000`
- **Serves** your game files so you can test them in a browser
The above will start a HTTP Server on address `http://localhost:5000`. Open up a browser and input that address, right now it should render the hero and all the enemies; nothing is moving - yet!
### Add code
1. **Add dedicated objects** for `hero` and `enemy` and `game object`, they should have `x` and `y` properties. (Remember the portion on [Inheritance or composition](../README.md) ).
1. **Add dedicated objects** for `hero` and `enemy` and `game object`, they should have `x` and `y` properties. (Remember the portion on [Inheritance or composition](../README.md)).
*HINT* `game object` should be the one with `x` and `y` and the ability to draw itself to a canvas.
>tip: start by adding a new GameObject class with its constructor delineated as below, and then draw it to the canvas:
> **Tip**: Start by adding a new `GameObject` class with its constructor delineated as below, and then draw it to the canvas:
```javascript
class GameObject {
constructor(x, y) {
this.x = x;
@ -168,12 +218,22 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
}
```
Now, extend this GameObject to create the Hero and Enemy.
**Understanding this base class:**
- **Defines** common properties that all game objects share (position, size, image)
- **Includes** a `dead` flag to track whether the object should be removed
- **Provides** a `draw()` method that renders the object on the canvas
- **Sets** default values for all properties that child classes can override
Now, extend this `GameObject` to create the `Hero` and `Enemy`:
```javascript
class Hero extends GameObject {
constructor(x, y) {
...it needs an x, y, type, and speed
super(x, y);
this.width = 98;
this.height = 75;
this.type = "Hero";
this.speed = 5;
}
}
```
@ -182,129 +242,152 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
class Enemy extends GameObject {
constructor(x, y) {
super(x, y);
(this.width = 98), (this.height = 50);
this.width = 98;
this.height = 50;
this.type = "Enemy";
let id = setInterval(() => {
const id = setInterval(() => {
if (this.y < canvas.height - this.height) {
this.y += 5;
} else {
console.log('Stopped at', this.y)
console.log('Stopped at', this.y);
clearInterval(id);
}
}, 300)
}, 300);
}
}
```
**Key concepts in these classes:**
- **Inherits** from `GameObject` using the `extends` keyword
- **Calls** the parent constructor with `super(x, y)`
- **Sets** specific dimensions and properties for each object type
- **Implements** automatic movement for enemies using `setInterval()`
2. **Add key-event handlers** to handle key navigation (move hero up/down left/right)
*REMEMBER* it's a cartesian system, top-left is `0,0`. Also remember to add code to stop *default behavior*
>tip: create your onKeyDown function and attach it to the window:
> **Tip**: Create your `onKeyDown` function and attach it to the window:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
const onKeyDown = function (e) {
console.log(e.keyCode);
// Add the code from the lesson above to stop default behavior
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Arrow keys
case 32:
e.preventDefault();
break; // Space
default:
break; // do not block other keys
}
};
window.addEventListener("keydown", onKeyDown);
```
**What this event handler does:**
- **Listens** for keydown events on the entire window
- **Logs** the key code to help you debug which keys are being pressed
- **Prevents** default browser behavior for arrow keys and spacebar
- **Allows** other keys to function normally
Check your browser console at this point, and watch the keystrokes being logged.
3. **Implement** the [Pub sub pattern](../README.md), this will keep your code clean as you follow the remaining parts.
The Publish-Subscribe pattern helps organize your code by separating event detection from event handling. This makes your code more modular and easier to maintain.
To do this last part, you can:
1. **Add an event listener** on the window:
```javascript
window.addEventListener("keyup", (evt) => {
if (evt.key === "ArrowUp") {
eventEmitter.emit(Messages.KEY_EVENT_UP);
} else if (evt.key === "ArrowDown") {
eventEmitter.emit(Messages.KEY_EVENT_DOWN);
} else if (evt.key === "ArrowLeft") {
eventEmitter.emit(Messages.KEY_EVENT_LEFT);
} else if (evt.key === "ArrowRight") {
eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
}
});
```
1. **Create an EventEmitter class** to publish and subscribe to messages:
```javascript
class EventEmitter {
constructor() {
this.listeners = {};
}
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach((l) => l(message, payload));
}
}
}
```
1. **Add constants** and set up the EventEmitter:
```javascript
const Messages = {
KEY_EVENT_UP: "KEY_EVENT_UP",
KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **Initialize the game**
window.addEventListener("keyup", (evt) => {
if (evt.key === "ArrowUp") {
eventEmitter.emit(Messages.KEY_EVENT_UP);
} else if (evt.key === "ArrowDown") {
eventEmitter.emit(Messages.KEY_EVENT_DOWN);
} else if (evt.key === "ArrowLeft") {
eventEmitter.emit(Messages.KEY_EVENT_LEFT);
} else if (evt.key === "ArrowRight") {
eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
}
});
```
**What this event system does:**
- **Detects** keyboard input and converts it to custom game events
- **Separates** input detection from game logic
- **Makes** it easy to change controls later without affecting game code
- **Allows** multiple systems to respond to the same input
2. **Create an EventEmitter class** to publish and subscribe to messages:
```javascript
function initGame() {
gameObjects = [];
createEnemies();
createHero();
eventEmitter.on(Messages.KEY_EVENT_UP, () => {
hero.y -=5 ;
})
eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
hero.y += 5;
});
eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
hero.x -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
```javascript
class EventEmitter {
constructor() {
this.listeners = {};
}
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
3. **Add constants** and set up the EventEmitter:
1. **Setup the game loop**
```javascript
const Messages = {
KEY_EVENT_UP: "KEY_EVENT_UP",
KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
**Understanding the setup:**
- **Defines** message constants to avoid typos and make refactoring easier
- **Declares** variables for images, canvas context, and game state
- **Creates** a global event emitter for the pub-sub system
- **Initializes** an array to hold all game objects
4. **Initialize the game**
Refactor the window.onload function to initialize the game and set up a game loop on a good interval. You'll also add a laser beam:
```javascript
function initGame() {
gameObjects = [];
createEnemies();
createHero();
eventEmitter.on(Messages.KEY_EVENT_UP, () => {
hero.y -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
hero.y += 5;
});
eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
hero.x -= 5;
});
4. **Setup the game loop**
Refactor the `window.onload` function to initialize the game and set up a game loop on a good interval. You'll also add a laser beam:
```javascript
window.onload = async () => {
@ -315,16 +398,22 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
laserImg = await loadTexture("assets/laserRed.png");
initGame();
let gameLoopId = setInterval(() => {
const gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
}, 100);
};
```
**Understanding the game setup:**
- **Waits** for the page to load completely before starting
- **Gets** the canvas element and its 2D rendering context
- **Loads** all image assets asynchronously using `await`
- **Starts** the game loop running at 100ms intervals (10 FPS)
- **Clears** and redraws the entire screen each frame
5. **Add code** to move enemies at a certain interval
Refactor the `createEnemies()` function to create the enemies and push them into the new gameObjects class:
@ -345,6 +434,48 @@ The above will start a HTTP Server on address `http://localhost:5000`. Open up a
}
}
```
**What the enemy creation does:**
- **Calculates** positions to center enemies on the screen
- **Creates** a grid of enemies using nested loops
- **Assigns** the enemy image to each enemy object
- **Adds** each enemy to the global game objects array
and add a `createHero()` function to do a similar process for the hero.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
**What the hero creation does:**
- **Positions** the hero at the bottom center of the screen
- **Assigns** the hero image to the hero object
- **Adds** the hero to the game objects array for rendering
and finally, add a `drawGameObjects()` function to start the drawing:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
**Understanding the drawing function:**
- **Iterates** through all game objects in the array
- **Calls** the `draw()` method on each object
- **Passes** the canvas context so objects can render themselves
Your enemies should start advancing on your hero spaceship!
}
}
```
and add a `createHero()` function to do a similar process for the hero.
@ -381,7 +512,21 @@ Use the Agent mode to complete the following challenge:
## 🚀 Challenge
As you can see, your code can turn into 'spaghetti code' when you start adding functions and variables and classes. How can you better organize your code so that it is more readable? Sketch out a system to organize your code, even if it still resides in one file.
As you can see, your code can turn into 'spaghetti code' when you start adding functions and variables and classes. How can you better organize your code so that it is more readable?
**Your challenge:**
Sketch out a system to organize your code, even if it still resides in one file. Consider these approaches:
- **Grouping related functions** together with clear comment headers
- **Separating concerns** - keep game logic separate from rendering
- **Using consistent naming** conventions for variables and functions
- **Creating modules** or namespaces to organize different aspects of your game
- **Adding documentation** that explains the purpose of each major section
**Reflection questions:**
- Which parts of your code are hardest to understand when you come back to them?
- How could you organize your code to make it easier for someone else to contribute?
- What would happen if you wanted to add new features like power-ups or different enemy types?
## Post-Lecture Quiz
@ -389,7 +534,13 @@ As you can see, your code can turn into 'spaghetti code' when you start adding f
## Review & Self Study
While we're writing our game without using frameworks, there are many JavaScript-based canvas frameworks for game development. Take some time to do some [reading about these](https://github.com/collections/javascript-game-engines).
While we're writing our game without using frameworks, there are many JavaScript-based canvas frameworks for game development. Take some time to do some [reading about these](https://github.com/collections/javascript-game-engines) and consider how they might solve some of the organizational challenges you identified.
**Explore these concepts:**
- Game engine architecture and design patterns
- Entity-Component-System (ECS) patterns in game development
- Performance optimization techniques for canvas-based games
- Modern JavaScript features that can improve game code organization
## Assignment

@ -2,10 +2,21 @@
## Instructions
Go over your current /app.js file in your game folder, and find ways to comment it and tidy it up. It's very easy for code to get out of control, and now's a good chance to add comments to ensure that you have readable code so that you can use it later.
Clean, well-documented code is essential for maintaining and sharing your projects. In this assignment, you'll practice one of the most important habits of professional developers: writing clear, helpful comments that explain your code's purpose and functionality.
Go over your current `app.js` file in your game folder, and find ways to comment it and tidy it up. It's very easy for code to get out of control, and now's a good chance to add comments to ensure that you have readable code so that you can use it later.
**Your task includes:**
- **Add comments** explaining what each major section of code does
- **Document functions** with clear descriptions of their purpose and parameters
- **Organize code** into logical blocks with section headers
- **Remove** any unused or redundant code
- **Use consistent** naming conventions for variables and functions
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------- |
| | `app.js` code is fully commented and organized into logical blocks | `app.js` code is adequately commented | `app.js` code is somewhat disorganized and lacks good comments |
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | --------- | -------- | ----------------- |
| **Code Documentation** | `app.js` code is fully commented with clear, helpful explanations for all major sections and functions | `app.js` code is adequately commented with basic explanations for most sections | `app.js` code has minimal comments and lacks clear explanations |
| **Code Organization** | Code is organized into logical blocks with clear section headers and consistent structure | Code has some organization with basic grouping of related functionality | Code is somewhat disorganized and difficult to follow |
| **Code Quality** | All variables and functions use descriptive names, no unused code, follows consistent conventions | Most code follows good naming practices with minimal unused code | Variable names are unclear, contains unused code, inconsistent style |

@ -4,77 +4,108 @@
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/35)
In this lesson you will learn how to shoot lasers with JavaScript! We will add two things to our game:
Collision detection is the heart of interactive game development, transforming static visuals into dynamic, engaging experiences. In this lesson, you'll implement one of gaming's most fundamental mechanics: the ability to shoot projectiles and detect when objects collide with each other. These concepts form the foundation of countless game interactions, from simple arcade games to complex modern titles.
- **A laser**: this laser is shot from your heroes ship and vertically upwards
- **Collision detection**, as part of implementing the ability to *shoot* we will also add some nice game rules:
- **Laser hits enemy**: Enemy dies if hit by a laser
- **Laser hits top screen**: A laser is destroyed if hitting the top part of the screen
- **Enemy and hero collision**: An enemy and the hero are destroyed if hitting each other
- **Enemy hits bottom of the screen**: An enemy and a hero are destroyed if the enemy hits the bottom of the screen
Building on your previous work with the space game, you'll add laser shooting capabilities and create a comprehensive collision detection system. You'll learn to think about game objects as mathematical rectangles, implement intersection algorithms, and create engaging game rules that respond to player actions. This technical foundation will give you the skills to create interactive experiences in any game or web application.
In short, you -- *the hero* -- need to hit all enemies with a laser before they manage to move to the bottom of the screen.
By the end of this lesson, you'll have a fully functional space combat system where you can shoot lasers at enemies, handle multiple types of collisions, and manage object lifecycles. These skills translate directly to modern web development, where interactive elements and dynamic user interfaces rely on similar collision detection principles.
✅ Do a little research on the very first computer game ever written. What was its functionality?
Let's be heroic together!
Let's build something amazing together!
## Collision detection
How do we do collision detection? We need to think of our game objects as rectangles moving about. Why is that you might ask? Well, the image used to draw a game object is a rectangle: it has an `x`, `y`, `width` and `height`.
Collision detection forms the core of interactive gameplay, determining when game objects interact with each other. Understanding this concept will unlock your ability to create engaging game mechanics and responsive user experiences.
If two rectangles, i.e a hero and enemy *intersect*, you have a collision. What should happen then is up to the rules of the game. To implement collision detection you therefore need the following:
We approach collision detection by treating all game objects as rectangles with defined boundaries. This mathematical representation allows us to perform precise calculations to determine when objects overlap or intersect.
1. A way to get a rectangle representation of a game object, something like this:
### Rectangle representation
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width
}
}
```
Every game object needs a way to describe its boundaries in space. Here's how we create a rectangle representation:
2. A comparison function, this function can look like this:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width
}
}
```
**Here's what this code does:**
- **Defines** the top boundary using the object's y coordinate
- **Establishes** the left boundary using the object's x coordinate
- **Calculates** the bottom boundary by adding height to the y position
- **Determines** the right boundary by adding width to the x position
### Intersection algorithm
Once we have rectangle representations, we need a function to test if they overlap:
```javascript
function intersectRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
```
**Breaking down what happens here:**
- **Checks** if the second rectangle is completely to the right of the first
- **Tests** if the second rectangle is completely to the left of the first
- **Verifies** if the second rectangle is completely below the first
- **Determines** if the second rectangle is completely above the first
- **Returns** `true` when none of these conditions are met (indicating overlap)
```javascript
function intersectRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
```
## Managing object lifecycles
## How do we destroy things
Game objects need a way to be removed from the game when they're no longer needed. This lifecycle management ensures optimal performance and proper game behavior.
To destroy things in a game you need to let the game know it should no longer paint this item in the game loop that triggers on a certain interval. A way to do this is to mark a game object as *dead* when something happens, like so:
The most efficient approach uses a flag-based system to mark objects for removal:
```javascript
// collision happened
enemy.dead = true
// Mark object for removal
enemy.dead = true;
```
Then you an proceed to sort out *dead* objects before repainting the screen, like so:
**Understanding this approach:**
- **Marks** the object as ready for removal without immediately deleting it
- **Allows** the current game loop iteration to complete safely
- **Prevents** errors from objects being accessed after deletion
Then filter out marked objects before the next render cycle:
```javascript
gameObjects = gameObject.filter(go => !go.dead);
gameObjects = gameObjects.filter(go => !go.dead);
```
## How do we fire a laser
**This filtering process:**
- **Creates** a new array containing only living objects
- **Removes** all objects marked with `dead: true`
- **Updates** the game state cleanly between frames
- **Maintains** optimal performance by keeping the object array clean
## Implementing laser mechanics
Laser firing combines user input handling with dynamic object creation, creating an engaging interactive experience. This mechanic forms the foundation of player agency in your game.
Firing a laser translates to responding to a key-event and creating an object that moves in a certain direction. We therefore need to carry out the following steps:
Implementing laser functionality requires coordinating several systems:
1. **Create a laser object**: from the top of our hero's ship, that upon creation starts moving upwards towards the screen top.
2. **Attach code to a key event**: we need to choose a key on the keyboard that represents the player shooting the laser.
3. **Create a game object that looks like a laser** when the key is pressed.
**Key components to implement:**
- **Create** laser objects that spawn from the hero's position
- **Handle** keyboard input to trigger laser creation
- **Manage** laser movement and lifecycle
- **Implement** visual representation for the laser projectiles
## Cooldown on our laser
## Implementing firing rate control
The laser needs to fire every time you press a key, like *space* for example. To prevent the game producing way too many lasers in a short time we need to fix this. The fix is by implementing a so called *cooldown*, a timer, that ensures that a laser can only be fired so often. You can implement that in the following way:
Without rate limiting, players could create hundreds of lasers instantly, breaking game balance and performance. A cooldown system ensures controlled, strategic firing mechanics.
Cooldown systems use timing mechanisms to enforce delays between actions:
```javascript
class Cooldown {
@ -82,41 +113,55 @@ class Cooldown {
this.cool = false;
setTimeout(() => {
this.cool = true;
}, time)
}, time);
}
}
class Weapon {
constructor {
constructor() {
this.cooldown = null;
}
fire() {
if (!this.cooldown || this.cooldown.cool) {
// produce a laser
// Create laser projectile
this.cooldown = new Cooldown(500);
} else {
// do nothing - it hasn't cooled down yet.
// Weapon is still cooling down
}
}
}
```
✅ Refer to lesson 1 in the space game series to remind yourself about *cooldowns*.
**Understanding the cooldown system:**
- **Initializes** cooldown state as `false` when created
- **Uses** `setTimeout()` to automatically reset the cooldown after the specified time
- **Checks** cooldown status before allowing new laser creation
- **Prevents** rapid-fire abuse while maintaining responsive controls
✅ Refer to lesson 1 in the space game series to remind yourself about cooldowns.
## Building the collision system
## What to build
You'll extend your existing space game code to create a fully functional combat system. This implementation will bring together all the concepts we've discussed into working game mechanics.
You will take the existing code (which you should have cleaned up and refactored) from the previous lesson, and extend it. Either start with the code from part II or use the code at [Part III- starter](/your-work).
Starting from your previous lesson's code, you'll add comprehensive collision detection with specific game rules that create engaging gameplay challenges.
> tip: the laser that you'll work with is already in your assets folder and referenced by your code
> 💡 **Pro Tip**: The laser sprite is already included in your assets folder and referenced in your code, ready for implementation.
- **Add collision detection**, when a laser collides with something the following rules should apply:
1. **Laser hits enemy**: enemy dies if hit by a laser
2. **Laser hits top screen**: A laser is destroyed if it hits the top part of our screen
3. **Enemy and hero collision**: an enemy and the hero is destroyed if hitting each other
4. **Enemy hits bottom of the screen**: An enemy and a hero is destroyed if the enemy hits the bottom of the screen
### Collision rules to implement
## Recommended steps
**Game mechanics to add:**
1. **Laser hits enemy**: Enemy object is destroyed when struck by a laser projectile
2. **Laser hits screen boundary**: Laser is removed when reaching the top edge of the screen
3. **Enemy and hero collision**: Both objects are destroyed when they intersect
4. **Enemy reaches bottom**: Game over condition when enemies reach the screen bottom
Locate the files that have been created for you in the `your-work` sub folder. It should contain the following:
## Setting up your development environment
Your starter files are organized in the `your-work` subfolder with all necessary assets and basic structure ready for enhancement.
### Project structure
```bash
-| assets
@ -128,155 +173,251 @@ Locate the files that have been created for you in the `your-work` sub folder. I
-| package.json
```
You start your project the `your_work` folder by typing:
**Understanding the file structure:**
- **Contains** all sprite images needed for the game objects
- **Includes** the main HTML document and JavaScript application file
- **Provides** package configuration for local development server
### Starting the development server
Navigate to your project folder and start the local server:
```bash
cd your-work
npm start
```
The above will start a HTTP Server on address `http://localhost:5000`. Open up a browser and input that address, right now it should render the hero and all the enemies, nothing is moving - yet :).
**This command sequence:**
- **Changes** directory to your working project folder
- **Starts** a local HTTP server on `http://localhost:5000`
- **Serves** your game files for testing and development
- **Enables** live development with automatic reloading
Open your browser and navigate to `http://localhost:5000` to see your current game state with the hero and enemies rendered on screen.
### Step-by-step implementation
### Add code
#### 1. Add rectangle collision bounds
1. **Setup a rectangle representation of your game object, to handle collision** The below code allows you to get a rectangle representation of a `GameObject`. Edit your GameObject class to extend it:
Extend your `GameObject` class to provide collision boundary information:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
}
```
**This method accomplishes:**
- **Creates** a rectangle object with precise boundary coordinates
- **Calculates** bottom and right edges using position plus dimensions
- **Returns** an object ready for collision detection algorithms
- **Provides** a standardized interface for all game objects
#### 2. Implement intersection detection
Create a utility function to test rectangle overlap:
```javascript
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
```
**This algorithm works by:**
- **Tests** four separation conditions between rectangles
- **Returns** `false` if any separation condition is true
- **Indicates** collision when no separation exists
- **Uses** negation logic for efficient intersection testing
#### 3. Implement laser firing system
##### Message constants
Define event messages for laser and collision handling:
```javascript
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
```
**These constants provide:**
- **Standardizes** event names throughout the application
- **Enables** consistent communication between game systems
- **Prevents** typos in event handler registration
##### Keyboard input handling
Add space key detection to your key event listener:
```javascript
} else if(evt.keyCode === 32) {
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}
```
**This input handler:**
- **Detects** space key presses using keyCode 32
- **Emits** a standardized event message
- **Enables** decoupled firing logic
##### Event listener setup
Register firing behavior in your `initGame()` function:
```javascript
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
if (hero.canFire()) {
hero.fire();
}
});
```
**This event listener:**
- **Responds** to space key events
- **Checks** firing cooldown status
- **Triggers** laser creation when allowed
Add collision handling for laser-enemy interactions:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
});
```
**This collision handler:**
- **Receives** collision event data with both objects
- **Marks** both objects for removal
- **Ensures** proper cleanup after collision
#### 4. Create the Laser class
Implement a laser projectile that moves upward and manages its own lifecycle:
```javascript
class Laser extends GameObject {
constructor(x, y) {
super(x, y);
this.width = 9;
this.height = 33;
this.type = 'Laser';
this.img = laserImg;
let id = setInterval(() => {
if (this.y > 0) {
this.y -= 15;
} else {
this.dead = true;
clearInterval(id);
}
```
2. **Add code that checks collision** This will be a new function that tests whether two rectangles intersect:
```javascript
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
```
3. **Add laser firing capability**
1. **Add key-event message**. The *space* key should create a laser just above the hero ship. Add three constants in the Messages object:
```javascript
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
```
1. **Handle space key**. Edit the `window.addEventListener` keyup function to handle spaces:
```javascript
} else if(evt.keyCode === 32) {
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}
```
1. **Add listeners**. Edit the `initGame()` function to ensure that hero can fire when the space bar is hit:
```javascript
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
if (hero.canFire()) {
hero.fire();
}
```
and add a new `eventEmitter.on()` function to ensure behavior when an enemy collides with a laser:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
})
```
1. **Move object**, Ensure the laser moves to the top of the screen gradually. You'll create a new Laser class that extends `GameObject`, as you've done before:
```javascript
class Laser extends GameObject {
constructor(x, y) {
super(x,y);
(this.width = 9), (this.height = 33);
this.type = 'Laser';
this.img = laserImg;
let id = setInterval(() => {
if (this.y > 0) {
this.y -= 15;
} else {
this.dead = true;
clearInterval(id);
}
}, 100)
}
}, 100);
}
}
```
**This class implementation:**
- **Extends** GameObject to inherit basic functionality
- **Sets** appropriate dimensions for the laser sprite
- **Creates** automatic upward movement using `setInterval()`
- **Handles** self-destruction when reaching screen top
- **Manages** its own animation timing and cleanup
#### 5. Implement collision detection system
Create a comprehensive collision detection function:
```javascript
function updateGameObjects() {
const enemies = gameObjects.filter(go => go.type === 'Enemy');
const lasers = gameObjects.filter(go => go.type === "Laser");
// Test laser-enemy collisions
lasers.forEach((laser) => {
enemies.forEach((enemy) => {
if (intersectRect(laser.rectFromGameObject(), enemy.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
first: laser,
second: enemy,
});
}
```
1. **Handle collisions**, Implement collision rules for the laser. Add an `updateGameObjects()` function that tests colliding objects for hits
```javascript
function updateGameObjects() {
const enemies = gameObjects.filter(go => go.type === 'Enemy');
const lasers = gameObjects.filter((go) => go.type === "Laser");
// laser hit something
lasers.forEach((l) => {
enemies.forEach((m) => {
if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
first: l,
second: m,
});
}
});
});
gameObjects = gameObjects.filter(go => !go.dead);
}
```
Make sure to add `updateGameObjects()` into your game loop in `window.onload`.
4. **Implement cooldown** on the laser, so it can only be fired so often.
Finally, edit the Hero class so that it can cooldown:
```javascript
class Hero extends GameObject {
constructor(x, y) {
super(x, y);
(this.width = 99), (this.height = 75);
this.type = "Hero";
this.speed = { x: 0, y: 0 };
this.cooldown = 0;
}
fire() {
gameObjects.push(new Laser(this.x + 45, this.y - 10));
this.cooldown = 500;
let id = setInterval(() => {
if (this.cooldown > 0) {
this.cooldown -= 100;
} else {
clearInterval(id);
}
}, 200);
}
canFire() {
return this.cooldown === 0;
}
});
});
// Remove destroyed objects
gameObjects = gameObjects.filter(go => !go.dead);
}
```
**This collision system:**
- **Filters** game objects by type for efficient testing
- **Tests** every laser against every enemy for intersections
- **Emits** collision events when intersections are detected
- **Cleans** up destroyed objects after collision processing
> ⚠️ **Important**: Add `updateGameObjects()` to your main game loop in `window.onload` to enable collision detection.
#### 6. Add cooldown system to Hero class
Enhance the Hero class with firing mechanics and rate limiting:
```javascript
class Hero extends GameObject {
constructor(x, y) {
super(x, y);
this.width = 99;
this.height = 75;
this.type = "Hero";
this.speed = { x: 0, y: 0 };
this.cooldown = 0;
}
fire() {
gameObjects.push(new Laser(this.x + 45, this.y - 10));
this.cooldown = 500;
let id = setInterval(() => {
if (this.cooldown > 0) {
this.cooldown -= 100;
} else {
clearInterval(id);
}
```
}, 200);
}
canFire() {
return this.cooldown === 0;
}
}
```
**Understanding the enhanced Hero class:**
- **Initializes** cooldown timer at zero (ready to fire)
- **Creates** laser objects positioned above the hero ship
- **Sets** cooldown period to prevent rapid firing
- **Decrements** cooldown timer using interval-based updates
- **Provides** firing status check through `canFire()` method
### Testing your implementation
At this point, your space game features fully functional combat mechanics! Test these features:
- **Navigate** using arrow keys for precise movement
- **Fire** lasers using the space bar with proper cooldown timing
- **Destroy** enemies by hitting them with laser projectiles
- **Experience** smooth object lifecycle management
At this point, your game has some functionality! You can navigate with your arrow keys, fire a laser with your space bar, and enemies disappear when you hit them. Well done!
Congratulations on building a complete interactive game system!
## GitHub Copilot Agent Challenge 🚀

@ -2,10 +2,47 @@
## Instructions
To better understand how collisions work, build a very small game with a few items that collide. Make them move via keypresses or mouse clicks, and make something happen to one of the items when it is hit. It could be something like a meteor hitting the earth, or bumper-cars. Get creative!
Apply your collision detection knowledge by creating a custom mini-game that demonstrates different types of object interactions. This assignment will help you understand collision mechanics through creative implementation and experimentation.
### Project requirements
**Create a small interactive game featuring:**
- **Multiple moving objects** that can be controlled via keyboard or mouse input
- **Collision detection system** using rectangle intersection principles from the lesson
- **Visual feedback** when collisions occur (object destruction, color changes, effects)
- **Game rules** that make collisions meaningful and engaging
### Creative suggestions
**Consider implementing one of these scenarios:**
- **Asteroid field**: Navigate a ship through dangerous space debris
- **Bumper cars**: Create a physics-based collision arena
- **Meteor defense**: Protect Earth from incoming space rocks
- **Collection game**: Gather items while avoiding obstacles
- **Territory control**: Competing objects trying to claim space
### Technical implementation
**Your solution should demonstrate:**
- Proper use of rectangle-based collision detection
- Event-driven programming for user input
- Object lifecycle management (creation and destruction)
- Clean code organization with appropriate class structure
### Bonus challenges
**Enhance your game with additional features:**
- **Particle effects** when collisions occur
- **Sound effects** for different collision types
- **Scoring system** based on collision outcomes
- **Multiple collision types** with different behaviors
- **Progressive difficulty** that increases over time
## Rubric
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | ----------------- |
| | Complete working code sample is produced, with items drawn to canvas, basic collision happening, and reactions occurring | Code is incomplete in some way | Code malfunctions |
| Criteria | Exemplary | Adequate | Needs Improvement |
|----------|-----------|----------|-------------------|
| **Collision Detection** | Implements accurate rectangle-based collision detection with multiple object types and sophisticated interaction rules | Basic collision detection works correctly with simple object interactions | Collision detection has issues or doesn't work consistently |
| **Code Quality** | Clean, well-organized code with proper class structure, meaningful variable names, and appropriate comments | Code works but could be better organized or documented | Code is difficult to understand or poorly structured |
| **User Interaction** | Responsive controls with smooth gameplay, clear visual feedback, and engaging mechanics | Basic controls work with adequate feedback | Controls are unresponsive or confusing |
| **Creativity** | Original concept with unique features, visual polish, and innovative collision behaviors | Standard implementation with some creative elements | Basic functionality without creative enhancements |

@ -1,32 +1,50 @@
# Build a Space Game Part 6: End and Restart
Every great game needs a satisfying conclusion and the ability to play again. In this final lesson of our space game series, you'll learn how to implement end conditions that determine when your game finishes and how to give players the option to restart for another adventure. These concepts are fundamental to creating engaging, replayable gaming experiences.
You've built an impressive space game with movement, collision detection, and scoring systems. Now you'll add the finishing touches that transform your project from a technical demo into a complete, polished game. Understanding end conditions and restart mechanics will also prepare you for building more complex games in the future.
By the end of this lesson, you'll have a fully functional space game with win/lose conditions, restart capabilities, and a smooth user experience. Let's bring your space game to its epic conclusion and make it infinitely replayable!
## Pre-Lecture Quiz
[Pre-lecture quiz](https://ff-quizzes.netlify.app/web/quiz/39)
There are different ways to express and *end condition* in a game. It's up to you as the creator of the game to say why the game has ended. Here are some reasons, if we assume we are talking about the space game you have been building so far:
## Understanding Game End Conditions
Game end conditions define when your game concludes and what triggers that conclusion. These conditions create goals for players and provide clear feedback about their performance. Let's explore the most common end condition patterns you can implement in your space game.
There are different ways to express an *end condition* in a game. It's up to you as the creator of the game to say why the game has ended. Here are some reasons, if we assume we are talking about the space game you have been building so far:
- **`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 level
- **Your ship has been destroyed**: There are definitely games where you lose the game if your ship is destroyed. Another common approach is that you have the concept of lives. Every time a your ship is destroyed it deducts a life. Once all lives have been lost then you lose the game.
- **You've collected `N` 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.
- **Complete a level**: This might involve several conditions such as `X` enemy ships destroyed, `Y` points collected or maybe that a specific item has been collected.
## Restarting
## Implementing Game Restart Functionality
Restart functionality is essential for creating an engaging gaming experience. When players enjoy your game, they'll want to play again immediately without having to reload the page or navigate away. A smooth restart mechanism keeps players engaged and provides opportunities to improve their performance.
Think about your favorite games - the best ones make it effortless to jump into another round. We'll implement a restart system that clears the previous game state and initializes everything fresh for a new adventure.
**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?
If people enjoy your game they are likely to want to replay it. Once the game ends for whatever reason you should offer an alternative to restart.
## What You'll Build
✅ Think a bit about under what conditions you find a game ends, and then how you are prompted to restart
In this lesson, you'll implement the final features that complete your space game experience. These additions will transform your game from a technical demonstration into a polished, replayable experience.
## What to build
**Game completion features you'll add:**
You will be adding these rules to your game:
1. **Victory condition**: Once all enemy ships are destroyed, the game ends with a celebration message
2. **Defeat condition**: When the player's lives reach zero, the game ends with a defeat message
3. **Restart mechanism**: Players can press Enter to start a fresh game after winning or losing
4. **State management**: The game properly clears previous state and reinitializes all systems
1. **Winning the game**. Once all enemy ships have been destroyed, you win the game. Additionally display some kind of victory message.
1. **Restart**. Once all your lives are lost or the game is won, you should offer a way to restart the game. Remember! You will need to reinitialize the game and the previous game state should be cleared.
## Getting Started
## Recommended steps
Before implementing the end conditions, let's set up your development environment and review the project structure. You'll be working with the files from your previous space game lessons.
Locate the files that have been created for you in the `your-work` sub folder. It should contain the following:
**Your project structure should contain:**
```bash
-| assets
@ -39,171 +57,249 @@ Locate the files that have been created for you in the `your-work` sub folder. I
-| package.json
```
You start your project the `your_work` folder by typing:
**To start your development server:**
```bash
cd your-work
npm start
```
The above will start a HTTP Server on address `http://localhost:5000`. Open up a browser and input that address. Your game should be in a playable state.
**Here's what this command does:**
- **Starts** a local HTTP server on `http://localhost:5000`
- **Serves** your game files with proper MIME types
- **Enables** live development with automatic reloading
Open your browser and navigate to `http://localhost:5000`. Your game should be in a playable state from the previous lessons.
> 💡 **Pro Tip**: To avoid warnings in Visual Studio Code, declare `gameLoopId` at the top of your file as `let gameLoopId;` instead of declaring it inside the `window.onload` function. This follows modern JavaScript variable declaration best practices.
## Implementation Steps
### Step 1: Create End Condition Tracking Functions
First, you'll create utility functions that check the current state of your game. These functions will help determine when the game should end based on different victory or defeat conditions.
```javascript
function isHeroDead() {
return hero.life <= 0;
}
function isEnemiesDead() {
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
return enemies.length === 0;
}
```
> tip: to avoid warnings in Visual Studio Code, edit the `window.onload` function to call `gameLoopId` as is (without `let`), and declare the gameLoopId at the top of the file, independently: `let gameLoopId;`
**Breaking down what happens here:**
- **Checks** if the hero's life has reached zero or below
- **Filters** the game objects to find only living enemy ships
- **Returns** `true` when all enemies have been destroyed
- **Uses** the logical comparison operators to create clear boolean results
- **Leverages** array filtering to efficiently count remaining enemies
### Add code
### Step 2: Update Event Handlers for End Conditions
1. **Track end condition**. Add code that keeps track of the number of enemies, or if the hero ship has been destroyed by adding these two functions:
Next, you'll modify your existing event handlers to check for end conditions after each collision. This ensures your game responds immediately when victory or defeat conditions are met.
```javascript
function isHeroDead() {
return hero.life <= 0;
```javascript
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);
});
```
**Here's what this code accomplishes:**
- **Handles** laser-enemy collisions by marking both objects as dead
- **Increments** the player's score when enemies are destroyed
- **Checks** for victory condition after each enemy destruction
- **Manages** hero-enemy collisions by reducing player lives
- **Prioritizes** defeat over victory to prevent conflicting end states
- **Triggers** appropriate end game sequences based on conditions met
### Step 3: Add New Message Constants
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.
```javascript
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
```
**In the above, we've:**
- **Added** constants for game end events to maintain consistency
- **Used** descriptive names that clearly indicate the event purpose
- **Followed** the existing naming convention for message types
### Step 4: Implement Restart Controls
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:**
```javascript
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
```
function isEnemiesDead() {
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
return enemies.length === 0;
**Add the new message constant:**
```javascript
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
```
**What you need to know:**
- **Extends** your existing keyboard event handling system
- **Uses** the Enter key as the restart trigger for intuitive user experience
- **Emits** a custom event that other parts of your game can listen for
- **Maintains** the same pattern as your other keyboard controls
### Step 5: Create the Message Display System
Your game needs a way to communicate victory, defeat, and restart instructions to players. You'll create a flexible message display function that can show different types of messages with appropriate styling.
**Create the `displayMessage()` function:**
```javascript
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:**
- **Sets** the font size and family for clear, readable text
- **Applies** a color parameter with "red" as the default for warnings
- **Centers** the text horizontally and vertically on the canvas
- **Uses** modern JavaScript default parameters for flexible color options
- **Leverages** the canvas 2D context for direct text rendering
**Create the `endGame()` function:**
```javascript
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"
);
}
```
1. **Add logic to message handlers**. Edit the `eventEmitter` to handle these conditions:
```javascript
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);
});
```
1. **Add new message types**. Add these Messages to the constants object:
```javascript
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
```
2. **Add restart code** code that restarts the game at the press of a selected button.
1. **Listen to key press `Enter`**. Edit your window's eventListener to listen for this press:
```javascript
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
```
1. **Add restart message**. Add this Message to your Messages constant:
```javascript
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
```
1. **Implement game rules**. Implement the following game rules:
1. **Player win condition**. When all enemy ships are destroyed, display a victory message.
1. First, create a `displayMessage()` function:
```javascript
function displayMessage(message, color = "red") {
ctx.font = "30px Arial";
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
```
1. Create an `endGame()` function:
```javascript
function endGame(win) {
clearInterval(gameLoopId);
// set a delay so we are sure any paints have finished
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)
}
```
1. **Restart logic**. When all lives are lost or the player won the game, display that the game can be restarted. Additionally restart the game when the *restart* key is hit (you can decide what key should be mapped to restart).
1. Create the `resetGame()` function:
```javascript
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);
}
}
```
1. Add a call to the `eventEmitter` to reset the game in `initGame()`:
```javascript
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
```
1. Add a `clear()` function to the EventEmitter:
```javascript
clear() {
this.listeners = {};
}
```
👽 💥 🚀 Congratulations, Captain! Your game is complete! Well done! 🚀 💥 👽
---
}, 200)
}
```
**Understanding this function:**
- **Stops** the game loop immediately to prevent further updates
- **Waits** 200 milliseconds to ensure all pending canvas operations complete
- **Clears** the entire canvas and fills it with a black background
- **Displays** conditional messages based on victory or defeat
- **Uses** green text for victory and red text (default) for defeat
- **Provides** clear instructions for restarting the game
### Step 6: Implement Game Reset Functionality
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:**
```javascript
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:**
- **Checks** if a game loop is currently running before resetting
- **Clears** the existing game loop to stop all current game activity
- **Removes** all event listeners to prevent memory leaks
- **Reinitializes** the game state with fresh objects and variables
- **Starts** a new game loop with all the essential game functions
- **Maintains** the same 100ms interval for consistent game performance
**Add the Enter key event handler to your `initGame()` function:**
```javascript
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
```
**Add the `clear()` method to your EventEmitter class:**
```javascript
clear() {
this.listeners = {};
}
```
**Key points to remember:**
- **Connects** the Enter key press to the reset game functionality
- **Registers** this event listener during game initialization
- **Provides** a clean way to remove all event listeners when resetting
- **Prevents** memory leaks by clearing event handlers between games
- **Resets** the listeners object to an empty state for fresh initialization
## Congratulations! 🎉
👽 💥 🚀 **Fantastic work, Captain!** Your space game is now complete with proper end conditions and restart functionality! You've successfully built a fully functional game that players can enjoy again and again. 🚀 💥 👽
**What you've accomplished:**
- **Implemented** victory and defeat conditions that respond to game events
- **Created** a smooth restart system that clears previous game state
- **Built** an intuitive user interface for game completion messages
- **Designed** a replayable gaming experience with proper state management
- **Applied** modern JavaScript patterns for event handling and game flow
## GitHub Copilot Agent Challenge 🚀
@ -213,9 +309,37 @@ Use the Agent mode to complete the following challenge:
**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.
## 🚀 Challenge
## 🚀 Optional Enhancement Challenge
**Add Audio to Your Game**: Enhance your gameplay experience by implementing sound effects! Consider adding audio for:
- **Laser shots** when the player fires
- **Enemy destruction** when ships are hit
- **Hero damage** when the player takes hits
- **Victory music** when the game is won
- **Defeat sound** when the game is lost
**Audio implementation example:**
```javascript
// 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:**
- **Creates** Audio objects for different sound effects
- **Resets** the `currentTime` to allow rapid-fire sound effects
- **Handles** browser autoplay policies by triggering sounds from user interactions
- **Manages** audio volume and timing for better game experience
Add a sound! Can you add a sound to enhance your game play, maybe when there's a laser hit, or the hero dies or wins? Have a look at this [sandbox](https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_audio_play) to learn how to play sound using JavaScript
> 💡 **Learning Resource**: Explore this [audio sandbox](https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_audio_play) to learn more about implementing audio in JavaScript games.
## Post-Lecture Quiz

@ -1,19 +1,157 @@
# Build a Sample Game
## Instructions
## Assignment Overview
Try building a small game where you practice on different end conditions. Vary between getting a number of points, the hero loses all lives or all monsters are defeated. Build something simple like a console based adventure game. Use the below game flow as inspiration:
Now that you've mastered game end conditions and restart functionality in your space game, it's time to apply these concepts to a completely new gaming experience. You'll design and build your own game that demonstrates different end condition patterns and restart mechanics.
This assignment challenges you to think creatively about game design while practicing the technical skills you've learned. You'll explore different victory and defeat scenarios, implement player progression, and create engaging restart experiences.
## Project Requirements
### Core Game Features
Your game must include the following essential elements:
**End Condition Variety**: Implement at least two different ways the game can end:
- **Point-based victory**: Player reaches a target score or collects specific items
- **Life-based defeat**: Player loses all available lives or health points
- **Objective completion**: All enemies defeated, specific items collected, or goals achieved
- **Time-based**: Game ends after a set duration or countdown reaches zero
**Restart Functionality**:
- **Clear game state**: Remove all previous game objects and reset variables
- **Reinitialize systems**: Start fresh with new player stats, enemies, and objectives
- **User-friendly controls**: Provide clear instructions for restarting the game
**Player Feedback**:
- **Victory messages**: Celebrate player achievements with positive feedback
- **Defeat messages**: Provide encouraging messages that motivate replay
- **Progress indicators**: Show current score, lives, or objective status
### Game Ideas and Inspiration
Choose one of these game concepts or create your own:
#### 1. Console Adventure Game
Create a text-based adventure with combat mechanics:
```
Hero> Strikes with broadsword - orc takes 3p damage
Orc> Hits with club - hero takes 2p damage
Orc> Hits with club - hero takes 2p damage
Hero> Kicks - orc takes 1p damage
Game> Orc is defeated - Hero collects 2 coins
Game> ****No more monsters, you have conquered the evil fortress****
```
## Rubric
**Key features to implement:**
- **Turn-based combat** with different attack options
- **Health points** for both player and enemies
- **Inventory system** for collecting coins or items
- **Multiple enemy types** with varying difficulty
- **Victory condition** when all enemies are defeated
#### 2. Collection Game
- **Objective**: Collect specific items while avoiding obstacles
- **End conditions**: Reach target collection count or lose all lives
- **Progression**: Items become harder to reach as game continues
#### 3. Puzzle Game
- **Objective**: Solve increasingly difficult puzzles
- **End conditions**: Complete all levels or run out of moves/time
- **Restart**: Reset to first level with cleared progress
#### 4. Defense Game
- **Objective**: Protect your base from waves of enemies
- **End conditions**: Survive all waves (victory) or base is destroyed (defeat)
- **Progression**: Enemy waves increase in difficulty and number
## Implementation Guidelines
### Getting Started
1. **Plan your game design**:
- Sketch the basic gameplay loop
- Define your end conditions clearly
- Identify what data needs to be reset on restart
2. **Set up your project structure**:
```
my-game/
├── index.html
├── style.css
├── game.js
└── README.md
```
3. **Create your core game loop**:
- Initialize game state
- Handle user input
- Update game logic
- Check end conditions
- Render current state
### Technical Requirements
**Use Modern JavaScript**:
- Apply `const` and `let` for variable declarations
- Use arrow functions where appropriate
- Implement ES6+ features like template literals and destructuring
**Event-Driven Architecture**:
- Create event handlers for user interactions
- Implement game state changes through events
- Use event listeners for restart functionality
**Clean Code Practices**:
- Write functions with single responsibilities
- Use descriptive variable and function names
- Add comments explaining game logic and rules
- Organize code into logical sections
## Submission Requirements
### Deliverables
1. **Complete game files**: All HTML, CSS, and JavaScript files needed to run your game
2. **README.md**: Documentation explaining:
- How to play your game
- What end conditions you implemented
- Instructions for restarting
- Any special features or mechanics
3. **Code comments**: Clear explanations of your game logic and algorithms
### Testing Checklist
Before submitting, verify that your game:
- [ ] **Runs without errors** in the browser console
- [ ] **Implements multiple end conditions** as specified
- [ ] **Restarts properly** with clean state reset
- [ ] **Provides clear feedback** to players about game status
- [ ] **Uses modern JavaScript** syntax and best practices
- [ ] **Includes comprehensive documentation** in README.md
## Assessment Rubric
| Criteria | Exemplary (4) | Proficient (3) | Developing (2) | Beginning (1) |
|----------|---------------|----------------|----------------|--------------|
| **Game Functionality** | Complete game with multiple end conditions, smooth restart, and polished gameplay experience | Full game with basic end conditions and functional restart mechanism | Partial game with some end conditions implemented, restart may have minor issues | Incomplete game with limited functionality and significant bugs |
| **Code Quality** | Clean, well-organized code using modern JavaScript practices, comprehensive comments, and excellent structure | Good code organization with modern syntax, adequate comments, and clear structure | Basic code organization with some modern practices, minimal comments | Poor code organization, outdated syntax, lacking comments and structure |
| **User Experience** | Intuitive gameplay with clear instructions, excellent feedback, and engaging end/restart experience | Good gameplay with adequate instructions and feedback, functional end/restart | Basic gameplay with minimal instructions, limited feedback on game state | Confusing gameplay with unclear instructions and poor user feedback |
| **Technical Implementation** | Demonstrates mastery of game development concepts, event handling, and state management | Shows solid understanding of game concepts with good implementation | Basic understanding with acceptable implementation | Limited understanding with poor implementation |
| **Documentation** | Comprehensive README with clear instructions, well-documented code, and thorough testing evidence | Good documentation with clear instructions and adequate code comments | Basic documentation with minimal instructions | Poor or missing documentation |
### Grading Scale
- **Exemplary (16-20 points)**: Exceeds expectations with creative features and polished implementation
- **Proficient (12-15 points)**: Meets all requirements with solid execution
- **Developing (8-11 points)**: Meets most requirements with minor issues
- **Beginning (4-7 points)**: Meets some requirements but needs significant improvement
## Additional Learning Resources
- [MDN Game Development Guide](https://developer.mozilla.org/en-US/docs/Games)
- [JavaScript Game Development Tutorials](https://developer.mozilla.org/en-US/docs/Games/Tutorials)
- [Canvas API Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
- [Game Design Principles](https://www.gamasutra.com/blogs/)
| Criteria | Exemplary | Adequate | Needs Improvement |
| -------- | ---------------------- | --------------------------- | -------------------------- |
| | full game is presented | game is partially presented | partial game contains bugs |
> 💡 **Pro Tip**: Start simple and add features incrementally. A well-polished simple game is better than a complex game with bugs!
Loading…
Cancel
Save