Mar 23, 2026  ·  Kashyap Tubati  ·  Sprint 6 Final Project

CS111 Blog

My final project walkthrough — 3 levels, 3 totally different mechanics, and every CS111 concept packed into one game.

What this blog covers: Control Structures (iteration, conditionals, nested conditions) · Data Types (numbers, strings, booleans, arrays, objects) · Operators (mathematical, string operations, boolean expressions) — all shown directly in the game I built.

▶ Play the Game — 3 Levels

WASD — Move E — Interact with Gate ESC — Next Level

Game Overview — All 3 Levels

Three levels, each one completely different. Level 1 is reflex-based, Level 2 is a maze, and Level 3 — the one I made — is a zone survival game that keeps shifting on you every few seconds.

LevelNameGoalMain CS Concept
1Cannonball DodgeDodge cannonballs with W/S, advance to the gate, press ESCAABB collision, for loop round cycle, boolean collision flag
2Escape RoomNavigate a barrier maze to find the master gate NPCNested conditionals for barrier detection, string NPC state
3Zone CatchStand in the correct coloured zone for 10 roundsBoolean survival check, array of zone objects, Math operators
Controls: WASD to move  ·  E to interact with the gate  ·  ESC to go to the next level

Zone Catch — My Level (Level 3)

Zone Catch is the level I designed and wrote from scratch. Every few seconds two coloured circles appear on screen, and a banner tells you which one is safe. You have to get into the right one before the timer hits zero — if you're standing outside it, or in the wrong one, you get eliminated. Survive 10 rounds, or find the golden gate that shows up from Round 6 and press E near it to escape early and win.

Under the hood it runs as a ZoneCatchOverlay class drawn on a separate fixed canvas stacked on top of the game engine's canvas using z-index: 9999. That way I didn't need to touch any engine code — just layered my logic right on top.

Every property in the constructor is a different data type on purpose. round and totalRounds are Numbers compared each time a round ends. roundActive, won, and gameOver are Booleans that guard every update and draw method. circles is an Array of Objects where each object has a safe boolean telling the survival check which zone the player needs to be in.
class ZoneCatchOverlay {
  constructor(gameEnv) {
    this.round       = 0;       // Number — current round
    this.totalRounds = 10;      // Number — rounds needed to win
    this.roundActive = false;   // Boolean — is a round running?
    this.won         = false;   // Boolean — did player escape?
    this.gameOver    = false;   // Boolean — is game over?
    this.circles     = [];      // Array — two zone objects per round
    this.colorPairs  = [        // Array of String pairs — zone colours
      ['#e63946', '#457b9d'],
      ['#f4a261', '#2a9d8f'],
    ];
  }
}

Control Structures

Iteration — for, forEach, while loops

Loops drive the game's pixel sampling, object processing, and round scheduling. The for loop below is the same pattern used when iterating over samplePoints in the Zone Catch survival check — looping over every offset in an array and accumulating a result.

// for...of — iterates pixel sample points in Zone Catch survival check
for (const [dx, dy] of samplePoints) {
  const pixel = ctx.getImageData(cx+dx, cy+dy, 1, 1).data;
  if (pixel[3] < 30) continue;
  const sd = Math.sqrt((pixel[0]-safeRGB.r)**2 + (pixel[1]-safeRGB.g)**2);
  if (sd < bestSafeDist) bestSafeDist = sd;
}

// forEach — processing each active cannonball in Level 1
cannonballs.forEach(ball => {
  ball.x -= ball.speed;
  if (ball.x + ball.r < 0) resetBall(ball);
});

Iterations Homework

// for loop — accumulates sum, same structure as pixel colour accumulation
function sumNumbers(n) {
  let sum = 0;
  for (let i = 1; i <= n; i++) {
    sum += i;
  }
  return sum;
}
console.log("Sum 1 to 5:", sumNumbers(5));  // 15

// while loop — drains power until zero
let power = 35;
while (power > 0) {
  power -= 10;
  if (power < 0) power = 0;
}
console.log("Power:", power);  // 0

Conditionals — if/else, state transitions

Every state transition in the game runs through an if/else check. The gate key handler below uses basic conditionals to guard two different requirements before triggering a win — if the key isn't E, return early; if the player is close enough, trigger the win, otherwise log the miss.

// if/else — gate interaction in Zone Catch
this._gateKeyHandler = (e) => {
  if (e.key !== 'e' && e.key !== 'E') return;        // wrong key → do nothing
  if (!this.roundActive || !this.gateVisible) return; // wrong state → do nothing

  const p = this._getPlayerCenter();
  if (p && this._dist(p.x, p.y, g.x, g.y) < g.r * 1.5) {
    this._triggerWin();                                 // close enough → win
  } else {
    console.log('[ZoneCatch] Too far from gate');       // else → miss
  }
};

