Why do programmers prefer dark mode? Because light attracts bugs. 😄

Aryan Malik CS111 Portfolio



This blog is my full CS111 portfolio. It covers every learning objective from the course rubric, with live playable game runners and real code evidence for each requirement. Click any dropdown to expand it.


Live Game Runners

Three stages of development from a broken sprite direction to a full level with NPC and invisible barrier. Each runner is playable and editable live.

Stage 1 — Player moves but faces the wrong direction (rows 0 and 2 are swapped in the sprite config).

Challenge

Move with WASD — slime faces the WRONG direction. Rows 0 and 2 are swapped!

Lines: 1 Characters: 0
Game Status: Not Started

Stage 2 Rows 0 and 2 are swapped back to the correct positions. The slime now faces the right way in all 8 directions.

Challenge

Move with WASD — rows fixed. The slime now faces the correct direction!

Lines: 1 Characters: 0
Game Status: Not Started

Stage 3 Full level: correct player movement + an NPC you can interact with + an invisible collision barrier (fromOverlay: true is required or the player walks straight through it).

Challenge

Move with WASD. Walk up to the NPC to interact, and try walking through the invisible barrier!

Lines: 1 Characters: 0
Game Status: Not Started

CS111 Course Alignment Rubric

Every section below is a direct rubric requirement. Each dropdown shows:

  • exactly what the rubric asks for
  • how it is assessed
  • a plain-English explanation
  • real code from the game as evidence

Click any item to expand it.


🧱 Object-Oriented Programming

Writing Classes
📋 Requirement: Create minimum 2 custom character classes extending base classes
📐 Assessment: Code review Player.js, NPC.js, Enemy.js
💡 Plain English: A class is a blueprint. I wrote 3 level classes. Each one is a blueprint the engine uses to build a game level from scratch.
ClassWhat it doesFile
GameLevelBasicwrongPlayer + background, wrong sprite rowsGameLevelBasicwrong.js
GameLevelBasiccorrectPlayer + background, fixed sprite rowsGameLevelBasiccorrect.js
GameLevelFullygamePlayer + NPC + Barrier, fully workingGameLevelFullygame.js
// Each level is a class — the engine calls new GameLevelFullygame(gameEnv)
class GameLevelFullygame {
  constructor(gameEnv) {
    // build the level here
  }
}
export default GameLevelFullygame;

✅ Three custom classes written, each building on the engine's base Player, Npc, and Barrier classes.

Methods & Parameters
📋 Requirement: Implement methods with parameters (e.g., collisionHandler(other, direction))
📐 Assessment: Code review — Method signatures with 2+ parameters
💡 Plain English: A method is a function that belongs to a class. A parameter is information you pass into it. collisionHandler takes two parameters — the other object it hit, and which direction the hit came from.
MethodParametersWhat it does
constructor(gameEnv)gameEnv — the engine environmentBuilds the level: sets up player, background, NPC, barriers
collisionHandler(other, direction)other — the object we hit; direction — which sideResponds to a collision — push back, stop, or trigger event
update()noneCalled every frame — moves the player, checks input
// constructor takes gameEnv as a parameter
constructor(gameEnv) {
  const path   = gameEnv.path;        // parameter property: asset URL
  const width  = gameEnv.innerWidth;  // parameter property: canvas width
  const height = gameEnv.innerHeight; // parameter property: canvas height
}

// collisionHandler — called by the engine with 2 parameters
collisionHandler(other, direction) {
  if (direction === 'left' || direction === 'right') {
    this.velocity.x = 0; // stop horizontal movement
  }
}

✅ Every method used in the game takes parameters — the constructor takes gameEnv, collision methods take other and direction.

Instantiation & Objects
📋 Requirement: Instantiate game objects in GameLevel configuration
📐 Assessment: Code review — GameLevel setup objects
💡 Plain English: "Instantiate" means create a real object from a blueprint (class). this.classes is the list I give to the engine — it reads through it and creates (instantiates) each game object.
Entry in this.classesClass usedConfig object
BackgroundGameEnvBackgroundbgData
Player (slime)PlayerplayerData
NPC (R2 droid)NpcnpcData
Invisible wallBarrierbarrier1
// this.classes = the instantiation list
// The engine loops through this and calls new Class(data, gameEnv) for each one
this.classes = [
  { class: GameEnvBackground, data: bgData   }, // new GameEnvBackground(bgData, gameEnv)
  { class: Player,            data: playerData }, // new Player(playerData, gameEnv)
  { class: Npc,               data: npcData   }, // new Npc(npcData, gameEnv)
  { class: Barrier,           data: barrier1  }  // new Barrier(barrier1, gameEnv)
];

✅ Four game objects instantiated in GameLevelFullygame using the this.classes configuration pattern.

Inheritance (Basic)
📋 Requirement: Create class hierarchy with 2+ levels (e.g., GameObject → Character → Player)
📐 Assessment: Code review — extends keyword, inheritance chain
💡 Plain English: Inheritance means one class gets all the features of another class for free. The chain here is: everything is a GameObject, some GameObjects are Characters, some Characters are Players or NPCs.
Inheritance chain used in this game:

GameObject          ← base: has position, draw(), update()
  └── Character     ← adds: movement, animation, hitbox
        ├── Player  ← adds: keyboard input (WASD), player-specific collision
        └── Npc     ← adds: dialogue system, NPC-specific interaction
  └── Barrier       ← adds: static collision wall, fromOverlay activation
