Control Structures
An explanation on conditionals and iterations.
Conditionals
Conditionals are a way to control the flow of code and act as decision making.
Below is an example of some conditionals
If statements
The most common type of conditionals are if statements. if statements run the code inside of them when the specified condition is met.
%% js
// This is a function from the GameControl of adventureGame
// In this function, if GameState.quizCompleted is true, then it runs the handleLevelEnd function.
checkQuizCompletion: function() {
if (GameState.quizCompleted) {
this.handleLevelEnd();
}
}
%% js
// This is an example from GameControl.js in adventureGame. This is the gameLoop that runs the game.
gameLoop: function() {
// Base case: leave the game loop
if (!GameEnv.continueLevel || GameState.quizCompleted) {
if (GameState.quizCompleted) {
this.routeToMapLevel();
} else {
this.handleLevelEnd();
}
return;
}
// Nominal case: update the game objects
GameEnv.clear();
for (let object of GameEnv.gameObjects) {
object.update(); // Update the game objects
}
//console.log('Current transitionNPCS before checkTransitions:', this.transitionNPCS);
this.checkTransitions();
//console.log('Current transitionNPCS after checkTransitions:', this.transitionNPCS);
this.checkQuizCompletion();
this.handleLevelStart();
// Recursively call this function at animation frame rate
requestAnimationFrame(this.gameLoop.bind(this));
}
If else statements
There are also if statements with another else component. If the condition is met, the code inside the curly braces for the if statement will run. If the condition is not met, it will run the else code. Here is an example below.
%% js
// This is the draw function in Background.js in adventureGame
draw() {
const ctx = GameEnv.ctx;
const width = GameEnv.innerWidth;
const height = GameEnv.innerHeight;
// This is the conditional
// If there is an image, then it draws it according to the canvas dimensions.
if (this.image) {
// Draw the background image scaled to the canvas size
ctx.drawImage(this.image, 0, 0, width, height);
// If there is not an image, then the canvas is filled with the color represented by the hex code to
// the canvas dimensions.
} else {
// Fill the canvas with fillstyle color if no image is provided
ctx.fillStyle = '#87CEEB';
ctx.fillRect(0, 0, width, height);
}
}
Switch statements
Another type of conditional are switch statements. Here is an example below.
%% js
// This is part of the code related to the movement of the player in adventureGame from Player.js.
// In this function, handleKeyUp, the velocity of the player is set to 0 when a key is no longer being pressed,
// stopping their movement.
handleKeyUp({ keyCode }) {
switch (keyCode) {
case this.keypress.up: // case clause
this.velocity.y = 0;
break; // break statement
case this.keypress.left: // case clause
this.velocity.x = 0;
break; // break statement
case this.keypress.down: // case clause
this.velocity.y = 0;
break; // break statement
case this.keypress.right: // case clause
this.velocity.x = 0;
break; // break statement
}
}
In a switch statement the code in the parenthesis are evaluated and then compared to the values of the different case clauses. If it matches the value of one of the cases, then the code in that case clause is run. The break statements stop the code from running the next case if another was already met and run.
Iterations
Iterations are the repitions of code usually through loops. Here are examples of some of the different loops below.
for Loops
The most common loop is the for Loop. Inside the parenthesis of the for loop is where it is set how many times it will run.
%% js
// This is a function from GameControl.js in adventureGame
handleLevelEnd: function() {
console.log('%c[GameControl] handleLevelEnd() called', 'color: purple; font-weight: bold;');
// More levels to play
if (this.currentLevelIndex < this.levelClasses.length - 1) {
alert("Level ended.");
} else { // All levels completed
alert("Game over. All levels completed.");
}
// Tear down the game environment
// This is the for loop. In this case it will keep running until the index is less than or equal to 0
for (let index = GameEnv.gameObjects.length - 1; index >= 0; index--) {
const obj = GameEnv.gameObjects[index];
if (obj && typeof obj.destroy === 'function') {
obj.destroy();
console.log(`%c[GameControl] Destroyed object: ${obj.id}`, 'color: red; font-weight: bold;');
} else {
console.warn('Object does not have a destroy method:', obj);
}
}
}
// Each part of the iteration set up
let index = GameEnv.gameObjects.length - 1 // sets index to the last element of the GameEnv.gameObjects array
index >= 0 // sets the loop to keep running until index = 0
index-- // decreases index by 1, moves counter to the previous object in the array
In this case, the number of iterations is not known as it’s dependent on how many elements are in the gameObjects array. In some other cases the starting number would be set. The counter for this loop is the variable index. Index is set to start with the last element of the GameEnv.gameObjects array. Then the code inside the for loop will be run. After running this code index is decremented, lowering it’s value by one. So then the loop is started again with the next element at the end of the gameObjects array. This continues until index reaches 0 and the array is empty.
While loops
Another type of loop is a while loop. A while loop keeps running until the condition is no longer true.
%% js
let i = 0;
// Loop keeps running until i is 5
while (i < 5) {
console.log("Iteration:", i);
i++; // Increments i, increasing it by 1
}
Other Loops
Two other loop types I ended up adding when changing the game.
map function
This is an array method that allows you to transform each element and create a new array from those new values.
Here is the syntax
array.map(callback(currentValue, index, array), thisArg)
callback is a function that is performed for each element in the array. It can take three arguments.
- currentValue: the current element being processed in the array
- index (optional): index of current element being processed in the array
- array (optional): the array that map was called upon thisArg (optional) is the value to use as this when executing the callback function
Here is the example of it that I put into the game below.
%% javascript
// This is from GameLevelMap.js in adventureGame
// the array
this.transitionNPCS = [
{ class: Npc, data: sprite_data_tux },
{ class: Npc, data: sprite_data_nomad },
{ class: Npc, data: sprite_data_octocat },
{ class: Npc, data: sprite_data_robot },
].map(npcData => {
const npcDataFormatted = {
...npcData.data, // copies properties
targetLevel: npcData.data.targetLevel,
INIT_POSITION: npcData.data.INIT_POSITION,
hitbox: npcData.data.hitbox,
pixels: npcData.data.pixels
};
this.transitinoNPCS is the array. npcData is the callback. For each npcData object, a new npcDataFormatted object is created.
forEach
This is also an array method like map. Similar to map, it performs a function once on every element in the array. Unlike map it doesn’t create a new array. It’s mainly used for things like logging or modifying elements.
Syntax is the same as map except for forEach
array.forEach(callback(currentValue, index, array), thisArg)
%% javascript
// This is the checkTransitions function from adventureGame. It has a forEach function in it.
checkTransitions: function() {
const player = GameEnv.gameObjects.find(obj => obj instanceof Player); // Adjust as needed
if (player) {
//console.log('Player position:', player.position);
//console.log('transitionNPCS:', this.transitionNPCS);
// forEach method
// For each npc there are collision checks.
this.transitionNPCS.forEach(npc => {
//console.log('Checking transition for NPC:', npc);
if (
player.position.x < npc.position.x + npc.hitbox.width &&
player.position.x + player.hitbox.width > npc.position.x &&
player.position.y < npc.position.y + npc.hitbox.height &&
player.position.y + player.hitbox.height > npc.position.y &&
npc.targetLevel
) {
console.log(`Transitioning to ${npc.targetLevel}`);
this.handleNPCTransition(npc.targetLevel);
}
});
}
},