Game Status: Not Started

CS111 Requirements

Category Objective How the Castle Game Implements It
Object-Oriented Programming Writing Classes Custom character classes extend base engine classes for player, NPC, and enemy behavior.
  Methods & Parameters Methods accept parameters for collision and interaction handling.
  Instantiation & Objects Levels instantiate game objects with class + data configs.
  Inheritance (Basic) Multi-level inheritance chain for game objects.
  Method Overriding Child classes override engine lifecycle methods.
  Constructor Chaining Subclasses call super(...) to wire base state.
Control Structures Iteration (HW) Loops iterate over game objects and collision checks.
  Conditionals (HW) Gameplay checks for states, collisions, and boundaries.
  Nested Conditions (HW) Multi-level checks for collision + win state.
Data Types Numbers Positions, velocity, hit counts, and timing.
  Strings (HW) Sprite paths, ids, and UI text.
  Booleans (HW) State flags for game logic.
  Arrays (HW) Collections of objects and config lists.
  Objects (JSON) Configuration objects for sprites and levels.
Operators Mathematical (HW) Movement and speed updates use math operators.
  String Operations (HW) Concatenation and template literals for UI.
  Boolean Expressions (HW) Compound logic for movement and collision.
Input/Output Keyboard Input Key events drive player movement and NPC interact.
  Canvas Rendering Sprites and frames drawn on canvas.
  GameEnv Configuration Game canvas is created and sized by GameEnv.
  API Integration Leaderboard uses fetch to POST/GET scores.
  Asynchronous I/O Promises handle fetch results and errors.
  JSON Parsing Local storage and API payloads parsed to objects.
Documentation Code Comments JSDoc blocks document engine classes.
  Mini-Lesson Documentation This notebook embeds the playable demo.
  Code Highlights Snippets below show OOP, collisions, and API use.
Debugging Console Debugging Logs track state and transitions.
  Hit Box Visualization Debug helper draws collision circle.
  Source-Level Debugging Breakpoints can be set in engine classes.
  Network Debugging Fetch calls are visible in Network tab.
  Application Debugging Local storage tracks game settings.
  Element Inspection Canvas elements are created per object.
Testing & Verification Gameplay Testing Levels are playable and transitions are tested live.
  Integration Testing Leaderboard calls return data or fall back locally.
  API Error Handling Fetch errors surface user-friendly messages.

CS111 Evidence & Explanations

Writing Classes

Custom classes extend the engine bases for unique behavior.

Code Runner Challenge

Classes & Objects

View IPYNB Source
class Projectile extends Character {
	// ...
}
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Methods & Parameters

Methods take parameters, for example: the spritesheet coin allows you to change its appearance.

Code Runner Challenge

Methods & Parameters

View IPYNB Source
// SpriteSheetCoin.js

/**
 * Change the sprite image
 * @param {string} imagePath - Path to the new image
 */
setSprite(imagePath) {
	this.spriteImagePath = imagePath;
	this.isImageLoaded = false;
	this.loadImage();
}
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Instantiation & Objects

Game levels instantiate objects with class + data configs.

Code Runner Challenge

Inheritance & Overriding

View IPYNB Source
// GameLevelArchery.js
this.classes = [
  { class: GameEnvBackground, data: image_data_floor },
  { class: FightingPlayer, data: sprite_data_mc },
  { class: Npc, data: sprite_data_villager },
  { class: Barrier, data: barrier_data },
  { class: Enemy, data: target_data },
];
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Inheritance (Basic)

The engine uses multi-level class chains.

Code Runner Challenge

Constructor Chaining

View IPYNB Source
// DeathBarrier.js

class DeathBarrier extends Barrier {
	// ...
}
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Code Runner Challenge

Iteration

View IPYNB Source
// FightingPlayer.js

class FightingPlayer extends Player {
	// ...
}
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Code Runner Challenge

Conditionals (Nested)

View IPYNB Source
// Ghost.js

class Ghost extends Enemy {
	// ...
}
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Method Overriding

Child classes override base behavior.

Code Runner Challenge

Objects & JSON Parsing

View IPYNB Source
// FightingPlayer.js

// Update player and the projectiles
update(...args) {
	super.update(...args);  // Do normal player updating
	
	// Track facing direction based on movement
	if (this.velocity.x > 0) this.currentDirection = 'right';
	else if (this.velocity.x < 0) this.currentDirection = 'left';
	
	// Update and clean up projectiles
	this.projectiles = this.projectiles.filter(p => !p.revComplete);
	this.projectiles.forEach(p => p.update());
}
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Constructor Chaining

Subclasses call super(...) to set base state.

Code Runner Challenge

Math, Strings, Booleans