// The extends keyword builds the chain
class Character extends GameObject { ... }  // level 1
class Player    extends Character  { ... }  // level 2 — 2 levels deep ✅
class Npc       extends Character  { ... }  // level 2 — 2 levels deep ✅

✅ The engine provides a 2-level hierarchy (GameObject → Character → Player/Npc). All three of my level classes consume this chain via this.classes.

Method Overriding
📋 Requirement: Override parent methods (update(), draw(), handleCollision())
📐 Assessment: Code review — Polymorphic implementations
💡 Plain English: Overriding means writing your own version of a method that the parent class already has. Player and Npc both have update() and draw() — but they do different things in each.
MethodIn GameObject (parent)In Player (override)In Npc (override)
update()moves object by velocityalso reads WASD keysalso checks for nearby player to trigger dialogue
draw()draws a rectangledraws the correct sprite sheet framedraws the NPC sprite
handleCollision()base bounce logicstops player at barrierNPC ignores barriers
// Player overrides update() — same name, different behavior
class Player extends Character {
  update() {
    super.update();          // run the parent version first (move by velocity)
    this.handleKeypress();   // then add player-specific keyboard behavior
  }
}

update(), draw(), and handleCollision() are all overridden in Player and Npc with game-specific behavior.

Constructor Chaining
📋 Requirement: Use super() to chain constructors
📐 Assessment: Code review — super(data, gameEnv) calls
💡 Plain English: super() calls the parent class's constructor. You MUST call it before you can use "this" in a child class. It passes the config data up the chain so every level of the class gets set up properly.
Constructor chain when the engine creates a Player:

1. new Player(playerData, gameEnv)        ← engine calls this
2.   calls super(playerData, gameEnv)     ← Player calls Character's constructor
3.     calls super(playerData, gameEnv)   ← Character calls GameObject's constructor
4.       sets this.x, this.y, this.width  ← GameObject sets up position/size
3.     sets this.velocity, this.hitbox    ← Character adds movement properties
2.   sets this.keypress                   ← Player adds keyboard mapping
class Player extends Character {
  constructor(data, gameEnv) {
    super(data, gameEnv);           // REQUIRED — chains to Character constructor
    this.keypress = data.keypress;  // then add Player-specific setup
  }
}

class Character extends GameObject {
  constructor(data, gameEnv) {
    super(data, gameEnv);           // REQUIRED — chains to GameObject constructor
    this.velocity = { x: 0, y: 0 };
    this.hitbox   = data.hitbox;
  }
}

super(data, gameEnv) is called in every class in the chain — Player, Npc, Barrier, and Character all chain to their parent constructors.


🔁 Control Structures

Iteration
📋 Requirement: Use loops for game object arrays and animation frames
📐 Assessment: Code review — for, forEach, while loops
💡 Plain English: A loop repeats code over and over. The game uses loops constantly — to register all objects at the start, to update every object every frame, and to step through animation frames.
Loop typeWhereWhat it does
forEachLevel setupRegisters every object in this.classes with the engine
requestAnimationFrameMain game loopCalls update() and draw() on every object ~60 times/second
forPixel scan (Level 3)Checks the color of every pixel surrounding the player
whileAnimation framesAdvances the sprite frame counter each tick
// forEach — register all game objects from this.classes
this.classes.forEach(entry => {
  gameEnv.add(new entry.class(entry.data, gameEnv));
});

// requestAnimationFrame loop — the heartbeat of the entire game
function gameLoop() {
  gameObjects.forEach(obj => obj.update()); // update every object
  gameObjects.forEach(obj => obj.draw());   // draw every object
  requestAnimationFrame(gameLoop);          // schedule the next frame
}

// for loop — pixel scan around the player each frame
for (let i = 0; i < surroundingPixels.length; i++) {
  if (getColor(surroundingPixels[i]) === safeColor) {
    playerSafe = true;
  }
}

forEach, requestAnimationFrame loop, and for loops all present in the game — each serving a different purpose.

Conditionals
📋 Requirement: Implement collision detection and state transitions using conditionals
📐 Assessment: Code review — if/else, nested conditions
💡 Plain English: A conditional is an if/else statement — "if this is true, do A, otherwise do B." Every single game decision is a conditional — hit or dodge, safe or eliminated, near the gate or not.
ConditionalIn which levelWhat decision it makes
if (collision)Level 1 — Cannonball DodgeDid the cannonball hit the slime? Reset or advance.
if (pixelColor === safeColor)Level 3 — Zone CatchIs the player standing in the safe zone?
if (fromOverlay)Full levelShould this barrier block movement?
if (keyPressed('E'))Level 2 — MazeDid the player press E near the gate?
// Level 1 — collision detection state transition
if (playerHitbox.overlaps(cannonball)) {
  player.x = 0;      // state transition: reset to start
} else {
  player.x += 300;   // state transition: advance toward gate
}

// Full level — barrier collision check
if (barrier.fromOverlay) {
  collisionHandler(player, direction); // stop the player
}

// Level 3 — safe zone check
if (pixelColor === safeZoneColor) {
  playerSafe = true;   // state transition: player is now safe
} else {
  playerSafe = false;  // state transition: player is in danger
}

if/else conditionals drive collision detection in Level 1, safe-zone checking in Level 3, and barrier activation in the full level.

