Iteration ยท Conditionals ยท Nested Conditions
Loops are used everywhere in the game โ to move lasers, check collisions, and iterate over game object arrays. The game uses for, forEach, and while-style animation loops.
// Iterate backwards through the lasers array โ safe to splice while looping
for (let i = this.lasers.length - 1; i >= 0; i--) {
const L = this.lasers[i];
L.x += L.vx; // move laser each frame
L.y += L.vy;
L.life -= 1; // count down lifespan
if (L.life <= 0) {
this.lasers.splice(i, 1); // remove expired laser
continue;
}
// ... collision checks below ...
}
for loop moves every laser every frame. Iterating backwards is essential: when splice(i, 1) removes a laser, everything above that index shifts down by one. A forward loop would skip the element that just moved into index i. Backwards iteration means only already-visited indices shift, so nothing is missed.
// forEach iterates all active lasers to draw them on the canvas
this.lasers.forEach(L => {
const ctx = this.gameEnv?.ctx;
if (!ctx) return;
ctx.fillStyle = L.isPlayerLaser ? '#58a6ff' : '#ff7b72'; // blue=player, red=enemy
ctx.beginPath();
ctx.arc(L.x, L.y, 5, 0, Math.PI * 2);
ctx.fill();
});
// forEach also used in leaderboard rendering
scores.forEach(({ player, score }, index) => {
console.log(`#${index + 1} ${player}: ${score}`);
});
forEach is used when you need to process every element and don't need to break early or splice. For rendering, every laser needs to be drawn without any removal logic โ forEach is the clean choice. For the for loop used during collision/removal, forEach can't be used because you need the index i to call splice.
Practice iterating backwards through an array and safely removing elements โ the same pattern used for lasers every frame.
// The game engine drives all updates in a requestAnimationFrame loop
// equivalent to: while (game is running) { update all objects; draw frame; }
function gameLoop() {
if (!this.running) return; // while condition check
this.update(); // update all game objects
requestAnimationFrame(this.gameLoop.bind(this));
}
// Inside PeppaBattleLevelBase.update() โ called 60x/sec by the loop
update() {
if (this.battleEnded) return;
this.updateLasers(); // runs the for loop above
this.drawLasers(); // runs the forEach above
this.checkBossDefeat();
}
requestAnimationFrame which calls the update function 60 times per second โ structurally identical to a while(running) loop. Every laser, every boss movement, every collision check happens inside this loop. Without iteration, nothing in the game would move.
Conditionals control every state transition in the game โ when the battle ends, whether the player wins or loses, and which level loads next. They use if/else chains and guard clauses.
update() {
// Conditional 1: guard clause โ stop everything if battle is over
if (this.battleEnded) return;
// Conditional 2: check win condition
if (boss.isDefeated && !this.battleEnded) {
this.battleEnded = true;
const isFinalLevel = ctrl.currentLevelIndex === ctrl.levelClasses?.length - 1;
if (isFinalLevel) {
this.showWinScreen();
} else {
this.messageTimeout = setTimeout(() => {
if (ctrl?.currentLevel) ctrl.currentLevel.continue = false;
}, 1100);
}
return;
}
// Conditional 3: lose condition
if (this.playerHealth <= 0 && !this.battleEnded) {
this.battleEnded = true;
this.showLoseScreenAndRestart();
}
}
if/else in the win path decides whether to show the final win screen or advance to the next level.
Run this to see the win/lose logic. Try changing playerHealth to 0 to trigger the lose path instead.
Nested conditions implement complex game logic where multiple state checks must pass simultaneously before an action fires โ like power-up activation, attack cooldowns, or multi-stage collision detection.
// Outer condition: player pressed SPACE this frame
if (this.attackRequested) {
this.attackRequested = false;
// Inner condition: cooldown elapsed AND boss is still alive
if (now - this.lastAttackAt >= this.attackCooldownMs && !boss.isDefeated) {
this.lastAttackAt = now;
this.spawnLaserStraight(px, py, player.direction, true);
}
}
&& to require both: enough time since the last shot AND the boss isn't already dead. If either inner condition fails, no laser fires and lastAttackAt doesn't reset โ the cooldown keeps ticking. This nested structure is what gives combat its rhythm.
// AABB collision โ 4 conditions must all be true simultaneously
if (hitLeft < boss.position.x + boss.width &&
hitLeft + hitW > boss.position.x &&
hitTop < boss.position.y + boss.height &&
hitTop + hitH > boss.position.y) {
// Nested: only player lasers damage boss, only enemy lasers damage player
if (L.isPlayerLaser) {
boss.takeDamage(1);
// Nested deeper: only flash if boss is still vulnerable after the hit
if (!boss.isDefeated) {
this.flashDamageIndicator();
}
} else {
this.damagePlayer(1);
}
this.lasers.splice(i, 1);
}
isPlayerLaser to decide who takes damage. The innermost check avoids flashing a damage indicator on an already-defeated boss. Each level of nesting is necessary โ collapsing them into one flat if-chain would require many more conditions and lose the logical structure.
Simulate the attack cooldown logic with nested if/else conditions โ the same pattern that gates laser firing in the game.
Nested Conditionals Homework โ Demonstrates complex decision-making logic using nested if-else statements, directly applicable to boss behavior and win/lose conditions.
Iterations Homework โ Shows proficiency in loops and iteration, essential for game loops, collision detection, and processing arrays of game objects.