🧩 Level 2 Blog: Alien Maze β€” CS111 Concepts in Action


Level 2 was a major complexity jump. The maze introduced invisible barriers, a DOM-based glow effect, a run timer, and a victory screen. This blog walks through every CS111 requirement and shows how the concept graduated from Trimester 1 into this level.

πŸ“‹ Table of Contents

  1. Control Structures β€” Iteration
  2. Control Structures β€” Conditionals
  3. Control Structures β€” Nested Conditions
  4. Data Types β€” Numbers
  5. Data Types β€” Strings
  6. Data Types β€” Booleans
  7. Data Types β€” Arrays
  8. Data Types β€” Objects (JSON)
  9. Operators β€” Mathematical
  10. Operators β€” String Operations
  11. Operators β€” Boolean Expressions
  12. Reflection

πŸ” Control Structures β€” Iteration

Where we learned it: Snake Game (Trimester 1)

// Snake game β€” Trimester 1
for (let i = 1; i < snake.length; i++) {
    if (snake[0].x === snake[i].x && snake[0].y === snake[i].y) {
        gameOver = true;
    }
}

How we used it in Level 2

The _findPlayer static method iterates over multiple possible object stores to locate the player, using a for...of loop with an array of candidate sources:

// GameLevel2.js β€” _findPlayer iterates over object stores
static _findPlayer(gameEnv) {
    const sources = [
        gameEnv?.gameObjects,
        gameEnv?.objects,
        gameEnv?.gameControl?.gameObjects,
    ].filter(Boolean);  // removes undefined/null entries

    for (const list of sources) {
        const arr = Array.isArray(list) ? list : Object.values(list);
        const found = arr.find(o =>
            o?.data?.id === 'playerData' || o?.id === 'playerData'
        );
        if (found) return found;
    }
    return null;
}

Growth: Snake used for to check self-collision. Level 2 uses for...of with .filter() and .find() to search a dynamic, heterogeneous set of object stores β€” iterating over uncertainty rather than a known array.


πŸ”€ Control Structures β€” Conditionals

Where we learned it: Calculator (Trimester 1)

// Calculator β€” Trimester 1
if (input === '=') {
    displayResult();
} else if (input === 'C') {
    clearDisplay();
} else {
    appendToDisplay(input);
}

How we used it in Level 2

The _glowBarrier helper uses an early-return conditional to prevent duplicate DOM glow elements β€” a common defensive pattern in game loops:

// GameLevel2.js β€” early-exit conditional in _glowBarrier
function _glowBarrier(barrierInstance) {
    const glowId = 'barrier-glow-' + barrierInstance.data.id;
    if (document.getElementById(glowId)) return; // guard: already glowing

    // ... create and append the glow div
    setTimeout(() => { glow.style.opacity = '0'; }, 300);
    setTimeout(() => { glow.remove(); }, 820);
}

Also, the _showVictoryScreen uses a conditional to format the elapsed time string:

// GameLevel2.js β€” conditional for time display
const timeStr = elapsedSeconds !== null
    ? `${Math.floor(elapsedSeconds / 60)}:${String(elapsedSeconds % 60).padStart(2, '0')}`
    : 'β€”';

Growth: Calculator conditionals handled user intent. Level 2 conditionals enforce game-state integrity β€” preventing duplicate DOM elements and handling null timer states gracefully.


πŸͺ† Control Structures β€” Nested Conditions

Where we learned it: Snake Game (Trimester 1)

// Snake game β€” Trimester 1
if (gameRunning) {
    if (direction === 'RIGHT') {
        if (headX + 1 >= COLS) {
            gameRunning = false;
        } else {
            headX++;
        }
    }
}

How we used it in Level 2

The onCollide barrier handler nests player-finding logic, position reset, and velocity zeroing inside a found-player check:

