Afra Binjerais – Midterm final

HAILSTORM HAVOC

My game draws significant inspiration from the hailstorm that occurred in Abu Dhabi a few weeks ago.

 

I was captivated by the idea of creating a game where the player must dodge hail. I envisioned a game centered around a car avoiding the hail, however, this concept proved to be overly complex, and after trial and error, I was able to overcome this challenge. To play the game: players must move the car away from the falling hail using the mouse click function. Interestingly, the mouse click function in my game serves two purposes, a feature I was unaware of until Pi assisted me in rectifying a misunderstanding. This allowed me to control both the car and a sprite, adding a fascinating layer to the gameplay.

function mousePressed() {
  console.log("Mouse Press");
  //just flipping between modes 0 and 1 in this example

  clouds.forEach((cloud) => cloud.startAnimation());
}

function mouseReleased() {
  clouds.forEach((cloud) => cloud.stopAnimation());
}

How the game works: 

    1. You must press Click to start
    2. To move the car you have to press the mouse
      1. The longer the press the further right the ball will go
    3. Dodge the hail (white balls) and avoid touching the red line as both are game over
    4. Press “space” to restart the game 

This is my game:


If it doesn’t work, visit:

https://editor.p5js.org/Afrabinjerais/sketches/q0cF4mKOG

The game is straightforward yet incorporates all the coding components we have learned to date. Its design is uncomplicated, effectively conveying the intended message. I got inspired by this p5 sketch and, the link takes you to a simple game on P5, which has a similar concept to my game, where the objects are falling from the sky.

https://editor.p5js.org/jordanBlueshift/sketches/vSO_bzkaD

 My favorite aspect of my game is the cloud sprites, which fidget whenever the mouse is clicked, creating the impression that the clouds are generating hail. On another hand,  I also encountered a significant challenge when integrating sound effects to play each time the score increased, which was undoubtedly the most difficult part for me to implement. 

    time += 1;
    if (frameCount % 60 == 0) {
      score++;
    }

function scoreUpdate() {
  // score += 10;
  if (score != prevScore) {
    scoreSound.play();
  }
  
  prevScore = score;
  
  fill(128, 128, 128, 150);
  rect(width - 100, 5, 75, 20, 5);
  fill(255);
  text("SCORE: " + int(score), width - 65, 15);
}

The issue stemmed from the points increasing too rapidly. By reducing the timer and implementing a modulo operation as suggested by the professor, I was able to resolve this problem. Looking ahead, or given more time, I would be eager to experiment with transforming the background to be interactive and making it move to visualize a street.  Although my initial attempt at this modification was unsuccessful, I am keen on dedicating time to delve into unfamiliar areas of coding to make this feature a reality.

This is the code for the game:

let gameMode = 0; // Variable to store the current game mode
let musicSound; // Variable to hold the music sound object
let gameoverSound; // Variable to hold the game over sound object
let scoreSound; // Variable to hold the score sound object
var landscape; // Variable to store the landscape graphics
var car_diameter = 15; // Diameter of the ball
var bomb_diameter = 10; // Diameter of the bombs
var xpoint; 
var ypoint; 
var zapperwidth = 6; // Width of the zapper
var numofbombs = 10; // Number of bombs
var bombposX = []; // Array to store X positions of bombs
var bombposY = []; // Array to store Y positions of bombs
var bombacceleration = []; // Array to store acceleration of each bomb
var bombvelocity = []; // Array to store velocity of each bomb
var time = 0; // Variable to track time, usage context not provided
var timeperiod = 0; // Variable to store a time period, usage not clear without further context
var score = 0; // Variable to store the current score
var posX; // X position, usage context not provided
var inMainMenu = true; // Boolean to check if the game is in the main menu
var prevScore = 0; // Variable to store the previous score
let font; // Variable to store font, usage context not provided


//Cloud Variables
let spritesheet;
let oneDimensionarray = [];

function preload() {
  spritesheet = loadImage("clouds.png");
  musicSound = loadSound("sounds/song.mp3");
  gameoverSound = loadSound("sounds/gameover.mp3");
  scoreSound = loadSound("sounds/score.mp3");
  glassbreak = loadSound("sounds/glassbreaking.wav");
  font = loadFont("fonts/Inconsolata_Condensed-Light.ttf");
  car = loadImage("car.png");
  car2 = loadImage("car2.png");
}

// Cloud class starts
class Cloud {
  constructor(x, y, speed, stepSpeed, scale) {
    this.x = x;
    this.y = y;
    this.scale = scale; // Add scale property
    this.speed = speed;
    this.stepSpeed = stepSpeed;
    this.step = 0;
    this.facingRight = false; // Initially moving to the left
    this.animationTimer = null;
  }

  move() {
    if (this.facingRight) {
      this.x += this.speed;
      if (this.x > width + spritesheet.width / 4) {
        this.x = -spritesheet.width / 4; // Wrap around to the left side
      }
    } else {
      this.x -= this.speed;
      if (this.x < -spritesheet.width / 4) {
        this.x = width + spritesheet.width / 4; // Wrap around to the right side
      }
    }
  }

  display() {
    push();
    if (!this.facingRight) {
      scale(-this.scale, this.scale); // Apply scale with horizontal flip
      image(oneDimensionarray[this.step], -this.x, this.y);
    } else {
      scale(this.scale, this.scale); // Apply scale
      image(oneDimensionarray[this.step], this.x, this.y);
    }
    pop();
  }

  advanceStep() {
    this.step = (this.step + 1) % 8;
  }

  startAnimation() {
    this.facingRight = true;
    clearInterval(this.animationTimer);
    this.animationTimer = setInterval(() => this.advanceStep(), this.stepSpeed);
  }

  stopAnimation() {
    this.facingRight = false;
    clearInterval(this.animationTimer);
  }
}

let clouds = [];
// Cloud class ends

function setup() {
  createCanvas(640, 480);
  textAlign(CENTER);
  musicSound.play();
  
var temp00 = 0, 
    temp01 = -20; 

// A while loop that increments temp01 based on temp00 until temp01 is less than the canvas height
while (temp01 < height) {
  temp00 += 0.02; // Increment temp00 by 0.02 in each loop iteration
  temp01 += temp00; // Increment temp01 by the current value of temp00
  timeperiod++; // Increment timeperiod in each iteration
}

// Calculate the initial position of posX based on zapperwidth and car_diameter
posX = zapperwidth + 0.5 * car_diameter - 2;

// Set xpoint and ypoint relative to the width and height of the canvas
xpoint = 0.7 * width; // Set xpoint to 70% of the canvas width
ypoint = height - 0.5 * car_diameter + 1; // Set ypoint based on the canvas height and car_diameter

initbombpos(); // Call the initbombpos function (presumably initializes bomb positions)

imageMode(CENTER); // Set the image mode to CENTER for drawing images centered at coordinates

// Initialize variables for width and height based on sprite sheet dimensions divided by specific values
let w = spritesheet.width / 2;
let h = spritesheet.height / 4;

// Nested for loops to extract sprite images from the sprite sheet and push them to oneDimensionarray
for (let y = 0; y < 4; y++) {
  for (let x = 0; x < 2; x++) {
    oneDimensionarray.push(spritesheet.get(x * w, y * h, w, h)); // Get sub-images from spritesheet and add to oneDimensionarray
  }
}

  
  // Create 3 clouds with horizontal offsets, different speeds and scales
  clouds.push(new Cloud(width / 8, height / 9, 0, 100, 0.9)); // First cloud
  clouds.push(new Cloud((2 * width) / 5, height / 9, 0, 100, 1.2)); // Second cloud
  clouds.push(new Cloud((2 * width) / 2, height / 9, 0, 200, 1.0)); // Third cloud
}

function draw() {
  background(58, 66, 94);

  if (gameMode == 0) {
  clouds.forEach((cloud) => {
      cloud.display();
    });
  textFont(font);
  fill(255);
  textSize(50); // Larger text size for the game title
  textAlign(CENTER, CENTER); // Align text to be centered
   text('HAILSTORM HAVOC', width / 2, height / 2 - 40);
  textSize(16); // Smaller text size for the directions
  // Draw the directions right below the game title
  text('DIRECTIONS:\n click mouse to dodge hail\n the longer the press the further right\n the car will go\n\n AVOID the red line - crossing it means game over', width / 2, (height / 2) + 50);
  textSize(20);
  text('Click to start!', width / 2, (height / 2) + 140);
   
}  
   else if (gameMode == 1) {
    clouds.forEach((cloud) => {
      cloud.move();
      cloud.display();
    });

    fill(239, 58, 38);
    rect(0, 0, zapperwidth, height);
    scoreUpdate();

    fill(255);
    noStroke();
    for (var i = 0; i < numofbombs; i++) {
      ellipse(bombposX[i], bombposY[i], bomb_diameter, bomb_diameter);
      
    }

    updatebombpos();

    
    // ellipse(xpoint, ypoint, car_diameter, car_diameter);
        image(car,xpoint, ypoint-30, car_diameter*5, car_diameter*5);

     
     xpoint -= 3;

    // Check if the mouse is pressed and the xpoint is within the canvas boundaries
if (mouseIsPressed && xpoint + 0.5 * car_diameter < width) {
  xpoint += 6; // Move the xpoint to the right by 6 units
}
     
     // Check if xpoint is less than or equal to posX or if a collision with a bomb has occurred
    if (xpoint <= posX || bombCollistonTest()) {
      gameover(); // Call the gameover function if either condition is true
    }
// Increment the score every 60 frames
    time += 1;
    if (frameCount % 60 == 0) {
      score++; // Increase score by 1
    }
  }

}

function updatebombpos() {
  // Iterate over each bomb
  for (var i = 0; i < numofbombs; i++) {
    bombvelocity[i] += bombacceleration[i]; // Update the velocity of the bomb by adding its acceleration
    bombposY[i] += bombvelocity[i]; // Update the Y position of the bomb based on its velocity
  }

  if (time > timeperiod) {
    initbombpos(); // Reinitialize the positions of the bombs by calling the initbombpos function
    time = 0;
  }
}

