CS111 Blog — Kashyap Tubati
My CS111 final project walkthrough — a 3-level game covering control structures, data types, and operators.
CS111 Blog
My final project walkthrough — 3 levels, 3 totally different mechanics, and every CS111 concept packed into one game.
Table of Contents
▶ Play the Game — 3 Levels
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.
| Level | Name | Goal | Main CS Concept |
|---|---|---|---|
| 1 | Cannonball Dodge | Dodge cannonballs with W/S, advance to the gate, press ESC | AABB collision, for loop round cycle, boolean collision flag |
| 2 | Escape Room | Navigate a barrier maze to find the master gate NPC | Nested conditionals for barrier detection, string NPC state |
| 3 | Zone Catch | Stand in the correct coloured zone for 10 rounds | Boolean survival check, array of zone objects, Math operators |
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.
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.
||: 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
| Type | Used For | Example from the Game |
|---|---|---|
| Number | Position, velocity, score tracking — round counter, zone radius, timer durations, pixel colour distances. | this.round = 0 · baseR = 95 - round * 3 |
| String | Character names, sprite paths, game states — zone colours as hex strings, HUD labels as template literals, key comparison. | color: '#e63946' · e.key !== 'e' |
| Boolean | Flags (isJumping, isPaused, isVulnerable) — roundActive, won, gameOver control which screen draws. Each circle has a safe boolean. | this.roundActive = false · safe: safeIdx === 0 |
| Array | Game 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
| Type | Operators Used | What 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 Operations | Template 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:
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:
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.
Homework
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