Aryan Malik
My CS111 journey — from a backwards slime to a full 3-level group game. Covers sprite debugging, game engine architecture, DevTools, and everything I learned in the class.
Aryan Malik — CS111 Portfolio
This blog covers my full CS111 journey — how I went from not knowing how sprite sheets worked to shipping a three-level group game. Each section below shows the actual code at each stage, with live game runners where relevant.
Part 1: First Level — Moving Player (Wrong Facing)
My first working level had a background and a slime you could move with WASD. The slime moved — but it faced the wrong direction. Moving left played the down animation. Moving down played the left animation.
The bug was in playerData: I had sprite sheet rows mapped to the wrong directions.
Challenge
Move with WASD — the slime faces the wrong direction. Row 0 and row 2 are swapped!
Play the runner below and notice the slime faces the wrong way:
Challenge
Move with WASD — rows 0 and 2 are swapped. The slime now faces the correct direction!
Part 2: The Fix — Swapping Rows 0 and 2
A sprite sheet is a grid where each row is a direction and each column is one animation frame. I inspected the actual slime.png and found row 0 is LEFT-facing, row 2 is DOWN-facing — I had them backwards.
| Direction | Wrong | Fixed |
|---|---|---|
down |
row 0 | row 2 |
left |
row 2 | row 0 |
Swapping those two numbers fixed every direction at once:
Challenge
Move with WASD. Walk up to the NPC to interact, and try walking through the invisible barrier!
Part 3: Full Level — NPC and Collision Barrier
With movement fixed I added two new game objects: an NPC and an invisible Barrier.
The NPC uses the same config pattern as the player — position, scale, sprite sheet rows — so learning it for the player transferred directly.
For barriers, the critical thing I learned: fromOverlay: true is required or the engine ignores the barrier entirely and the player walks straight through it.
const dbarrier_1 = {
id: 'dbarrier_1', x: 309, y: 89, width: 28, height: 252,
visible: false, // invisible wall
fromOverlay: true // required — activates collision
};
Walk up to the NPC to interact, and try to walk through the invisible wall:
%%js
// GAME_RUNNER: Move with WASD. Walk up to the NPC to interact, and try walking through the invisible barrier!
import GameControl from '/assets/js/GameEnginev1/essentials/GameControl.js';
import GameEnvBackground from '/assets/js/GameEnginev1/essentials/GameEnvBackground.js';
import Player from '/assets/js/GameEnginev1/essentials/Player.js';
import Npc from '/assets/js/GameEnginev1/essentials/Npc.js';
import Barrier from '/assets/js/GameEnginev1/essentials/Barrier.js';
class GameLevelFullygame {
constructor(gameEnv) {
const path = gameEnv.path;
const bgData = {
name: 'custom_bg',
src: path + '/images/gamebuilder/bg/alien_planet.jpg',
pixels: { height: 772, width: 1134 }
};
const playerData = {
id: 'playerData',
src: path + '/images/gamebuilder/sprites/slime.png',
SCALE_FACTOR: 5,
STEP_FACTOR: 1000,
ANIMATION_RATE: 50,
INIT_POSITION: { x: 100, y: 300 },
pixels: { height: 225, width: 225 },
orientation: { rows: 4, columns: 4 },
down: { row: 2, start: 0, columns: 3 },
downRight: { row: 1, start: 0, columns: 3, rotate: Math.PI/16 },
downLeft: { row: 0, start: 0, columns: 3, rotate: -Math.PI/16 },
left: { row: 0, start: 0, columns: 3 },
right: { row: 1, start: 0, columns: 3 },
up: { row: 3, start: 0, columns: 3 },
upLeft: { row: 2, start: 0, columns: 3, rotate: Math.PI/16 },
upRight: { row: 3, start: 0, columns: 3, rotate: -Math.PI/16 },
hitbox: { widthPercentage: 0, heightPercentage: 0 },
keypress: { up: 87, left: 65, down: 83, right: 68 }
};
const npcData = {
id: 'NPC',
greeting: 'Working',
src: path + '/images/gamify/r2_idle.png',
SCALE_FACTOR: 5,
ANIMATION_RATE: 50,
INIT_POSITION: { x: 500, y: 300 },
pixels: { height: 223, width: 505 },
orientation: { rows: 1, columns: 3 },
down: { row: 0, start: 0, columns: 3 },
right: { row: 0, start: 0, columns: 3 },
left: { row: 0, start: 0, columns: 3 },
up: { row: 0, start: 0, columns: 3 },
hitbox: { widthPercentage: 0.1, heightPercentage: 0.2 },
dialogues: ['Working']
};
const barrier1 = {
id: 'barrier1', x: 309, y: 89, width: 28, height: 252,
visible: false,
hitbox: { widthPercentage: 0.0, heightPercentage: 0.0 },
fromOverlay: true
};
this.classes = [
{ class: GameEnvBackground, data: bgData },
{ class: Player, data: playerData },
{ class: Npc, data: npcData },
{ class: Barrier, data: barrier1 }
];
}
}
export const gameLevelClasses = [GameLevelFullygame];
export { GameControl };
Part 4: Debugging With DevTools
Once the game was running, things still broke — just silently. The browser’s DevTools became my main diagnostic tool.
Console — Reading Errors