// GameLevel2.js β€” nested conditions in barrier onCollide
onCollide: function () {
    _glowBarrier(this);
    const player = GameLevel2._findPlayer(gameEnv);
    if (player) {                              // outer: player exists?
        const init = player.data?.INIT_POSITION ?? { x: 100, y: 300 };
        player.x = init.x;
        player.y = init.y;
        if (player.position) {                 // inner: has .position object?
            player.position.x = init.x;
            player.position.y = init.y;
        }
        if (player.velocity) {                 // inner: has .velocity object?
            player.velocity.x = 0;
            player.velocity.y = 0;
        }
    }
    GameLevel2._showRestartFlash();
}

Growth: Snake nested conditions checked game state and then direction. Level 2 nests to handle optional properties on a live game object whose exact shape depends on which engine version is running.


πŸ”’ Data Types β€” Numbers

Where we learned it: Snake Game (Trimester 1)

// Snake game β€” Trimester 1
const CELL_SIZE = 20;
const FPS = 8;
let score = 0;

How we used it in Level 2

Numbers are everywhere in Level 2 β€” from maze wall proportions (as decimals) to DOM animation timings:

// GameLevel2.js β€” fractional positions for proportional barrier layout
const mazeTop        = makeBarrier('maze_top',    0.20, 0.15, 0.60, 0.02);
const mazeWall1      = makeBarrier('maze_wall_1', 0.30, 0.25, 0.02, 0.30);
const mazeWall5      = makeBarrier('maze_wall_5', 0.60, 0.25, 0.02, 0.35);

// Numbers used for DOM animation timing (ms)
setTimeout(() => { glow.style.opacity = '0'; }, 300);
setTimeout(() => { glow.remove(); }, 820);

// Victory screen β€” time computation with numbers
const elapsed = GameLevel2._startTime
    ? Math.floor((Date.now() - GameLevel2._startTime) / 1000)
    : null;

Growth: Snake used whole-number grid positions. Level 2 uses fractional proportional coordinates (0.0–1.0) so the maze scales to any screen size β€” a key step toward resolution-independent design.


πŸ”€ Data Types β€” Strings

Where we learned it: Snake Game (Trimester 1)

// Snake game β€” Trimester 1
const direction = 'RIGHT';
if (direction === 'UP') { headY--; }

How we used it in Level 2

Strings carry the entire popup UI β€” HTML template literals construct rich multi-element DOM popups:

// GameLevel2.js β€” string as full HTML template (startup popup)
overlay.innerHTML = `
    <div style="background:linear-gradient(145deg,#0d1b2a,#1b2d45); ...">
        <div style="font-size:48px;margin-bottom:12px;">πŸ€–</div>
        <h2 style="font-size:1.6rem;color:#4fc3f7;">ALIEN MAZE</h2>
        <p>Level 2 Briefing</p>
        <p>Touch a hidden wall and it will
           <strong style="color:#f48fb1;">glow red β€” then you restart.</strong>
        </p>
        <button id="maze-start-btn">START MISSION</button>
    </div>
`;

Strings also serve as unique DOM element IDs for safe lookup:

const glowId = 'barrier-glow-' + barrierInstance.data.id;
if (document.getElementById(glowId)) return;

Growth: Snake strings were direction labels. Level 2 strings are full UI documents β€” template literal HTML is a string operation that builds entire interface elements at runtime.


βœ… Data Types β€” Booleans

Where we learned it: Snake Game (Trimester 1)

// Snake game β€” Trimester 1
let gameRunning = true;
if (!gameRunning) return;

How we used it in Level 2

Level 2 uses a static boolean _startTime flag (null = not started, number = started) and a transition guard boolean:

// GameLevel2.js β€” static timer flag
GameLevel2._startTime = null;  // null = timer not running; Date.now() = running

// In the victory screen handler β€” transition guard
setTimeout(() => {
    if (this.gameEnv && this.gameEnv.gameControl &&
        !this.gameEnv.gameLevelTransitionTriggered) {  // boolean NOT guard
        this.gameEnv.gameLevelTransitionTriggered = true;
        this.gameEnv.gameControl.currentLevel.continue = false;
    }
}, 4000);

