📋 CS111 LEARNING OBJECTIVES — JUMP TO SECTION

1 → Object-Oriented Programming
Writing classes • Inheritance • Method overriding • Constructor chaining • Instantiation
2 → Methods & Parameters
Parameters • Return values • Single responsibility
3 → Control Structures
Iteration • Conditionals • Nested conditions
4 → Data Types & Operators
Numbers • Strings • Booleans • Arrays • Objects • Math • Boolean expressions
5 → Input / Output
Keyboard input • Canvas rendering • GameEnv config
6 → API, Async I/O & JSON
API calls • async/await • JSON.parse • localStorage
7 → State & Collision Logic
State objects • Hitbox math • Circle collision
8 → Debugging
Console • DevTools • Network • Storage • Elements
9 → Testing & Verification
Gameplay checklist • Live demo
10 → Software Engineering & SDLC
Git • Planning • Branching • Deployment


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 ObjectiveRequired EvidenceSection
Writing classesMinimum 2 custom classesSection 1.1
Methods and parametersMethods with 2+ parametersSection 2.1
Instantiation and objectsGame objects created with newSection 1.3
Inheritance2+ level class hierarchySection 1.2
Method overridingOverride lifecycle methodsSection 1.2
Constructor chainingsuper(data, gameEnv)Section 1.2
IterationLoops over arraysSection 3.1
Conditionsif/else branchingSection 3.2
Nested conditionsMulti-level logicSection 3.3
NumbersPosition, velocity, scoreSection 4.1
StringsNames, paths, statesSection 4.2
BooleansFlagsSection 4.3
ArraysCollectionsSection 4.4
Objects / JSONObject literals and parsed dataSection 4.5
Math operatorsPhysics and scoringSection 4.6
String operationsPaths and display textSection 4.7
Boolean expressionsCompound conditionsSection 4.8
Keyboard inputEvent listeners and movementSection 5.1
Canvas renderingDraw sprites/assetsSection 5.2
GameEnv configCanvas size and settingsSection 5.3
API integrationLeaderboard and AI NPCSection 6.1
Async I/Oasync/awaitSection 6.2
JSON parsingJSON.parse / JSON.stringifySection 6.3
DocumentationLesson and code highlightsAll sections
DebuggingDevTools and logsSection 8
TestingGameplay verificationSection 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

  CodeRunner — Writing Classes Creates a class with a constructor, two methods, and a return value. Same pattern as the game's level classes.

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));
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...
  GameRunner — Instantiation in a Live Level This level is built entirely from one object literal and one this.classes array — the engine instantiates each entry.

Challenge

Topic demo: objects, arrays, strings, numbers, and GameEnv configuration create a runnable level.

Lines: 1 Characters: 0
Game Status: Not Started

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;
    }
}
  CodeRunner — Iteration & Conditionals A for-of loop with an if condition scores only the collected coins. Change a coin's collected value and re-run to see the condition skip it.

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}`);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...
  GameRunner — Loops in a Live Level Walk the player into the yellow targets. Each collection is one loop iteration where the if condition passes — watch the counter.

Challenge

Topic demo: loops and conditions count collected targets while the player moves with WASD.

Lines: 1 Characters: 0
Game Status: Not Started

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;
  CodeRunner — Data Types in Action A coin array (array of objects) filtered with a boolean condition, scored with math, and displayed with a template literal string.

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}`);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

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();
}
  GameRunner — Keyboard Input & Canvas Output Press Q to open the sprite menu. Selecting a sprite is a keydown event that immediately updates the Canvas rendering.

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.

Lines: 1 Characters: 0
Game Status: Not Started

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();
};
  CodeRunner — JSON.stringify, JSON.parse & Score Math Converts a state object to a JSON string, parses it back, then computes the score formula. Same steps the game uses on game over.

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}`);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

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. |

  Playable Demo — GameLevelSeek The full Seek level live. Use during presentation to demonstrate sprite switching (Q key), collectible spawning (loops), and console output.
Game Status: Not Started

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. |

✅ CS111 Completion Summary

✅ Writing classes (2+ custom classes) — Section 1.1
✅ Methods with 2+ parameters — Section 2.1
✅ Instantiation & objects — Section 1.3
✅ Inheritance (extends) — Section 1.2
✅ Method overriding — Section 1.2
✅ Constructor chaining (super) — Section 1.3
✅ Iteration (loops) — Section 3.1
✅ Conditionals (if/else) — Section 3.2
✅ Nested conditions — Section 3.3
✅ Numbers — Section 4.1
✅ Strings & template literals — Section 4.2
✅ Booleans — Section 4.3
✅ Arrays — Section 4.4
✅ Objects / JSON — Section 4.5
✅ Math operators — Section 4.6
✅ String operations — Section 4.7
✅ Boolean expressions — Section 4.8
✅ Keyboard input — Section 5.1
✅ Canvas rendering — Section 5.2
✅ GameEnv configuration — Section 5.3
✅ API integration — Section 6.1
✅ Async I/O (async/await) — Section 6.2
✅ JSON parsing — Section 6.3
✅ Documentation — all sections
✅ Debugging (5 DevTools panels) — Section 8
✅ Testing checklist — Section 9