function initbombpos() {
  for (var i = 0; i < numofbombs; i++) {
    bombacceleration[i] = random(0.02, 0.03);
    bombvelocity[i] = random(0, 5);
    bombposX[i] = random(zapperwidth + 0.5 * car_diameter, width);
    bombposY[i] = random(-20, -0.5 * car_diameter) + 190;
  }
} //This function initializes the position and motion properties of each bomb by assigning random values within specified ranges.

function bombCollistonTest() {
  var temp = 0.5 * (car_diameter + bomb_diameter) - 2;
  var distance;
// Iterate over each bomb to check for a collision
  for (var i = 0; i < numofbombs; i++) {
    distance = dist(xpoint, ypoint, bombposX[i], bombposY[i]);
    if (distance < temp) {
      return true;
    }
  }
  return false;
} //This function checks for collisions between the player and each bomb by comparing the distance between them to a threshold. If any bomb is too close (within the threshold), it returns true (collision detected). Otherwise, it returns false.

function gameover() {
  image(car2,xpoint, ypoint-30, car_diameter*5, car_diameter*5);
  musicSound.pause();
  glassbreak.play();
  gameoverSound.play();
  textFont(font);
  fill(255);
  textSize(50); // Set the text size
  textAlign(CENTER, CENTER); // Align text to the center
  text("GAME OVER", width / 2, height / 2 - 20); // Center the text horizontally and vertically
  textSize(15); // Decreased text size for "press space to restart" text
  text("Press space to restart", width / 2, height / 2 + 20); // Positioned below "GAME OVER" text
  noLoop();
}

function scoreUpdate() {
  // score += 10;
  if (score != prevScore) //// Play the scoring sound only if the current score has changed from the previous score 
  {
    scoreSound.play();
  }
  
  prevScore = score;
  
  fill(128, 128, 128, 150);
  rect(width - 100, 5, 75, 20, 5);
  fill(255);
  text("SCORE: " + int(score), width - 65, 15);
}

function keyPressed() {
  if (keyCode === 32) {
    // Check if the key pressed is space (keyCode 32)
    restartGame(); // Call the function to restart the game
  }
  
}

function mousePressed() {
  if (gameMode==0)
    gameMode=1;
  console.log("Mouse Press");
  //just flipping between modes 0 and 1 

  clouds.forEach((cloud) => cloud.startAnimation());
}

function mouseReleased() {
  clouds.forEach((cloud) => cloud.stopAnimation());
}

function restartGame() {
  // Reset all game variables to their initial values
  musicSound.play();
  gameoverSound.pause();
  time = 0;
  score = 0;
  posX = zapperwidth + 0.5 * car_diameter - 2;
  xpoint = 0.5 * width;
  ypoint = height - 0.5 * car_diameter + 1;
  initbombpos();
  // Restart the game loop
  loop();
} 
//This function resets the game environment and variables to their initial state, essentially restarting the game. It resumes background music, pauses any game over sound, resets score and time, repositions the player and bombs, and restarts the game loop.

Enjoy!

 

References of pictures:

https://bnnbreaking.com/finance-nav/al-ains-unprecedented-hailstorm-a-costly-blow-to-the-car-sales-sector

https://bnnbreaking.com/weather/uaes-al-ain-transformed-unprecedented-hailstorm-blankets-streets-in-white

 

 

Midterm | Pyro Dancer Release

Game Description

Pyro Dancer is an endless-runner 2D game with heavy inspirations from notable titles, such as Castlevania and Metroid inspire the game. Paired with 8-bit sound and theme by Haynes, Pyro Dancer tries to mimic the retro feel of older games.

TOP |Castlevania -Grimoire of Souls- Official Website | KONAMI

Castlevania.

The player assumes the role of a knight, invading the evil castle. Upon entering the castle, he is greeted by dozens of fireballs. To win the game, the player must dodge the fireball and achieve 500 score points. If the player runs out of health points before reaching the score, they will die.

Development Process.

Knight Sprite

Sketches, Ideas, Drawings!

The game uses sprites, images, and the p5 Play library. All assets used are linked in this document, please refer to it for further information.  The library is extensively used to allow better control over the sprites and performance.

In the beginning, I sketched, drew, and created a map of my code. Generally, it is split into three parts: a) Hero class, which functions as a class that generates every component of the player’s character.

class Hero {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    //HEALTH
    this.heroHealth = 100;
    this.healthbar;
    //HERO
    this.hero;
    //BARRIER
    this.barrier;
    this.barrier2;
    this.barrierX = this.x;
  }

b) Fireball class, which serves as the group to generate, spawn, and display the fireball obstacles.

class Fireballs {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.amount;
    this.fireballs = new Group();
    this.fireball;
    this.fireballAni;
  }

c) Main sketch, which encompasses both previous classes and displays the game properly. In this file, many functions initialize, check, and update the game. The game itself is split into three parts: The main menu, the Game Screen, and the Victory / Death Screen.

Main Screen

Game Screen

Death Screen

Challenges & Difficulties

Figuring out how the sprite system works in the p5 Play library was quite a challenge, however, it helped me a ton when configuring the fireball and character. But because of the way they draw sprites, this also made a huge problem when it comes to display updates.

I tried to code the game to allow fullscreen mode, but it never really worked properly. However, the game is coded so that it fits any kind of display resolution, but a manual refresh is needed to properly display the elements inside. I suspect this is mostly because of how spaghettified my code has become to the point that I could not re-arrange ways to update the display.

Conclusion

I truly enjoyed the process of making games with p5 js. It is not the best tool to make one, but it does have some unique capabilities that are interesting to explore. However, at some point, I became frustrated and named a function psychopathically:

function butcherSprites() {
  //remove all sprites
  player.hero.remove();
  obstacle.fireballs.removeAll();
  player.healthbar.opacity = 0;
  homeButton.opacity = 1;
}
Try out the game here!

p5.js Web Editor | Pyro Dancer – Release (p5js.org) [Display with Code]

Pyro Dancer – Release (p5js.org) [Windowed Fullscreen]

Midterm Project – Musical Brick Breaker Game

Concept: Recreating one of my favorite childhood game – The Musical Brick Breaker Game

So I chose to have the concept for this project, is to create a musical soothing classical Brick Breaker game using the p5.js library. The game involves controlling a paddle to bounce a ball and break bricks at the top of the screen. The user interacts with the game by moving the paddle horizontally using the left and right arrow keys. The goal is to break all the bricks without letting the ball fall below the paddle. The game provides feedback through visual cues such as the ball bouncing off objects, disappearing bricks, and a scoring system. Moreover, sound effects further enhance the user experience.

Designing the Code: Elaborating important areas

1) Ball Behavior: Within the Ball class, I define the behavior of the ball. This includes its movement across the screen, detection of collisions with other objects (such as the paddle and bricks), and rendering on the canvas. This encapsulation allows for clear organization and modularization of ball-related functionality.

2) Paddle Control: The Paddle class covers the movement and display of the paddle. It handles user input from the keyboard to move the paddle horizontally across the screen, ensuring precise control for the player.

3) Brick Management: Each brick in the game is represented by the Brick class. This class manages the display of individual bricks on the canvas and provides methods for their creation, rendering, and removal during gameplay.

4) User Interaction: The mousePressed function responds to user input by triggering specific game actions, such as starting or resetting the game. This function enhances the interactivity of the game and provides a seamless user experience.

Additional functions, such as createBricks and resetGame, are responsible for initializing game elements (such as bricks) and resetting the game state, respectively. These functions streamline the codebase and improve readability by encapsulating repetitive tasks.

By breaking down the code into these specific components, I ensured a clear and organized structure, facilitating easier maintenance.

Minimizing Risk: Code I’m proud of,

display() {
    fill(255, 0, 0);
    ellipse(this.x, this.y, this.radius * 2);
  }
  
  checkCollision() {
    if (this.x > paddle.x && this.x < paddle.x + paddle.width && this.y + this.radius > paddle.y) {
      this.speedY *= -1;
      paddleHitSound.play();
    }
  }
  
  bounce() {
    this.speedY *= -1;
    ballHitSound.play();
  }
  
  hits(brick) {
    let closestX = constrain(this.x, brick.x, brick.x + brick.width);
    let closestY = constrain(this.y, brick.y, brick.y + brick.height);
    let distance = dist(this.x, this.y, closestX, closestY);
    return distance < this.radius;
  }
}

One major complex aspect of the project is implementing collision detection between the ball and other game objects (paddle, bricks, walls). Ensuring accurate collision detection is crucial for the game’s mechanics and overall user experience. To minimize the risk of errors in this area, I employed two  strategies:

1) Collision Detection Algorithm: Implementing this collision detection algorithms is essential because, for example in the Ball class, I used a method called hits(brick) to check if the ball collided with a brick. This method calculates the distance between the ball and the brick’s edges to determine if a collision occurred. Moreover, By using the dist() function in favor with appropriate ball coordinates, I ensured this accurate collision detection is perfectly executed.

2) Testing with Edge Cases: To validate the accuracy of this collision detection algorithm, I conducted repeated testing with various edge cases. This includes scenarios where the ball collides with the corners of bricks or with multiple objects simultaneously. By systematically testing these cases and analyzing the results, I came to conclusion that the collision detection behaves as expected under different conditions.

Prototypes (wire-frame notes) :

Here’s the Game:

Features & Game Mechanics:
– Game initializes with a start screen displaying “Musical Brick Breaker Game” and along with three themes to choose.
– The player controls the paddle using the left and right arrow keys.
– The ball bounces off the paddle, walls, and bricks.
– When the ball hits a brick, it disappears, and the player earns points.
– If the ball falls below the paddle, the game ends.
– Once game ends, it displays either “Game Over” or “Congrats” message along with the score and a feature for click to return Home.
– Clicking on the canvas after the game ends resets the game, allowing the player to replay.