// if/else — cannonball reset in Level 1
if (ball.x + ball.r < 0) {
  ball.x     = canvas.width + ball.r;    // off left → reset to right
  ball.speed = 2 + Math.random() * 3;
} else {
  ball.x -= ball.speed;                  // still on screen → keep moving
}

Conditionals Homework

// Grade classifier — if/else chain for state transitions
function getGrade(score) {
  if (score >= 90) {
    return "A";
  } else if (score >= 80) {
    return "B";
  } else if (score >= 70) {
    return "C";
  } else {
    return "F";
  }
}
console.log(getGrade(85));  // B
console.log(getGrade(72));  // C

Nested Conditions — Complex game logic

The Zone Catch survival check uses multi-level conditionals — first checking if there are any opaque pixels under the player (are they in any zone?), then checking colour distance to the safe zone, then comparing against the danger zone. Any one of the three failing = eliminated.

The final check chains three conditions with ||: die if there are no coloured pixels under the player (outside both zones), die if the safe colour is too far away (wrong circle), or die if the danger colour is actually closer (still wrong). Three completely different failure modes handled in nested conditionals — power-up + collision + direction all at once.
// Nested conditions — Zone Catch survival check
if (!hasOpaquePixel) {
  // Level 1: not inside any zone at all
  this._triggerDeath();
} else if (bestSafeDist > 80) {
  // Level 2: inside a zone but not the safe colour
  this._triggerDeath();
} else if (bestSafeDist >= bestDangerDist) {
  // Level 3: safe colour is further away than danger colour
  this._triggerDeath();
} else {
  this._nextRound();
}

Nested Conditionals Homework

const factorsOf50 = [1, 2, 5, 10, 25, 50];

for (let i = 1; i <= 50; i++) {
  if (i > 25) {
    if (factorsOf50.includes(i)) {
      console.log(`${i} is in the upper range and is a factor of 50.`);
    } else {
      if (i % 3 === 0) {
        console.log(`${i} is in the upper range and divisible by 3.`);
      } else {
        console.log(`${i} is in the upper range (no match).`);
      }
    }
  } else {
    if (factorsOf50.includes(i)) {
      if (i % 2 === 0) {
        console.log(`${i} is an EVEN factor of 50.`);
      } else {
        console.log(`${i} is an ODD factor of 50.`);
      }
    } else {
      if (i % 3 === 0) {
        console.log(`${i} is 25 or less and divisible by 3.`);
      } else {
        console.log(`${i} is 25 or less (no match).`);
      }
    }
  }
}

Data Types

TypeUsed ForExample from the Game
NumberPosition, velocity, score tracking — round counter, zone radius, timer durations, pixel colour distances.this.round = 0 · baseR = 95 - round * 3
StringCharacter names, sprite paths, game states — zone colours as hex strings, HUD labels as template literals, key comparison.color: '#e63946' · e.key !== 'e'
BooleanFlags (isJumping, isPaused, isVulnerable) — roundActive, won, gameOver control which screen draws. Each circle has a safe boolean.this.roundActive = false · safe: safeIdx === 0
ArrayGame object collections, level data — this.circles holds zone objects, colorPairs stores colour options, samplePoints holds pixel offsets.this.circles = [] · colorPairs = [['#e63946','#457b9d'],...]
Object (JSON)Configuration objects, sprite data — each circle is an object with x, y, r, color, safe. Gate and player position are also objects.{ x:400, y:300, r:80, color:'#e63946', safe:true }

Numbers — Position, velocity, score tracking

Numbers are everywhere there's math: zone radius shrinks by 3 each round, the distance formula uses exponentiation, Math.max() prevents zones getting impossibly small:

// Arithmetic on Numbers — zone radius calculation
const baseR = Math.max(50, 95 - this.round * 3);

// Distance formula — numeric properties x, y used in math
_dist(ax, ay, bx, by) {
  return Math.sqrt((ax - bx) ** 2 + (ay - by) ** 2);
}

// Round duration decreases 200ms each round, minimum 3800ms
_roundDuration() {
  return Math.max(3800, this.baseRoundDuration - (this.round - 1) * 200);
}

Strings — Character names, sprite paths, game states

Zone colours are stored as hex strings and passed to canvas drawing calls. The gate key handler compares e.key to a string. All HUD labels are strings built with template literals:

// String literals — hex colour stored as String
const colorPairs = [['#e63946', '#457b9d'], ['#f4a261', '#2a9d8f']];