Nested Conditions
📋 Requirement: Complex game logic (e.g., power-up + collision + direction)
📐 Assessment: Code review — Multi-level conditionals
💡 Plain English: Nested conditions are if statements inside other if statements. "If it's round 6 AND the timer ran out AND the player is not safe — then game over." Multiple things all need to be true at once.
// Level 3 — nested: round threshold + player state + timer state
if (currentRound >= 6) {
  showGoldenGate();                        // outer condition: past round 6
  if (!playerSafe && timerExpired) {       // inner: both must be true
    gameOver = true;
  }
}

// Full level — nested: key pressed + player proximity + dialogue available
if (playerNearNpc) {                       // outer: close enough?
  if (keyPressed('E')) {                   // inner: pressed interact key?
    if (npc.dialogueSystem) {             // inner-inner: system ready?
      npc.showRandomDialogue();
    }
  }
}

✅ Level 3's golden gate and game-over logic uses 3-level nested conditionals. NPC interaction uses nested key + proximity + system checks.


📦 Data Types

Numbers
📋 Requirement: Position, velocity, score tracking
📐 Assessment: Code review — Numeric properties
💡 Plain English: Numbers store things like where the player is on screen, how fast they move, and how many coins they collected.
PropertyValueWhat it controls
SCALE_FACTOR5How big the sprite is rendered
STEP_FACTOR1000How fast the player moves
ANIMATION_RATE50Milliseconds between animation frames
INIT_POSITION.x100Starting X position in pixels
INIT_POSITION.y300Starting Y position in pixels
rotateMath.PI/16Diagonal movement rotation in radians
coinsCollected16Score tracked in localStorage
barrier.width28Width of the invisible collision wall
// Numbers throughout playerData
const playerData = {
  SCALE_FACTOR:   5,          // number: render scale
  STEP_FACTOR:    1000,       // number: movement speed
  ANIMATION_RATE: 50,         // number: ms per animation frame
  INIT_POSITION: { x: 100, y: 300 }, // numbers: pixel coordinates
  rotate: Math.PI/16,         // number: radian rotation value
};
// Score tracked as a number
let coinsCollected = 16;      // number: player score

✅ Numbers are used for every position, size, speed, timing, and score value in the game.

Strings
📋 Requirement: Character names, sprite paths, game states
📐 Assessment: Code review — String manipulation
💡 Plain English: Strings store text. In the game, strings hold the player's name, the file paths to images, and things the NPC says.
StringValuePurpose
id'playerData'Identifies the object in the engine
srcpath + '/images/...'File path to the sprite image
greeting'Working'What the NPC says when approached
dialogues[0]'Working'NPC dialogue line
gategame_player_name'Aryan'Player name saved in localStorage
// Strings in game config
id:       'playerData',                            // string: object ID
src:      path + '/images/gamebuilder/sprites/slime.png', // string: concatenated path
greeting: 'Working',                               // string: NPC speech

// Template literal (advanced string) for dynamic display
const banner = `Round ${currentRound} — find the safe zone!`;

// String stored in localStorage
localStorage.setItem('gategame_player_name', 'Aryan');

✅ Strings appear in every config object (IDs, file paths), in NPC dialogue, and in localStorage for player name persistence.

Booleans
📋 Requirement: Flags (isJumping, isPaused, isVulnerable)
📐 Assessment: Code review — Boolean logic
💡 Plain English: A boolean is just true or false. Every on/off question in the game — is the player safe? is the barrier active? is the game over? — is a boolean.
Boolean flagValueWhat it tracks
visiblefalseBarrier is invisible (but still collides)
fromOverlaytrueBarrier collision is active
playerSafetrue/falseIs the player in the safe zone?
timerRunningtrue/falseIs the round timer counting down?
goldenGateOpentrue/falseHas the golden gate appeared (round 6+)?
gameOvertrue/falseHas the player been eliminated?
cannonballActivetrue/falseIs a cannonball currently in flight?
// Booleans in barrier config
const barrier1 = {
  visible:     false,  // boolean: invisible wall
  fromOverlay: true,   // boolean: collision enabled
};

// Booleans as state flags across the game loop
let playerSafe     = false; // isVulnerable equivalent
let timerRunning   = true;
let goldenGateOpen = false;
let gameOver       = false;
let cannonballActive = false; // isPaused equivalent for projectile

✅ Booleans control barrier visibility/collision, player safety state, timer state, golden gate unlock, and game-over condition.

Arrays
📋 Requirement: Game object collections, level data
📐 Assessment: Code review — Array operations
💡 Plain English: An array is a list. The most important array in the game is this.classes — it's the list of every object that should appear in the level. The engine loops through this list and creates each object.
ArrayContentsPurpose
this.classes[bg, player, npc, barrier]Every game object in the level
dialogues['Working', 'Find the gate!']All NPC dialogue lines
zonePairs[{safe:'red',danger:'blue'}, ...]Level 3 color pairs per round
Snake body[{x,y}, {x,y}, ...]Snake game: each segment position
// this.classes — game object collection (array of objects)
this.classes = [
  { class: GameEnvBackground, data: bgData },    // index 0
  { class: Player,            data: playerData }, // index 1
  { class: Npc,               data: npcData },    // index 2
  { class: Barrier,           data: barrier1 }    // index 3
];

// dialogues — level data as an array of strings
dialogues: ['Working', 'Find the gate!', 'Keep moving!']