Additional Features:
– Sound effects are played when the ball hits the paddle and when it hits a brick.
– The player earns points for each brick broken, and the score is displayed on the screen.
– Background music plays throughout the game to enhance the gaming experience, moreover different sound tracks were added to different themes selected.

Here’s a snapshot taken during the game-play of all the themes:


Complete Code Snippet (With Comments):

// Variables to hold background images for different themes
let backgroundImage;
let marvelBackgroundImage;
let dcBackgroundImage;

// Game objects
let ball;
let paddle;
let bricks = [];

// Brick layout parameters
let brickRowCount = 3;
let brickColumnCount = 5;
let brickWidth = 80;
let brickHeight = 20;
let brickPadding = 10;
let brickOffsetTop = 50;
let brickOffsetLeft = 30;

// Game score
let score = 0;

// Sound effects and background music variables
let ballHitSound;
let paddleHitSound;
let backgroundMusic;
let marvelSoundtrack;
let dcSoundtrack;

// Game state management variables
let gameState = 'home'; // Possible states: 'home', 'playing', 'gameOver'
let theme = 'default'; // Current theme: 'default', 'marvel', 'dc'
let gameStarted = false; // Flag to indicate if the game has started

// Preload function to load images and sounds before the game starts
function preload() {
  backgroundImage = loadImage('background_image.jpg');
  marvelBackgroundImage = loadImage('marvel_background.jpg');
  dcBackgroundImage = loadImage('dc_background.jpg');
  ballHitSound = loadSound('ball_hit.mp3');
  paddleHitSound = loadSound('paddle_hit.mp3');
  backgroundMusic = loadSound('background_music.mp3');
  marvelSoundtrack = loadSound('marvel_soundtrack.mp3');
  dcSoundtrack = loadSound('dc_soundtrack.mp3');
}

// Setup function to initialize game elements
function setup() {
  createCanvas(500, 400); // Set canvas size
  paddle = new Paddle(); // Initialize paddle
  ball = new Ball(); // Initialize ball
  createBricks(); // Create brick layout
}

// Main draw loop to render the game frame by frame
function draw() {
  background(255); // Clear the canvas with a white background
  // Apply background image based on the current theme with opacity
  if (theme === 'default') {
    tint(255, 127); // Half opacity
    image(backgroundImage, 0, 0, width, height);
  } else if (theme === 'marvel') {
    tint(255, 127); // Half opacity
    image(marvelBackgroundImage, 0, 0, width, height);
  } else if (theme === 'dc') {
    tint(255, 127); // Half opacity
    image(dcBackgroundImage, 0, 0, width, height);
  }
  noTint(); // Reset tint effect for drawing other elements without opacity

  // Display the appropriate screen based on the game state
  if (gameState === 'home') {
    displayHomeScreen(); // Display home screen with theme options
  } else if (gameState === 'playing') {
    playGame(); // Main game logic
  } else if (gameState === 'gameOver') {
    displayGameOver(); // Display game over screen
  }
}

// Handler for mouse press events to interact with the game
function mousePressed() {
  if (gameState === 'home') {
    checkButtonPressed(); // Check if any theme button was pressed
  } else if (gameState === 'gameOver') {
    resetGame(); // Reset game to initial state
    gameState = 'home'; // Return to home screen
  } else {
    if (!gameStarted) {
      gameStarted = true; // Start the game
    }
  }
}

// Function to check if a theme button was pressed and change the game state accordingly
function checkButtonPressed() {
  const buttonWidth = 200;
  const buttonHeight = 50;
  const startY = height / 2 - 75;
  const gap = 60;

  // Detect button press based on mouse coordinates
  if (mouseX >= width / 2 - buttonWidth / 2 && mouseX <= width / 2 + buttonWidth / 2) {
    stopAllMusic(); // Stop any currently playing music
    // Check which button was pressed and update the theme and game state
    if (mouseY >= startY && mouseY <= startY + buttonHeight) {
      theme = 'default';
      gameState = 'playing';
      backgroundMusic.loop(); // Start playing default background music
    } else if (mouseY >= startY + gap && mouseY <= startY + gap + buttonHeight) {
      theme = 'marvel';
      gameState = 'playing';
      marvelSoundtrack.loop(); // Start playing Marvel soundtrack
    } else if (mouseY >= startY + 2 * gap && mouseY <= startY + 2 * gap + buttonHeight) {
      theme = 'dc';
      gameState = 'playing';
      dcSoundtrack.loop(); // Start playing DC soundtrack
    }
  }
}

// Function to display the home screen with game title and theme selection buttons
function displayHomeScreen() {
  fill('black');
  textSize(32);
  textAlign(CENTER, CENTER);
  textFont('Georgia');
  text("Musical Brick Breaker Game", width / 2, 100); // Game title
  
  // Display theme selection buttons
  textSize(20);
  const buttonWidth = 200;
  const buttonHeight = 50;
  const startY = height / 2 - 75;
  const gap = 60;

  // Default theme button
  stroke(0);
  strokeWeight(2);
  fill(255, 0, 0, 200); // Semi-transparent red
  rect(width / 2 - buttonWidth / 2, startY, buttonWidth, buttonHeight, 20); // Rounded corners
  fill(0); // Black text
  noStroke();
  text("Default", width / 2, startY + 28);

  // Marvel theme button
  fill(0, 0, 255, 200); // Semi-transparent blue
  stroke(0);
  strokeWeight(2);
  rect(width / 2 - buttonWidth / 2, startY + gap, buttonWidth, buttonHeight, 20); // Rounded corners
  fill(255); // White text
  noStroke();
  text("Marvel Universe", width / 2, startY + gap + 25);

  // DC theme button
  fill(255, 255, 0, 200); // Semi-transparent yellow
  stroke(0);
  strokeWeight(2);
  rect(width / 2 - buttonWidth / 2, startY + 2 * gap, buttonWidth, buttonHeight, 20); // Rounded corners
  fill(0); // Black text
  noStroke();
  text("DC Universe", width / 2, startY + 2 * gap + 28);
}

// Function to handle gameplay logic
function playGame() {
  ball.update();
  ball.checkCollision();
  ball.display();
  
  paddle.display();
  paddle.update();
  
  // Loop through and display all bricks, check for collisions
  for (let i = bricks.length - 1; i >= 0; i--) {
    bricks[i].display();
    if (ball.hits(bricks[i])) {
      ball.bounce();
      bricks.splice(i, 1); // Remove hit brick from array
      score += 10; // Increase score
    }
  }
  
  // Check if game is over (no bricks left)
  if (bricks.length === 0) {
    gameState = 'gameOver';
  }
  
  // Display current score
  fill('rgb(2,46,82)');
  textSize(20);
  textAlign(LEFT);
  text("Score: " + score, 20, 30);
}

// Function to display game over screen
function displayGameOver() {
  fill('rgb(24,21,21)');
  textSize(32);
  textAlign(CENTER, CENTER);
  if (score >= 150) {
   text("Congratss! Score: " + score, width / 2, height / 2+50);
   textSize(18);
   text("You mastered this universe, click to win others", width / 2, height / 2 + 90);
  }
  else{
  text("Game Over!! Score: " + score, width / 2, height / 2+50);
  textSize(20);
  text("Click to return Home", width / 2, height / 2 + 90);
  }
}

// Function to reset the game to initial state
function resetGame() {
  gameStarted = false;
  score = 0;
  ball.reset();
  createBricks(); // Re-initialize bricks
}

// Function to stop all music tracks
function stopAllMusic() {
  backgroundMusic.stop();
  marvelSoundtrack.stop();
  dcSoundtrack.stop();
}

// Function to initialize bricks based on row and column counts
function createBricks() {
  bricks = [];
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      let brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
      let brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
      bricks.push(new Brick(brickX, brickY));
    }
  }
}
// Class representing the ball object
class Ball {
  constructor() {
    this.radius = 10; // Radius of the ball
    this.reset(); // Reset ball position and speed
  }

  // Reset ball position and speed
  reset() {
    this.x = width / 2; // Initial x position at the center of the canvas
    this.y = paddle.y - 10; // Initial y position above the paddle
    this.speedX = 5; // Initial speed along the x-axis
    this.speedY = -5; // Initial speed along the y-axis
  }

  // Update ball position
  update() {
    this.x += this.speedX; // Update x position
    this.y += this.speedY; // Update y position
    // Bounce off the sides of the canvas
    if (this.x < this.radius || this.x > width - this.radius) {
      this.speedX *= -1; // Reverse speed along the x-axis
    }
    // Bounce off the top of the canvas
    if (this.y < this.radius) {
      this.speedY *= -1; // Reverse speed along the y-axis
    } 
    // Check if ball falls below the canvas
    else if (this.y > height - this.radius) {
      gameState = 'gameOver'; // Set game state to 'gameOver'
    }
  }

  // Check collision with the paddle
  checkCollision() {
    // Check if ball collides with paddle
    if (this.x > paddle.x && this.x < paddle.x + paddle.width && this.y + this.radius > paddle.y) {
      this.speedY *= -1; // Reverse speed along the y-axis
      paddleHitSound.play(); // Play paddle hit sound
    }
  }

  // Display the ball
  display() {
    fill(255, 0, 0); // Red fill color
    stroke(0); // Black thin outline
    strokeWeight(1); // Thin outline weight
    ellipse(this.x, this.y, this.radius * 2); // Draw ball as circle
  }

  // Bounce the ball (reverse speed along y-axis)
  bounce() {
    this.speedY *= -1; // Reverse speed along the y-axis
    ballHitSound.play(); // Play ball hit sound
  }

  // Check if ball hits a brick
  hits(brick) {
    // Calculate distance between ball center and brick center
    let distX = Math.abs(this.x - brick.x - brick.width / 2);
    let distY = Math.abs(this.y - brick.y - brick.height / 2);
    // Check if distance is less than combined radii of ball and brick
    if (distX > (brick.width / 2 + this.radius) || distY > (brick.height / 2 + this.radius)) {
      return false; // No collision
    }
    return true; // Collision detected
  }
}