| Error | Meaning |
|---|---|
404 |
Wrong file path or file doesn’t exist |
ERR_CONNECTION_REFUSED |
Server is offline |
CORS policy blocked |
Server rejected a cross-origin request — server-side fix only, can’t patch in JS |
User initialization failed (non-critical) |
Auth failed but game still runs — not fatal |
The CORS errors came from the game on gates.opencodingsociety.com calling flask.opencodingsociety.com/api/id. Different subdomains, and Flask wasn’t configured to allow it. Once I understood that, I stopped trying to fix it in game code.
Application Panel — Game State

The Application tab shows what the game is actually storing in localStorage. Two entries in my game:
gategame_player_name→Aryanescaperoom_coinsCollect...→16
If data wasn’t loading right, I could check here to see what was actually stored vs what the code expected — faster than adding console.log statements everywhere.
Part 5: The Group Game — 3-Level Slime Escape
My team built a complete three-level game on the shared class game engine. You play as a slime trying to reach the sea.
| Version | Link | What It Is |
|---|---|---|
| Standalone | pages.opencodingsociety.com/gategame | Original self-contained build — all 3 levels in one flow |
| Platform | gategame-integration | Wired into the shared Gamify engine — required our exports to match the platform’s API |
Getting the standalone game was one challenge. Getting it running inside the shared platform was harder — level class names can’t conflict, exports have to match what the engine expects, and the game loop has to hand control back cleanly when a level ends.
Level 1: Cannonball Dodge
Cannonballs fire from the right. Use W/S to dodge. Dodge = advance 300px toward the gate. Get hit = reset.
if (playerHitbox.overlaps(cannonball)) {
player.x = 0; // reset
} else {
player.x += 300; // advance
}
A single if/else runs the entire mechanic. Boolean flags tracked whether a cannonball was active mid-flight.
Level 2: Escape Room Maze
Navigate a maze with an NPC guide. Find the gate, press E then Esc to advance. Every barrier is a data-driven config object in an array — the maze layout is just data.
// All barriers live in an array — loop registers them all
barriers.forEach(b => this.classes.push({ class: Barrier, data: b }));
Changing the maze means editing the array, not rewriting logic.
Level 3: Zone Catch
Two colored zones appear. A banner says which is safe. Get inside before the timer bar empties or you’re eliminated. Round 6+ adds a golden gate for early escape.
// Pixel color scan — engine checks colors around player each frame
if (getPixelColor(player) === safeColor) { playerSafe = true; }
if (currentRound >= 6) { showGoldenGate(); }
This level stacked the most concepts — pixel scanning in a loop, boolean state across rounds, and a conditional that unlocks a new mechanic at a threshold.
Part 6: CS111 Concepts — How They Showed Up in Game Code
Every concept I learned in class had a direct equivalent in the game.
| Concept | In Class | In the Game |
|---|---|---|
| Variables | Store a value | playerX, SCALE_FACTOR, coinsCollected |
| Data Types | String, Number, Boolean, Object | IDs are strings; positions are numbers; state flags are booleans; configs are objects |
| Conditionals | if/else |
Hit-or-dodge check, safe zone check, golden gate unlock |
| Loops | for, forEach |
Game loop runs every frame; barriers registered via forEach; pixel scan per frame |
| Arrays | List of values | this.classes holds every game object; dialogues array; zone pair list |
| Objects | Key-value data | Every playerData, bgData, npcData config is a plain object |
| Classes | Blueprints for objects | Every level is a class the engine instantiates with new |
| Boolean flags | True/false state | playerSafe, cannonballActive, gameOver |
| Functions | Reusable logic | showRandomDialogue(), launchCannonball(), advanceToNextLevel() |
The thing that made it click: every mechanic in the game is just a CS concept applied to a specific problem. The hit detection is a conditional. The game loop is a loop. The level config is an object. Once I saw that, the concepts stopped being abstract.
Boolean State Management
Managing a multi-round game required tracking a lot of state with booleans:
let playerSafe = false; // in the safe zone?
let timerRunning = true; // round timer active?
let goldenGateOpen = false; // past round 6?
let gameOver = false; // game done?
if (!playerSafe && timerExpired) { gameOver = true; }
if (currentRound >= 6) { goldenGateOpen = true; }
Naming booleans clearly and updating them in the right order was one of the harder debugging lessons — especially across multiple rounds.
Final Reflections
| What I Did | What I Learned |
|---|---|
| Level with wrong sprite rows | Read the sprite sheet before mapping rows |
| Fixed row mapping | Debugging is reading, not guessing |
| Added NPC and Barrier | fromOverlay: true required; config patterns transfer across objects |
| DevTools Console | CORS is server-side; 404 means wrong path |
| DevTools Application | localStorage shows actual state — check it first |
| Level 1: Cannonball Dodge | Conditionals and booleans control game logic |
| Level 2: Escape Room Maze | Data-driven arrays make levels easy to edit |
| Level 3: Zone Catch | Pixel scanning, multi-round booleans, nested logic |
| Snake | Arrays are the data structure under simple mechanics |
| Fish Parallax | Loop + array + math = polished visual effect |
| Shipped standalone game | Building a complete self-contained product |
| Gamify integration | Writing code that fits someone else’s architecture |
| Networking unit | The network stack explains every error I debugged |
I came in knowing what variables and loops were. I’m leaving knowing how to use them to build something.