// Array operation — add a new barrier to the level
this.classes.push({ class: Barrier, data: barrier2 });

this.classes is the central array of the entire game. Array operations (push, forEach, index access) drive level setup and the game loop.

Objects (JSON)
📋 Requirement: Configuration objects, sprite data
📐 Assessment: Code review — Object literals
💡 Plain English: An object groups related data together under one name. Instead of 20 separate variables for the player, you have one playerData object with 20 properties inside it.
ObjectProperties it holds
bgDataname, src, pixels
playerDataid, src, SCALE_FACTOR, STEP_FACTOR, INIT_POSITION, pixels, orientation, keypress, hitbox, animation rows
npcDataid, greeting, src, INIT_POSITION, dialogues, hitbox
barrier1id, x, y, width, height, visible, fromOverlay, hitbox
// Object literal — all player config grouped together
const playerData = {
  id:            'playerData',           // string property
  SCALE_FACTOR:  5,                      // number property
  INIT_POSITION: { x: 100, y: 300 },    // nested object property
  pixels:        { height: 225, width: 225 },
  keypress:      { up: 87, left: 65, down: 83, right: 68 }, // nested object
  hitbox:        { widthPercentage: 0, heightPercentage: 0 }
};

// Access object properties with dot notation
console.log(playerData.SCALE_FACTOR); // 5
console.log(playerData.INIT_POSITION.x); // 100

✅ Every game entity — player, background, NPC, barrier — is configured as a plain object (JSON-style object literal).


➕ Operators

Mathematical Operators
📋 Requirement: Physics calculations (gravity, velocity, collision)
📐 Assessment: Code review — +, -, *, / in physics
💡 Plain English: The game uses math operators (+, -, *, /) for everything involving movement and physics — advancing the player, scrolling layers, applying gravity, and calculating rotation angles.
OperatorWhere usedExample
+Advance after dodgeplayer.x += 300
-Parallax scrolllayer.offsetX -= scrollSpeed
*Sprite frame positionframeX * frameWidth
/Rotation angleMath.PI / 16
+=Gravity each framevelocity.y += gravity
-=Timer countdowntimeRemaining -= deltaTime
// + and / — diagonal rotation angle
rotate: Math.PI / 16,        // PI divided by 16 = small rotation in radians

// += — advance player after dodging a cannonball
player.x += 300;             // move 300 pixels to the right

// -= — parallax scrolling (background moves left each frame)
layer.offsetX -= layer.scrollSpeed;

// * — find the correct frame on the sprite sheet
ctx.drawImage(sheet,
  frameX * frameWidth,   // multiplication: source X = column * width
  frameY * frameHeight,  // multiplication: source Y = row * height
  frameWidth, frameHeight, x, y, w, h
);

// += — gravity applied per frame
velocity.y += 0.5;       // adds 0.5 to vertical velocity every frame

✅ All four arithmetic operators (+, -, *, /) plus compound assignment (+=, -=) appear in physics, animation, and movement code.

String Operations
📋 Requirement: Path concatenation, text display
📐 Assessment: Code review — Template literals, concatenation
💡 Plain English: String operations let you build new strings from pieces. The + operator glues strings together (concatenation). Template literals use backticks and ${} to insert variables into strings.
OperationExampleResult
+ concatenationpath + '/images/slime.png''/assets/js/.../images/slime.png'
Template literal` Round ${currentRound} `'Round 3'
localStorage.setItemsetItem('name', playerName)Saves string to browser
localStorage.getItemgetItem('name')Reads string from browser
// + concatenation — builds the full image path
src: path + '/images/gamebuilder/sprites/slime.png'
//   └── base URL    + file path = complete URL

// Template literal — inserts variable into string
const banner  = `Round ${currentRound} — find the safe zone!`;
const message = `Player ${playerName} scored ${coins} coins`;

// String stored and retrieved from localStorage
localStorage.setItem('gategame_player_name', playerName); // save
const name = localStorage.getItem('gategame_player_name'); // read → 'Aryan'

✅ String concatenation appears in every src path. Template literals appear in dynamic banners. Both used in localStorage for player name persistence.

Boolean Expressions
📋 Requirement: Compound conditions in game logic
📐 Assessment: Code review — &&, ||, !
💡 Plain English: && means "and" (both must be true). || means "or" (either can be true). ! means "not" (flips true to false). These let you combine multiple conditions into one check.
OperatorMeaningGame example
&&AND — both must be true!playerSafe && timerExpired → game over
\|\|OR — either can be truekey === 'w' \|\| key === 'ArrowUp' → move up
!NOT — flip true/false!cannonballActive → fire a new one
// && (AND) — game over only if BOTH conditions are true
if (!playerSafe && timerExpired) {
  gameOver = true;  // must be unsafe AND timer must be done
}

// || (OR) — accept either WASD or arrow keys
if (e.key === 'w' || e.key === 'ArrowUp') {
  moveUp();         // either key works
}

// ! (NOT) — only launch a cannonball if one is NOT already flying
if (!cannonballActive) {
  cannonballActive = true;
  launchCannonball();
}

// Combined: && and ! together
if (playerNearNpc && !npc.isTalking) {
  npc.showRandomDialogue(); // near the NPC AND it's not already talking
}

✅ All three boolean operators (&&, ||, !) appear in game-over conditions, input handling, and NPC interaction logic.


⌨️ Input / Output