// Class representing the paddle object
class Paddle {
  constructor() {
    this.width = 100; // Width of the paddle
    this.height = 20; // Height of the paddle
    this.x = (width - this.width) / 2; // Initial x position at the center of the canvas
    this.y = height - 35; // Initial y position near the bottom of the canvas
    this.speed = 10; // Speed of the paddle
  }

  // Display the paddle
  display() {
    fill(0, 0, 255); // Blue fill color
    stroke(0); // Black thin outline
    strokeWeight(1); // Thin outline weight
    rect(this.x, this.y, this.width, this.height); // Draw paddle as rectangle
  }

  // Update paddle position based on keyboard input
  update() {
    // Move paddle left
    if (keyIsDown(LEFT_ARROW) && this.x > 0) {
      this.x -= this.speed;
    } 
    // Move paddle right
    else if (keyIsDown(RIGHT_ARROW) && this.x < width - this.width) {
      this.x += this.speed;
    }
  }
}

// Class representing a brick object
class Brick {
  constructor(x, y) {
    this.x = x; // x position of the brick
    this.y = y; // y position of the brick
    this.width = brickWidth; // Width of the brick
    this.height = brickHeight; // Height of the brick
  }

  // Display the brick
  display() {
    fill(200, 50, 50); // Red fill color
    stroke(0); // Black thin outline
    strokeWeight(1); // Thin outline weight
    rect(this.x, this.y, this.width, this.height); // Draw brick as rectangle
  }
}


 


Reflection: Ideas added to this Final Midterm

1) User Experience: I’ve incorporated different themes so that the user won’t be limited to play in one environment. In addition, all the three themes have their separate backgrounds and soundtracks.

2) End Card Message:  I’ve implemented a conditional statement to display “Congrats” message if the user wins and “Game Over” message if the player losses along with the displaying scores on a side.

3) Immersive Audio Design: To ensure that the soundtrack and the paddle hit sound doesn’t overlap, I first reduced the volume (decibels) of the soundtrack using Audacity software before importing it into P5.js.

4) Areas I can Improve Further: Firstly, I aim to introduce difficulty levels to enhance the game’s complexity. Currently, players have three themes to choose from. By adding difficulty levels, players would face nine different variations (3 themes x 3 levels), offering a certain complex gaming experience.

Secondly, I plan to implement full-screen gameplay. However, attempting to switch to full-screen mode has caused disruptions in the game’s initial phase. It has affected the ball’s movement, needed adjustments in the number of bricks, required an increase in paddle size, and led to a decline in background quality. Additionally, P5.js has been slow in loading and executing the code, posing further challenges.

Conclusion: I’m very much satisfied with the output of my game, I never imagined I can implement complex algorithms and great features of P5.js programming: OOPS and Functions to recreate my favorite childhood game. For further learning experience, I’ll try to incorporate the changes I mentioned earlier.

Reference: Used Tools/ Software
1) Dall-E for background images : https://openai.com/dall-e-3
2) Audacity Software for sound editing. https://www.audacityteam.org
3) Coding Garder (Youtube Channel): https://www.youtube.com/watch?v=3GLirU3SkDM

Midterm Project – Space

This project combines the thrill of techno music with the interactivity of hand gestures, providing players with a unique gaming experience. Using a webcam, players can control the spaceship’s movement with simple hand gestures, making the game both engaging and intuitive.

The game environment features a spaceship navigating through a field of objects. The objective is to avoid colliding with the objects while collecting points. The game utilizes the hand pose machine learning model to detect hand gestures, allowing players to control the spaceship’s movement by opening or closing their hand. Additionally, players can make a fist to pause or resume the game, adding an extra layer of interaction.

One aspect of the project that I’m particularly proud of is the seamless integration of hand gestures for controlling the spaceship. By leveraging the hand pose model provided by the ml5.js library, I was able to accurately detect and interpret hand movements in real time, providing players with responsive and intuitive controls. Additionally, the dynamic gameplay, with objects spawning randomly and increasing in speed over time, keeps players engaged and challenged throughout the game.

function detectHands() {
  if (predictions.length > 0) {
    const landmarks = predictions[0].landmarks;
    // highlightHand(landmarks, 'green'); // Optional: Uncomment to see hand landmarks

    if (isClosedFist(landmarks)) {
      let currentTime = millis();
      if (currentTime - fistDetectedTime > fistToggleDelay) {
        isGamePaused = !isGamePaused;
        console.log(isGamePaused ? "Game Paused" : "Game Resumed");
        fistDetectedTime = currentTime;
      }
    } else if (isOpenHand(landmarks)) {
      let averageX = landmarks.reduce((acc, val) => acc + val[0], 0) / landmarks.length;
      if (averageX < width / 2) {
        spaceship.setDir(-1);
      } else {
        spaceship.setDir(1);
      }
    } else {
      spaceship.setDir(0);
    }
  }
}

// Check if the hand is open 
function isOpenHand(landmarks) {
  let minDist = Infinity;
  for (let i = 4; i <= 20; i += 4) {
    for (let j = i + 4; j <= 20; j += 4) {
      let dist = distanceBetweenPoints(landmarks[i], landmarks[j]);
      if (dist < minDist) {
        minDist = dist;
      }
    }
  }
  return minDist > 50;
}

Also, another key element that enhances the immersive experience of the Gesture-Controlled Game is the synchronization of the music with the background elements. By integrating dynamic sound effects and music, the game creates a cohesive audio-visual experience that engages players on multiple sensory levels.

function drawGameElements() {
    let level = amplitude.getLevel();
    let size = map(level, 0, 1, 5, 20); //size for more impact

    // Cycle through HSB colors 
    colorMode(HSB, 360, 100, 100, 100);
    colorPhase = (colorPhase + 1) % 360;

    for (let i = 0; i < 400; i++) {
        let x = noise(i * 0.1, frameCount * 0.01) * width;
        let y = noise((i + 100) * 0.1, frameCount * 0.01 + level * 5) * height;

        // Dynamic stroke color
        let hue = (colorPhase + i * 2) % 360;
        let alpha = map(level, 0, 1, 50, 100); //alpha based on volume for dynamic visibility

        // Simulated glow effect
        for (let glowSize = size; glowSize > 0; glowSize -= 4) {
            let glowAlpha = map(glowSize, size, 0, 0, alpha); 
            stroke(hue, 80, 100, glowAlpha);
            strokeWeight(glowSize);
            point(x, y);
        }
    }

    colorMode(RGB, 255);
    spaceship.show();
    spaceship.move();
    handleParticles();
}

Improvements:

Optimization and Performance: Consider optimizing the game’s performance, especially when dealing with graphics and rendering. This includes minimizing unnecessary calculations and rendering, especially within loops like drawGameElements().

Game Mechanics Refinement: Assess and refine the game mechanics for a better gameplay experience. This could involve adjusting spaceship movement controls, particle spawning rates, or particle effects to enhance engagement and challenge.

Midterm Project | Space Adventure | Aadil

Concept

I had initially intended to make an automated generative kaleidoscope but there were lots of issues with it and the sketch would lag a lot because of the large amount of random numbers generated. So, I decided to make a fun 2-Player game .

I used to play this game on my phone called 2 Player Games that had a variety of games in it that could be played by 2 players. One of them was Star Catcher, which is what inspired me to make this project.

Star Catcher is a game where you have two spaceships each controlled by a player and stars randomly appear on the screen . The player who collects a required number of stars first wins the game . There are asteroids that the spaceships can collide with and also collision between the spaceships of the players. I could not find an online version of this game for PC so I thought that it would be great if I could build something like this .

I had do something’s differently for the keyboard control and Fullscreen options as compared to the mobile version which just relies on touch . I came up with the following rules and specifications for the game :

  1. There has to be a start scene ,  a game scene and a final winning scene
  2. The game starts after spacebar is pressed from the start scene, the first spaceship is controlled by WASD and the second spaceship is controlled by arrow keys. The movement must be smooth and there must be acceleration and deceleration rather than just velocity. The spaceships must rotate and be capable of moving in all directions.
  3. The goal is to collect 10 stars before the opponent.
  4. There are asteroids generated randomly that can collide with the player spaceship to make them bounce
  5. There is sound interaction for collisions, start screen, star collection and winning scene.

Sketch

Please run the sketch in p5 (it does not enter into Fullscreen mode properly in WordPress) . The Project contains 3 classes in addition to the sketch.js files . If using a browser that has esc as default key for exiting Fullscreen , make sure to use ‘e’ instead of esc to exit fullscreen.

Link to the sketch –p5.js Web Editor | IMMidtermProjectSpaceDodge (p5js.org)

How the Project works/ some Implementation details

The Project has 3 classes :

  1. Player Class:
    • Represents the players in the game.
    • Handles player movement based on keyboard input.
    • Updates player position, velocity, and angle.
    • Detects collisions with other players and asteroids.
  2. Asteroid Class:
    • Represents the asteroids in the game.
    • Updates asteroid position and rotation.
    • Displays asteroids on the canvas.
  3. Star Class:
    • Represents the stars that players can collect for points.
    • Updates star position and detects collisions with players.
    • Displays stars on the canvas.

The movement is smoothened out by adding acceleration and friction which are two variables defined in the beginning of the code .

I also have the following helper functions :

Helper Functions:

  • Functions for handling asteroid generation, resetting the game state, toggling Fullscreen mode, and resetting sound flags.
      1. handleAsteroidGeneration(): Generates a new set of asteroids based on the current game mode (Fullscreen or windowed) with random positions, speeds, sizes, and rotation speeds.
      2. handlePlayerAsteroidCollision(player): Checks for collisions between a player and asteroids and adjusts player velocity accordingly upon collision.
      3. handleCollision(): Detects and handles collisions between players in the game.
      4. resetGame(): Resets game variables and objects to their initial state for starting a new game.
      5. toggleFullscreen(): Toggles the game between Fullscreen and windowed mode.
      6. exitFullscreen(): Exits Fullscreen mode and adjusts game elements accordingly.
      7. resetSoundFlags(): Resets flags related to sound effects to their initial state.
      8. drawStartMenu(): Draws the start menu interface on the start canvas.
      9. drawEndScreen():
        • Draws the end screen interface when the game is over.