// barrier visible flag
const mazeTop = makeBarrier(...);
// { visible: false, ... } β€” boolean hides barrier visually while keeping collision

Growth: Snake used one gameRunning boolean. Level 2 uses booleans for idempotency β€” the !gameLevelTransitionTriggered guard prevents the level-transition from firing twice even if the handler runs multiple times.


πŸ“¦ Data Types β€” Arrays

Where we learned it: Snake Game (Trimester 1)

// Snake game β€” Trimester 1
let snake = [{ x: 5, y: 5 }];
snake.unshift(newHead);
if (!ateFood) snake.pop();

How we used it in Level 2

The _findPlayer method builds a temporary array of candidate object stores and filters it:

// GameLevel2.js β€” array of object stores, filtered and searched
const sources = [
    gameEnv?.gameObjects,
    gameEnv?.objects,
    gameEnv?.gameControl?.gameObjects,
].filter(Boolean);  // removes any undefined entries

for (const list of sources) {
    const arr = Array.isArray(list) ? list : Object.values(list);
    const found = arr.find(o => o?.data?.id === 'playerData' || o?.id === 'playerData');
    if (found) return found;
}

The NPC dialogue and the full class registry also use arrays:

dialogues: ['You made it through the maze! Ready for the next level?'],

this.classes = [
    { class: GameEnvBackground, data: bgData },
    { class: Player,            data: playerData },
    // ... 11 total entries
];

Growth: Snake arrays were mutable data structures we modified each frame. Level 2 uses arrays as search spaces β€” .filter() and .find() replace manual for-loop searching.


πŸ—‚οΈ Data Types β€” Objects (JSON)

Where we learned it: Calculator (Trimester 1)

// Calculator β€” Trimester 1
const state = { currentInput: '', operator: '', result: 0 };

How we used it in Level 2

The makeBarrier factory function returns a fresh barrier object β€” a factory pattern β€” making object creation reusable:

// GameLevel2.js β€” factory function returns configuration objects
const makeBarrier = (id, x, y, w, h) => ({
    id,
    x, y,
    width: w,
    height: h,
    visible: false,
    hitbox: { widthPercentage: 0.0, heightPercentage: 0.0 },
    onCollide: function () {
        _glowBarrier(this);
        const player = GameLevel2._findPlayer(gameEnv);
        // ... reset player position
        GameLevel2._showRestartFlash();
    }
});

// All 10 barriers created from the same factory
const mazeTop    = makeBarrier('maze_top',    0.20, 0.15, 0.60, 0.02);
const mazeWall1  = makeBarrier('maze_wall_1', 0.30, 0.25, 0.02, 0.30);

Growth: Calculator objects stored UI state. Level 2 objects include methods as properties (onCollide) β€” functions are values in JavaScript, and embedding them in objects is the foundation of OOP.


βž• Operators β€” Mathematical

Where we learned it: Calculator (Trimester 1)

// Calculator β€” Trimester 1
result = parseFloat(a) + parseFloat(b);
result = parseFloat(a) % parseFloat(b); // modulo

How we used it in Level 2

Math operators compute the elapsed time display β€” division, modulo, and Math.floor working together:

// GameLevel2.js β€” elapsed time formatting with math operators
const elapsed = Math.floor((Date.now() - GameLevel2._startTime) / 1000);

// Inside _showVictoryScreen:
const timeStr = `${Math.floor(elapsedSeconds / 60)}:${String(elapsedSeconds % 60).padStart(2, '0')}`;
//                 ↑ minutes (division)              ↑ seconds (modulo)

The glow effect uses DOM coordinate math:

// GameLevel2.js β€” math operators for DOM overlay positioning
const bx = barrierInstance.x ?? 0;
const by = barrierInstance.y ?? 0;
const cr = canvas.getBoundingClientRect();

glow.style.left = `${cr.left + bx}px`;  // addition
glow.style.top  = `${cr.top  + by}px`;  // addition