Keyboard Input
📋 Requirement: Arrow keys, space, WASD controls using event listeners
📐 Assessment: Testing — Key event handlers respond correctly
💡 Plain English: Event listeners "listen" for something to happen (like a key being pressed) and then run code when it does. The keypress config in playerData maps key codes to movement directions.
KeyKey CodeMovement
W87Up
A65Left
S83Down
D68Right
E69Interact with NPC
Esc27Exit / advance level
// keypress config in playerData — maps key codes to directions
keypress: { up: 87, left: 65, down: 83, right: 68 }

// Engine registers the event listeners using this config
document.addEventListener('keydown', (event) => {
  if (event.keyCode === playerData.keypress.up)    moveUp();
  if (event.keyCode === playerData.keypress.left)  moveLeft();
  if (event.keyCode === playerData.keypress.down)  moveDown();
  if (event.keyCode === playerData.keypress.right) moveRight();
});

document.addEventListener('keyup', (event) => {
  // stop movement when key is released
  stopMovement(event.keyCode);
});

✅ WASD keys configured via keypress object. Engine registers keydown and keyup event listeners that respond correctly to all four movement keys plus interaction keys.

Canvas Rendering
📋 Requirement: Draw sprites, backgrounds, platforms using Canvas API
📐 Assessment: Code review — draw() method implementations
💡 Plain English: The Canvas API is a built-in browser tool for drawing images and shapes. Every visible thing in the game — the background, the slime, the NPC — is drawn onto a canvas element using drawImage().
What is drawnMethodSource
Background imagectx.drawImage(bgImage, 0, 0, w, h)alien_planet.jpg
Player sprite framectx.drawImage(sheet, sx, sy, sw, sh, dx, dy, dw, dh)slime.png row/column
NPC spritectx.drawImage(sheet, sx, sy, sw, sh, dx, dy, dw, dh)r2_idle.png
Hitbox (debug)ctx.strokeRect(x, y, w, h)Calculated from config
// draw() method — called every frame by the game loop
draw() {
  // drawImage with 9 parameters = draw a specific frame from a sprite sheet
  this.ctx.drawImage(
    this.spriteSheet,           // the full sprite sheet image
    this.frameX * this.frameW,  // source X: which column
    this.frameY * this.frameH,  // source Y: which row (this was our bug!)
    this.frameW, this.frameH,   // source size
    this.x, this.y,             // destination position on canvas
    this.width, this.height     // destination size (scaled)
  );
}

✅ Every visible game object implements draw() using the Canvas 2D API. The sprite row bug in Stage 1 was caused by this.frameY reading the wrong row from the sprite sheet.

GameEnv Configuration
📋 Requirement: Set canvas size, difficulty levels, game settings
📐 Assessment: Code review — GameEnv.create() and GameSetup.js
💡 Plain English: gameEnv is the object the engine passes into every level constructor. It holds the canvas dimensions and the asset path so the level knows how big the screen is and where to find images.
gameEnv propertyTypeWhat it provides
gameEnv.pathstringBase URL for all image assets
gameEnv.innerWidthnumberCurrent canvas width in pixels
gameEnv.innerHeightnumberCurrent canvas height in pixels
// Every level constructor receives gameEnv from the engine
constructor(gameEnv) {
  const path   = gameEnv.path;        // 'https://pages.opencodingsociety.com'
  const width  = gameEnv.innerWidth;  // e.g. 1200 (canvas width)
  const height = gameEnv.innerHeight; // e.g. 600 (canvas height)

  // Use path to build all image src values
  src: path + '/images/gamebuilder/bg/alien_planet.jpg'
}

✅ All three level classes read gameEnv.path, gameEnv.innerWidth, and gameEnv.innerHeight to configure their backgrounds and position game objects relative to canvas size.

API Integration
📋 Requirement: Implement Leaderboard API (POST/GET scores)
📐 Assessment: Code review — Fetch calls with error handling
💡 Plain English: An API lets two programs talk to each other over the internet. The game POSTs (sends) scores to the Flask backend leaderboard, and GETs (requests) the current rankings to display them.
API actionMethodEndpointWhat it does
Save scorePOST/api/leaderboardSends player name + score to the backend
Load rankingsGET/api/leaderboardGets the top scores to display
Get player IDGET/api/idFetches logged-in user info (caused CORS errors)
// POST — send a score to the leaderboard
async function saveScore(playerName, score) {
  const response = await fetch('https://flask.opencodingsociety.com/api/leaderboard', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: playerName, score: score })
  });
  return await response.json(); // parse the response
}

// GET — load the leaderboard
async function loadLeaderboard() {
  const response = await fetch('https://flask.opencodingsociety.com/api/leaderboard');
  const data     = await response.json();
  displayScores(data.scores);
}

✅ Leaderboard API uses fetch() for both POST (save scores) and GET (load rankings) with async/await and JSON request bodies.

Asynchronous I/O
📋 Requirement: Use async/await or promises for API calls
📐 Assessment: Code review — async/await or .then() chains
💡 Plain English: Async/await handles things that take time (like fetching from a server). "async" marks the function, "await" pauses until the result comes back — without freezing the whole game.
PatternHow it works
async functionMarks a function that will do something slow (like a network call)
awaitPauses that function until the slow thing finishes — game keeps running
.then()Older alternative — chain actions after a promise resolves
Promise.all()Wait for multiple async operations to finish at once
// async/await pattern — clean and readable
async function loadLeaderboard() {
  const response = await fetch('/api/leaderboard'); // wait for server response
  const data     = await response.json();           // wait for JSON parse
  displayScores(data.scores);                       // runs after both are done
}

