Portfolio Home
Hey, I’m Kashyap (Kash) Tubati. I work toward becoming a thoughtful software engineer by shipping games, utilities, and small experiments in code.
Development Environment
Every project begins with the tooling — these are the platforms I rely on day to day.
Js Lessons
A few of the teaching artifacts I’ve put together along the way — click in to read them.
Class Progress
Coding milestones from class, each one playable in the browser.
My Projects
My main build — the Gate Game — plus the full source on GitHub.
About the Gate Game 🚪
The Gate Game is a multi-stage JavaScript game built on top of an object-oriented engine. The project is split into three connected sublevels — Cannon Ball, Escape Room, and Zone Catch — each one exercising a different set of CS 111 concepts under a shared engine.
Object-Oriented Programming: Every sublevel is wrapped in its own class (GameLevelCannonball, GameLevelEscaperoom, GameLevelZonecatch) that accepts a gameEnv in its constructor and assembles the level out of engine primitives. Entities like Player, Npc, Enemy, and Collectible all inherit from a shared GameObject → Character base chain, so each level just composes the pieces it needs.
// Composition through the engine's class hierarchy
{ class: Player, data: sprite_data_player },
{ class: Npc, data: sprite_data_gatekeeper },
{ class: Enemy, data: sprite_data_cannon },
{ class: Collectible, data: sprite_data_key },
Methods & Parameters: Engine classes expose overridable methods like update(), draw(), and handleCollision(other, direction). Each sublevel customizes behavior through helper functions — for example, a zone-builder that converts a relative rectangle into pixel-space configuration.
// Helper that takes parameters and returns a config object
function zone(id, [rx, ry, rw, rh]) {
return {
id,
x: Math.round(rx * width),
y: Math.round(ry * height),
w: Math.round(rw * width),
h: Math.round(rh * height),
};
}
Control Structures & Data Types: Arrays hold level data (cannon trajectories, room puzzles, capture zones), booleans gate state (isLocked, hasKey, zoneCleared), and strings handle sprite paths and dialogue lines fed into the DialogueSystem.
// Arrays of object literals drive the level layout
const cannons = [
{ id: 'c1', x: 0.10, y: 0.80, angle: 45, power: 12 },
{ id: 'c2', x: 0.50, y: 0.70, angle: 30, power: 15 },
{ id: 'c3', x: 0.85, y: 0.85, angle: 60, power: 10 },
];
Input/Output & Async: Player movement runs through the engine's keyboard listeners; the gatekeeper NPC triggers async transitions when a sublevel is cleared, using requestAnimationFrame for the fade and setTimeout to hand control to GameControl.transitionToLevel().
// Async hand-off to the next gate
requestAnimationFrame(() => {
fade.style.opacity = '1';
setTimeout(() => {
topGame.transitionToLevel();
}, 800);
});
CS 111 Learning Objectives, Evidence & Assessment
Click any objective below to expand its evidence and assessment details. Eight categories, 35 objectives, each linked to its own walkthrough page.
Designing modular, reusable game pieces through classes, inheritance, and overrides — the load-bearing structure under every entity in the Gate Game.
01Writing Classes
▸ View Details & Code Example
Writing custom classes forms the foundation of our game's architecture. By encapsulating state and behavior within a class structure, we can create multiple independent instances of game entities without duplicating logic. It acts as a blueprint for everything drawn on the canvas.
In this project, specific entities like the Player and NPCs are defined as their own classes. These classes manage their own internal properties like speed, position, and specific sprite animation frames, making the overall game state much easier to reason about.
class Player extends Character {
constructor(data, gameEnv) {
super(data, gameEnv);
this.speed = 5;
this.isJumping = false;
}
}
02Methods & Parameters
collisionHandler(other, direction).▸ View Details & Code Example
Methods are functions bound to objects that define what those objects can do. When methods accept parameters, they become flexible enough to handle dynamic interactions, such as calculating damage based on an incoming projectile's speed.
The collision handler is a prime example. By passing both the object we collided with and the direction of the impact, a single method can decide whether the player should take damage, bounce off, or collect an item.
handleCollision(otherEntity, impactDirection) {
if (otherEntity.type === 'enemy' && impactDirection === 'bottom') {
this.bounce();
otherEntity.destroy();
}
}
03Instantiation & Objects
▸ View Details & Code Example
Instantiation is the process of bringing a class blueprint to life in memory. Rather than hardcoding every enemy in the engine, we use configuration arrays that describe which classes to instantiate and what data to feed them.
This data-driven approach allows us to build entirely new levels just by modifying arrays of JSON objects, keeping the core game loop completely separated from the level design.
const levelEntities = [
{ class: Player, data: playerData },
{ class: Enemy, data: cannonData }
];
// Engine maps over this array and calls 'new'
levelEntities.forEach(ent => new ent.class(ent.data));
04Inheritance (Basic)
extends chain▸ View Details & Code Example
Inheritance lets us share common functionality across different classes. A base GameObject handles universal tasks like drawing a bounding box, while a Character handles gravity, and a Player adds user input logic.
This drastically reduces redundant code. If we need to change how gravity works, we only update the Character class, and every entity that extends it automatically inherits the fix.
class Character extends GameObject {
applyGravity() {
this.y += this.gravityVelocity;
}
}
class Player extends Character { /* Inherits applyGravity */ }
05Method Overriding
update(), draw(), or handleCollision().▸ View Details & Code Example
Method overriding occurs when a child class provides a specific implementation of a method that is already defined in its parent class. This allows the game loop to blindly call update() on every object, trusting each object to update itself correctly.
For instance, an animated sprite might override the base draw() method to cycle through sprite sheets instead of just drawing a static image block.
// In Player class
update() {
super.update(); // Call base physics
this.handleInput(); // Add custom input handling
}
06Constructor Chaining
super() to forward initialization data up the chain.super(data, gameEnv) calls▸ View Details & Code Example
When a subclass is created, it must initialize the parent class's properties before accessing its own. We do this using the super() function inside the constructor.
By passing the environment and configuration data up the chain, the base GameObject can properly register the entity in the game world without the subclass needing to know the low-level engine details.
constructor(spriteData, gameEnv) {
// Pass configuration up to GameObject
super(spriteData, gameEnv);
// Now safe to set child properties
this.inventory = [];
}
The loops and branches that drive the game loop — stepping frames, resolving collisions, and reacting to player state.
01Iteration
for, forEach, while usage▸ View Details & Code Example
Iteration is how we process collections of data efficiently. In game development, the most common use case is the render loop, which must iterate over every single active entity in the level every fraction of a second.
Using higher-order array methods like forEach keeps the logic clean and avoids off-by-one errors common in traditional `for` loops.
// Run every frame
gameEntities.forEach(entity => {
entity.update();
entity.draw(ctx);
});
02Conditionals
if/else.▸ View Details & Code Example
Conditionals are the decision-making gates of the code. They check the current state of the game and execute different blocks of logic depending on whether a condition evaluates to true or false.
We use simple conditionals constantly to prevent illegal states, such as stopping a player from jumping if they are already in mid-air.
if (this.isGrounded) {
this.jump();
} else {
this.playFallAnimation();
}
03Nested Conditions
▸ View Details & Code Example
Sometimes game logic depends on multiple overlapping states. Nested conditionals allow us to evaluate a primary requirement before checking secondary, more specific criteria.
For example, if an entity takes damage, we first check if it's the player, and if so, we check a secondary condition to see if they currently have an invincibility shield active.
if (collisionDetected) {
if (this.hasShield) {
this.shieldHealth -= 1;
} else {
this.die();
}
}
The values that position, label, flag, and configure everything inside the level.
01Numbers
▸ View Details & Code Example
Numbers in JavaScript cover both integers and floating-point values. They are the backbone of all spatial logic in our game engine, representing coordinates, bounding box dimensions, and velocity vectors.
Because screen updates happen at 60 frames per second, positional numbers often carry deep decimal precision to ensure smooth, stutter-free movement.
this.x += this.vx * deltaTime;
this.score += 100;
this.opacity = 0.5; // Decimal value
02Strings
▸ View Details & Code Example
Strings handle all textual data. From defining asset file paths to identifying specific element IDs in the DOM, strings are critical for configuration and rendering text to the user.
In state machines, string literals are often used as simple but readable status flags (like "idle", "running", or "dead") to trigger different animation loops.
const spriteSheetPath = "/assets/player_run.png";
if (this.state === "idle") {
this.setAnimation(spriteSheetPath);
}
03Booleans
isJumping, isPaused, hasKey.▸ View Details & Code Example
Booleans represent a strict true or false state. They are incredibly lightweight and fast to evaluate, making them perfect for binary game states.
We use boolean flags extensively to lock out inputs, toggle pause menus, or track whether a one-time objective has been successfully completed within a level.
let isPaused = false;
function togglePause() {
isPaused = !isPaused;
}
04Arrays
▸ View Details & Code Example
Arrays provide ordered, indexed collections of data. They are ideal for grouping multiple instances of similar items, such as bullets fired by the player or enemies currently active in the room.
We dynamically push to and splice from arrays during gameplay to manage object pooling and clean up entities that have been destroyed or moved off-screen.
const activeProjectiles = [];
activeProjectiles.push(new Bullet(this.x, this.y));
// Remove bullet at index 0
activeProjectiles.splice(0, 1);
05Objects (JSON)
▸ View Details & Code Example
JavaScript Objects (and JSON syntax) map string keys to values, creating structured, readable data packets. They are essential for passing complex configuration options into functions without relying on strict parameter ordering.
In our game, we define entirely reusable hitboxes and sprite frame definitions as object literals, allowing us to tweak physical dimensions safely in one place.
const playerConfig = {
width: 64,
height: 64,
hitbox: { offsetX: 10, offsetY: 5 }
};
Math operators move the world, boolean operators decide what happens, string operators assemble the text the player reads.
01Mathematical
▸ View Details & Code Example
Math operators execute the arithmetic that powers movement. Simple addition moves an entity across the screen, while multiplication is used for scaling and applying friction to slow objects down.
We use the modulo operator to cycle through sprite animation frames seamlessly, resetting the frame counter to zero once it hits the maximum frame count.
this.currentFrame = (this.currentFrame + 1) % this.maxFrames;
this.velocity *= 0.9; // Apply friction
02String Operations
▸ View Details & Code Example
String operations allow us to concatenate variables into readable text dynamically. This is primarily handled through modern template literals, which evaluate expressions embedded directly in the string.
We use these heavily when updating the UI overlay, rendering real-time score updates, or generating dynamic file paths based on entity IDs.
const currentScore = 1500;
document.getElementById('ui').innerText = `Score: ${currentScore} pts`;
03Boolean Expressions
&&, ||, !▸ View Details & Code Example
Boolean operators (AND, OR, NOT) combine multiple conditions into a single truthable statement. They help us flatten deeply nested `if` statements into cleaner logic lines.
In our game, checking if a player is attempting to jump involves ensuring they are on the ground AND pressing the jump key AND not currently stunned.
if (isGrounded && keys.space && !isStunned) {
executeJump();
}
The connection between the player and the game world, and between the game and external services like the leaderboard and NPC AI.
01Keyboard Input
▸ View Details & Code Example
Keyboard input is captured using global window event listeners. By tracking `keydown` and `keyup` events, we can maintain an active dictionary of which keys are currently being pressed.
This asynchronous tracking ensures that multiple keys can be pressed simultaneously (like running and jumping) without input blocking.
const keys = {};
window.addEventListener('keydown', e => keys[e.code] = true);
window.addEventListener('keyup', e => keys[e.code] = false);
02Canvas Rendering
draw() implementations▸ View Details & Code Example
The Canvas API is our visual output layer. The engine continuously clears the canvas context and redraws every entity at its new calculated coordinates.
We use the advanced signatures of `drawImage` to slice specific frames out of large spritesheets, projecting them onto the relative viewport of the screen.
// Draw specific frame from spritesheet
ctx.drawImage(
this.img,
frameX, frameY, frameWidth, frameHeight,
this.x, this.y, this.width, this.height
);
03GameEnv Configuration
GameEnv.create() and GameSetup.js▸ View Details & Code Example
The `GameEnv` serves as a global singleton object that houses the environment constraints. This includes the dynamic width and height of the playable area based on window resizes.
Centralizing this ensures that responsive layout scaling trickles down to every entity's collision and boundary checks uniformly.
const GameEnv = {
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
gravity: 0.5
};
04API Integration
▸ View Details & Code Example
API Integration lets our game talk to external servers. Using the Fetch API, we can send player data securely to a backend database for persistence.
This allows us to save high scores globally and fetch the current top players to populate the leaderboard UI upon level completion.
fetch('https://api.example.com/scores', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user: 'Kash', score: 500 })
});
05Asynchronous I/O
async/await around API requests..then() chains▸ View Details & Code Example
Network requests take time. Asynchronous functions ensure the browser doesn't freeze the game loop while waiting for a server response.
By wrapping our fetches in `async/await`, the code reads sequentially but executes in a non-blocking background thread, preserving the 60fps framerate.
async function fetchLeaderboard() {
const response = await fetch('/api/leaderboard');
const data = await response.json();
updateUI(data);
}
06JSON Parsing
JSON.parse() and destructuring▸ View Details & Code Example
When data comes back from our APIs or localStorage, it's often in a raw string format. We parse this JSON back into usable JavaScript objects to extract values.
This process is crucial for dynamic dialogue trees powered by AI, where stringified JSON logic is safely unpacked into usable chat objects.
const rawData = localStorage.getItem('playerSave');
if (rawData) {
const parsedState = JSON.parse(rawData);
this.inventory = parsedState.inventory;
}
JSDoc, mini-lessons, and annotated highlights that turn working code into code that explains itself.
01Code Comments
▸ View Details & Code Example
Code comments provide context to complex logic. By adhering to standard JSDoc conventions, we enable intelligent autocompletion features in IDEs for anyone else working on the engine.
Properly documented parameters and return types save substantial time during debugging and onboarding new developers.
/**
* Calculates collision between two game objects.
* @param {GameObject} a - The primary object
* @param {GameObject} b - The secondary object
* @returns {boolean} True if intersecting
*/
function checkCollision(a, b) { ... }
02Mini-Lesson Documentation
▸ View Details & Code Example
Writing about code reinforces our understanding of it. Mini-lessons break down complex implementations into digestible markdown walkthroughs for other students.
We embed live iframe versions of the specific mechanic being discussed so readers can immediately interact with the concept.
Check out the isolated physics demo below:
03Code Highlights
▸ View Details & Code Example
Code highlights focus attention on the core mechanisms of a solution without overwhelming the reader with boilerplate setup files.
By using formatted code blocks with targeted in-line annotations, we clearly communicate the intent and clever workarounds utilized in the final build.
// Highlight: Bitwise floor for faster rendering
const renderX = (this.x + 0.5) | 0;
ctx.drawImage(img, renderX, renderY);
Working across the Chrome DevTools surface — from console traces in the game loop to network failures in the leaderboard API.
01Console Debugging
console.log tracing for state, variables, and method calls.▸ View Details & Code Example
Console debugging is the first line of defense. By outputting live variable states directly to the developer console, we can verify that calculations match our expectations in real time.
Using different log levels like `warn` and `error` helps visually filter standard state updates from critical failures during runtime.
console.log(`Player position: X=${this.x}, Y=${this.y}`);
if (!this.img) console.error("Sprite failed to load!");
02Hit Box Visualization
▸ View Details & Code Example
Visualizing hitboxes makes invisible math visible. Since sprites often contain transparent padding, rendering a strict stroke rectangle around the actual collision data helps isolate clipping issues.
We built a global debug toggle that maps over all entities and commands the canvas context to draw these borders in bright, contrasting colors.
if (GameEnv.debugMode) {
ctx.strokeStyle = 'red';
ctx.strokeRect(this.hitbox.x, this.hitbox.y, this.hitbox.w, this.hitbox.h);
}
03Source-Level Debugging
▸ View Details & Code Example
Source-level debugging allows us to freeze the game entirely at a specific line of code. This is superior to console logging when hunting down rapid, repeating errors inside the render loop.
By injecting a breakpoint, we can slowly step over individual statements, inspecting the full call stack and local scope to find exactly where data mutates incorrectly.
function resolveCollision(a, b) {
debugger; // Browser will pause execution here
const overlap = a.x - b.x;
// Step through logic manually...
}
04Network Debugging
▸ View Details & Code Example
The DevTools Network tab exposes exactly what our client sends to and receives from remote servers. This is essential for diagnosing failed leaderboard fetches or broken asset URLs.
We check status codes, payload formatting, and headers to resolve CORS errors securely before pushing integrations to production.
// If fetch fails silently, check DevTools Network tab
// Ensure payload shape matches API expectations:
const validPayload = { "score": 9000, "token": "abc" };
05Application Debugging
▸ View Details & Code Example
Modern games require persistent state across sessions. We debug this using the DevTools Application panel, which grants direct access to `localStorage` and cookies.
By manually clearing or modifying these stores, we can reliably simulate first-time user experiences and test progressive unlocking systems.
// Debugging local state reset
function clearSaveData() {
localStorage.removeItem('level_progress');
console.log('Save cleared for testing.');
}
06Element Inspection
▸ View Details & Code Example
While the game renders inside the canvas, the surrounding UI (menus, dialogue boxes) exists as standard HTML elements. The Elements panel allows us to verify CSS constraints and Z-index layering.
This ensures our canvas correctly occupies the intended viewport space without stretching or causing unintended scrollbars.
/* Checked in Elements Panel to verify canvas behavior */
canvas {
display: block;
width: 100vw;
height: 100vh;
}
Showing the level actually plays and the integrations actually work — through gameplay, end-to-end checks, and graceful error handling.
01Gameplay Testing
▸ View Details & Code Example
Manual playtesting ensures the game actually feels good. It verifies that physics tune values make sense, level geometry is passable, and boundary walls properly constrain the player.
We methodically test edge cases—like holding multiple conflicting keys or intentionally dying at the exact moment a level transitions—to ensure the engine doesn't crash.
// Test command to simulate level completion
function cheatWinLevel() {
this.player.x = finalDoor.x;
this.player.interact();
}
02Integration Testing
▸ View Details & Code Example
Integration testing confirms that our distinct systems function correctly when wired together. We verify that finishing a level properly triggers the API, successfully updating the remote database.
Using distinct environments (like a local test server vs. the production backend) keeps test scores from polluting the real player leaderboards.
const API_URL = GameEnv.isDev ? 'localhost:3000' : 'api.prod.com';
fetch(`${API_URL}/submit_score`, { ... });
03API Error Handling
try/catch around API calls and network failures.▸ View Details & Code Example
Connections drop and servers fail; resilient code prepares for this. Wrapping external network calls in `try/catch` blocks prevents fatal errors from taking down the entire game.
Instead of a frozen white screen, we intercept the error and provide a smooth UI fallback, allowing the user to play offline or try saving their data again later.
try {
await submitScore(100);
} catch (error) {
console.warn("Offline mode active. Score not saved.");
showUIFeedback("Network error, could not save.");
}
CS 111 Required Evidence Checklist
The high-level rubric the 35 objectives above roll up into. Each item has to be visibly present in the project for credit.
- ✅ 2+ custom character classes extending base classes (Character, Enemy, or Npc)
- ✅ 5+ methods with parameters and return values (overriding
update(),draw(),handleCollision(), etc.) - ✅ GameLevel configuration that uses object literals to instantiate entities
- ✅ JSDoc comments on custom classes and methods (>10% comment density)
- ✅ API integration: leaderboard (POST/GET scores) plus NPC AI, both with error handling
- ✅ Debugging competence across DevTools (Console, Network, Application, Sources) for game logic, APIs, and login/state
- ✅ Mini-lesson documentation hosted in the personal portfolio (comic/visual style with an embedded runtime demo)
- ✅ Code highlights covering OOP hierarchy, API calls, collision logic, and state management
- ✅ A complete, playable custom level tested in GameBuilder and in the team repository
JavaScript is accepted in place of Java for CS 111 credit at Mira Costa College. See the CS 111 Course Info and the Math & CS Pathway.