and an event handler:

  • key Pressed: Handles key presses for starting the game, toggling Fullscreen, and exiting Fullscreen.
Assets Used

Assets used for making the game are :

Images:

  1. Asteroid image: ‘Images/asteroid.png’
  2. Star image: ‘Images/star2.png’
  3. Background image: ‘Images/Space_background.jpg’

Audio:

(All audio used was found from Pixabay and trimmed to fit the game sound needed)

  1. Start menu background music: ‘AudioFiles/start_menu.mp3’
  2. Winner music: ‘AudioFiles/win_sound.mp3’
  3. Collision spacecraft sound: ‘AudioFiles/bump.mp3’
  4. Collect star sound: ‘AudioFiles/star_collect.mp3’

Font:

    1. Font file: ‘Roboto-Regular.ttf’

Key Challenges Faced 

  1. The movement implementation was quite challenging , I had to make sure to implement acceleration , deceleration and collision and make sure it looks realistic . I experimented with values to determine what looks good for the canvas size and full screen.
  2. Sounds- I faced difficulty implementing audio and spent a lot of time making sure that audio did not overlap. At the end, I decided to use Boolean flags to check whether an audio is being played or not.
  3. Switch to Fullscreen- I had some issues switching back to full screen, of course when going in full screen you need to change the speed of the spaceships and the number of asteroids to make the game fun.

 

Code that I am Proud Of

