Back Index Next
OOP Index Data Types

Control Structures

Click a category to see examples from Heist.exe.


Control Structures in Heist.exe

Control structures determine how, when, and how many times code runs. In Heist.exe, they’re what make the Guard bounce between walls, prevent gems from being collected twice, and build the entire scene at level start from a single array.


How They Work Together

flowchart TD
    A([HEIST Loop Starts]) --> B{Is game running?}
    B -->|No| Z([Exit])
    B -->|Yes| C["Loop through gameObjects"]
    C --> D{Is it a Guard?}
    D -->|No| G["Update object"]
    D -->|Yes| E{Player Destroyed?}
    E -->|Yes| G
    E -->|No| F{Collision detected?}
    F -->|No| H["stayWithinCanvas"]
    H --> I{Hit a wall?}
    I -->|No| G
    I -->|Yes| J["Reverse velocity"]
    F -->|Yes| K["destroy player"]
    J --> G
    K --> G
    G --> C

The Three Core Structures

🔁 Iteration — Repeat — execute code multiple times based on a condition or collection

Exactly what it is: A loop repeats a block of code multiple times. Without loops, if you wanted to create 17 game objects, you’d have to write new Guard(...); new Guard(...); new Gem(...); seventeen times. With a loop, you iterate through a list once and create all 17 objects in one pass.

From Heist.exe: HeistL2.js uses forEach to instantiate all game objects at level setup:

// HeistL2.js — this.classes is a 17-entry array
this.classes = [
    { class: GameEnvBackground, data: image_data_bg },
    { class: HeistPlayer, data: sprite_data_mc },
    { class: Gem, data: gem_data_1 },
    { class: Gem, data: gem_data_2 },
    { class: Guard, data: sprite_data_guard1 },
    { class: Guard, data: sprite_data_guard2 },
    { class: Barrier, data: border_top },
    { class: Barrier, data: border_bottom },
    { class: Barrier, data: border_left },
    { class: Barrier, data: border_right },
    // ... 7 more entries
];

// Game engine iterates this.classes:
this.classes.forEach(({ class: Class, data }) => {
    new Class(data, gameEnv);  // Run once per entry: 17 times total
});

// Without forEach, you'd have to write:
// new GameEnvBackground(image_data_bg, gameEnv);
// new HeistPlayer(sprite_data_mc, gameEnv);
// new Gem(gem_data_1, gameEnv);
// new Gem(gem_data_2, gameEnv);
// ... 13 more identical lines with different variables

Each frame, the game engine loops through all gameObjects and calls update():

// Pseudo-code: every frame
gameObjects.forEach(obj => {
    obj.update();  // Guard bounces and checks collision
                   // Gem checks if collected
                   // Player checks for keypresses
});
Loop Use when…
for You know the count upfront: for (let i = 0; i < 17; i++)
while You repeat until a condition changes: while (isPlaying) { ... }
forEach You’re walking through an array: array.forEach(item => ...)

Conditions — Decide — branch code based on true/false checks

Exactly what it is: An if / else statement checks whether something is true right now, then runs different code depending on the answer. It’s a fork in the road: if the condition is true, go down one path; otherwise, go down another.

From Heist.exe: Gem.js gates collection on the permanentlyCollected flag:

// Gem.js — collect() method checks state before running
collect() {
    if (this.permanentlyCollected) return;  // Already collected? Exit immediately
    this.permanentlyCollected = true;       // Mark as collected
    this.collected = true;

    if (this.gameEnv) {
        if (!this.gameEnv.stats) this.gameEnv.stats = { coinsCollected: 0 };
        this.gameEnv.stats.coinsCollected = (this.gameEnv.stats.coinsCollected || 0) + this.value;
    }
    console.log(`Gem collected! +${this.value}`);
}

Guard.js checks player state before triggering collision:

// Guard.js — update() method — lines 12-28
update() {
    this.draw();
    // Only check collision if player hasn't been destroyed yet
    if (!this.playerDestroyed && this.collisionChecks()) {  // Two conditions must both be true
        this.handleCollisionEvent();
    }
    this.position.y += this.velocity.y;
    this.stayWithinCanvas();
}

Nested Conditions — Refine — place checks inside checks to handle layered logic

Exactly what it is: A nested condition is an if statement inside another if statement. It lets you handle more complex scenarios where you need to decide based on multiple conditions in order.

From Heist.exe: Guard.stayWithinCanvas() (lines 33-53) has four separate boundary blocks. Each uses a nested structure to check both position and velocity:

// Guard.js — stayWithinCanvas() — lines 33-53
stayWithinCanvas() {
    // Check bottom boundary
    if (this.position.y + this.height > this.gameEnv.innerHeight) {
        this.position.y = this.gameEnv.innerHeight - this.height;
        this.velocity.y *= -1;  // Bounce: reverse direction
        console.log(this.velocity.y);
    }
    
    // Check top boundary
    if (this.position.y < 0) {
        this.position.y = 1;
        this.velocity.y *= -1;  // Bounce: reverse direction
        console.log(this.velocity.y);
    }
    
    // Check right boundary
    if (this.position.x + this.width > this.gameEnv.innerWidth) {
        this.position.x = this.gameEnv.innerWidth - this.width;
        this.velocity.x = 0;  // Stop horizontal movement
    }
    
    // Check left boundary
    if (this.position.x < 0) {
        this.position.x = 0;
        this.velocity.x = 0;  // Stop horizontal movement
    }
}

More complex nesting — HeistPlayer.handleCollisionReaction(): Multiple levels of checks to determine which direction to push the player:

// heistPlayer.js — lines 23-45: Nested conditions for collision resolution
if (touchPoints.right && !touchPoints.left) {
    // Player hit barrier from the left (barrier is on player's right)
    this.position.x = barrierLeft - this.width - BUFFER;
    this.velocity.x = 0;
} else if (touchPoints.left && !touchPoints.right) {
    // Player hit barrier from the right (barrier is on player's left)
    this.position.x = barrierRight + BUFFER;
    this.velocity.x = 0;
}

if (touchPoints.bottom && !touchPoints.top) {
    // Player hit barrier from above (barrier below)
    this.position.y = barrierTop - this.height - BUFFER;
    this.velocity.y = 0;
} else if (touchPoints.top && !touchPoints.bottom) {
    // Player hit barrier from below (barrier above)
    this.position.y = barrierBottom + BUFFER;
    this.velocity.y = 0;
}

Quick Reference

Structure Keyword Purpose
Iteration forEach, for, while Repeat actions
Condition if, else if, else Branch on truth
Nested condition if inside if Layer decisions

Rule of thumb: If something needs to repeat, use a loop. If something needs to choose, use a condition. If the choice depends on another choice, nest them.