// String comparison — gate key check
if (e.key !== 'e' && e.key !== 'E') return;

// Template literal — HUD text built from Numbers + Strings
ctx.fillText(`Round ${this.round} / ${this.totalRounds}`, x, y);

Strings Homework

let str1 = "Homework", str2 = "JavaScript is fun", str3 = "I like coding";
console.log(`Length of str1: ${str1.length}`);
console.log(`str1 — first: ${str1[0]}, last: ${str1[str1.length - 1]}`);
let sentence = `${str1}: ${str2}. ${str3}!`;
console.log(sentence);

Booleans — Flags (isJumping, isPaused, isVulnerable)

Every state flag in the game is a boolean. roundActive guards the update and draw loops — nothing runs if it's false. Each circle object carries a safe boolean set at spawn time using a strict equality check:

// Boolean flag — guards entire game loop
if (!this.roundActive || this.gameOver) return;

// Boolean from expression — set at circle spawn
const safeIdx = Math.round(Math.random());
this.circles = [
  { color: shuffled[0], safe: safeIdx === 0 },  // evaluates to Boolean
  { color: shuffled[1], safe: safeIdx === 1 },
];

Arrays — Game object collections, level data

this.circles holds the two zone objects each round. colorPairs stores the available colour combinations. samplePoints is an array of pixel offsets looped over in the survival check:

// Array of Objects — the two zones spawned each round
this.circles = [
  { x: x0, y: y0, r: baseR, color: shuffled[0], safe: true  },
  { x: x1, y: y1, r: baseR, color: shuffled[1], safe: false },
];

// Array of offsets — iterated in pixel survival check
const samplePoints = [[0,0],[-8,0],[8,0],[0,-8],[0,8]];

Arrays Homework

const myGames = ["COD", "Minecraft", "Forza", "Clash", "Roblox"];
console.log("First:", myGames[0]);
console.log("Last:",  myGames[myGames.length - 1]);

let shoppingList = ["milk", "eggs", "bread", "cheese"];
shoppingList[1] = "butter";
shoppingList.push("yogurt");
shoppingList.splice(2, 1);

const numbers = [10, 25, 30, 15, 20];
let sum = 0;
for (let i = 0; i < numbers.length; i++) { sum += numbers[i]; }
console.log("Total:", sum);

Objects (JSON) — Configuration objects, sprite data

Each zone circle is a configuration object. Sprite hitbox data comes in as a nested object from the engine's JSON config — read with optional chaining and a nullish fallback:

// Object literal — zone configuration
const gate = { x: gx, y: gy, r: 30, alpha: 1.0, visible: true };

// Nested object access — sprite config from JSON
const hbW = player.spriteData?.hitbox?.widthPercentage ?? 0.4;

JSON Homework

const resume = {
  fullName:  "Kash Tubati",
  email:     "kash.tubati@gmail.com",
  education: "9th Grade",
  address:   { city: "San Diego", state: "California", country: "USA" },
  skills:    ["JavaScript", "Python", "Problem Solving", "Debugging"]
};
console.log("City: " + resume.address.city);
let jsonString   = JSON.stringify(resume);
let parsedResume = JSON.parse(jsonString);
console.log(parsedResume);

Operators

TypeOperators UsedWhat They Do in the Game
Mathematical+ - * / ** % Math.sqrt() Math.max()Physics: 95 - round * 3 shrinks zones. (ax-bx)**2 is the distance formula. Math.max(50, ...) enforces a minimum radius.
String OperationsTemplate literals `${}`, concatenation +, .length, indexing [i]Path concatenation for sprite images. `Round ${round}` builds HUD text. Hex string indexing in colour parsing.
Boolean Expressions&& || ! === !== < >=Compound conditions: !roundActive || !gateVisible short-circuits the gate check. bestSafeDist >= bestDangerDist catches the wrong zone.

Mathematical — Physics calculations (gravity, velocity, collision)

Every movement, collision, and sizing calculation runs on mathematical operators. Zone radius shrinks by 3px per round; the distance formula uses exponentiation and square root; Math.random() positions zones unpredictably each round:

// Arithmetic — zone sizing with + - *
const baseR  = Math.max(50, 95 - this.round * 3);
const margin = baseR + 16;

// ** and / — distance formula (physics collision)
_dist(ax, ay, bx, by) {
  return Math.sqrt((ax - bx) ** 2 + (ay - by) ** 2);
}

// % — modulo used for wrapping round index into colorPairs
const pair = this.colorPairs[this.round % this.colorPairs.length];