Implementing the movement mechanism was a bit difficult , as it had to be smooth and the spaceship had to change direction , this is implemented in the player class as follows:

  class Player{
//..........
update() {
    // Apply friction to the acceleration
    this.acceleration.mult(1 - friction);

    // Apply friction to the velocity
    this.velocity.mult(1 - friction);

    // Update velocity based on acceleration
    this.velocity.add(this.acceleration);

    // Limit the velocity to the maximum speed
    this.velocity.limit(maxSpeed);

    // Check if maximum speed is reached
    this.maxSpeedReached = this.velocity.mag() >= maxSpeed;

    // Update position based on velocity
    this.x += this.velocity.x;
    this.y += this.velocity.y;

    // Wrap around the canvas boundaries
    if (this.x < 0) {
      this.x = width;
    } else if (this.x > width) {
      this.x = 0;
    }
    if (this.y < 0) {
      this.y = height;
    } else if (this.y > height) {
      this.y = 0;
    }

    // Calculate angle
    if (this.velocity.x !== 0 || this.velocity.y !== 0) {
      this.angle = PI / 2 - atan2(-this.velocity.y, this.velocity.x);
    }
/* ........*/
}

This code brings acceleration and friction to the player movement, the calculate angle part changes the orientation of the spaceship with respect to its velocity vectors.

The collision mechanism was implemented in the two functions below :

 function handleCollision() {
  // Check for collision between players
   // Check for collision between players
    if (player1.collides(player2) && !player1Collision && !player2Collision){
        // Set collision state for both players
        player1Collision = true;
        player2Collision = true;

        // Record the time of collision
        lastCollisionTimePlayer1 = millis();
        lastCollisionTimePlayer2 = millis();}
  if (player1.collides(player2)) {
    // Calculate direction vector between the centers of the players
    let collisionVector = createVector(player2.x - player1.x, player2.y - player1.y).normalize(); //gives unit vector in direction of centeres and stores it in collision vector

    // Calculate the magnitude of the velocity components along the collision vector
    let velocity1AlongCollision = player1.velocity.dot(collisionVector);
    let velocity2AlongCollision = player2.velocity.dot(collisionVector);
    
    // Calculate the new velocities after the collision ,swaps the velocities
    let newVelocity1AlongCollision = velocity2AlongCollision;
    let newVelocity2AlongCollision = velocity1AlongCollision;


    // Update velocities with the new components
    let newVelocity1 = p5.Vector.add(player1.velocity, p5.Vector.mult(collisionVector, newVelocity1AlongCollision - velocity1AlongCollision));
    let newVelocity2 = p5.Vector.add(player2.velocity, p5.Vector.mult(collisionVector, newVelocity2AlongCollision - velocity2AlongCollision));

    player1.velocity.set(newVelocity1.x, newVelocity1.y);
    player2.velocity.set(newVelocity2.x, newVelocity2.y);

    // Play collision sound when spacecraft collides with another spacecraft
    if (!collisionSpacecraftSoundIsPlaying) {
      collisionSpacecraftSound.play();
      collisionSpacecraftSoundIsPlaying = true;
    }
  } else {
    // Reset collision sound flag when no collision occurs
    collisionSpacecraftSoundIsPlaying = false;
  }
}




function handlePlayerAsteroidCollision(player) {
  // Determine which player is being checked
  let isPlayer1 = player === player1;

  // Check if the player is in a collision state
  let playerCollision = isPlayer1 ? player1Collision : player2Collision;
  let lastCollisionTime = isPlayer1 ? lastCollisionTimePlayer1 : lastCollisionTimePlayer2;

  if (!playerCollision) {
    for (let asteroid of asteroids) {
      if (player.collides(asteroid)) {
        // Calculate collision angle
        let angle = atan2(player.y - asteroid.y, player.x - asteroid.x);

        // Calculate the relative velocity of the player with respect to the asteroid
        let relativeVelocity = p5.Vector.sub(player.velocity, createVector(asteroid.speed * cos(angle), asteroid.speed * sin(angle)));

        // Reflect player's velocity based on collision angle and asteroid's speed
        let velocityMagnitude = relativeVelocity.mag();
        let reflectionAngle = atan2(-relativeVelocity.y, -relativeVelocity.x);
        let newVelocity = p5.Vector.fromAngle(reflectionAngle);
        newVelocity.setMag(velocityMagnitude);

        player.velocity.set(newVelocity);

        // Set collision state for the player
        if (isPlayer1) {
          player1Collision = true;
          lastCollisionTimePlayer1 = millis();
        } else {
          player2Collision = true;
          lastCollisionTimePlayer2 = millis();
        }

        // Play collision sound when spacecraft collides with a rock
        if (!collisionSpacecraftSoundIsPlaying) {
          collisionSpacecraftSound.play();
          collisionSpacecraftSoundIsPlaying = true;
        }
      }
    }
  }
}

 

Another piece of code that is interesting is to solve the challenge I faced to change the asteroid count and speed of asteroids when the screen is made Fullscreen. This is implemented in the function below:

function handleAsteroidGeneration() {
  asteroids = []; // Clear existing asteroids
  let count = isFullscreen ? fullscreenAsteroidCount : defaultAsteroidCount;
  let speedMin = isFullscreen ? fullscreenAsteroidSpeedMin : defaultAsteroidSpeedMin;
  let speedMax = isFullscreen ? fullscreenAsteroidSpeedMax : defaultAsteroidSpeedMax;
  
  // Generate new asteroids based on the mode (fullscreen/windowed)
  for (let i = 0; i < count; i++) {
    let x = random(windowWidth); // Random x position
    let y = random(windowHeight); // Random y position
    let speed = random(speedMin, speedMax); // Random speed within the specified range
    let angle = random(TWO_PI); // Random initial rotation angle
    let rotationSpeed = random(-0.05, 0.05); // Random rotation speed
    let size = random(60,120); // Random size
    asteroids.push(new Asteroid(x, y, size, speed, angle, rotationSpeed));
  }
}

I have also implemented a similar switch for acceleration as indicated in the variable declarations that is modified in the toggle and exit Fullscreen functions.

let accelerationFullscreen = 0.7; // Acceleration value for fullscreen mode
let accelerationWindowed = 0.3; // Acceleration value for windowed mode
let acceleration = isFullscreen ? accelerationFullscreen : accelerationWindowed; // Set initial acceleration value based on current mode 
function toggleFullscreen() {
  fullscreen(true); // Switch to fullscreen
  isFullscreen = true;
  handleAsteroidGeneration(); // Handle asteroid generation for fullscreen
  acceleration = accelerationFullscreen; // Set acceleration for fullscreen mode
}

function exitFullscreen() {
  fullscreen(false); // Exit fullscreen
  isFullscreen = false;
  handleAsteroidGeneration(); // Handle asteroid generation for windowed mode
  resizeCanvas(600, 600); // Resize canvas back to original size
  acceleration = accelerationWindowed; // Set acceleration for fullscreen mode
}

 

This changes the speed as well as number of asteroids spawned according to the screen mode so that the user encounters roughly the same amount of challenge in both cases. Since these variables have been defined at top, they can be changed anytime .

Reflection

Overall, I am pretty happy with the final game I created. I learnt a lot of new things in p5 and realized how the organization of the code into classes representing various types of objects made it much easier to understand and develop. There is some scope for improvement that could be done in the future such as :

  1. Adding more Players to the game (The player class is already implemented).
  2. Adding more sounds to the game.
  3. Adding extra fun objects and classes such as a ‘black hole’ that pulls the spaceship towards it for some time and or a ‘super star’ that gives more points than usual but stays only for a certain duration of time.
  4. Changing the sprite of the spaceship? Right now, it’s just simple triangles but the code would also work for better sprites.
  5. Experimenting with different trails. I have a simple spacecraft booster with one large and two small ellipses but its possible to make more sophisticated ones and attach it to player class.

Crafting “Garden Guardian”: The Journey of My Midterm Project

When coming up with the idea for “Garden Guardian,” I wanted to make a charming, aesthetic game that had a challenging twist. A basic garden planting game didn’t seem very exciting on its own. I needed to add something to it.

However, this wasn’t always the case. In the very beginning, this project was pretty basic and boring. My first draft was just a game where you could plant flowers in a field, and that’s all it did. I didn’t focus much on how it looked during this early phase. It was pretty simple and not very attractive. This is what it looked like at first.

With the core functionality in place, I could now turn my attention to the aesthetics. To enhance the visuals, I utilized a combination of images generated by Dall-E and icons sourced from Google images. This allowed me to give the project a more polished and appealing look while retaining the foundational code I had already developed.

The game was pretty, but I wasn’t satisfied. That’s when I decided to throw in some pests causing trouble for the player’s garden. These pest invasions make the simple act of growing flowers into more of a defensive mission. Cultivating a colourful garden is still the main goal, but now you have to protect it as well.

Imagine this: you start with a blank canvas, ready to transform it into a breathtaking field of blooms. With a simple click, you can choose between flowers to add splashes of colour. 

But just when you think you’ve mastered the game, the real fun begins! Pesky pests appear out of nowhere, trying their best to kill your floral babies. That’s when your skills as a true Garden Guardian will be put to the test.

With this project, I really wanted to challenge my coding skills. One of the first challenges I faced was designing a game state management system. I wanted smooth transitions between the introduction screen, gameplay, and instructions. Through trial and error, I eventually settled on a streamlined approach, with the draw() function acting as the game’s heartbeat, constantly updating and rendering all the visuals used in the game (icons, buttons and backgrounds) based on the current state.

The drawGame() function became the centrepiece of my code, responsible for orchestrating the entire garden experience. I spent SO MANY hours refining this function, ensuring that the rendering of the garden background, the placement of icons, and the display of planted flowers all worked seamlessly together. I’m particularly proud of the highlighting technique I implemented, which draws attention to the currently selected icon, enhancing the overall user experience.

// Highlight the selected icon
noFill();
stroke(255, 204, 0); 
strokeWeight(2); 
let selectedIconIndex = selectedFlowerType - 1;
if (selectedIconIndex >= 0 && selectedIconIndex < iconPositions.length) {
  let pos = iconPositions[selectedIconIndex];
  rect(pos.x, pos.y, 50, 50, 10);

One of the most rewarding aspects of this project was creating the Flower class. Building a system to manage the lifecycle of each flower, from planting to potential infestation and treatment, was a true test of my object-oriented programming skills. The introducePest() method, which simulates the arrival of a pest and sets a timer for the flower’s demise if left untreated, was a particularly satisfying challenge to overcome. This took way too much time but as my mother would say, it felt like eating a ladoo (a delicious Indian dessert) when I could finally get it to work!

class Flower {
  constructor(x, y, type) {
    this.x = x;
    this.y = y;
    this.type = type;
    this.size = 50;
    this.hasPest = false;
    this.pestTimer = null;
  }

  display() {
    let img = [flowerImg1, flowerImg2, flowerImg3][this.type - 1];
    image(img, this.x - this.size / 2, this.y - this.size / 2, this.size, this.size);
    if (this.hasPest) {
      image(pestImg, this.x - this.size / 2, this.y - this.size / 2, this.size, this.size);
    }
  }

  introducePest() {
    if (!this.hasPest) {
      this.hasPest = true;
      this.pestTimer = setTimeout(() => this.die(), map(targetFlowers.planted, 0, targetFlowers.total, 4000, 3000));
    }
  }

  treatWithPesticide() {
    if (this.hasPest) {
      clearTimeout(this.pestTimer);
      this.hasPest = false;
      gameTime += 3;
    }
  }

  die() {
    let index = flowers.indexOf(this);
    if (index !== -1) {
      flowers.splice(index, 1);
      targetFlowers.planted = max(0, targetFlowers.planted - 1);
    }
  }
}

The Flower class encapsulates all the properties and behaviours of each flower in the garden. From managing the flower’s position, type, and size to handling pest infestations and treatment, this class is the backbone of the game’s core mechanics.

The introducePest() method is a prime example of the thought process behind crafting engaging gameplay. When a pest is introduced, a timer is set to simulate the potential demise of the flower if left untreated. The duration of this timer is dynamically adjusted based on the number of flowers already planted, increasing the difficulty as the game progresses. I had to do a lot of research (and some help from ChatGPT) to get this section working.  

Conversely, the treatWithPesticide() method allows players to counter the pest threat by using the pesticide icon. When a flower is treated, the pest timer is cleared, the hasPest flag is reset, and the player is rewarded with a few extra seconds on the game timer, encouraging strategic decision-making.

The die() method handles the removal of a flower from the game when it succumbs to a pest infestation. By splicing the flower from the flowers array and adjusting the targetFlowers.planted count, the game state is seamlessly updated, reflecting the player’s progress towards the target.

Throughout the development process, I encountered numerous roadblocks and debugging nightmares. However, each obstacle was an opportunity to learn and grow. I quickly realised the importance of modular code, which led me to create separate functions and classes for specific tasks, improving the overall readability and maintainability of my code.

Looking back on this journey, I’m filled with a sense of accomplishment and gratitude. “Garden Guardian” not only allowed me to create an entertaining game but also served as a valuable learning experience. I gained a deeper understanding of game mechanics, object-oriented programming, and the intricacies of creative coding with p5.js. Most importantly, I discovered the joy of problem-solving and the satisfaction of seeing my code come to life in the form of an engaging interactive experience.

However, there are still so many areas where I can improve and expand “Garden Guardian”: The scoring system needs work. Right now, you just win by planting enough flowers before time runs out. But I want a better system that scores you based on things like how many pests you treated, the variety of flowers planted, and maybe even keeping your garden completely pest-free. 

The difficulty progression could be better too. I think having the difficulty adapt based on the player’s performance would make it more engaging. If someone is struggling, the game could spawn fewer pests or give more time.

Visually, while the current look is charming, adding more detailed graphics, animations and effects could really enhance the overall aesthetic appeal. And new gameplay elements like power-ups, special abilities or different game modes could add lots of replay value.

During development, I ran into some tough challenges. Managing all the different timers and making sure the countdown was accurate when players gained extra time was really tricky. Weird edge cases, like planting flowers outside the garden area still need some work. 

Working through the issues I faced was a huge learning experience for me. It really emphasised the importance of thorough testing, keeping my code organised, and anticipating potential problems. Moving forward, I’ll apply those lessons to make my games even more polished.

“Garden Guardian” may be a small project, but it represents a big milestone in my coding journey. I had so much fun tackling challenges and adding and improving features and I cannot wait to experiment with game dev more!

Midterm Project

Concept:

My midterm project sheds light on the issue of space debris and how there is a lot of space junk resulting from the satellites and other objects in space that are no longer functioning. It is a very important issue as we need to take care of outer space and keep it sustainable as we are using it for so many different aspects such as GPS, weather forecasting, and other important technologies.

How to play:

To play this game, you can control the spaceship with your head movement to shift it either up or down and catch as many space debris as possible. Make sure not to hit any dwarf planets coming your way.

https://editor.p5js.org/alreem/full/aXQa2BjsH

Challenges & solutions:

My biggest struggle for this was having to mirror the createCapture, which eventually worked but I ended up removing it as it affected other parts of the code and would lag very much if it is played as multiplayer. I fixed this problem by removing the control of the x-axis (right or left movement) and keeping the direction moving up and down only. Even if I removed that aspect, it still seems as if the user is moving sideways due to the moving background.

for(let i=0; i<people.length; i++){
    let pose = people[i].pose;
    
    let nose = pose.nose;
  
    image (imgss, width/2,nose.y, 30, 30); 
    
    
    if(debris.contains(nose.x, nose.y)) {
      debris.pop();
      score++; }
    if(planet.contains(nose.x, nose.y)) {
      gameState = 'end';
    }
  }

Overall logic:

For the game itself, I mainly used OOP to control the planet and the debris. The parameters included the size, speed, x and y position, collision detection, and moving the object. For the poseNet, I used it to detect the person’s nose and move the spaceship accordingly.

Improvements:

For future improvements, I would want to let the user move in all four directions and have it as a multiplayer game where more people can join. I would also want the game to keep moving faster as time goes by.

References:

Music: https://www.youtube.com/watch?v=AzL10r8AeQ8

Photos: Google search

Intro page & Instructions:

Dwarf planet (what you have to avoid):

Space debris (what you have to collect):

Final Page (where your score is shown):

Aerial view of Earth surrounded by space junk from space ships and satellites; 3D; 3D Illustration

Code help: mainly from https://p5js.org/reference/

Midterm Project- Catch the Fish 。˚ 。˚

Link to sketch: https://editor.p5js.org/Hazpaz/full/H-g4lq8jJ

Concept and inspiration

Amazon.com: Haktoys Fishing Game Play Set Includes 21 Fish and 4 Fishing Poles on Rotating Board with Music On/Off Switch for Quiet Play | Board Game for 1-4 Players : CDs & Vinyl

For this project, I’ve drawn inspiration from a childhood game my brother used to play, called the “Fishing game.” It involved catching fish with a fishing hook within a time limit. I’ve adapted this concept into my own version of the game, adding some twists and modifications. Instead of a traditional fishing game with a box of fish, my game features fish scattered randomly across the canvas. The objective remains the same: capture each fish individually using a fishing hook (the mouse) and deposit them into a fish bowl before time runs out. This adaptation allows for a unique gameplay experience while still capturing the essence of the original game.

How it works

1. Starting:

– When you first open the game, you’ll see a logo on the screen.

– To start playing, you just need to click anywhere on the logo.

2. Instructions Screen:

– After clicking the logo, you’ll see some instructions on how to play the game.

– These instructions include how to choose the number of fish in the game and how to start playing.

3. Gameplay:

– Once you’re ready, the game starts.

– You’ll see a background with a fishbowl and some fish swimming around.

– Your goal is to catch all the fish and put them in the fishbowl before time runs out.

4. Catching Fish:

– To catch a fish, you just need to move your mouse cursor close to it.

– When you’re close enough, the fish will be caught on your fishing hook.

5. Ending the Game:

– The game ends when either you catch all the fish and put them in the fishbowl or the timer runs out.

– If you catch all the fish in time, you win!

– If time runs out before you catch all the fish, you lose.

6. Restarting the Game:

– If you want to play again, you can press the Enter key to restart the game.

– If you want to go to the home page (instructions page) click on any key.

7. Music:

-Throughout the game, there’s background music to enhance the gaming experience.

-There’s different music for when you win or lose, adding to the atmosphere of the game.

8. Fullscreen Mode:

– You can also toggle fullscreen mode by pressing the ‘f’ key.

9. Game Elements:

-The game includes various elements such as shapes (the ellipses behind the slider), images (fish, fishing hook, fish bowl, backgrounds), text, and sound to create an immersive experience.

– The game is also developed using Object Oriented Programming, with classes called “Fish.js” and “Timer.js”.

Highlights of some code that I’m particularly proud of

The playGame() function is responsible for handling the main gameplay dynamics such as the timer, fishbowl, fishing hook and music. I’m proud of how I tackled most of the logics of the game in this function. Here is the code snippet of the function.

// Play the game function
function playGame() {
  
  image(gameBg, 0, 0, windowWidth, windowHeight); // The background of the game 
  timer.update(); // Update timer  
  
  numFishiesSlider.hide(); //hides the slider
  
  // Draw time left at the top center of the screen
  textSize(50);
  textAlign(CENTER, CENTER);
  fill(0);
  text("Time left: " + timer.getTime() + " seconds", windowWidth / 2, 100);
  
  // Draw fish bowl
  image(fishBowl, windowWidth / 2 - 50, windowHeight / 2 - 50, 200, 200);
  
  // Draw fishing hook
  image(fishingHook, mouseX - 25, mouseY - 25, 70, 70);
  
  // Display and update fishies
  for (let fish of fishies) { // Checks each element of the "fishies" array
    
    fish.display();
    
    // Hooking the fishies to the hook
    if (dist(mouseX, mouseY, fish.x, fish.y) < 25) {
      
      fish.hooked = true;
      
    }
    
    if (fish.hooked) {
      
      fish.move(mouseX, mouseY); // The fish is hooked to the hook
      
    }
  }
  

  // Check if all fishies are inside fish bowl
  let allFishiesInside = true;
  for (let fish of fishies) {
    
    if (!fish.insideBowl) {
      
      allFishiesInside = false;
      break;
      
    }
  }
  
  
  if (allFishiesInside) {
    
    gameFinished = true;
    timer.stop();
    
    //play music
    if (timer.timeLeft > 0) {
      
      victoryMusic.play(); // Play victory music if game finished before countdown ends
      gameMusic.stop(); // Stop background music
      
    } 
    
    else {
      
      losingMusic.play(); // Play losing music if countdown ends before game finished
      gameMusic.stop(); // Stop background music
      
    }
    
    gameState = 'end';    // Change to end game state

  }

  // Check if time is up
  if (timer.getTime() === 0) {
    gameFinished = false; // Player loses if time is up
    gameState = 'end';
    losingMusic.play(); // Play losing music
    gameMusic.stop(); // Stop background music
  }
}

 

The continuation of the playGame() function is the displayEndscreen() function which displays the end screen on the basis of winning or loosing the game. At first, I had trouble understanding how this code worked, but eventually, I figured it out and got it to function properly.

//function for the endscreen
function displayEndScreen() {
  
  fill(255);
  textSize(50);
  textAlign(CENTER, CENTER);
  
  if (gameFinished == false) {
    
    //displaying that the player lost the game
    fill('#F44336');
    background(gameOver)
    // text("Game Over!", windowWidth / 2, windowHeight / 2);
    text("Time left: " + timer.getTime() + " seconds", windowWidth / 2, windowHeight / 2 + 280 + 60);
    
  } 
  
  else {
    
    //displaying that the player won the game
    fill('rgb(16,96,16)');
    background(win)
    // text("You WIN!", windowWidth / 2, windowHeight / 2);
    
  }
  
  // instructing the player on how to restart the game
  textSize(50);
  fill('#710404');
  text("Click Enter key to restart", windowWidth / 2, windowHeight / 2 + 570);
  text("Click any key to go to Home page", windowWidth / 2, windowHeight / 2 + 620);

  
  
}

Challenges with the project

1. Implementing the functionality to drag and drop each fish individually to the fish bowl has posed a significant challenge.

2. Providing users with the ability to select their preferred difficulty level by choosing the number of fishes in the game: I initially attempted to let users input the number of fish they wanted using a text box, but it didn’t work as expected. So, I switched to using a slider instead. Although it was challenging to code initially, I refined the logic and eventually got it to work.

3. Difficulty in adjusting the timer according to the number of fish that the player inputed.

4. Making the whole experience of the game in full screen posed a significant challenge for me, forcing me to change some design choices for the game.

Future Improvements

1. Allow players to customize their fishing hook or fishbowl with different designs or colors.

2. High scores boards for each levels.

3. Enable users to drag the fishes individually to the fish bowl for a more interactive experience.

4- Make the game a bit more responsive to the screen size.

5. Add more colors and deigns to the overall design of the game to make it more aesthetic.

 

Luke Nguyen – Midterm

 

 

Overall concept:

When I was a kid, I was obsessed with the game Chicken Invaders. I remembered spending 2-3 hours playing it every time I was free from school and homework. It was a way of life for my PC back then. And so, I wanted to emulate the engine of that game for this mid-term project and have fun creating it. I call my interactive game “Alien Slayer.”This is my initial sketch/design for the game. I ended up not following up in terms of arranging the aliens. This is because I approached designing the game using arrays and randomness. And creating lines of aliens did not feel realistic at all.

 

How the project works:

Here are the images of the game that use:

I found a sprite sheet for the spaceship and edited it down into just 4 basic movement directions.

The rules of the game are very intuitive: to kill all the aliens. That said, the number of aliens generated and the speeds at which they ascend down the screen are randomized using this:

if (random(1) < 0.008) {
    aliens.push(
      new alien(
        width / 10,
        width / 10,
        random(width - 40),
        random(-10, 0),
        random(1, 3)
      )
    );
  }

So the difficulty of the game is randomized throughout the game.

The spaceship is first created and placed at the starting position using the class:

class spaceship {
  constructor(w, h, x, y, direction, speed) {
    this.w = width/10;
    this.h = height/10;
    this.x = width/2;
    this.y = height - 60;
    this.direction = 0;
    this.speed = 5;
  }

  move() {
    if (keyIsPressed) {
      if (keyCode == UP_ARROW) {
        if (0 < this.y) {
          this.direction = 0;
          this.y -= this.speed;
        }
      }

      if (keyCode == DOWN_ARROW) {
        if (this.y < height - this.h) {
          this.direction = 1;
          this.y += this.speed;
        }
      }

      if (keyCode == LEFT_ARROW) {
        if (0 < this.x) {
          this.direction = 2;
          this.x -= this.speed;
        }
      }

      if (keyCode == RIGHT_ARROW) {
        if (this.x < width - this.w) {
          this.direction = 3;
          this.x += this.speed;
        }
      }
    }
  }

  display() {
    if (this.direction == 0) {
      image(spaceshipUP, this.x, this.y, this.w, this.h);
    }

    if (this.direction == 1) {
      image(spaceshipDOWN, this.x, this.y, this.w, this.h);
    }

    if (this.direction == 2) {
      image(spaceshipLEFT, this.x, this.y, this.w, this.h);
    }

    if (this.direction == 3) {
      image(spaceshipRIGHT, this.x, this.y, this.w, this.h);
    }
  }
  
  setW(w){
    this.w=w;
  }
  
  setH(h){
    this.h=h;
  }
  
  setX(x){
    this.x=x;
  }
  setY(y){
    this.y=y;}
  
  setDir(direction){
    this.direction = direction;
  }
}

The laser beams are also created using class:

class laser {
  constructor(w, h, x, y, direction) {
    this.w = w;
    this.h = h;
    this.x = x;
    this.y = y;
    this.direction = direction;
    this.speed = 5;
  }

  display() {
    if (this.direction == 0) {
      image(laservertical, this.x, this.y, this.w, this.h);
    }

    if (this.direction == 1) {
      image(laservertical, this.x, this.y, this.w, this.h);
    }
  }

  move() {
    if (this.direction == 0) {
      this.y -= this.speed;
    }
    if (this.direction == 1) {
      this.y += this.speed;
    }
  }
}

And the aliens are also PRE-created using class:

class alien {
  constructor(w, h, x, y, speed) {
    this.w = w;
    this.h = h;
    this.x = x;
    this.y = y;
    this.speed = random(1, 3);
  }

  move() {
    this.y += this.speed;
  }

  display() {
    image(alien_image, this.x, this.y, this.w, this.h);
  }

  hits(obj) {
    return (
      obj && 
      obj.x > (this.x - obj.w) + 10 &&
      obj.x < (this.x + this.w) - 10 &&
      obj.y > (this.y - obj.h) + 12 &&
      obj.y < (this.y + this.h) - 15 
    );
  }
}

When the user presses the space bar, a laser beam is released. The user can press the space bar multiple times. The number of laser beams released correspond to the number of times the space bar is hit.

//   Display laser beams
  for (let i = lasers.length - 1; i >= 0; i--) {
    drawingContext.shadowBlur = 10;
    drawingContext.shadowColor = color("rgb(181,0,255))");
    lasers[i].display();
    drawingContext.shadowBlur = 0;
    lasers[i].move();

  }
}

// Add the laser beams to an array to shoot with sound
function keyPressed() {
  if (key == " ") {
    lasers.push(
      new laser(
        width / 12,
        width / 12,
        spaceship1.x + spaceship1.w / 10,
        spaceship1.y - spaceship1.h / 1.4,
        spaceship1.direction
      )
    );
    beam.play();
  }
}

When the laser beam hits the alien:

for (let j = 0; j < lasers.length; j++) {
      if (aliens[i].hits(lasers[j])) {
        aliens.splice(i, 1);
        lasers.splice(j, 1);
        counter_scores++;
        break;
      }
    }

When the alien hits the spaceship:

if (aliens[i].hits(spaceship1)) {
      counter_lives--;
      aliens.splice(i, 1);
      continue;
    }

I am particularly proud of these two code snippets because I had such a hard time figuring out how to slice and make either the laser beam or the obliterated alien disappear on the screen. The hardest part was to figure out the loop using continue() and break so as the game can continue. I spent a lot of time fixing the errors relating to this.

The final rule is when the alien hits the Earth, at which point there is no return and the player will automatically lose:

if (aliens[i].y > height) {
      console.log("Game Over");
      textStyle(BOLD);
      textAlign(CENTER);
      aliens.splice(i, 1);
      gameState = "stop";
      continue;
    }

In terms of statistics, there is a score counter and a lives counter. Lives counter determines how the player progresses through out the game.

Besides checking the hit conditions, other parts that I’m proud of include coding the movement of the spaceship, coding the reset of the game, figuring out the coordinates where the spaceship meets the alien and the coordinates where the laser beam hits the alien, and creating the backgrounds and images that I use:

Creating the game start, instructions, and game restart screens also took me a lot of time. I had to refer the example sketch “Ramsha Bilal” by Save the Ocean from last year given by you to figure it out. It took me a lot of time until I could do it right, ie. creating user input before starting and creating a new session start after the previous session is completed.

Areas for improvement and problems:

In terms of using arrays, I want to make some aliens appear next to one another and move down the screen at the same time. It will vary the difficulty of the game even more. I also wanted to make the buttons for getting user’s input more aesthetic and varied, instead of what I have now.

Problems I ran into:

I couldn’t generate the stars properly so I had to refer to this YouTube tutorial from Back to Code: https://www.youtube.com/watch?v=xbmwfg5U9-s

I couldn’t make the game run in full screen properly without the elements shifting around although it still run:

https://editor.p5js.org/luke.s.ng/full/g7Hnh5lV4

There was a minor break error. If the user shoots many lasers at the same time to kill the alien, sometimes this error will pop up. During testing, the error appeared with a random number of lasers, REGARDLESS of the high score (or number of aliens killed) and the number of aliens on screen. It was a random bug while playing. But I figured out I had to add “break” to the checking condition when the laser beam hits the alien.


The lives counter initially didn’t display the number of lives when it reaches 0 properly.

I also had a lot of problems creating the starting/restarting user input pages mainly because of the buttons’ coordinates. It took me a lot of time to figure out.

Overall, it has been a fun process with lots of trial and error. I learned a lot about Object-Oriented Programming as well as debugging.

Midterm – NYUAD Ideal Food Tournament

https://editor.p5js.org/hk3863/full/bszj5rkSJ

Concept

One of the games that went viral in South Korea is the Ideal Type World Cup. I have tried this game several times, and it seemed quite simple but also very interesting in the way that it makes the user think of their preferences. Another similar game is the “Balance Game,” which requires the user to choose between two extreme situations, for example, getting 10 million dollars after 10 years or one million dollars immediately. I think what makes this game interesting is that it presents situations that people can relate to.

When I decided to create an Ideal Type World Cup, I started wondering about something that could make this game much more relatable to students. So, I came up with an idea to make this game using campus food. All NYUAD students get campus dirhams, flex dirhams, and meal swipes, which could be used on campus. I think our campus offers a vast variety of food. Each student probably has a preference for certain types of food and dining places. So, my final concept for the project is to make the NYUAD Ideal Food Tournament, which will feature different campus foods.

My game contains 32 candidates, which are campus foods. The game is in the form of a tournament where the game contains overall 5 rounds. The user has to choose between the two foods that they like more. Then, the winners of each round pass to the next round until there is a winner.

How My Project Works

My code contains four game states: “OPENING,” “CAMPUS_MAP,” and “GAME.”

  1. “OPENING”

The first thing users see is the opening page. The OpeningScreen class in my code is designed to manage and display the initial screen of my project, typically shown when the game or application first starts.

The constructor of the OpeningScreen class takes three parameters: schoolInfo, logoImage, and diningInfo. These parameters are stored as properties of the class and represent the textual information about the school, the graphical logo image, and the information about dining options available on campus, respectively.

The display method is responsible for rendering the content to the screen. It dynamically adjusts the sizes and positions of the text and logo based on the current window size, ensuring that the visuals adapt nicely to different screen dimensions.

  1. “CAMPUS_MAP”

The next page that appears after clicking on the canvas is the Campus Map page. The CampusMap class in this code snippet is designed to provide users with detailed information about dining options available at a university campus. This class contains images of dining halls, marketplaces, and other food outlets, accompanied by informative labels and detailed descriptions.

  1. “GAME”

When the user clicks on the screen again, the gameState changes to “GAME.” During this gameState, the code displays two candidates, updates the winner, and runs the logic for making this game.

4. “ENDING”

What I am Proud of

I am particularly proud of the design for this project. Since the project is related to our campus, I exclusively used NYU colors and also downloaded the official NYU font. The game looks aesthetically pleasing. Additionally, I really like the design of the Campus Map page. I have masked the three campus images displayed on that page into a circular shape so that they look pleasing to the eye. Also, for the background, I used our campus map to design the page in a way that the images are located where they actually are in real life.

let campusmappicturesSize = width / 5
  let campusmask = createGraphics(campusmappicturesSize, campusmappicturesSize) 
  campusmask.ellipse(campusmappicturesSize / 2, campusmappicturesSize / 2, campusmappicturesSize, campusmappicturesSize); // Create a circular mask

    diningHallsImage.mask(campusmask);
    marketplaceImage.mask(campusmask);
    outletsImage.mask(campusmask);

I am also particularly proud of the logic I have created for updating candidates for each round.

function handleGameClicks(candidate1Position, candidate2Position, imageSize) {
  // Check if click is within candidate 1 image bounds
  if (mouseX > candidate1Position.x - imageSize / 2 && mouseX < candidate1Position.x + imageSize / 2 &&
      mouseY > candidate1Position.y - imageSize / 2 && mouseY < candidate1Position.y + imageSize / 2) {
    nextRoundCandidates.push(currentPair[0]);
    updateCurrentPair();
  } 
  // Check if click is within candidate 2 image bounds
  else if (mouseX > candidate2Position.x - imageSize / 2 && mouseX < candidate2Position.x + imageSize / 2 &&
           mouseY > candidate2Position.y - imageSize / 2 && mouseY < candidate2Position.y + imageSize / 2) {
    nextRoundCandidates.push(currentPair[1]);
    updateCurrentPair();
  } else {
    showMessage = true;
    messageStartFrame = frameCount; // Reset message timer
  }
}

This function, ‘handleGameClicks’, is designed to process mouse clicks within the game, specifically to determine if a click occurred on one of the two candidate images. It checks if the mouse’s X and Y positions fall within the bounds of either candidate 1’s or candidate 2’s image by comparing the mouse coordinates against the images’ positions and sizes. If the click is within the bounds of candidate 1’s image, that candidate is added to the ‘nextRoundCandidates’ array, and a function to update the current pair of candidates is called. Similarly, if the click is within candidate 2’s bounds, that candidate is added to the array, and the pair update function is called. If the click doesn’t fall within the bounds of either image, a message is triggered to show by setting ‘showMessage’ to true and resetting a timer for how long the message should be displayed.

The ‘showMessage’ variable triggers the display of the text “Please click on one of the pictures” when a user’s click does not fall within the bounds of either candidate 1’s or candidate 2’s image.

if (showMessage) {
      displayMessage("Please click on one of the pictures");
function updateCurrentPair() {
  if (nextRoundCandidates.length === 1 && currentRoundCandidates.length === 0) {
    // Only one candidate left, game should end
    gameIsOver = true;
    gameState = "ENDING";
    
    
  } else if (currentRoundCandidates.length > 0) {
    // There are still candidates in the current round to be paired
    currentPair = [currentRoundCandidates.shift(), currentRoundCandidates.shift()];
  } else if (nextRoundCandidates.length > 1) {
    // Current round is empty but there are enough candidates for another round
    currentRoundCandidates = shuffle(nextRoundCandidates.slice()); // Create a new round from remaining candidates
    nextRoundCandidates = []; // Reset for the next round
    roundNumber++;
    currentPair = [currentRoundCandidates.shift(), currentRoundCandidates.shift()];
  } else {
    
  }
}

‘updateCurrentPair()’ manages the transition of game states and the setup of candidates for comparison in each round of the game. If there is exactly one candidate left and no more in the current round, it signifies the end of the game by setting ‘gameIsOver’ to true and changing the ‘gameState’ to “ENDING,” indicating a winner has been determined. If there are candidates still available in the current round, it forms a new pair for comparison by removing the first two candidates from ‘currentRoundCandidates’ and assigning them to ‘currentPair.’ In cases where the current round has ended but there are multiple candidates left for the next round (‘nextRoundCandidates.length > 1’), it shuffles the ‘nextRoundCandidates’ to start a new round, empties the ‘nextRoundCandidates’ for future use, increments the round number, and assigns a new pair to ‘currentPair’.

Improvements and Problems

In the Campus Map page, I was thinking of making the page more interactive. My initial thought was to display text about the dining place in a rectangle if the mouse is located over one of the three images. However, I had some difficulties making the description appear when the mouse is located inside the circle. My code was detecting the wrong location for images, thus, showing the rectangular description at the wrong timing. So, for further improvements, I would like to add this feature. Also, I would like to add some buttons instead of requiring users to just click on the screen to start the game.

Overall, this midterm experience helped me to figure out how to build logics for projects, and I learned a lot of new skills to make what I have imagined.