View IPYNB Source
// FightingPlayer.js

/**
 * A version of the Player class with added functionality for shooting projectiles (arrows).
 * @param data - Initial data for the player character.
 * @param gameEnv - The game environment object, providing access to game state and resources.
 * 
 * This class extends the basic Player class to allow for SPACE to create arrows
 */
class FightingPlayer extends Player {
    // Construct the class, with a list of stored projectiles
    constructor(data = null, gameEnv = null) {
        super(data, gameEnv);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Iteration

Loops drive collision checks and state updates.

Code Runner Challenge

Local Storage

View IPYNB Source
// Scythe.js

for (const player of players) {
	// Calculate center points of both sprites
	const scytheCenterX = this.position.x + this.width / 2;
	const scytheCenterY = this.position.y + this.height / 2;
	const playerCenterX = player.position.x + player.width / 2;
	const playerCenterY = player.position.y + player.height / 2;

	// Calculate distance between center points
	const dx = playerCenterX - scytheCenterX;
	const dy = playerCenterY - scytheCenterY;
	const distance = Math.sqrt(dx * dx + dy * dy);

	// Use relative hit distance based on sprite sizes
	const HIT_DISTANCE = (this.width + player.width) / 3; // One-third of combined width

	if (distance <= HIT_DISTANCE) {
		this.handleCollisionWithPlayer();
		break;
	}
}
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Conditionals

State checks and boundary logic use if/else blocks.

// GameLevelOutside.js

// This is where the interactions for starting the game are handled
interact: function () {
	// Clear any existing dialogue first to prevent duplicates
	if (this.dialogueSystem && this.dialogueSystem.isDialogueOpen()) {
		this.dialogueSystem.closeDialogue();
	}

	// Create a new dialogue system if needed
	if (!this.dialogueSystem) {
		this.dialogueSystem = new DialogueSystem();
	}

	// ..lots of code ommitted...
}

Nested Conditions

Gameplay rules combine multiple checks.

// GameLevelArchery.js
if (window.archeryGameStarted) {
  this.position.x += this.velocity.x;
  if (this.position.x <= 0) {
    this.velocity.x = this.speed;
  } else if (this.position.x + this.width >= this.gameEnv.innerWidth) {
    this.velocity.x = -this.speed;
  }
}

Numbers

Positions, speed, and counters are numeric.

// GameLevelArchery.js
this.hitsRemaining = 30;
this.position.x += this.velocity.x;
this.speed += 0.5;

Strings

Strings define ids, greetings, and asset paths.

// GameLevelOutside.js
const image_src_floor = path + "/images/projects/castle-game/castleOutsideV2.png";
const sprite_data_mc = { id: 'Knight', greeting: "Hi, I am a Knight." };

Booleans

Flags control state and logic flow.

// GameLevelArchery.js
window.archeryGameStarted = false;
if (window.archeryVictory) {
  // stop the round
}

Arrays

Arrays hold collections of game objects and configs.

// GameLevelOutside.js

const left_wall = {
	id: 'left-wall-1',
	greeting: "This is a curved barrier, you cannot pass through it!",
	splinePoints: [
		{ x: 318/1110*width, y: 749/760*height },
		{ x: 435/1110*width, y: 587/760*height },
		{ x: 293/1110*width, y: 497/760*height },
		{ x: 273/1110*width, y: 436/760*height },
		{ x: 142/1110*width, y: 402/760*height },
		{ x: 98/1110*width, y: 332/760*height },
		{ x: 233/1110*width, y: 246/760*height },
		{ x: 342/1110*width, y: 302/760*height },
		{ x: 355/1110*width, y: 361/760*height },
		{ x: 447/1110*width, y: 435/760*height },
		{ x: 506/1110*width, y: 327/760*height }
	],
	// Optional: Add visual properties if you want to render the barrier
	visible: true,
	color: '#8B4513',  // Brown color for wooden barrier
	lineWidth: 5        // Line thickness for visual representation
};
// GameLevelOutside.js
this.classes = [
  { class: Player, data: sprite_data_mc },
  { class: StrictNpc, data: sprite_data_darkKnight },
];

Objects (JSON)

Sprite and level configs are structured objects.

// GameLevelOutside.js
const sprite_data_mc = {
  id: 'Knight',
  SCALE_FACTOR: 15,
  INIT_POSITION: { x: 0.5 * width, y: 0.75 * height },
  pixels: { height: 432, width: 234 },
};

Mathematical

Math operators handle movement and physics.

// Projectile.js
update() {
	// Move projectile
	this.position.x += this.velocity.x;
	this.position.y += this.velocity.y;
	// ...
}

String Operations

Concatenation and template literals build UI text.

// ArcheryEndScreen.js
const timeLabel = document.createElement('div');
timeLabel.textContent = `Time taken: ${formattedTime}`;

Boolean Expressions

Compound conditions drive collision and movement logic.

// Projectile.js
if (!this.stuck && xDiff <= TARGET_SIZE/2.0 && yDiff <= TARGET_SIZE/2.0) {
	this.stuck = true;
	this.stuckTarget = nearestTarget;
	this.offset.x = this.position.x - nearestTarget.position.x;
	this.offset.y = this.position.y - nearestTarget.position.y;
	this.velocity = {x: 0, y: 0}; // stop moving
	nearestTarget.hitsRemaining -= 1;
	console.log(`Target hit, now has ${nearestTarget.hitsRemaining} health`);

	if (nearestTarget.hitsRemaining <= 0){
		try { showEndScreen(this.gameEnv); } catch (e) { console.warn('Error showing victory screen:', e); }
		
	}
}

Keyboard Input

Key listeners drive player and NPC interaction.

// FightingPlayer.js
window.addEventListener('keydown', this._attackHandler);

Canvas Rendering

Characters draw sprites on canvas every frame.

// Projectile.js
draw() {
	const ctx = this.ctx;
	this.clearCanvas();

	if (!this.imageLoaded) {
		return;
	}

	// ...
}

GameEnv Configuration

GameEnv creates and sizes the canvas.

// GameEnv.js
create() {
  this.setCanvas();
  this.setTop();
  this.setBottom();
  this.size();
}

API Integration

Leaderboard posts and fetches scores with fetch.

// Leaderboard.js
return fetch(url, { ...fetchOptions, method: 'POST', body: JSON.stringify(requestBody) })
  .then(res => res.json());

Asynchronous I/O

Promise chains handle async network results.

// Leaderboard.js
fetch(`${javaURI}/api/events/SCORE_COUNTER`, fetchOptions)
  .then(res => res.json())
  .then(data => this.displayLeaderboard(data))
  .catch(err => {
    console.error('Error fetching dynamic leaderboard:', err);
  });

JSON Parsing

Local storage and payloads are parsed into objects- for example, in the leaderboard system that we utilize.

// Leaderboard.js
const stored = JSON.parse(localStorage.getItem(storageKey) || '[]');
const transformed = stored.map(e => ({
  id: e.id,
  payload: { user: e.payload.user, score: e.payload.score },
}));

Code Comments

JSDoc blocks document game level classes and methods.

// GameLevelArchery.js

/**
 * GameLevelArchery
 * 
 * Defines the configuration for the Archery mini-game level.
 * This class constructs the objects that will exist in the level,
 * including the background, player, NPC, barrier, and moving target.
 * 
 * Each object is described with a configuration object that determines
 * sprite properties, positioning, animations, and gameplay behavior.
 */
class GameLevelArchery {

    /**
     * Friendly name of the game level
     * @static
     * @type {string}
     */
    static friendlyName = "Level 2: Archery";


    /**
     * Creates a new Archery level configuration.
     *
     * @param {GameEnvironment} gameEnv - The main game env object
     */
    constructor(gameEnv) {

		// ...rest of code ommitted...
	}
}

Mini-Lesson Documentation

This notebook includes the runnable Castle Game demo above.

Code Highlights

See the sections above for OOP, collisions, and API snippets tied to each objective.

Console Debugging

Logging is used for transitions and diagnostics.

// GameLevelOutside.js

// Clean up current level properly
if (gameControl.currentLevel) {
	// Properly destroy the current level
	console.log("Destroying current level...");
	gameControl.currentLevel.destroy();

	// Force cleanup of any remaining canvases
	const gameContainer = document.getElementById('gameContainer');
	if (gameContainer) {
		const oldCanvases = gameContainer.querySelectorAll('canvas:not(#gameCanvas)');
		oldCanvases.forEach(canvas => {
			console.log("Removing old canvas:", canvas.id);
			canvas.parentNode.removeChild(canvas);
		});
	}
}

console.log("Setting up archery level...");

Hit Box Visualization

A debug helper draws collision bounds in our spline barrier class.

// SplineBarrier.js
draw() {
	// Draw spline curve on the spline barrier's canvas
	if (!this.ctx || !this.canvas) return;
	if (!this.visible) return;

	// Clear canvas
	this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

	// Get curve points
	const curvePoints = SplineBarrier.getCurvePoints(this.splinePoints);
	if (curvePoints.length === 0) return;

	// Draw the spline curve
	this.ctx.strokeStyle = this.barrierColor;
	this.ctx.lineWidth = this.lineWidth;
	this.ctx.lineCap = 'round';
	this.ctx.lineJoin = 'round';

	this.ctx.beginPath();
	this.ctx.moveTo(curvePoints[0].x, curvePoints[0].y);

	for (let i = 1; i < curvePoints.length; i++) {
		this.ctx.lineTo(curvePoints[i].x, curvePoints[i].y);
	}

	this.ctx.stroke();
}

Source-Level Debugging

Breakpoints can be set in update loops and transitions.

// Player.js (though you can do this in any file)
update() {
  super.update();
  // set breakpoint here to inspect velocity, position, collisions
}

Deubgging with breakpoints

Network Debugging

Fetch calls appear in DevTools Network tab.

// Leaderboard.js
fetch(`${javaURI}/api/events/ELEMENTARY_LEADERBOARD`, fetchOptions)
  .then(res => res.json());

Deubgging with Networking

Application Debugging

Local storage is used for player skin and progress flags.

// GameLevelOutside.js
window.localStorage.setItem('castleGame.playerSkin', normalized);

Deubgging with Local Storage

Element Inspection

Each spline barrier creates its own canvas element.

// SplineBarrier.js
// Create a canvas for drawing the spline (not for collision)
this.canvas = document.createElement('canvas');
this.canvas.id = this.id;
this.canvas.width = this.gameEnv.innerWidth;
this.canvas.height = this.gameEnv.innerHeight;

…as well as other game objects (eg characters)

Element Inspection

Gameplay Testing

Play through levels to confirm transitions and collisions.

Game Demo

Integration Testing

Leaderboard fetches from backend or falls back to local storage.

// Leaderboard.js
if (!this.hasBackend) {
  const stored = JSON.parse(localStorage.getItem(storageKey) || '[]');
  this.displayLeaderboard(stored);
}

API Error Handling

Errors are caught and reported with user-friendly messages.

// Leaderboard.js
return fetch(url, fetchOptions)
  .then(res => {
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  })
  .catch(error => {
    console.error('Error fetching leaderboard:', error);
  });

Code Runner Demos

%%js

// CODE_RUNNER: Classes & Objects | hide_edit: true

class Player {
  constructor(name) {
    this.name = name;
    this.hp = 100;
  }

  status() {
    return `${this.name}: ${this.hp} hp`;
  }
}

const p = new Player("Knight");
console.log(p.status());
%%js

// CODE_RUNNER: Methods & Parameters | hide_edit: true

class SpriteSheet {
  setSprite(path) {
    return `Sprite set to ${path}`;
  }
}

const sheet = new SpriteSheet();
console.log(sheet.setSprite("/images/sprite.png"));
%%js

// CODE_RUNNER: Inheritance & Overriding | hide_edit: true

class Enemy {
  attack() {
    return "Enemy attacks";
  }
}

class Ghost extends Enemy {
  attack() {
    return "Ghost attacks silently";
  }
}

console.log(new Enemy().attack());
console.log(new Ghost().attack());
%%js

// CODE_RUNNER: Constructor Chaining | hide_edit: true

class Base {
  constructor(id) {
    this.id = id;
  }
}

class Child extends Base {
  constructor(id, level) {
    super(id);
    this.level = level;
  }
}

const c = new Child("npc-1", 2);
console.log(c);
%%js

// CODE_RUNNER: Iteration | hide_edit: true

const hits = [3, 2, 1];
let total = 0;

for (const h of hits) {
  total += h;
}

console.log(`Total hits: ${total}`);
%%js

// CODE_RUNNER: Conditionals (Nested) | hide_edit: true

const x = 7;

if (x > 0) {
  if (x % 2 === 1) {
    console.log("positive odd");
  } else {
    console.log("positive even");
  }
} else {
  console.log("non-positive");
}
%%js

// CODE_RUNNER: Objects & JSON Parsing | hide_edit: true

const payload = '{"id":"score1","score":42}';
const obj = JSON.parse(payload);

console.log(obj.id, obj.score);
%%js

// CODE_RUNNER: Math, Strings, Booleans | hide_edit: true

const speed = 2.5;
const time = 4;
const distance = speed * time;
const label = `Distance: ${distance}`;
const isFar = distance > 8 && distance < 15;

console.log(label);
console.log(`isFar=${isFar}`);
%%js

// CODE_RUNNER: Local Storage | hide_edit: true

// if key exists already, log it
if (window.localStorage.getItem('cs111.coderunnerdemo')) {
	console.log(window.localStorage.getItem('cs111.coderunnerdemo'));
}

window.localStorage.setItem('cs111.coderunnerdemo', "test");
console.log(window.localStorage.getItem('cs111.coderunnerdemo'));