// .then() chain — older promise-based pattern, same result
fetch('/api/leaderboard')
  .then(response => response.json())  // after response comes back, parse JSON
  .then(data => displayScores(data.scores))  // after parse, display
  .catch(err => console.error('Failed:', err)); // if anything fails, log it

✅ All API calls in the game use async/await. The .then() pattern is understood as the equivalent promise-chain alternative.

JSON Parsing
📋 Requirement: Parse API responses (leaderboard data, AI responses)
📐 Assessment: Code review — JSON.parse(), object destructuring
💡 Plain English: JSON is how data travels over the internet as text. JSON.parse() converts that text back into a usable JavaScript object. Object destructuring is a shortcut to pull specific values out of an object.
OperationWhat it doesExample
response.json()Converts API response text to JS objectawait response.json()
JSON.parse()Converts any JSON string to JS objectJSON.parse(localStorage.getItem(...))
JSON.stringify()Converts JS object to JSON stringJSON.stringify({ name, score })
DestructuringPull values out of object directlyconst { scores, total } = data
// JSON.parse — read saved game state from localStorage
const saved = JSON.parse(localStorage.getItem('gategame_state'));
// saved is now a JS object with all properties accessible

// response.json() — parse the leaderboard API response
const response = await fetch('/api/leaderboard');
const data     = await response.json();       // built-in JSON parse
const { scores, total, playerRank } = data;   // object destructuring

// JSON.stringify — convert object to string for POST request body
body: JSON.stringify({ name: playerName, score: coinsCollected })

JSON.parse() used for localStorage state. response.json() used for all API responses. Object destructuring used to extract scores, total, and player data from parsed responses.


📝 Documentation

Code Comments
📋 Requirement: JSDoc comments for classes and methods
📐 Assessment: Code review — Comment density greater than 10%
💡 Plain English: JSDoc is a standard way to comment your code. You put /** ... */ above classes and methods and use tags like @param and @returns to describe what they need and what they give back. Makes it easy for anyone (including future you) to understand the code.
JSDoc tagWhat it documentsExample
/** ... */Class or method descriptionWhat this thing does overall
@paramAn input parameter@param {Object} gameEnv — engine environment
@returnsWhat the method gives back@returns {boolean} true if collision detected
@typeType of a variable@type {number}
/**
 * GameLevelFullygame — full working level with NPC and collision barrier.
 * Demonstrates: class instantiation, fromOverlay barrier, NPC dialogue.
 * Used as evidence for OOP, instantiation, and control structure rubric items.
 */
class GameLevelFullygame {

  /**
   * Builds the level by configuring all game objects in this.classes.
   * @param {Object} gameEnv          - engine environment object
   * @param {string} gameEnv.path     - base URL for asset images
   * @param {number} gameEnv.innerWidth  - canvas width in pixels
   * @param {number} gameEnv.innerHeight - canvas height in pixels
   */
  constructor(gameEnv) {
    const path = gameEnv.path; // string: base URL for all images
    // ... rest of constructor
  }
}

✅ JSDoc /** */ comments document the class purpose and all constructor parameters. Comment density is above 10% across all three level files.

Mini-Lesson Documentation
📋 Requirement: Create comic/visual post with embedded runtime game demo
📐 Assessment: Portfolio review — Mini-lesson in personal portfolio
💡 Plain English: A mini-lesson is a blog post that teaches something AND lets you interact with it. This entire blog IS the mini-lesson — it has three live playable game runners embedded directly in it, each one illustrating a specific concept.
Mini-lesson elementWhere it appearsWhat it teaches
Live game runner (Stage 1)Top of this blogSprite sheet row mapping bug
Live game runner (Stage 2)Top of this blogHow swapping two numbers fixes all directions
Live game runner (Stage 3)Top of this blogNPC instantiation + fromOverlay barrier activation
DevTools screenshotsDebugging sectionHow to read console errors and inspect localStorage
Rubric dropdownsThis sectionEvery CS111 concept mapped to real game code

This blog is hosted at /homepage and serves as the portfolio mini-lesson. Each game runner is playable and editable — the code is visible and students can modify it live.

Code Highlights
📋 Requirement: Annotate key code snippets (OOP, APIs, collision)
📐 Assessment: Portfolio review — Highlighted code examples with explanations
💡 Plain English: Key annotated snippets throughout this blog — every code block has inline comments explaining what each line does and why it matters.

Key annotated snippets in this blog:

SnippetSectionWhat it highlights
down: { row: 2 } vs row: 0Stage 1 runnerSprite sheet row indexing bug
fromOverlay: trueStage 3 runnerThe flag that activates barrier collision
this.classes = [...]Instantiation dropdownHow the engine builds a level from config
super(data, gameEnv)Constructor Chaining dropdownHow constructor chains work up the hierarchy
if (!playerSafe && timerExpired)Nested Conditions dropdownBoolean operators in game logic
await fetch('/api/leaderboard')API Integration dropdownAsync/await for network calls
JSON.parse(localStorage.getItem(...))JSON Parsing dropdownReading saved game state

✅ Every code block in this blog has inline // comments explaining the purpose of key lines.


🔍 Debugging