Growth: Calculator math was the product being computed. Level 2 math is infrastructure β€” converting milliseconds to M:SS format and mapping canvas coordinates to screen coordinates.


πŸ”— Operators β€” String Operations

Where we learned it: Snake Game (Trimester 1)

// Snake game β€” Trimester 1
document.title = 'Snake β€” Score: ' + score;

How we used it in Level 2

Template literals are the dominant string pattern β€” used for CSS strings, HTML content, and the formatted timer:

// GameLevel2.js β€” template literals throughout

// CSS positioning via template literals
glow.style.cssText = `
    position: fixed;
    left:   ${cr.left + bx}px;
    top:    ${cr.top  + by}px;
    width:  ${bw}px;
    height: ${bh}px;
    border: 3px solid rgba(255, 80, 80, 1);
    box-shadow: 0 0 18px 6px rgba(255, 60, 60, 0.8);
`;

// String method: padStart for zero-padded seconds
String(elapsedSeconds % 60).padStart(2, '0')

// Dynamic DOM ID via concatenation
const glowId = 'barrier-glow-' + barrierInstance.data.id;

Growth: Snake used simple + concatenation. Level 2 uses template literals with expressions (${...}) and string methods like .padStart() β€” a full upgrade to modern JavaScript string tooling.


⚑ Operators β€” Boolean Expressions

Where we learned it: Snake Game (Trimester 1)

// Snake game β€” Trimester 1
if (headX < 0 || headX >= COLS || headY < 0 || headY >= ROWS) {
    endGame();
}

How we used it in Level 2

Level 2 uses the nullish coalescing (??) and optional chaining (?.) operators alongside classic && chains:

// GameLevel2.js β€” modern boolean/nullish operators

// ?? (nullish coalescing): use right side if left is null/undefined
const init = player.data?.INIT_POSITION ?? { x: 100, y: 300 };
const bx   = barrierInstance.x ?? 0;
const bw   = barrierInstance.width ?? 20;

// ?. (optional chaining): short-circuit on null/undefined without throwing
gameEnv?.gameObjects
gameEnv?.gameControl?.gameObjects

// Classic && guard chain
if (this.gameEnv && this.gameEnv.gameControl &&
    !this.gameEnv.gameLevelTransitionTriggered) { ... }

Growth: Snake used || for boundary conditions. Level 2 introduces ?? and ?. β€” modern JavaScript operators that make null-safe property access concise and expressive.


πŸ’­ Reflection

Level 2 was the hardest level to build because everything was invisible β€” the maze walls, the collision logic, and the player feedback all had to be engineered from scratch without the game engine providing visual help.

CS111 Concept Trimester 1 Form Level 2 Form Growth
Iteration Self-collision loop for...of over heterogeneous object stores Iterating unknown structures
Conditionals Route button input Early-exit guard + ternary formatting Defensive + functional conditionals
Nested Conditions Direction + boundary Player existence β†’ property existence β†’ reset Multi-layer null safety
Numbers Grid coordinates Fractional proportional coordinates + timings Resolution-independent math
Strings Direction labels Full HTML template literals Strings as UI documents
Booleans Game running flag Idempotency guard + null-as-boolean pattern Booleans for state safety
Arrays Snake body Candidate search arrays with .filter() / .find() Arrays as search spaces
Objects Flat state object Factory-pattern objects with embedded methods Functions as object values
Math Operators Arithmetic results Time formatting (division + modulo) + DOM offsets Math as infrastructure
String Ops + concatenation Template literals + .padStart() Modern string tooling
Boolean Expressions || boundary checks ?? nullish coalescing + ?. optional chaining Modern null-safety operators

The invisible maze was a metaphor for the work: you can’t see the walls until you hit them, and the same was true of the bugs. Every CS111 concept had to be applied defensively β€” assuming the game object might be undefined, the timer might not have started, the canvas might not exist yet. Defensive programming is CS111 in survival mode.