CS111 Final Project Evidence
CS111 learning objective evidence
📋 CS111 LEARNING OBJECTIVES — JUMP TO SECTION
▲
Top<div style="background:#010a18; border:1px solid #0d4a8a; border-radius:10px; padding:18px 26px; margin:18px 0;">
CS111 Rubric Map
Every required objective mapped to the section where the evidence lives. Use this to check completeness.
| Learning Objective | Required Evidence | Section |
|---|---|---|
| Writing classes | Minimum 2 custom classes | Section 1.1 |
| Methods and parameters | Methods with 2+ parameters | Section 2.1 |
| Instantiation and objects | Game objects created with new | Section 1.3 |
| Inheritance | 2+ level class hierarchy | Section 1.2 |
| Method overriding | Override lifecycle methods | Section 1.2 |
| Constructor chaining | super(data, gameEnv) | Section 1.2 |
| Iteration | Loops over arrays | Section 3.1 |
| Conditions | if/else branching | Section 3.2 |
| Nested conditions | Multi-level logic | Section 3.3 |
| Numbers | Position, velocity, score | Section 4.1 |
| Strings | Names, paths, states | Section 4.2 |
| Booleans | Flags | Section 4.3 |
| Arrays | Collections | Section 4.4 |
| Objects / JSON | Object literals and parsed data | Section 4.5 |
| Math operators | Physics and scoring | Section 4.6 |
| String operations | Paths and display text | Section 4.7 |
| Boolean expressions | Compound conditions | Section 4.8 |
| Keyboard input | Event listeners and movement | Section 5.1 |
| Canvas rendering | Draw sprites/assets | Section 5.2 |
| GameEnv config | Canvas size and settings | Section 5.3 |
| API integration | Leaderboard and AI NPC | Section 6.1 |
| Async I/O | async/await | Section 6.2 |
| JSON parsing | JSON.parse / JSON.stringify | Section 6.3 |
| Documentation | Lesson and code highlights | All sections |
| Debugging | DevTools and logs | Section 8 |
| Testing | Gameplay verification | Section 9 |
</div><div style="background:#020f22; border:1px solid #0d4a8a; border-radius:10px; padding:18px 24px; margin:28px 0 10px 0;">
1 — Object-Oriented Programming
</div><div style="background:#020f22; border:1px solid #0d4a8a; border-radius:10px; padding:18px 24px; margin:10px 0;">
1.1 — Writing Classes
What it is: A class is a blueprint for creating objects. You define it once with the class keyword, then use new to create as many objects from it as you need. Each object gets its own copy of the class's data. A constructor is a special method that runs automatically when the object is created — it sets up the initial state. The CS111 rubric requires a minimum of 2 custom classes.
Where it appears in my project: Two custom level classes: GameLevelAquaticGameLevel and GameLevelBasketball. Each has a constructor that sets up game state, plus lifecycle methods (initialize, update, destroy) the engine calls automatically.
</div>
%%js
// Custom level classes prove class creation, constructors, lifecycle methods, and state.
class GameLevelAquaticGameLevel {
constructor(gameEnv) {
this.gameEnv = gameEnv;
this.questState = questState;
this.challengeState = challengeState;
this.levelCompleted = false;
}
initialize() {
this.ensureTopMenuBar?.();
this.ensureQuestHud?.();
}
destroy() {
this.clearSurfaceTrash?.();
this.clearChallengeStarfish?.();
}
}
class GameLevelBasketball {
constructor(gameEnv) {
this.gameEnv = gameEnv;
this.projectiles = [];
this.caught = false;
}
update() {
const player = this.findById('BasketballPlayer');
const lebron = this.findById('LeBron');
if (!player || !lebron) return;
}
}
1.2 — Inheritance
What it is: Inheritance means a child class automatically gets all the properties and methods of its parent class without rewriting them. You use the extends keyword to set this up. This creates a class hierarchy — a chain of classes where each one builds on the one above it.
Where it appears in my project: AquaticSharkEnemy extends Npc — the shark automatically gets Npc's drawing, position tracking, and collision detection. The hierarchy is GameObject → Character → Npc → AquaticSharkEnemy.
1.3 — Constructor Chaining
What it is: super() inside a constructor calls the parent class's constructor. JavaScript requires this before you can use this in a child constructor — without it, the code throws a ReferenceError. It makes sure every level of the class hierarchy sets itself up in the correct order.
Where it appears in my project: super(data, gameEnv) appears in both AquaticSharkEnemy and MermaidQuestNpc, chaining up through Npc → Character → GameObject before any child-specific properties are set.
1.4 — Method Overriding
What it is: Method overriding means a child class defines a method with the same name as one in its parent, replacing its behavior. The child's version runs instead of the parent's. If you want to keep the parent's behavior too, you call super.methodName() first and then add your own logic after.
Where it appears in my project: AquaticSharkEnemy overrides update() with its own movement logic, and overrides handleCollision() to return a hit result when it catches the player. Both are lifecycle methods originally defined on the parent Npc class.
%%js
class AquaticSharkEnemy extends Npc {
constructor(data, gameEnv) {
super(data, gameEnv);
this.motion = { vector: { x: 1, y: 0 }, speed: 2 };
}
update() {
this.position.x += this.motion.vector.x * this.motion.speed;
this.position.y += this.motion.vector.y * this.motion.speed;
this.draw();
}
handleCollision(other, direction) {
if (other?.spriteData?.id === 'playerData' && direction) {
return { hit: true, reason: 'player caught by shark' };
}
return { hit: false };
}
}
class MermaidQuestNpc extends Npc {
constructor(data, gameEnv) {
super(data, gameEnv);
this.questAccepted = false;
}
interact(player, questState) {
if (!questState.firstQuest.accepted) {
questState.firstQuest.accepted = true;
return 'Quest accepted';
}
if (questState.firstQuest.collected >= questState.firstQuest.starfishTotal) {
questState.firstQuest.completed = true;
return 'Quest complete';
}
return 'Keep collecting starfish';
}
}
1.5 — Instantiation & Objects
What it is: Instantiation is the act of calling new ClassName() to create a real, usable object from a class blueprint. Until you call new, the class is just a definition — nothing exists yet. After you call new, you get back an independent object with its own copy of all the class's properties. You can create as many separate instances as you want from the same class, and changing one never affects the others. An object literal ({ key: value }) is a simpler way to create a one-off data record without needing a class at all.
Where it appears in my project: The code below shows instantiation three ways: new SeekScoreTracker(4) creates an instance of a custom class. new Coin(coin_1, gameEnv) and new Player(sprite_data_player, gameEnv) create game object instances — the engine does this automatically for each entry in this.classes. The questState block shows object literals — records created directly with { } instead of a class.
%%js
// ── INSTANTIATION: calling new ClassName() creates a real object ─────────────
// 1. new with a custom class — SeekScoreTracker is defined with 'class', instantiated with 'new'
// Each call to new creates an independent object with its own .collected counter
const tracker1 = new SeekScoreTracker(4); // instance 1 — tracks 4 coins
const tracker2 = new SeekScoreTracker(8); // instance 2 — completely separate, tracks 8 coins
tracker1.collect(2);
// tracker1.collected = 2, tracker2.collected = 0 — changing one never affects the other
// 2. new with GameEngine classes — the engine calls these for each entry in this.classes
// This is exactly what happens when a level loads
const player = new Player(sprite_data_player, gameEnv); // creates one Player instance
const coin1 = new Coin(coin_data_1, gameEnv); // creates one Coin instance
const coin2 = new Coin(coin_data_2, gameEnv); // second independent Coin instance
const barrier = new Barrier(barrier_bench_top, gameEnv); // creates one Barrier instance
// player, coin1, coin2, barrier are four separate objects — same classes, independent state
// 3. this.classes tells the engine what to instantiate — it reads this array at level startup
// and calls new class(data, gameEnv) for every entry automatically
this.classes = [
{ class: Player, data: sprite_data_player }, // engine calls: new Player(sprite_data_player, gameEnv)
{ class: Coin, data: coin_data_1 }, // engine calls: new Coin(coin_data_1, gameEnv)
{ class: Coin, data: coin_data_2 }, // engine calls: new Coin(coin_data_2, gameEnv) — second instance, same class
{ class: Barrier, data: barrier_bench_top }, // engine calls: new Barrier(barrier_bench_top, gameEnv)
];
// ── OBJECT LITERALS: creating a record directly with { } — no class needed ───
// Object literals are for one-off data records, not reusable blueprints
const questState = {
firstQuest: { accepted: false, collected: 0, starfishTotal: 8, completed: false },
secondQuest: { accepted: false, collected: 0, trashTotal: 12, completed: false }
};
// Access with dot notation: questState.firstQuest.collected += 1
Code Runner Challenge
Run this class example to show constructor state, methods, parameters, and return values.
View IPYNB Source
%%js
// CODE_RUNNER: Run this class example to show constructor state, methods, parameters, and return values.
class SeekScoreTracker {
constructor(totalCoins) {
this.totalCoins = totalCoins;
this.collected = 0;
}
collect(amount = 1) {
this.collected = Math.min(this.totalCoins, this.collected + amount);
return this.getStatus();
}
getStatus() {
return `${this.collected}/${this.totalCoins} coins collected`;
}
}
const tracker = new SeekScoreTracker(4);
console.log(tracker.collect(1));
console.log(tracker.collect(2));
Challenge
Topic demo: objects, arrays, strings, numbers, and GameEnv configuration create a runnable level.
2 — Methods with Parameters & Return Values
2.1 — Methods with Parameters
What it is: A method is a function that belongs to a class. Parameters are named inputs listed in the parentheses — they let the same method handle many different inputs without writing separate functions for each case. The CS111 rubric requires methods with 2 or more parameters.
Where it appears in my project: isHitboxCollision(a, b) takes two game objects as parameters and returns true or false. isCircleHittingObject(projectile, obj) takes a circle and a rectangle and uses nearest-point math to check overlap. handleCollision(other, direction) takes the colliding object and which side was hit.
2.2 — Return Values
What it is: A return value is the result a method sends back to whatever called it. You use the return keyword. Without a return value, the method just performs an action with no result to use. Methods with return values let you reuse the result in other logic.
Where it appears in my project: findById(id) returns the matching game object or null. isHitboxCollision(a, b) returns a boolean the collision system uses to decide what happens next. handleCollision(other, direction) returns an object .
%%js
findById(id) {
return this.gameEnv.gameObjects.find((obj) => obj?.spriteData?.id === id) || null;
}
isHitboxCollision(a, b) {
const ar = this.getHitboxRect(a);
const br = this.getHitboxRect(b);
return ar.left < br.right && ar.right > br.left && ar.top < br.bottom && ar.bottom > br.top;
}
isCircleHittingObject(projectile, obj) {
const rect = this.getHitboxRect(obj);
const nearestX = Math.max(rect.left, Math.min(projectile.x, rect.right));
const nearestY = Math.max(rect.top, Math.min(projectile.y, rect.bottom));
const dx = projectile.x - nearestX;
const dy = projectile.y - nearestY;
return (dx * dx + dy * dy) <= (projectile.radius * projectile.radius);
}
3 — Control Structures
3.1 — Iteration
What it is: Iteration means repeating a block of code for every item in a collection, or a set number of times. JavaScript has several loop forms: for gives you an index counter, for...of gives you each element directly, and forEach calls a function once per element. Loops are essential because you never know in advance how many projectiles or collectibles will exist.
Where it appears in my project: The Basketball projectile loop uses a backwards for loop (i--) so removing an expired projectile mid-loop doesn't skip the next one. Each frame, every projectile moves by its velocity and is removed if out of bounds.
3.2 — Conditionals
What it is: A conditional is an if/else statement that picks one of two paths based on a true/false test. Only the matching branch runs — the other is skipped completely. Conditionals let the program react differently to different situations without writing two separate programs.
Where it appears in my project: Inside the projectile loop, if (this.isProjectileOutOfBounds(projectile) || now - projectile.bornAt > this.projectileLifeMs) checks two removal conditions. Only projectiles that pass this test are removed — everything else keeps moving.
3.3 — Nested Conditions
What it is: Nested conditions are if statements inside other if statements. The outer check must pass before the inner check even runs. This lets you enforce a strict order — you can't reach step 3 without passing steps 1 and 2 first.
Where it appears in my project: The Aquatic quest gate: if (q1.accepted) is the outer check. Inside that, if (q1.collected >= q1.starfishTotal) is the second check. Inside that, q1.completed = true only runs when both outer conditions are true. This is what makes the quest progress in the correct order.
%%js
for (let i = this.projectiles.length - 1; i >= 0; i -= 1) {
const projectile = this.projectiles[i];
projectile.x += projectile.vx;
projectile.y += projectile.vy;
if (this.isProjectileOutOfBounds(projectile) || now - projectile.bornAt > this.projectileLifeMs) {
this.removeProjectileAt(i);
continue;
}
}
if (q1.accepted) {
if (q1.collected >= q1.starfishTotal && !q1.completed) {
q1.completed = true;
updateQuestHud();
return;
}
if (q1.completed && !q2.accepted) {
this.dialogueSystem.showDialogue('Talk to Slime for Aquatic Quest #2.', 'Mermaid', null);
return;
}
}
Code Runner Challenge
Run this loop to show arrays, iteration, conditionals, and score updates.
View IPYNB Source
%%js
// CODE_RUNNER: Run this loop to show arrays, iteration, conditionals, and score updates.
const coins = [
{ id: 'coin-1', collected: true, value: 50 },
{ id: 'coin-2', collected: false, value: 50 },
{ id: 'coin-3', collected: true, value: 50 }
];
let score = 0;
for (const coin of coins) {
if (coin.collected) {
score += coin.value;
console.log(`${coin.id} added ${coin.value} points`);
}
}
console.log(`Final score: ${score}`);
Challenge
Topic demo: loops and conditions count collected targets while the player moves with WASD.
4 — Data Types & Operators
4.1 — Numbers
What it is: A number in JavaScript is any numeric value — whole numbers (integers) like 3 or decimals (floats) like 4.75. Integers count things like lives and score. Floats power smooth physics because positions and velocities update in tiny fractional steps each frame.
Where it appears in my project: wave: 1, score: 0, waveTarget: 14 are integers tracking game state. SCALE_FACTOR: 11, ANIMATION_RATE: 110, and the dx/dy/dist physics values are numbers used in position and velocity math every frame.
4.2 — Strings
What it is: A string is a sequence of characters in quotes — 'like this' or "like this". Template literals use backticks and $ to embed any expression directly inside a string without using + to glue pieces together.
Where it appears in my project: id: 'BasketballPlayer', leaderboardKey: 'aquatic_challenge_leaderboard_v1', and greeting: 'Ball handler ready.' are strings. score.textContent = \`Score: $\` uses a template literal to build the HUD display string.
4.3 — Booleans
What it is: A boolean is exactly true or false — nothing else. Booleans act as flags that the code checks to decide what to do next. They prevent one event (like a hit or a score) from triggering twice by flipping from false to true after the first time.
Where it appears in my project: caught: false starts as false and flips to true when LeBron catches the player. preGameLocked: true blocks shooting until the game starts. accepted, completed, and inSurface in questState are all booleans that gate each quest stage.
4.4 — Arrays
What it is: An array is an ordered list of values in square brackets: [item1, item2, item3]. Arrays can grow with push() and shrink with filter() or splice() at runtime. The game stores all live objects in arrays so the loop can update every one without knowing in advance how many exist.
Where it appears in my project: this.projectiles = [] starts empty and grows each time the player shoots. spriteOptions holds all choosable sprites. this.classes is an array of objects the engine reads to instantiate the level. surfaceTrashIds tracks which trash items are active.
4.5 — Objects & JSON
What it is: An object literal () groups related data under one name — you access properties with dot notation like obj.property. JSON (JavaScript Object Notation) uses the exact same syntax, so objects in code and data from an API look and work identically.
Where it appears in my project: questState is a nested object — questState.firstQuest.collected drills two levels deep. challengeState stores wave, score, and leaderboard key together. JSON.parse(raw) and JSON.stringify(scores) convert between objects and strings for localStorage.
4.6 — Math Operators
What it is: Math operators are + - * / % **. += and -= add or subtract from a variable in place. % (modulo) gives the remainder after division — useful for wrapping values. Math.hypot(dx, dy) computes the straight-line distance between two points using the Pythagorean theorem.
Where it appears in my project: const dist = Math.hypot(dx, dy) finds how far LeBron is from the player. lebron.position.x += (dx / dist) * speed uses division and multiplication to move LeBron toward the player at a consistent speed regardless of angle. time * 10 + coins * 50 computes the round score.
4.7 — String Operations
What it is: String operations build or combine strings. The + operator concatenates: 'hello' + ' world'. Template literals with $ are cleaner for embedding values. String methods like .toLowerCase() normalize input for comparison.
Where it appears in my project: path + '/images/projects/characters/BaskCourt.png' builds the asset URL by concatenating the base path with the filename. score.textContent = \`Score: $\` uses a template literal to update the HUD. event.key.toLowerCase() normalizes the E key regardless of caps lock.
4.8 — Boolean Expressions
What it is: Boolean expressions combine conditions using && (AND — both must be true), || (OR — at least one must be true), and ! (NOT — flips true to false). The ternary operator (condition ? valueIfTrue : valueIfFalse) is a one-line if/else.
Where it appears in my project: q2.accepted && q2.inSurface — both must be true to unlock the second quest return stage. !player || !player.canvas — guards against null references. modeParam === 'challenge' ? 'challenge' : 'story' is a ternary that sets the game mode from the URL.
%%js
const challengeState = {
wave: 1,
waveTarget: 14,
collectedThisWave: 0,
score: 0,
lastSavedScore: 0,
leaderboardKey: 'aquatic_challenge_leaderboard_v1'
};
this.projectiles = [];
this.caught = false;
this.preGameLocked = true;
const image_src_court = path + '/images/projects/characters/BaskCourt.png';
score.textContent = `Score: ${challengeState.score}`;
const dx = player.position.x - lebron.position.x;
const dy = player.position.y - lebron.position.y;
const dist = Math.hypot(dx, dy);
lebron.position.x += (dx / dist) * speed;
Code Runner Challenge
Run this loop to show arrays, iteration, conditionals, and score updates.
View IPYNB Source
%%js
// CODE_RUNNER: Run this loop to show arrays, iteration, conditionals, and score updates.
const coins = [
{ id: 'coin-1', collected: true, value: 50 },
{ id: 'coin-2', collected: false, value: 50 },
{ id: 'coin-3', collected: true, value: 50 }
];
let score = 0;
for (const coin of coins) {
if (coin.collected) {
score += coin.value;
console.log(`${coin.id} added ${coin.value} points`);
}
}
console.log(`Final score: ${score}`);
5 — Input / Output
5.1 — Keyboard Input
What it is: Keyboard input is captured with addEventListener('keydown', callback). The callback runs every time a key is pressed. Because the game loop runs 60 times per second but key events only fire once per press, the standard pattern is to write the key state into a dictionary and have the loop read it every frame — this gives smooth held-key movement.
Where it appears in my project: keypress: maps WASD key codes to directions for the engine to listen to. document.addEventListener('keydown', this.handleShootKey) registers the E key for shooting. document.addEventListener('keydown', this.handleRestartKey) registers R for restart.
5.2 — Canvas Rendering
What it is: Canvas rendering uses the HTML <canvas> element and its 2D context object (ctx) to draw shapes and images. Every frame is a complete redraw from scratch — you clear the canvas, then draw everything in the correct order. Common methods: ctx.arc() for circles, ctx.fillRect() for rectangles, ctx.drawImage() for sprites.
Where it appears in my project: drawProjectileSprite uses ctx.beginPath(), ctx.arc(cx, cy, r, 0, Math.PI * 2), and ctx.fill() to draw the basketball projectile as an orange circle at the correct position each frame. Generated canvases are also used for starfish, coins, and trash sprites.
5.3 — GameEnv Configuration
What it is: GameEnv configuration is how you tell the engine what to create and how big to make the canvas. Instead of hardcoding pixel values, you read them from gameEnv.innerWidth and gameEnv.innerHeight — this makes the game adapt to any screen size automatically.
Where it appears in my project: gameEnv.innerWidth and gameEnv.innerHeight set sprite start positions proportionally. gameEnv.path provides the base URL so asset paths work on any deployment. this.classes is the engine's configuration hook — it reads this array to know what to instantiate.
%%js
keypress: { up: 87, left: 65, down: 83, right: 68 }
document.addEventListener('keydown', this.handleRestartKey);
document.addEventListener('keydown', this.handleShootKey);
handleShootKey(event) {
if (event.key.toLowerCase() !== 'e' || event.repeat) return;
if (this.preGameLocked || this.caught) return;
this.spawnProjectileFromPlayer(player, performance.now());
}
drawProjectileSprite(ctx, width, height) {
const cx = width / 2;
const cy = height / 2;
const r = Math.min(width, height) * 0.42;
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.fillStyle = '#f68b1f';
ctx.fill();
}
Challenge
Topic demo: keyboard input opens a real sprite menu; choosing a sprite swaps the Player image and updates Canvas/DOM immediately. Press Q or click Open Sprite Menu.
6 — API Integration, Async I/O & JSON
6.1 — API Integration
What it is: An API (Application Programming Interface) is a way for your code to talk to an external service over the internet. You send a request and receive a response. The fetch() function does this in JavaScript. The CS111 rubric requires demonstrating that your project can send or receive data through an API.
Where it appears in my project: this.leaderboard.submitScore(username, score, 'Basketball') calls the GameEngine's Leaderboard API to save the round score to the server. AiNpc.showInteraction(this) calls the AI NPC system. Both calls are wrapped in error handlers so a failure doesn't crash the game.
6.2 — Asynchronous I/O
What it is: Asynchronous means the code doesn't wait in place — it starts a task and moves on, then handles the result when it arrives. async marks a function as asynchronous. await pauses only that function until a Promise resolves — the rest of the program keeps running. .catch() handles errors without crashing.
Where it appears in my project: transitionToSurface = async () => — without await, the trash would spawn before the animation finished. .catch((err) => console.warn('Leaderboard score submit failed:', err)) handles network failures gracefully.
6.3 — JSON Parsing
What it is: JSON (JavaScript Object Notation) is the standard text format for storing and sending data. JSON.stringify(obj) converts a JavaScript object into a JSON string so it can be stored or sent. JSON.parse(str) converts it back into a JavaScript object you can work with using dot notation.
Where it appears in my project: localStorage.setItem(challengeState.leaderboardKey, JSON.stringify(scores)) saves the leaderboard array as a string. const parsed = raw ? JSON.parse(raw) : [] reads it back as an array of objects. The questState object uses the same structure as a JSON API response.
%%js
submitRoundScore() {
if (!this.leaderboard || this.scoreSubmittedThisRound) return;
const score = Math.round((this.currentTime * 10) + (this.getCoinsCollected() * 50));
const username = (this.gameEnv?.game?.uid && String(this.gameEnv.game.uid)) || 'Player';
this.leaderboard.submitScore(username, score, 'Basketball')
.catch((err) => console.warn('Leaderboard score submit failed:', err));
}
try {
AiNpc.showInteraction(this);
} catch (err) {
console.error('Kirby AI interaction failed:', err);
}
const raw = localStorage.getItem(challengeState.leaderboardKey);
const parsed = raw ? JSON.parse(raw) : [];
localStorage.setItem(challengeState.leaderboardKey, JSON.stringify(scores));
const transitionToSurface = async () => {
await animatePlayerSwim(14);
spawnSurfaceTrash();
};
Code Runner Challenge
Run this JSON and scoring example to show objects, parsing, math, and state changes.
View IPYNB Source
%%js
// CODE_RUNNER: Run this JSON and scoring example to show objects, parsing, math, and state changes.
const roundState = {
player: 'SeekPlayer',
secondsSurvived: 32,
coinsCollected: 4,
completed: true
};
const saved = JSON.stringify(roundState);
const restored = JSON.parse(saved);
const finalScore = restored.secondsSurvived * 10 + restored.coinsCollected * 50;
console.log(restored);
console.log(`Final score: ${finalScore}`);
6.4 — State Objects & URL Parameters
What it is: questState is a nested object where each property is another object with its own flags and counters. Changing a single property like firstQuest.accepted = true is enough to change what the entire game does next. URLSearchParams reads the URL query string at load time to configure behavior without editing code.
Where it appears in my project: questState.firstQuest and questState.secondQuest each have accepted, started, completed, and collected properties. const modeParam = new URLSearchParams(window.location.search).get('mode') reads ?mode=challenge from the URL to switch modes.
%%js
const questState = {
firstQuest: {
accepted: false,
started: false,
completed: false,
starfishTotal: 8,
collected: 0
},
secondQuest: {
accepted: false,
inSurface: false,
returning: false,
completed: false,
trashTotal: 12,
collected: 0
}
};
const modeParam = new URLSearchParams(window.location.search).get('mode');
this.gameMode = modeParam === 'challenge' ? 'challenge' : 'story';
7 — State Management & Collision Logic
7.1 — Rectangle Collision Detection
What it is: Collision detection is the math that checks whether two objects are overlapping on screen. For rectangles, you compare the edges: if the left edge of A is to the left of the right edge of B, and the right edge of A is to the right of B's left edge, and the same for top and bottom — they overlap. Shrinking the hitbox slightly makes collisions feel fair.
Where it appears in my project: getHitboxRect(obj) reduces each object's collision box by 20% on all sides (widthReduction = width * 0.2) and returns a rectangle with left/right/top/bottom values. isHitboxCollision(a, b) compares the four edges to detect overlap. shark.isCollision(player) uses the built-in GameEngine version.
7.2 — Circle Collision Detection
What it is: Circle collision works differently from rectangles — you find the closest point on the rectangle to the circle's center, then check if that distance is less than the circle's radius. If it is, they're touching. This is used for the basketball projectile because circular objects don't collide cleanly with rectangle math.
Where it appears in my project: isCircleHittingObject(projectile, obj) uses Math.max / Math.min to clamp the circle center to the rectangle bounds, then checks (dx * dx + dy * dy) <= (projectile.radius * projectile.radius) — avoiding a slow square root by comparing squared distances instead.
%%js
getHitboxRect(obj) {
const width = obj.width || 0;
const height = obj.height || 0;
const pos = obj.position || { x: 0, y: 0 };
const widthReduction = width * 0.2;
const heightReduction = height * 0.2;
return {
left: pos.x + widthReduction,
right: pos.x + width - widthReduction,
top: pos.y + heightReduction,
bottom: pos.y + height
};
}
shark.isCollision(player);
if (shark.collisionData?.hit) {
this.showSharkGameOver();
}
8 — Debugging
8.1 — Console Debugging
What it is: Console debugging uses console.log(), console.warn(), and console.error() to print values at key moments so you can see what the code is doing. The key rule: never log inside the game loop — it runs 60 times per second and will print thousands of lines instantly. Only log at one-time state transitions.
Where it appears in my project: console.log('GameLevelSeek.js loaded:', new Date().toISOString()) confirms the file loaded. console.log('Sprite switched:', spriteOption.label) confirms which sprite was selected. console.error is used for the AI NPC failure and console.warn for the non-fatal leaderboard failure.
8.2 — DevTools: Sources — Breakpoints
What it is: A breakpoint pauses code execution at a specific line so you can inspect every variable's value at that exact moment. This is more powerful than console logs because you can step through code one line at a time and watch values change live. Open DevTools → Sources tab → click a line number to set one.
Where it appears in my project: Set breakpoints inside update(), handleShootKey(event), submitRoundScore(), and shark.update to inspect live values. In Sources, click the line number next to the code, then trigger the action in-game — execution pauses and the Scope panel shows all variables.
8.3 — DevTools: Network, Application & Elements
What it is: The Network tab records every HTTP request — use it to verify that the leaderboard API call was sent and what response came back. The Application tab shows localStorage — use it to confirm scores were saved correctly. The Elements tab shows the live DOM — use it to confirm HUD elements were created with the right CSS.
Where it appears in my project: Network tab: inspect the leaderboard POST request headers, payload, and response after a Basketball round ends. Application tab: check basketball_best_time, basketball_best_coins, and aquatic_challenge_leaderboard_v1 in Local Storage. Elements tab: inspect #aquatic-quest-hud, #basketball-time-hud, #seek-sprite-menu.
%%js
console.log('GameLevelSeek.js loaded:', new Date().toISOString());
console.log('Sprite switched:', spriteOption.label);
console.log('All coins collected!');
console.error('Kirby AI interaction failed:', err);
console.warn('Leaderboard score submit failed:', err);
localStorage.getItem('basketball_best_time');
localStorage.getItem('basketball_best_coins');
localStorage.getItem('aquatic_challenge_leaderboard_v1');
9 — Testing & Verification
9.1 — Gameplay Testing Checklist
What it is: Testing means running the program deliberately with a specific input and verifying that the output matches what you expected. A good test covers the happy path (things work correctly) and checks that failure cases are handled gracefully. Each row in the checklist is one test — a single action and the exact result that should follow. If the result doesn't match, that's a confirmed bug.
Where it appears in my project: The checklist covers all three levels: Aquatic story mode quest progression, Aquatic challenge mode, Basketball projectile and scoring mechanics, and Seek sprite switching. Every test was run manually before submission and all items should be checked off.
Testing and Verification Checklist
| Test | Expected result | |—|—| | Start Aquatic story mode | Player spawns underwater, quest HUD appears, Mermaid offers quest. | | Accept Mermaid quest | Starfish spawn and collection count updates. | | Collect all starfish | Mermaid completes quest one and sends player to Slime. | | Accept Slime quest two | Player transitions to surface and trash spawns. | | Collect all trash | Player returns underwater, Kirby disappears, Slime can complete the level. | | Touch shark | Game over overlay appears. | | Start Aquatic challenge mode | Challenge HUD appears, starfish wave spawns, leaderboard can save score. | | Start Basketball | Intro dialogue appears and timer starts after pressing Start. | | Collect coins | Coin count increases and score calculation includes coins. | | Press E in Basketball | Basketball projectile spawns and can stun LeBron. | | Get caught in Basketball | Round score submits and reset flow begins. | | Press Q in Seek | Sprite menu opens and closes. | | Select sprite in Seek | Player sprite changes and animation data updates. |
10 — Software Engineering & SDLC
10.1 — Software Development Life Cycle
What it is: The SDLC is the structured process a software team follows from idea to finished product: planning, building, testing, and deploying. Good engineering practices make code easier to build in parallel, easier to debug when something breaks, and easier to improve later. Version control, code reviews, and documented tests are core practices.
Where it appears in my project: This project used Git for source control with commits for game code, notebook code, and documentation. The three levels were built as separate files so team members could work in parallel without conflicts. The testing checklist served as the sprint burndown. The notebook is Jekyll-compatible so it deploys automatically with the portfolio build workflow.
Software Engineering and SDLC Evidence
| Practice area | Evidence | |—|—| | Planning changes | The project is split into three levels with separate responsibilities: Aquatic for story quests, Basketball for survival scoring, and Seek for customization. | | Checklists and burndown | The testing checklist at the end of this notebook is a sprint verification list. | | Coding with comments | Code comments explain shark AI, transition scenes, challenge wave spawning, collision walls, projectile drawing, and HUD setup. | | Mini-lesson documentation | The portfolio lesson uses notebook cells plus runnable GameRunner demos. | | Source control | The project is in Git. Evidence includes commits for game code, notebook lesson code, and this documentation. | | Forking, branching, PRs, merging | Team features can be developed on branches, reviewed in PRs, and merged into the portfolio. | | Building and deployment | The notebook is Jekyll-compatible and can be deployed with the portfolio build workflow. | | Presentation and live review | The runtime game demos allow the teacher to play, inspect, and review code live. | | Retrospective revision | The project uses separate modes, classes, and helpers, so features can be revised without rewriting everything. |