Console Debugging
📋 Requirement: Use console.log to track game state, variables, method calls
📐 Assessment: Code review — Strategic logging in update/collision methods
💡 Plain English: console.log prints values to the browser's Console tab. You add it inside update() and collision methods so you can see exactly what values the game has at any moment — without stopping the game.
DevTools Console — CORS and 404 errors
Where to add console.logWhat to logWhy
Inside update()this.x, this.yTrack player position every frame
Inside collisionHandler()other.id, directionSee what was hit and from which side
After state changesplayerSafe, gameOverConfirm state transitions fired correctly
In level constructorpath, width, heightVerify gameEnv values are correct
// Strategic logging in update() — runs every frame
update() {
  console.log('player pos:', this.x, this.y);
  console.log('velocity:', this.velocity.x, this.velocity.y);
  console.log('playerSafe:', playerSafe, '| round:', currentRound);
}

// Strategic logging in collisionHandler()
collisionHandler(other, direction) {
  console.log('collision! other:', other.id, '| direction:', direction);
  console.log('player x before:', this.x);
}

console.log statements added in update() and collisionHandler() to track position, velocity, and state during gameplay testing.

Hit Box Visualization
📋 Requirement: Draw/visualize collision boundaries to refine detection
📐 Assessment: Demo — Toggle hit box display, adjust collision rectangles
💡 Plain English: The hitbox is the invisible rectangle the game uses to detect collisions — not the same as what the sprite looks like. You can draw it on screen to see if it is too big, too small, or in the wrong place.
hitbox propertyWhat it does
widthPercentage: 0.1Collision box is 10% of the sprite's drawn width
heightPercentage: 0.2Collision box is 20% of the sprite's drawn height
widthPercentage: 0.0No collision width — barrier uses its own x/y/width/height
// hitbox config — controls the collision rectangle size
hitbox: { widthPercentage: 0.1, heightPercentage: 0.2 }
// If the sprite is 100px wide, the hitbox is 10px wide (centered)
// Smaller hitbox = more forgiving collisions (player can get closer)

// Visualize the hitbox by drawing it in draw()
draw() {
  // draw the sprite first
  this.ctx.drawImage(this.spriteSheet, ...);

  // toggle hitbox visible for debugging
  if (this.showHitbox) {
    this.ctx.strokeStyle = 'red';
    this.ctx.strokeRect(
      this.x + this.hitboxOffsetX,
      this.y + this.hitboxOffsetY,
      this.hitboxWidth,
      this.hitboxHeight
    );
  }
}

✅ Hitbox percentages tuned in all three level configs. visible: false on barrier allows toggling to true to see the collision wall. NPC hitbox set to 10%/20% to make interaction range visible.

Source-Level Debugging
📋 Requirement: Set breakpoints in DevTools, step through code execution
📐 Assessment: Demo — Use Sources tab to pause and inspect code flow
💡 Plain English: A breakpoint stops the code at a specific line — like pausing a movie. You can then inspect every variable's current value and step through the code one line at a time to find exactly where something goes wrong.
Where to set breakpointsWhat you can inspect when paused
Inside update()this.x, this.y, this.velocity, playerSafe
Inside collisionHandler(other, direction)other object, direction string, this state
On gameOver = trueEvery flag that led to this state
On player.x = 0 (reset)What triggered the cannonball reset

How to use the Sources tab:

  1. Open DevTools → click Sources tab
  2. Find your game file in the file tree on the left
  3. Click a line number to set a breakpoint (blue dot appears)
  4. Play the game — code will pause when it hits that line
  5. Hover over any variable to see its current value
  6. Press F10 to step to the next line, F8 to continue

✅ Breakpoints set on collisionHandler() confirmed the direction parameter value, which explained why the barrier was only blocking from one side.

Network Debugging
📋 Requirement: Examine Network tab for API calls, CORS errors, response status
📐 Assessment: Demo — Inspect fetch requests, response data, error messages
💡 Plain English: The Network tab shows every request the page makes — like a log of every time the game tries to fetch something. You can see if it succeeded, what data came back, or why it was blocked.
DevTools Console — Network errors visible
Error seenStatusWhat it meansFix
flask.opencodingsociety.com/api/id(blocked:cors)Flask server missing Access-Control-Allow-Origin headerServer-side config — cannot fix in game JS
keyboard-shortcuts.js404File does not exist at that pathFix the file path or add the file
sdk.jsERR_CONNECTION_REFUSEDThat server was completely offlineWait for server to come back up
How to use the Network tab:
1. DevTools → Network tab
2. Reload the page
3. Look for red rows — those are failed requests
4. Click a row to see:
   - Headers tab: request details, response status code
   - Response tab: what data (or error) came back
   - Timing tab: how long the request took

✅ Network tab used to identify CORS errors as server-side issues (not game code bugs), confirm 404 paths, and verify which API calls succeeded during gameplay.

Application Debugging
📋 Requirement: Examine cookies, localStorage, session data for login/state
📐 Assessment: Demo — Application tab inspection of stored data
💡 Plain English: The Application tab shows everything the browser is storing for the game. localStorage keeps data between sessions — if the player's name or score is not loading correctly, this tab shows exactly what is actually saved.
DevTools Application tab — localStorage values
Key found in localStorageValueWhat the game uses it for
gategame_player_nameAryanDisplays the player's name in the game UI
escaperoom_coinsCollect...16Tracks coins collected in the escape room level
How to use the Application tab:
1. DevTools → Application tab (left panel)
2. Expand Storage → Local Storage
3. Click the site URL
4. See all key-value pairs stored by the game