Mathematical Expressions Homework

// Modulo — divisibility check
let y = 17;
let remainder = y % 5;
if (remainder === 0) {
  console.log(y + " is divisible by 5.");
} else {
  console.log(y + " is NOT divisible by 5. Remainder: " + remainder);
}
let a = 15, b = 25;
console.log("Total: " + (a + b));

String Operations — Path concatenation, text display

Template literals build every piece of HUD text. Sprite image paths are concatenated strings. The hex colour string is parsed character-by-character when converting to RGB for the pixel comparison:

// Template literal — HUD display
ctx.fillText(`Round ${this.round} / ${this.totalRounds}`, x, y);
ctx.fillText(`Safe zone: ${safeColor}`, x, y + 30);

// Concatenation — sprite path building
const spritePath = baseURL + 'images/' + spriteName + '.png';

// String indexing — hex colour parsing for pixel comparison
_hexToRgb(hex) {
  const m = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return m ? { r: parseInt(m[1],16), g: parseInt(m[2],16), b: parseInt(m[3],16) } : null;
}

String Operations Homework

let str1 = "Homework", str2 = "JavaScript is fun";

// .length and indexing
console.log(`Length: ${str1.length}`);
console.log(`First: ${str1[0]}, Last: ${str1[str1.length - 1]}`);

// Template literal — same pattern as all game HUD text
let display = `${str1}: ${str2}!`;
console.log(display);

// Concatenation — path building
let path = "images/" + "player" + ".png";
console.log(path);  // images/player.png

Boolean Expressions — Compound conditions in game logic

Boolean expressions with &&, ||, and ! guard every state transition. The gate check short-circuits with || so the distance calculation never runs unless both flags pass. The survival check uses >= to compare colour distances:

// && — both conditions must be true
if (this.roundActive && this.gateVisible) {
  this._drawGate();
}

// || — any one failing triggers early return
if (!this.roundActive || !this.gateVisible) return;

// Compound boolean expression — survival check
if (!hasOpaquePixel || bestSafeDist > 80 || bestSafeDist >= bestDangerDist) {
  this._triggerDeath();
}

Boolean Expressions Homework

// && — both must be true
function isPositiveAndEven(num) {
  let isPositive = num > 0;
  let isEven     = num % 2 === 0;
  return isPositive && isEven;
}
console.log(isPositiveAndEven(8));   // true
console.log(isPositiveAndEven(-4));  // false

// || and ! — at least one true, or negation
function canPass(hasTicket, isMember) {
  return hasTicket || isMember;
}
console.log(canPass(false, true));    // true
console.log(!canPass(false, false));  // true — negated

Other Games My Team Built

On top of the three levels I made, my teammates built some other games that hit the same CS111 concepts from different directions:

How they connect: Pong demonstrates OOP classes (Paddle, Ball, Renderer), physics math operators for velocity and collision, boolean game-state flags, and iteration via requestAnimationFrame. Connect 4 uses timer-driven state transitions (booleans + iteration). Blackjack uses arrays for the deck, nested conditionals for hand evaluation, and mathematical expressions for card values and betting.

Blackjack

Blackjack is embedded live below — it demonstrates arrays (deck shuffling and hand management), nested conditionals (ace value switching, win/loss/tie logic), mathematical expressions (hand totalling, betting), and strings (card suit and value display). Each round auto-bets $10:

Blackjack

Pong

Pong is embedded live below — it demonstrates OOP (separate Paddle, Ball, Renderer, and Game classes), mathematical operators for physics (velocity, spin, collision), boolean flags for game state, and a requestAnimationFrame loop for smooth animation. Choose PvP or AI mode:


Connect 4

Connect 4 is embedded live below — it demonstrates timer-driven state transitions (booleans), iteration over the board array to check winners, and requestAnimationFrame for the animated disc drop and confetti.

🔴 Connect 4 🟡

Every homework below maps directly to a concept above:

  • Iterations — for/forEach/while loops used in pixel sampling, object processing, and round scheduling
  • Nested Conditionals — multi-level if/else is the backbone of the Zone Catch survival check
  • Mathematical Expressions — modulo, arithmetic, and Math methods used in zone sizing and distance checks
  • Strings — template literals, string indexing, and hex colour strings throughout
  • Boolean Expressions — every guard clause and state flag; compound conditions in the survival check
  • Arrays — circles array, colorPairs, samplePoints all drive Zone Catch logic
  • Objects (JSON) — JSON.stringify and JSON.parse; nested object config for sprites and gates
The point: Every single homework has a real equivalent somewhere in the game. It's not just practice — the concepts genuinely show up in the final project.