1. Code Documentation β€” JSDoc Comments

Good documentation lets anyone (including future-you) understand what code does. The game engine uses JSDoc-style comments throughout. From Character.js:

Code Runner Challenge

Add JSDoc comments to the class below. Every method needs a description, @param, and @returns tag.

View IPYNB Source
/**
 * Character is a dynamic class that manages the data and events for objects like player and NPCs.
 *
 * @property {Object} position - The current position of the object.
 * @property {Object} velocity - The current velocity of the object.
 * @property {number} size     - The size of the object.
 * @method draw    - Draws the object on the canvas.
 * @method update  - Updates the object's position each frame.
 * @method destroy - Removes the object from the game environment.
 */
class Character extends GameObject {
    /**
     * @param {Object|null} data    - The sprite data for the object. If null, a default red square is used.
     * @param {Object|null} gameEnv - The game environment context.
     */
    constructor(data = null, gameEnv = null) { ... }
}
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

And from Player.js β€” method-level JSDoc:

Code Runner Challenge

Test error handling. The first call hits a bad URL to trigger error handling. The second hits a good one.

View IPYNB Source
/**
 * Handles key up events to stop the player's velocity.
 * Removes the key from the active pressedKeys array.
 *
 * @param {Object} event - The keyup event object containing keyCode.
 */
handleKeyUp({ keyCode }) {
    if (keyCode in this.pressedKeys) {
        delete this.pressedKeys[keyCode];
    }
    this.updateVelocity();
    this.updateDirection();
}
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Comment density target: > 10% β€” for every ~10 lines of code there should be at least 1 line of comments.

%%js
//CODE_RUNNER: Add JSDoc comments to the class below. Every method needs a description, @param, and @returns tag.

/**
 * ScoreTracker manages player score and high score for a game session.
 *
 * @property {number} score     - Current session score.
 * @property {number} highScore - All-time high score.
 * @property {string} playerName - The player's display name.
 */
class ScoreTracker {
    /**
     * @param {string} playerName - Display name shown on leaderboard.
     */
    constructor(playerName) {
        this.playerName = playerName;
        this.score      = 0;
        this.highScore  = 0;
    }

    /**
     * Adds points to the current score. Updates high score if beaten.
     * @param {number} points - Points to add (must be positive).
     * @returns {number} The updated score.
     */
    addPoints(points) {
        if (points > 0) this.score += points;
        if (this.score > this.highScore) this.highScore = this.score;
        return this.score;
    }

    // TODO: Add JSDoc for this method
    reset() {
        this.score = 0;
    }

    // TODO: Add JSDoc for this method
    getSummary() {
        return `${this.playerName}: Score=${this.score}, Best=${this.highScore}`;
    }
}

const tracker = new ScoreTracker('Seonyoo');
tracker.addPoints(100);
tracker.addPoints(250);
console.log(tracker.getSummary());
tracker.reset();
tracker.addPoints(50);
console.log(tracker.getSummary());

2. Gameplay Testing β€” Level & Collision Verification

Manual gameplay testing checklist for the game engine:

Test What to verify Pass criteria
Player movement W/A/S/D and arrow keys Player moves in correct direction, stops at boundary
Jump physics Press W with gravity enabled Player rises then falls naturally
Coin collision Walk into a coin Coin disappears, score increments
Enemy collision Shark touches player Explosion animation plays, level restarts
Level completion Reach end platform Transition to next level triggers
Boundary check Move to canvas edge Player doesn’t leave the screen

From Shark.js β€” the collision handling code that must work correctly:

handleCollisionEvent() {
    var player = this.gameEnv.gameObjects.find(obj => obj instanceof Player);
    this.velocity.x = 0;
    this.velocity.y = 0;
    this.explode(player.position.x, player.position.y);
    player.destroy();
    this.playerDestroyed = true;
    setTimeout(() => {
        this.gameEnv.gameControl.currentLevel.restart = true;
    }, 2000);  // test: does the 2000ms delay feel right?
}

3. API Integration Testing

After wiring up the Leaderboard API, verify it works end-to-end:

// Integration test: POST a score, then GET leaderboard to confirm it saved
async function testLeaderboardIntegration() {
    const testPayload = { playerName: 'TestUser', score: 9999 };

    // Step 1: POST score
    const postRes = await fetch(`${javaURI}/api/scores`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify(testPayload)
    });
    console.log('POST status:', postRes.status);  // expect 200 or 201

    // Step 2: GET leaderboard and verify test entry appears
    const getRes  = await fetch(`${javaURI}/api/scores`, { credentials: 'include' });
    const scores  = await getRes.json();
    const found   = scores.find(s => s.playerName === 'TestUser' && s.score === 9999);
    console.log('Score saved correctly:', !!found);  // expect true
}

4. API Error Handling

Every fetch call needs a try/catch and an !response.ok check. From login.js:

fetch(options.URL, requestOptions)
    .then(response => {
        if (!response.ok) {                          // HTTP errors (4xx, 5xx)
            const errorMsg = 'Login error: ' + response.status;
            console.log(errorMsg);
            document.getElementById(options.message).textContent = errorMsg;
            return;                                  // exit early
        }
        options.callback();                          // success path
    })
    .catch(error => {                                // network errors (CORS, server down)
        console.log('Possible CORS or Service Down error: ' + error);
        document.getElementById(options.message).textContent =
            'Possible CORS or service down error: ' + error;
    });
%%js
//CODE_RUNNER: Test error handling. The first call hits a bad URL to trigger error handling. The second hits a good one.

async function fetchWithErrorHandling(url, label) {
    try {
        const response = await fetch(url);

        if (!response.ok) {
            // HTTP error β€” server responded but with error code
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const data = await response.json();
        console.log(`[${label}] SUCCESS β€” got data:`, Object.keys(data));
        return data;

    } catch (error) {
        // Catches both network failures AND our thrown HTTP errors
        console.error(`[${label}] ERROR:`, error.message);
        return null;
    }
}

// Test 1: bad endpoint
await fetchWithErrorHandling(
    'https://official-joke-api.appspot.com/jokes/does_not_exist',
    'Bad endpoint'
);

// Test 2: working endpoint
await fetchWithErrorHandling(
    'https://official-joke-api.appspot.com/random_joke',
    'Good endpoint'
);

Summary

Area What to do Tool
Documentation JSDoc /** */ on every class + method; > 10% comment density Code review
Gameplay testing Play through level; check collision, movement, level transitions Live demo
Integration testing POST score β†’ GET leaderboard; confirm AI NPC responds DevTools Network tab
API error handling try/catch + !response.ok on every fetch Code review