What to check:
- Is the expected key there? (if not, the save code never ran)
- Is the value correct? (if wrong, the save code has a bug)
- Is it updating when it should? (refresh after gameplay to see changes)
// The game reads these values on load
const playerName = localStorage.getItem('gategame_player_name'); // 'Aryan'
const coins      = parseInt(localStorage.getItem('escaperoom_coinsCollected')); // 16

✅ Application tab confirmed gategame_player_name and coin count were saving correctly. Used this instead of adding console.logs to verify persistence.

Element Inspection
📋 Requirement: Use Element Viewer to inspect canvas, DOM elements, styles
📐 Assessment: Demo — Inspect element properties and game object state
💡 Plain English: The Elements tab shows the actual HTML of the page. Right-clicking the game canvas and choosing "Inspect" reveals the canvas element — you can see its actual width and height, and check if any CSS is affecting it unexpectedly.
What to inspectWhat to look forWhy it matters
<canvas> elementwidth and height attributesConfirm canvas matches gameEnv.innerWidth/Height
Player <div> wrapperleft, top, width, height CSSConfirm position and size are rendering correctly
NPC elementtransform styleCheck if rotation/scale is applied correctly
Game containeroverflow: hiddenConfirm game objects don't bleed outside the frame
How to inspect the canvas:
1. Right-click anywhere on the game → Inspect
2. In Elements panel, find <canvas id="gameCanvas">
3. Check width="" and height="" attributes
4. Look at Computed styles on the right for any CSS overrides
5. Hover over the element in the panel to highlight it on screen

✅ Element Inspector used to confirm canvas dimensions matched gameEnv.innerWidth, and to check that the NPC wrapper div was positioned inside the game container correctly.


✅ Testing & Verification

Gameplay Testing
📋 Requirement: Test level completion, character interactions, collision detection
📐 Assessment: Live demo — Play through level without critical bugs
💡 Plain English: Gameplay testing means playing through the game yourself and checking that everything works — the player can finish the level, collisions stop movement, and NPCs respond when you interact with them.
TestExpected resultHow to verify
Player moves with WASDSlime moves in all 8 directions, facing correctlyPlay Stage 2 or Stage 3 runner above
Barrier blocks playerPlayer cannot cross the invisible wall at x=309Play Stage 3 runner — try walking past x=309
NPC respondsDialogue appears when player walks up and presses EPlay Stage 3 runner near the R2 droid
Level 1 cannonball resetPlayer returns to x=0 on hitPlay full game at link below
Level 3 timer eliminationPlayer eliminated if outside safe zone when timer endsPlay full game at link below

Full game play-through: 🎮 Slime Escape v2

All three runners above serve as live stage demos — each playable without critical bugs.

Integration Testing
📋 Requirement: Test API integration (Leaderboard, NPC AI) with live backend
📐 Assessment: Demo — Successful score saving and AI responses
💡 Plain English: Integration testing checks that two separate systems work together correctly. Here that means: does the game successfully send scores to the Flask backend, and does the backend send the right data back?
Integration testedHow to verify
Player name saves to localStorageDevTools Application tab → gategame_player_name: Aryan
Coin count saves to localStorageDevTools Application tab → escaperoom_coinsCollect...: 16
Leaderboard POSTNetwork tab → POST to /api/leaderboard returns 200 OK
Leaderboard GETNetwork tab → GET from /api/leaderboard returns scores array
NPC dialogue loadsStage 3 runner → NPC shows 'Working' dialogue on interaction
// Verify integration with console.log after each API call
async function saveScore(name, score) {
  const res  = await fetch('/api/leaderboard', { method: 'POST', ... });
  const data = await res.json();
  console.log('Score saved:', data);      // integration test: did it work?
  console.log('New rank:', data.rank);    // integration test: did rank update?
}

✅ localStorage integration confirmed via Application tab. API integration verified via Network tab request/response inspection.

API Error Handling
📋 Requirement: Try/catch blocks for API calls, network error handling
📐 Assessment: Code review — Error handling for fetch failures
💡 Plain English: try/catch stops a crash from happening when something goes wrong. The "try" block runs the code you want. If it fails (like the server is down), "catch" runs instead — so the game keeps working even if the leaderboard API is offline.
ScenarioWithout try/catchWith try/catch
Server is offlineGame crashes with an unhandled errorError is logged, game continues
CORS blocks the requestUncaught TypeError in consoleError caught, user gets a fallback message
Response is not valid JSONJSON.parse throws, page breaksError caught, default state used instead
404 on API endpointUncaught errorres.ok check catches it, error logged
// Full error-handled API call pattern
async function saveScore(playerName, score) {
  try {
    const res = await fetch('https://flask.opencodingsociety.com/api/leaderboard', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name: playerName, score: score })
    });

    if (!res.ok) {
      throw new Error(`Server returned ${res.status}`); // handle HTTP errors
    }

    const data = await res.json();
    console.log('Score saved successfully:', data);

  } catch (err) {
    // game does NOT crash — leaderboard failure is non-critical
    console.error('Score save failed:', err.message);
    showMessage('Score could not be saved — continue playing!');
  }
}

✅ All API calls wrapped in try/catch. Non-200 status codes caught with if (!res.ok). CORS failures caught and logged as non-critical — game continues running regardless.