Midterm – Crack The Egg

Inspiration & Concept:

For my final project in “Introduction to Computer Science,” I spent nearly two weeks brainstorming ideas before deciding between two games: “Crack the Egg” (a new game) and “BrickBreaker,” which I used to play on my father’s BlackBerry phone. I ultimately chose to make “BrickBreaker,” but promised myself that I would someday create “Crack the Egg” as well. When I learned about the opportunity to make a game for “Introduction to Interactive Media,” I knew immediately that I wanted to create “Crack the Egg.” The inspiration for this game came from another childhood game I used to play called Piano Tiles. Instead of having randomly generated tiles one after the other, my game would continuously generate eggs of various health in a way that they are all equidistant to each other, vertically and horizontally. Piano Tiles looks like the following:

On the other hand, Crack the Egg looks like this:

In “Crack the Egg,” the player takes on the role of a worker in a factory that heavily relies on eggs for production. The worker’s job is to stand next to three conveyor belts and manually inspect the eggs to ensure quality. There are three types of eggs in the game: “normal,” “bad,” and “very bad.” The player must avoid cracking the “normal” eggs while single tapping to crack the “bad” eggs and double tapping to crack the “very bad” eggs. As the game progresses, the conveyor belts will increase in speed, making it increasingly difficult for the player to inspect the eggs and the probability of encountering a non-normal egg will increase as well. Every “normal” egg that passes through un-cracked adds to the player’s score along with every non-normal egg that is perfectly cracked. However, if the player cracks a “normal” egg or fails to crack a “bad” or “very bad” egg, they will lose one of their six lives. The player will tap the eggs using their mouse.

Working of Game:

Two classes have been created: the “Egg” class and the “Game” class. The “Egg” class contains information such as the egg’s dimensions, coordinates, randomly generated health, and “normal status.” It manages the eggs’ movement from the bottom to the top of the canvas using a global speed variable. Additionally, the class includes a function that verifies whether the egg was appropriately handled based on its health status. Furthermore, the egg class ensures that the egg image displayed corresponds to its health and normalcy status. Eggs with a health value greater than or equal to three are blue in color and have a positive normalcy status. Eggs with a health value of two (requiring two clicks) are yellow-red with flames, and those with a health value of one (requiring one click) are yellow with chicken legs. Both of these types of eggs have a negative normalcy status. Health zero corresponds to a normal egg yolk or a chick for non-normal eggs. The egg class also manages what occurs when a particular egg is clicked during the game, which involves reducing the egg’s health. The Egg class looks like this:

//Egg class includes all the functions for each individual egg
class Egg
{
  constructor(x, y, e_width, e_height, e_index, e_up)
  {
    //coordinates for the egg
    this.egg_x = x;
    this.egg_y = y;
    //quality of egg and a number greater than 3 would mean a normal egg
    this.egg_health = int(random(1, globalRandomness)); //1 because 0 is yolk or chicken
    //dimensions for the egg
    this.egg_height = e_height;
    this.egg_width = e_width;
    
    //index in array to see place the eggs behind the last element beneath the canvas
    this.index_in_array = e_index;
    
    this.up_distance = e_up; //vertical distance between two adjacent eggs
    
    this.normal = true; //contains boolean value to see if the egg is normal or not
    if (this.egg_health < 3) //any egg with less than 3 health means that it is not normal
    {
      this.normal = false;
    }
  }
  
  movement()
  {
    
      this.egg_y = this.egg_y - globalGameSpeed; //uses the globalGameSpeed to move the eggs. we use global because it keeps changing over time
  }
  
  error_detection() //detects if the egg has been treated right. this is called when the egg is above the canvas
  {
    if (this.egg_health < 3 && this.egg_health > 0 && this.normal == false) //if the egg that is not normal has not been fully cracked (i.e health < 0), then an error has been made
    {
      return true;
    }
    else if (this.egg_health == 0 && this.normal == true) //if the normal egg has been cracked accidentally
    {
      return true;
    }
    else
    {
      return false;
    }
  }
  
  display()
  {
    //normal eggs (blue coloured)
    if (this.egg_health >= 3)
    {
      normalEggPicture.resize(this.egg_width, this.egg_height);
      image(normalEggPicture, this.egg_x, this.egg_y);
    }
    //eggs that need to be tapped twice (firey egg)
    else if (this.egg_health == 2)
    {
      smallEggPicture.resize(this.egg_width, this.egg_height);
      image(smallEggPicture, this.egg_x, this.egg_y);
    }
    //eggs that need to be clicked once and has chicken legs out
    else if (this.egg_health == 1)
    {
      bigEggPicture.resize(this.egg_width, this.egg_height);
      image(bigEggPicture, this.egg_x, this.egg_y);
      
    }
    //if the egg is cracked and wasnt supposed to be cracked then the yolk
    else if (this.egg_health <= 0 && this.normal == true)
    {
      eggYolkPicture.resize(this.egg_width, this.egg_height);
      image(eggYolkPicture, this.egg_x, this.egg_y);
    } 
    else if (this.egg_health <= 0 && this.normal == false) //if the non normal eggs are fully cracked then the chicken
    {
      chickPicture.resize(this.egg_width, this.egg_height);
      image(chickPicture, this.egg_x, this.egg_y);
    }
    
    fill(0,0,0);
    rect(0, this.egg_y - this.up_distance/2, width, 4); //horizontal lines between eggs to create like a grid
  }
  
  egg_clicked() //if the egg is clicked
  {
    if (this.egg_health >= 3) //the egg is normal
    {
      this.egg_health = 0; //then the egg becomes yolk
    }
    else
    {
      this.egg_health = this.egg_health - 1; //otherwise for non-normal egg decrease health by 1
    }
  }
}

In contrast, the Game class is relatively more complex, containing the dimensions and coordinates of various buttons, such as the start, instruction, and back buttons, along with the score and player lives. The constructor incorporates three arrays, one for each conveyor belt, which append 15 eggs to each array while passing various arguments, including coordinates. Initially, 50 eggs were in each array, but this was reduced to 15 to speed up the game loop time. Four different game states are maintained in the game class, representing the menu screen, instruction screen, game start screen, and restart screen, which alternate between the screens. I am particularly proud of this implementation as it allowed me to navigate through the coding process smoothly and transition between screens seamlessly. The eggs rise above the canvas, are checked for proper handling, and their coordinates are changed to be beneath the last egg in the line, instead of popping them every time and adding new objects, which proved to be a much faster and efficient method. The game keeps track of the score, compares it to the high score for the session, and ends when all the lives are lost. The player can restart the game by pressing the Enter key after the game ends. The Game class looks like this:

//game class contains everything the game needs
class Game
{
  constructor()
  {
    //reinitialize these variables here just in case a user decides to restart game without pressing play
    globalGameSpeed = 2;
    globalRandomness = 10;
    
    //canvas dimensions 
    this.canvas_width = 300;
    this.canvas_height = 600;
    
    //start button on menu - coordinates
    this.start_button_x = this.canvas_width/6;
    this.start_button_y = 4.5 * (this.canvas_height/6);
    
    //instruction button on menu - coordinates
    this.instruction_button_x = this.canvas_width/6;
    this.instruction_button_y = 5* (this.canvas_height/6);
    
    //dimensions for the start and instruction buttons to be used for mouseClicked interactivity
    this.button_width = 2*300/3;
    this.button_height = 30;
    this.button_arc = 20;
    
    
    this.score = 0; //will keep the score of all the good eggs that have successfully passed, and bad eggs that have been successfully cracked
    this.player_lives = 6; //will keep track of the lives for the player and will be displayed as well
    
    //dimensions of the eggs
    this.egg_width = 75;
    this.egg_height = 100;
    this.side_difference = 12.5; //difference of eggs from each other on the side
    this.up_difference = 25; //difference of eggs from each other from the top
    
    this.num_eggs = 15; //total number of eggs in each array kept 15 to allow for quicker loops
    //the three arrays will contain objects of egg class and will each represent a different conveyer belt
    this.egg_array3 = [];
    this.egg_array2 = [];
    this.egg_array = [];
    
    //this loop creates objects of Egg() class and assigns them their position and values
    for (let i = 0; i < this.num_eggs; i++)
    {
      this.egg_array[i] = new Egg(this.canvas_width - this.side_difference - this.egg_width, this.canvas_height + i*(this.egg_height + this.up_difference), this.egg_width, this.egg_height, i, this.up_difference);
      
      
      this.egg_array2[i] = new Egg(this.canvas_width - (3 * this.side_difference) - (2 * this.egg_width), this.canvas_height + i*(this.egg_height + this.up_difference), this.egg_width, this.egg_height, i, this.up_difference);
      
      this.egg_array3[i] = new Egg(this.canvas_width - (5 * this.side_difference) - (3 * this.egg_width), this.canvas_height + i*(this.egg_height + this.up_difference), this.egg_width, this.egg_height, i, this.up_difference);
      
    }
    
    //controls what is shown to the user at a given time
    this.menu_screen = true;
    this.start_screen = false;
    this.instruction_screen = false;
    this.restart_screen = false;
    
    //dimensions and details for the back button that will be present in the instruction screen
    this.back_button_x = 20;
    this.back_button_y = 20;
    this.back_button_width = 75;
    this.back_button_height = 35; 
  }
  
  //this will control the movement of three belts/arrays simultaneously
  belt_movement()
  {
    
    for (let i = 0; i < this.num_eggs; i++)
    {
      //each indidivual movement of an egg
      this.egg_array[i].movement();
      this.egg_array2[i].movement();
      this.egg_array3[i].movement();
      
      if (this.egg_array[i].egg_y + this.egg_array[i].egg_height <= 0) //if the egg goes above the canvas
      {
        
        if (this.egg_array[i].error_detection() == true) //we see if the egg has been rightfully treated or not
        {
          this.player_lives = this.player_lives - 1; //if there is an error detection, this will decrease the life of the player
        }
        else //if it has been rightfully treated
        {
          this.score = this.score + 1; //it will increase the score of the player
          score_count = score_count + 1; //score_count will also be increased to check if the game needs to be made harder. this is reinitialized in the draw function
        }
        this.egg_array[i].egg_y = this.egg_array[this.num_eggs - 1].egg_y + ((i+1) * (this.egg_height + this.up_difference)); //the egg that has gone above the canvas comes and attaches itself under the last element in the array using the index but its own index will remain the same
        

        this.egg_array[i].egg_health = int(random(1,globalRandomness)); //change the health and the quality of the egg once again to another random value
        
      if (this.egg_array[i].egg_health < 3) //also change the normal or not normal status for the egg. any random number above 3 is for eggs that are normal
        {
          this.egg_array[i].normal = false;
        }
        else
        {
          this.egg_array[i].normal = true;
        }
        
      }
      
      //do the same for egg_array2 and egg_array3
      if (this.egg_array2[i].egg_y + this.egg_array2[i].egg_height <= 0)
      {
        if (this.egg_array2[i].error_detection() == true)
        {
          this.player_lives = this.player_lives - 1;
        }
        else
        {
          this.score = this.score + 1;
          score_count = score_count + 1;
        }
        
        this.egg_array2[i].egg_y = this.egg_array2[this.num_eggs - 1].egg_y + ((i+1) * (this.egg_height + this.up_difference));
        this.egg_array2[i].egg_health = int(random(1,globalRandomness));
        
        if (this.egg_array2[i].egg_health < 3)
        {
          this.egg_array2[i].normal = false;
        }
        else
        {
          this.egg_array2[i].normal = true;
        }
      }
      
      if (this.egg_array3[i].egg_y + this.egg_array3[i].egg_height <= 0)
      {
        if (this.egg_array3[i].error_detection() == true)
        {
          this.player_lives = this.player_lives - 1;
        }
        else
        {
          this.score = this.score + 1;
          score_count = score_count + 1;
        }
        
        this.egg_array3[i].egg_y = this.egg_array3[this.num_eggs - 1].egg_y + ((i+1) * (this.egg_height + this.up_difference));
        this.egg_array3[i].egg_health = int(random(1,globalRandomness));
        if (this.egg_array3[i].egg_health < 3)
        {
          this.egg_array3[i].normal = false;
        }
        else
        {
          this.egg_array3[i].normal = true;
        }
      }
      
      //displays all the eggs with appropraite images
      this.egg_array[i].display();
      this.egg_array2[i].display();
      this.egg_array3[i].display();
    }
  }
  
  //displays different types of screens to the user
  screenDisplay()
  {
    if (this.menu_screen == true) //if the starting menu is displayed
    {
      image(startmenu_display_picture, 0,0); //display the picture with the logo
      fill("#541675"); //box color
      noStroke();
      //boxes for the start and instruction buttons
      rect(this.start_button_x, this.start_button_y, this.button_width, this.button_height, this.button_arc); //boxes to act as buttons
      rect(this.instruction_button_x, this.instruction_button_y, this.button_width, this.button_height, this.button_arc);
      //if the mouse hovers over the start button
      if (mouseX >= this.start_button_x && mouseX <= this.start_button_x + this.button_width && mouseY >= this.start_button_y && mouseY <= this.start_button_y + this.button_height) //if the mouse hovers over the button
      {
        fill("green"); //turn the text green
      }
      else
      {
        fill("gold"); //otherwise the text will be gold
      }
      //write START in the button
      textSize(20);
      text("START", this.start_button_x + ((this.button_width)/3.2), this.start_button_y + (this.button_height/1.3));
      //mouse hovering feature for the instruction button
    if (mouseX >= this.instruction_button_x && mouseX <= this.instruction_button_x + this.button_width && mouseY >= this.instruction_button_y && mouseY <= this.instruction_button_y + this.button_height)
    {
      fill("green");
    }
    else
    {
      fill("gold");
    }
      text("INSTRUCTIONS", this.instruction_button_x + ((this.button_width)/11), this.instruction_button_y + (this.button_height/1.3));
   }
    //if the start button is clicked
    else if (this.start_screen == true)
    {
      if (this.player_lives > 0) //the game hasnt ended
      {
        this.belt_movement();
      }
      else //the game has ended
      {
        this.restart_screen = true; //restart screen would be displayed in the next iteration
        this.start_screen = false; //current game state would be changed
      }
    }
    //if the instruction button is clicked then this if statement would run to show the instruction screen
    else if (this.instruction_screen == true)
    { 
      image(instruction_display_picture, 0 ,0); //image will all the instructions
      
      //Image for backbutton is resized to fit the desired dimensions and then added at the appropriate location in the instruction screen
      back_button.resize(this.back_button_width, this.back_button_height);
      image(back_button, this.back_button_x, this.back_button_y);
    }
    else if (this.restart_screen == true) //the game has ended
      {
        if (this.score > highscore)
        {
          highscore = this.score; //update highscore if needed
        }
        image(simple_game_over, 0, 0); //display the already edited restart screen
        fill(0, 0, 0);
        textSize(17);
        text("YOUR SCORE: ", 72, 347); //display score
        text(this.score, 192, 347);
        text("HIGH SCORE: ", 72, 425); //display high score
        text(highscore, 192, 424);
      }
  }
}

After all of this was done, the final product looks like this:
Full Screen: https://editor.p5js.org/mt4610/full/XgShKTIyL

Problems – Reflections:

While developing this game, I encountered several issues. Initially, I faced problems with using the “random” function in classes despite having used it before. It took me about 1.5 days, with the assistance of a friend, to come up with a solution. I added “new P5()” and “remove” at the beginning and end of the code, respectively, which enabled the program to run again. I also struggled with organizing the eggs in a loop and encountered issues such as overlapping and unequal distances. I decided to take a break, rewrite the code from scratch, and found a new solution. Rather than moving the eggs to their initial position, I looped them back to the end. Another issue I faced was with the restart game feature, which I spent hours trying to fix. I realized the next day that I had a typing error, which prevented the game from restarting. Although most issues were resolved, I was unable to find a way to save the highscore indefinitely. I managed to read from the file, but overwriting the high score was challenging. Every time I tried to save the score, a new file was created and downloaded to the user’s PC. As a result, I decided to create a high score feature for the current session and leave the indefinite high score for the future. I also had to balance the game’s speed increase and the probability of finding bad eggs to ensure that the game was neither too easy nor too difficult. In terms of creativity, I chose not to include sounds for tapping or cracking eggs, as they became repetitive/annoying over time. In the future, I plan to add extra features such as extra lives and slowing down the speed for a set period. Additionally, I aim to enhance the game’s aesthetics.

Midterm Project – Memory Card Game

Concept:

Introducing my latest creation – a memory card game with a unique twist! As one of the most popular games enjoyed by people of all ages in various forms, I wanted to put my own spin on it by incorporating a theme that I feel passionately about – food. The result is a game that is both fun and engaging and adds a personal touch that sets it apart from other versions.

I aim to showcase Pakistani Cuisine through this game, which shares similarities with cuisines from neighboring countries such as Nepal, India, and Sri Lanka. The increasing globalization has led to a rise in the popularity and recognition of desi food. Through this memory game, I hope to introduce players to lesser-known dishes that they may not have come across before. As a result, the unique names of these dishes will stay with them for a long time.

Final Outcome:

Game Code:

Following is the code for the game:

let numMatches = 0; //to see if all matches found and game ends
let numTries = 0; //counter to keep track of counts
let delayStartFC;
//10 card image variables
let card1;
let card2 ;
let card3;
let card4;
let card5;
let card6;
let card7;
let card8;
let card9;
let card10;
//array variables for image of cards and tiles etc
let faces = [];
let flippedTiles = []; 
let tiles = [];
let selected = [];
let possibleFaces;
let startgame;
//variables for images and sounds
let backgroundImage;
let instructionsImage;
let endImage;
let mymusic;
let flipsound;
let clicksound;
let flipSide;
let gameState; //to enter different stages: instructions, game, end
let NUM_COLS = 4;
let NUM_ROWS = 5;
function preload() 
{
  //loading all images and sounds
  flipSide = loadImage("back.jpg");
  card1 = loadImage("card1.jpg");
  card2 = loadImage("card2.jpg");
  card3 = loadImage("card3.jpg");
  card4 = loadImage("card4.jpg");
  card5 = loadImage("card5.jpg");
  card6 = loadImage("card6.jpg");
  card7 =  loadImage("card7.jpg");
  card8 = loadImage("card8.jpg");
  card9 =  loadImage("card9.jpg");
  card10 = loadImage("card10.jpg");
  startgame = loadImage ("startgame.png");
  backgroundImage = loadImage ("game.png");
  instructionsImage = loadImage("instructions.png")
  endImage = loadImage("end.jpg");
  mymusic = loadSound('Baisara-Beera-Kalpana-Patowary-Papon (mp3cut.net).mp3');
  flipsound = loadSound ('Card-flip-sound-effect-1.mp3');
  clicksound = loadSound('mixkit-game-click-1114.wav');
}

function setup() 
{
  // Set up canvas and other initialization tasks
  createCanvas(600, 700);
  gameState = 'start';
  mymusic.loop(); //stays on throughout
  frameRate(120);
}

class Tile 
{
     constructor(x, y, face) 
    {
      this.x = x;
      this.y = y;
      this.size = 100;
      this.face = face;
      this.isFaceUp = false;
      this.isMatch = false;
    }
    draw ()
    {
      //draws back if not face up otherwise the image of food
      if (this.isFaceUp)  
          image(this.face, this.x, this.y, this.size, this.size);
      else 
          image(flipSide, this.x, this.y, this.size, this.size);
      
      //black outline of the tiles
      stroke(0);
      strokeWeight(3);
      noFill();
      rect(this.x, this.y, this.size, this.size,5);
    }
  
    isUnderMouse(x,y)
    {
      //checks if when clicked the tile is under the mouse
        return x >= this.x && x <= this.x + this.size  && y >= this.y && y <= this.y + this.size;
    }
}
//function to randomly shuffle the array of tiles with 2 copies each
function shuffleArray(array) 
{
    let counter = array.length;
    // while there are elements in the array
    while (counter > 0) 
    {
        //selecting a random index
        let index = Math.floor(Math.random() * counter);
        // decrementing counter
        counter--;
        //swap last element with it
        var temp = array[counter];
        array[counter] = array[index];
        array[index] = temp;
    }
}
function draw() 
{
  if (gameState == 'start')
  {  
        //displaying image for background of game
        image(startgame, 0,0,600,700); 
        //start button
        let c = color('#FF69B4'); 
        fill(c);
        noStroke();
        circle(302.5, 350, 112);
        fill('black');
        textSize(20);
        textStyle(BOLD);
        text('Start', 280, 355);
  } 
  else if (gameState == 'instructions')
  {
    //instruction page is an image I made from canva
    image(instructionsImage,0,0,600,700);
  }
  else if (gameState == 'beingSet')
  {
    //this is just done once when the start button is clicked 
    faces = [card1, card2, card3, 
             card4, card5, card6, 
             card7, card8, card9, 
             card10]; //storing all images into array
    // making an array of 2 cards and then randomising
    possibleFaces = faces.slice(0);
    for (let i = 0; i < (NUM_COLS * NUM_ROWS) / 2; i++) 
    {
        // Randomly pick one from the array of remaining faces
        var randomInd = floor(random(possibleFaces.length));
        var face = possibleFaces[randomInd];
        // Push twice onto array
        selected.push(face);
        selected.push(face);
        // Remove from array
        possibleFaces.splice(randomInd, 1);
    }
    shuffleArray(selected); //shuffling the array
    
    // Creating a Tile object for each card
    for (var i = 0; i < NUM_COLS; i++) 
    {
        for (var j = 0; j < NUM_ROWS; j++) 
        {
            var tileX = (i * 100) + 55+ (i*30);
            var tileY = (j * 100) + 70 +(j*17);
            var tileFace = selected.pop();
            tiles.push(new Tile(tileX, tileY, tileFace));
        }
    }
        // Changing the game state to 'playing'
    gameState = 'playing';
  }
  else if (gameState == 'playing') 
  {
    // Displaying the background image
    image(backgroundImage, 0,0,600,700);
    // Checking if enough time has passed since the last card flip
    if (delayStartFC && (frameCount - delayStartFC) > 25) 
    {
        // Hiding the flipped cards that are not matches
        for (let i = 0; i < tiles.length; i++) 
        {
          if (!tiles[i].isMatch) 
              tiles[i].isFaceUp = false;
        }
// Resetting the flippedTiles array and delayStartFC variable
        flippedTiles = [];
        delayStartFC = null;
     }
    
    // Drawing each tile on the canvas
    for (let j = 0; j < tiles.length; j++) 
        tiles[j].draw();
   
    // Checking if the game is over
    if (numMatches == tiles.length/2)
        gameState = 'end';
} 
  else if (gameState == 'end')
  {
    image(endImage, 0,0,600,700);
    let c = color('#d90166');
    fill(c);
    noStroke();
    //play again button
    circle(302.5, 350, 112);
    fill('black');
    textSize(15);
    textStyle(BOLD);
    text('Play Again', 266, 355);
    textSize(20);
    //diplaying the tries as a score for game
    text("You found them all in " + numTries + " tries!", 146,488);
    } 
  
mouseClicked = function() 
{ 
  if(gameState == 'start')
    {
      //checking ditance between click and start button 
      let distance = dist(mouseX, mouseY, 302.5, 350);
      if (distance < 112/2) 
        {
          //play click sound
          clicksound.play();
           gameState = 'beingSet';
        }
    // checking distance between click and instructions button
      if (mouseX>220 && mouseX<380 && mouseY>500 && mouseY<540)
        {
          clicksound.play();
          gameState = 'instructions';
        }
    }
  else if (gameState == 'playing')
  {    
    //loop through all the tiles
    for (let i = 0; i < tiles.length; i++) 
      {
          let tile = tiles[i];
          if (tile.isUnderMouse(mouseX, mouseY)) 
          {
  // Check if the tile can be flipped (only if less than 2 tiles are flipped and the tile is not already face-up)
              if (flippedTiles.length < 2 && !tile.isFaceUp) 
              {
                  tile.isFaceUp = true;
                  flipsound.play();
                  flippedTiles.push(tile); //adds to flippedtiles array
                  if (flippedTiles.length === 2) 
                  {
                      numTries++; //increments the tries counter
                      if (flippedTiles[0].face === flippedTiles[1].face) //if they match
                      {
                          flippedTiles[0].isMatch = true;
                          flippedTiles[1].isMatch = true;
                          flippedTiles.length = 0; //empties flipped array because match found
                          numMatches++;
                      }
                      delayStartFC = frameCount;
                  }
              } 
          }
      }
  }
  else if (gameState == 'instructions')
    {
      //clicking the startgame button
      if (mouseX > 149 && mouseX < 449 &&
      mouseY > 600 && mouseY < 615)
        {
          clicksound.play();
          gameState = 'beingSet';
        }
    }
  else if (gameState == 'end')
    {
      //clicking restart button 
      let distance = dist(mouseX, mouseY, 302.5, 350);
      if (distance < 112/2) 
        {
          clicksound.play();
         gameState = 'start';
        }
    }
};
}

Process:

Initially, I developed a tile class and successfully displayed the tiles on the screen using the draw function in my program. Although the game was functional, I encountered challenges when attempting to change the game state from the starting page to the playing and end pages. To overcome this hurdle, I referred to the coding examples provided by Professor Mang for changing the game state. However, I had to modify my code to make it compatible with the “if-else” loops used to change the game state. I eventually decided to categorize my game states further and added a new category called “beingSet”, which is only called once before the game begins.

During my coding process, I encountered another obstacle while creating cards for my game using Canva. Initially, the cards included an image, the name of the dish, and a brief description. However, due to the limited dimensions of my game’s canvas, the description on the cards was difficult to read, and it also compromised the visibility of the dish’s name. As a result, I had to recreate all the cards without the description. Despite my efforts, I was disappointed that I could not include the descriptions on the cards.

previously made cards:

final card: 

I just solved another issue in my game a few minutes ago by adding a single line of code:

frameRate(120);

This line of code significantly improved the performance of my game. Previously, as the game progressed towards the end, it would slow down. However, with the addition of this line of code, my game’s speed increased, making it more challenging and an actual test of memory for the players. Nevertheless, I need to investigate why the cards take longer to flip over towards the end, despite the game starting at a normal speed.

Future Improvements:

While I am immensely proud of my game, there is always room for improvement. One area that could be enhanced is by adding a menu with the description of every dish. This could be introduced as an introductory section, where players click on a button and are taken to a menu with forward and back buttons to navigate through an array of images. This would give players the opportunity to familiarize themselves with the dishes, the details of the dish, and the corresponding images before they start playing. This would not only enhance the players’ overall gaming experience but also provide a fun and interactive way to learn about Pakistani cuisine.

Midterm Project – Space Shooter

Below is the game I have created, a retro styled classic: Space Shooter!

and below is the code for this game:

//Declaring all variables to be used
let bullets = [];
let enemies = [];
let stars = [];
let powerups = [];
let gamestate;
let posterImage;
let fighter;
let player;
let laser;
let enemy;
let score = 0;
let title_screen_audio;
let playing_screen_audio;
let end_screen_audio;
let laser_sound_effect;
let enemySpawnRate =1;
let timeElapsed = 0;


function preload() {
  posterImage = loadImage('SpaceShooterBackground.png'); //Loading main menu
  fighter = loadImage('NicePng_spaceship-png_138961.png'); //Loading spaceship
  laser = loadImage('LaserBullet.png'); //Loading laser bullet
  enemy = loadImage('invader.png'); //Loading enemies
  font = loadFont('GameOverFont.ttf'); //Loading Game Over Screen Font
  title_screen_audio = loadSound("SkyFire (Title Screen).mp3");
  playing_screen_audio = loadSound("Battle in the Stars.mp3"); //Loading menu music
  end_screen_audio = loadSound("gameEndMusic.mp3");  //Load Game Over Screen Music
  laser_sound_effect = loadSound("lasershot.wav"); //Load laser sound effect
}

function setup() {
  createCanvas(400, 600);
  gamestate = 'menu'; //Setting gamestate menu
  player = new Spaceship(67, 61); //Initializing Sapceship class as the player

  for (let i = 0; i < 100; i++) {
    stars[i] = new Star(random(width), random(height), random(1, 5)); //Randomly generating stars
  }
}

function draw() {
  if (gamestate == 'menu') {
    image(posterImage, 0, 0, width, height); //Displaying menu screen if gamestate is menu
    if (!title_screen_audio.isPlaying())
      {
        title_screen_audio.play(); //Plays menu music
      }
  }
  if (gamestate == 'playing') {
    title_screen_audio.stop(); //Stops menu music when gamestate is playing
    if (!playing_screen_audio.isPlaying())
      {
        playing_screen_audio.play(); //Plays battle music
      }
    background(1, 22, 64);
    player.show();

    for (let star of stars) {
      star.show(); //Displaying stars
    }

    // Add enemies randomly
    if (frameCount % (60 / enemySpawnRate) == 0) {
      let enemy = new Enemy(random(width-50), -50, random(1, 4));
      enemies.push(enemy);
    }

    for (let i = 0; i < bullets.length; i++) {
      bullets[i].update(); //Adding velocity to bullets
      bullets[i].show(); //Displaying bullets
      for (let j = 0; j < enemies.length; j++) {
        let enemyHitbox = enemies[j].getHitbox(); //Initializing enemies with hitbox
        if (bullets[i].hits(enemyHitbox)) {
          bullets.splice(i, 1); //Remove bullets when it hits an enemy
          enemies[j].hits(); //Registers hit to enemy
          score += 1; //Incremements score on hit
          enemies.splice(j, 1); // remove the enemy object from the array
          break;
        }
      }
    }

    for (let i = 0; i < enemies.length; i++) {
      enemies[i].update(); //Makes enemies fall
      enemies[i].show(); //Displays enemies
    }

    let anyEnemyReachedBottom = false; // flag to indicate if any enemy has     reached the bottom
    for (let i = 0; i < enemies.length; i++) {
      if (enemies[i].reachedBottomFlag) {
        anyEnemyReachedBottom = true; //Turns true when enemy reaches bottom
        break;
      }
    }

    if (anyEnemyReachedBottom) {
      gamestate = 'gameover'; //Sets gamestate to gameover once enemy reaches the bottom
    }

    textSize(20);
    strokeWeight(1);
    textFont(font);
    fill(255);
    text("Score: " + score, 10, 30); //Displays score at top left
  }

  if (gamestate == 'gameover') {
    playing_screen_audio.stop(); //Stops battle music
    if (!end_screen_audio.isPlaying())
      {
        end_screen_audio.play(); //Plays defeat music
      }
    background(1, 22, 64);
    for (let star of stars) {
      star.show(); //Displays stars
    }
    textSize(30);
    strokeWeight(1);
    fill(255);
    textFont(font);
    text("Game Over", width / 2 - 80, height / 2 - 20);
    text("Score: " + score, width / 2 - 65, height / 2 + 20);
    text("Press Space to retry!", width / 2 - 150, height / 2 + 60);
  }
  
  timeElapsed += deltaTime / 1000;
  if (timeElapsed >= 40) { // increase spawn rate every 40 seconds
    enemySpawnRate++;
    timeElapsed = 0;
  }
  
}
function mouseClicked() {
  if (gamestate == 'menu') {
    gamestate = 'playing'; //Changes gameststate on mouseclick
  }
  if (gamestate == 'playing') {
    let bullet = new Bullet(mouseX + 3, height - 20, 10);
    bullets.push(bullet); //Fires bullet on every click
    laser_sound_effect.play(); //Plays laser sound on every click
  }
}

function keyPressed(){
  if (key == ' ' && gamestate == 'gameover'){
    score = 0;
    bullets = [];
    enemies = [];
    restartGame();
    end_screen_audio.stop(); //Restarts game by pressing space on game over screen
  }
}

function restartGame() {
  gamestate = 'playing';
  enemySpawnRate = 1;
}

class Bullet {
  //Setting variables
  constructor(x, y, velocity) {
    this.x = x;
    this.y = y;
    this.velocity = velocity;
    this.size = 20; 
  }

  update() {
    this.y -= this.velocity; //Fires bullet upward
  }

  show() {
    image(laser, this.x - this.size/2, this.y - this.size/2, this.size, this.size); //Laser image
  }

  hits(hitbox) {
    let d = dist(this.x, this.y, hitbox.x + hitbox.width/2, hitbox.y + hitbox.height/2);
    if (d < (this.size + hitbox.width)/2) {
      return true; //Hitbox registration
    } else {
      return false;
    }
  }
}

class Spaceship {
  constructor(x,y) {
    this.x = x;
    this.y = y; // Setting variables
  }
  show(){
    //Prevents fighter from going out of bounds
    if (mouseX - 30 < 0)
      {
        image(fighter,0,height-50,this.x,this.y);
      }
    else if (mouseX - 30 > width)
      {
        image(fighter,width - this.x,height-50,this.x,this.y);
      }
    else
      {
        image(fighter,mouseX-30,height-50,this.x,this.y);
      }
    
    
  }
}
//Creates stars in background
class Star {
  constructor(x, y, size) {
    this.x = x;
    this.y = y;
    this.size = size;
  }

  show() {
    stroke(255);
    strokeWeight(this.size);
    point(this.x, this.y);
  }
}

class Enemy {
  constructor(x, y, velocity) {
    this.x = x;
    this.y = y;
    this.velocity = velocity;
    this.size = 30;
    this.reachedBottomFlag = false; // flag to indicate if this enemy has reached the bottom
  }

  update() {
    this.y += this.velocity;
    if (this.y >= height) {
      this.reachedBottomFlag = true; //Detects if enemy has reached bottom
    }
  }

  show() {
    image(enemy, this.x, this.y, this.size, this.size); //Creating enemies images
  }

  hits() {
    this.velocity = 0; //Sets velocity to 0 on hit
  }

  getHitbox() {
    return {
      x: this.x,
      y: this.y,
      width: this.size,
      height: this.size //Creates hitbox of enemies
    }
  }
} 

I was inspired by the many retro classic games. These games bring a long a sense of nostalgia that no other games bring. Since I am an avid space enjoyer and all things sci-fi, I decided to combine these 2 concepts into the game Space Shooter. The story is you are a space fighter defending the human race from these purple invaders! Your mission is to prevent these invaders to breaking through into your back line.

Behind the scenes, I used Canva in order to create the opening menu of this game. I then created separate classes for the player’s spaceship, enemies, bullets, and stars. I tied the spaceship position and laser firing position to the mouseX coordinate while invaders fall between a random set of x values. I essentially created a hitbox for the enemies so the laser can register whether it hits an enemy or not. I also made it so that it tracks how long the game goes on and after every 30 seconds, the spawn rate of the enemies increasing making the game get progressively difficult. Just for some extra personalization, I made it so the stars in the background are randomly generated so there is always something that is a tiny bit new in every playthrough.

I am especially proud of making the hitbox method in order to deal with collision. I initially struggled with collision but after researching and messing around, I managed to finally solve it and in quite the efficient way I like to think. I’m also proud of the laser bullet and invader sprites. I managed to find a good vector sprite for the spaceship but importing different sized bullets and invaders could be messy. So, I made them on my own using online software allowing me to personalize them into whatever size or shape I need. This further simplified my work and going forward and gave it a bit more of my own touch and creativity into this project.

I still feel as there are many more improvements that can be made into this. For starters, I could add other enemy types where they require multiple hits in order to be destroyed. I can also add powerups where the game could be slowed for example. I was also told about adding a pause screen which I initially thought wouldn’t fit as it is a retro game where pause menus didn’t exist but still could be added as a quality-of-life improvement.

Lovers Meet

Concept
For this project, I was inspired by a game called “Barbie bike styling ride,” so much so that I wanted to replicate it. So along with creating a game for my midterm project, I wanted to live those cute childhood memories and enjoy making this game. The game is themed behind the famous love story we have all heard about, Romeo and Juliet. So the task of the game is to help Romeo reach Juliet with the highest number of scores, crossing all the obstacles in his way to reach his love.

Code and project explanation
Coding and making this project required first to lay down the code’s structure and how I would like to manage the different screens of the game. At the start of the game’s making, I was trying to understand the most efficient way to structure code and organize the different images and the media being imported. I use the variable gameState to keep track of the different screens (the start screen, the game screen, the difficulty screen, the instructions screen, and the end screen) in the display. Then to structure everything more easily, I divided the code into different files, which include: “screens.js,” “player.js,” “obstacles.js.”, and “functions.js.” I am really proud of the button function, the arrangement of my code, the jumps of the character, and the scorekeeping.

the screen setup

function startScreen() {
  image(start_pg, 0, 0, width, height);
  button("Start", "#B667E6", 3*width/4-150, 3*height/4-50, 120, 30, 5 )
  button("Instructions", "#B667E6", 3*width/4-150, 3*height/4-10, 120, 30, 5 )
  button("Difficulty", "#B667E6", 3*width/4-150, 3*height/4+30, 120, 30, 5 )
}

function instructionScreen(){
  image(instruction_pg, 0,0, width,height);
  button("Return", "#B667E6", width-150, height-50, 120, 30, 5 );
}

function difficultyScreen(){
  image(difficulty_pg, 0,0, width,height);
  
  button("Return", "#B667E6", width-150, height-50, 120, 30, 5 );
  
  button("EASY", "#B667E6", 50, 150, 155, 100, 25 );
  button("MEDIUM", "#B667E6", 225, 150, 155, 100, 25 );
  button("HARD", "#B667E6", 400, 150, 155, 100, 15 );
}

function gameScreen() {
  // Draw game background image
  background(0);
  
  image(game_pg, v1, 0, width, height);
  image(game_pg, v2, 0, width, height);
  
  v1 -= game_speed;
  v2 -= game_speed;
  
  if(v1 < -width) { v1 = width;} 
  if(v2 < -width) { v2 = width;}
  
  if (random(1) < 0.005) {
    obs.push(new Obstacles(width, height - 50, 50, 50));
  }

  for (let ob of obs) {
    ob.move();
    ob.show();

    if (p.hits(ob)) {
      gameState = "endScreen"
    }
    else{
       score += 1;
    }
    
    if(ob.x +ob.w<0){
      obs.splice(ob)
    }
  }
  p.show();
  p.move();
  if (keyIsDown(32)) {
    p.jump();
  }
}

function endScreen(){
  image(end_pg, 0,0, width,height);
  button("Restart", "#B667E6", width-150, height-50, 120, 30, 5 );
  textSize(50); // set text size
  fill(255); // set text color
  text("SCORE:", 450, 220); 
  text(score, 450, 270); 
  
}

The player class

class Player {
    constructor() {
      this.r = 200; //size of the image
      this.x = 50;
      this.y = height - this.r;
      this.vy = 0; //velocity with which it goes up
      this.gravity = 1; //gravity with which it comes down
    }
  
    jump() {
      //jump only if its in the ground
      // if (this.y == height - this.r){
        this.vy = -25; //velocity with which it jumps
       // }
    }
  
    hits(obs) {
      let x1 = this.x + this.r * 0.5;
      let y1 = this.y + this.r * 0.5;
      let x2 = obs.x + obs.w * 0.5;
      let y2 = obs.y + obs.h * 0.5;
      return collideCircleCircle(x1, y1, this.r, x2, y2, obs.w);
    }
  
    move() {
      this.y += this.vy; //reduces the height inturn makes the player move upwards
      this.vy += this.gravity; //adding gravity so that it comes down
      this.y = constrain(this.y, 0, height - this.r); 
    }
  
    show() {
      image(player, this.x, this.y + 50, this.r, this.r);
    }
  }

 

Problems faced and Areas of improvement
One of the most crucial problems that I ran into was making to make the player jump. At first, I was using the keyPressed function, but then it wasn’t working because it was not getting referenced in the draw directly, so I used the keyIsDown to perform the same; this indeed is a minor error, but it took a lot of time to debug this down. Mostly I faced difficulties in the starting regarding the logistics of the game and how to build it, but once we had a structure built up in mind, then it was easy to build.

This project can be extended to make it more realistic with the player and the obstacle in a more 3D version. We could also add more features to the set up of the game with functions and buttons to mute the volume or have different levels.

Midterm Project – ‘Save the Ocean’ Game

Concept

‘Save the Ocean’ is an adaptation of the famous snakes game but with a new concept. It aims to raise awareness of the plastic pollution in our oceans around the world and with this intent in mind, I developed this game where the player controls a diver (instead of a snake) and uses arrow keys on the keyboard to move around the ocean – the canvas. The diver’s mission is to collect as much plastic waste as possible while avoiding obstacles – the monsters.

The Game

Fullscreen view here: https://editor.p5js.org/ramshabilal/full/uDXF9FUDA

Key elements of the game

Start Page

The game has a start page that shows the cover of the game along with a start button, which, when pressed moves onto the instructions page. Game music also begins playing on this page. An image I designed for the game is used as a background while the button is added using shapes and text.

Instructions Page

With an online image as a background, this page highlights the key information and instructions needed to understand how the game is to be played.  A button ‘Go’ when pressed takes the player to the main game page where the game is to be played.

Main Game Page

The main game page has:

  • The diver: using a segment class I made, the diver object can be moved around using keyboard navigations.
  • Monsters: Monsters are generated at random positions (coordinates saved in arrays) on the canvas and if the diver collides with them, 10 points are deducted from the player’s “Life” – game stops when Life gets to zero.
  • Plastic Waste: These are the green plastic bottles that the user has to collect to save the ocean. Whenever plastic is collected, 10 points are added to the user’s score. At the end of the game, the high score is updated – if the user exceeds the previous high score, they break their own record. This layer is added to compel users to play again.
  • Informational text: Score, high score, and life are displayed for the user to see.

Game over page

This is the page displayed when the game ends i.e. life = zero. Here, the score and high score with the corresponding message are displayed. If the user breaks their high score record, they are informed here as well. Music also changes on this page. The user can also press a button to go back to the start – the game is reset using a reset game function and the user can start again (without rerunning the code).

Difficulty

The difficulty of the game increases with time – as time passes, the game speeds up and the number of monsters in the ocean also increases.

Some of my design considerations

A color scheme was designed for the game – blue and purple were the dominant colors in this theme to create a coherent and complete look and experience for the user and to fit the “ocean” theme of the game.

Initially, this was my cover for the game:

However, to reveal more about what the game will be and to match the theme and colors, I changed it to this design I made:

Buttons changed color when the mouse hovered over them to indicate they need to be pressed.

The main music played in the game was chosen to match the ocean theme. Moreover, pop and coin sound effects were added which corresponded to the collisions with monster and plastic waste respectively.

A simple yet fun font was used uniformly throughout the game to create a coherent and fun look while keeping everything minimal and easy to follow.

Code I am proud of

As  I developed the game, unique challenges popped up that required creative solutions. Developing these solutions was the part I was most proud of as it required problem-solving.

One example is when I wanted to create more than one monster at a time as opposed to the one monster-at-a-time thing I had initially. For this, I created arrays to store random coordinates and loops to create the monsters.

function createMonsters(){
//for every monster, update its speed and if it collides with the walls, reverse their direction of motion
  for (let i = 0; i < numOfMonsters; i++){
    image(monsterImg, monsterXs[i], monsterYs[i], monsterWidth, monsterHeight);
    
    monsterXs[i] += monsterSpeeds[i];
  if (monsterXs[i] < 0 || monsterXs[i]+monsterWidth > width)
    {
      monsterSpeeds[i] = -monsterSpeeds[i];
    }
  }
}

Another challenge I faced was that when the user would play again, the conditions would not reset. For this, firstly i introduced game states to smoothly switch between different stages of the game and also created a reset page function to refresh the game to start conditions.

function stopPage(){
  background('rgb(100,229,255)');
  image(gameOverImg, 0,0,width,height);
  fill('white');
  textAlign(CENTER);
  textFont(scoreFont);
  textSize(24);
  if (score > highscore)
  {
    print('score is ' + score + ' highscore is ' + highscore);
    
    text('Congratulations. You helped free the ocean of harmful plastic and you just beat your previous highscore!', 0, height/2, width);
    fill('gold')
    text('New Highscore =  ' + score, 10, height*2/3, width);
  }
    else 
    { 
      fill('white');
       text('Congratulations. You helped free the ocean of harmful plastic!',10, height/2, width);
      
       text('Score =  ' + score + '\n\nHighscore = '+ highscore, 10, height*2/3, width);
    }
  fill('red')
  ellipse(width/2, 530,200,80);
  fill('white')
  text('Play Again', 295, 538);
  if (mouseX > width/2-100  && mouseX < width/2+100 && mouseY > 530-40 && mouseY < 530+40){
    fill('gold');
    ellipse(width/2, 530,200,80);
    fill('white')
    text('Play Again', 295, 538);
    if (mouseIsPressed){
      reset = true;
      gameOverSong.stop();
      gameState='start';
    }
  }
}

function resetGame(){
  gameOverSong.stop();
  song.loop();
  life =100;
  if (score > highscore){
    highscore = score;
  }
  score = 0;
  startPlaying=false;
  x1=0;
  x2=width;
  direction='start';
  newSeg.setX(width/2);
  newSeg.setY(height/2);
  
  for (i = numOfMonsters; i > 2;i--)
  {
    monsterSpeeds.splice(i, 1);
    monsterXs.splice(i, 1);
    monsterYs.splice(i, 1);
  }
  numOfMonsters = 3;
  step =3;
  
  for (i =0; i<numOfMonsters; i++)
    {
      monsterSpeeds[i] = random(-2,2);
      monsterXs[i] = random(0,width);
      monsterYs[i] = random(0,height);
    }
}

Also, while resetting, to go back to the original number of monsters, I splice their arrays keeping only some monsters and reset their speeds to the initial range (while keeping it random).

I also used other boolean flags to control the flow of the game and as a whole, I am very proud of the entire code – functions, class, etc – that I wrote.

//seg is class for the diver and step is the speed of the diver
let newSeg, segWidth = 60, step = 3;
let direction = 'start', gameState = 'start';

//booleans to store various game states/reset etc. startPlaying checks if game level has started, reset checks if the game is started again to reset the variables, playgameover stores if the game over song should be played or not
let startPlaying = false, reset = true, playgameover = true;

//for images and songs 
let img, diverImg, instrImg, bgImg, plasticImg, monsterImg, gameOverImg;
let song, gameOverSong, coinSong,powSong;
let cnv; 

//for scrolling background 
let x1 = 0, x2, scrollSpeed = 1;

//for the green plastic bottles 
let plasticX = 100, plasticY = 200, plasticWidth =70, plasticHeight = 70;

//to save randomly chosen x and y coordinates for the monsters
let monsterXs = [], monsterYs = [];
let monsterWidth =60, monsterHeight = 60; 

//initially num of monsters and array to store their randomly chosen speeds (within a range)
let numOfMonsters = 3, monsterSpeeds =[];
let highscore = 0, score = 0, life = 100;

//preload required images, songs, and fonts
function preload() {
  img = loadImage('images/1.png');
  song = loadSound('music.mp3');
  gameOverSong = loadSound('music/gameover.mp3');
  coinSong = loadSound('music/coin.mp3');
  powSong = loadSound('music/pow.mp3');
  bgImg = loadImage("images/game_bg.png");
  plasticImg = loadImage("images/plastic.png");
  monsterImg = loadImage("images/monster.png");
  gameOverImg = loadImage("images/gameOverImg.png");
  instrImg = loadImage("images/instructions.jpg");
  diverImg = loadImage("images/diver-1.png");
  regularFont ='Georgia';
  scoreFont = loadFont("fonts/Sunrise.ttf");
}

//class to represent the segment/object for the diver 
class Segment {
  constructor(x, y){
    this.x = x;
    this.y = y;
    noStroke();
    fill(55);
    ellipse(this.x, this.y, segWidth);
    image(diverImg, this.x-55, this.y-segWidth, 100,80);
  }
  //methods to change direction of diver
   moveRight(){
    if (this.x > width){
       this.x = 0;
     }else {
    this.x += step;
     }
    noFill();
    ellipse(this.x, this.y,segWidth);
    image(diverImg, this.x-55, this.y-35,100,80);
     
  }
   moveLeft(){
     if (this.x < 0){
       this.x = width;
     }
     else
    {
      this.x -= step;
    }
     //flip the image when diver moves left
     noFill();
    ellipse(this.x, this.y,segWidth);
     push();
     //flipping along x-axis to create turning effect
     scale(-1,1);
    image(diverImg, -(this.x+55), this.y-35,100,80);
     pop();
  }
   moveUp(){
     if (this.y < 0){
       this.y = height;
     }
     else{
       this.y -= step;
     }
     //fill(55);
     noFill();
    ellipse(this.x, this.y,segWidth);
     image(diverImg, this.x-55, this.y-35,100,80);
  }
   moveDown(){
     if (this.y > height){
       this.y = 0;
     }
     else {
       this.y += step;
     }
     noFill();     
    ellipse(this.x, this.y,segWidth);
    image(diverImg, this.x-segWidth, this.y-35,100,80);
  }
  updatePosition()
  {
    if (direction === 'right')
    {
     this.moveRight();
    }
  if (direction === 'left')
    {
      this.moveLeft();    
    }
  if (direction === 'up')
    {
      this.moveUp();
    }
  if (direction === 'down')
    {
      this.moveDown(); 
    }
  }
  //get and set methods
  getX(){
    return this.x;
  }
  getY(){
    return this.y;
  }
  blink(){
    fill(random(200,255));
    ellipse(this.x,this.y, segWidth);
  }
  setX(x){
    this.x=x;
  }
  setY(y){
    this.y=y;
  }
}

//during set up, play song, initialize the x and y coordinates for monsters, and create diver segment 
function setup() {
  cnv = createCanvas(600, 600);
 // frameRate(20);
  song.loop();
  
  //for scrolling background
  x2=width;
  newSeg = new Segment(width/2, height/2);
  for (i =0; i<numOfMonsters; i++)
    {
      append(monsterXs,random(0,width));
      append(monsterYs,random(0,height));
      append(monsterSpeeds, random(-2,2));
    }
  
  //increase speed of monsters and diver and increment number of monsters every 20 seconds when game is playing
  setInterval(increaseSpeed, 20000); 
}

function draw() {
//draw according to the state of the game 
  
  //reset game whenever game is started again
  if (gameState === 'start'){
    if (reset === true){
      resetGame();
      reset = false;
    }
    playgameover=true; //flag to check if game over song should be played or not
    startPage(); //if game started, show start page
  }
  else if (gameState === 'play'){
    playGame(); 
  }
  //switch song when game/level is over and show the stop page
  else if (gameState === 'stop'){
    song.stop();
    if(playgameover === true){
      gameOverSong.loop();
      playgameover = false;
    }
    stopPage();
  } 
}

function startPage(){
   textAlign(LEFT, BASELINE);
  image(img, 0, 0,600,600);
  noFill();
  //when mouse enters the canvas - display the start button
  if (mouseX< 580 && mouseX > 20 && mouseY < 580 && mouseY > 20) 
     {
      fill('rgba(10,108,180,0.85)');
      ellipse(width*4/5, height*5/6, 150,100);
      fill('white');
      textFont(scoreFont);
      textSize(32);
      text('Start',425, 513);
     } 
  //when mouse hovers over start button, change its color
    if (mouseX < width*4/5+75 && mouseX > width*4/5-75 && mouseY < height*5/6 + 50 && mouseY > height*5/6-50)
    {
      fill('#6338B1');
      ellipse(width*4/5, height*5/6, 170,120);
      textFont(scoreFont);
      textSize(38);
      fill('white')
      text('Start',415, 513);
      //if start button is pressed, change game state
      if (mouseIsPressed)
          {
            gameState = 'play'
          }
    
    }
  fill(255, 60, 100);
}

function playGame(){
  smooth();
  background('rgb(100,229,255)');
  showInstructions();
if (startPlaying === true){
    startPlayingGame();
  }
}

function showInstructions(){
  background('rgb(100,117,255)')
  fill(255)
  square(25,25,550);
  image(instrImg, 30,30,540,540);
  textSize(20);
  textFont(scoreFont)
  fill('white');
   textAlign(CENTER);
  text('Instructions', width/2, 100);
  textSize(18);
  
   textAlign(LEFT,BASELINE);
  text ('Welcome!\n\nToday, you are a diver. Your mission is to \ncollect as much plastic waste from the \nocean as possible to protect marine life.\n\nMove around using the left, right, up, and \ndown arrow keys on your keyboard to \ncollect trash.\n\nEvery piece of trash collected = +10 points\n\nBeware! The ocean has monsters! Life = 100. \nEach encounter with a monster = -10 life.\nCome on! Let us free the ocean from plastic \nwaste!', 70, 130, width);
  ellipse(width/2,555, 80);
  fill(0);
  text('GO', width/2-12,560)
  if (mouseX > width/2-50  && mouseX < width/2+50 && mouseY > 540-50 && mouseY < 540+50)
    {
      fill('rgb(221,166,252)');
       ellipse(width/2,555, 80);
      fill(255);
      text('GO', width/2-12,560)
      if(mouseIsPressed)
     {
       startPlaying = true;
     }
    }
}

function startPlayingGame()
{
  //scrolling background
  background('rgb(100,229,255)');
  image(bgImg, x1, 0, width, height);
  image(bgImg, x2,width, height);
  
  //starting text
  text('Use arrow keys to start moving', width/4, height/3);
  fill(55);
  ellipse(width/2, height/2, segWidth-20);
  
  //if player starts moving, begin updating position - start game
  if (direction === 'up' || direction === 'down' || direction === 'left' || direction === 'right' )
 {
   background('rgb(100,229,255)');
   image(bgImg, x1, 0, width, height);
   image(bgImg, x2, 0, width, height);
   
  x1 -= scrollSpeed;
  x2 -= scrollSpeed;
  
  if (x1 < -width){
    x1 = width;
  }
  if (x2 < -width){
    x2 = width;
  }
   newSeg.updatePosition();
   updatePlastic();
   createMonsters();
   checkPlasticCollisions();
   checkMonsterCollisions();
   displayScoreandLife();
   checkLife();
 }
}

function keyPressed() {
  if (keyCode === RIGHT_ARROW) {
    direction = 'right';
    //print('right')
  } else if (keyCode === LEFT_ARROW) {
    direction = 'left';
    //print('left');
  } else if (keyCode === UP_ARROW) {
    direction = 'up';
    //print('up')
  }
  else if (keyCode === DOWN_ARROW) {
    direction = 'down';
    //print('down')
  }
}

function updatePlastic(){
  image(plasticImg, plasticX, plasticY, plasticWidth, plasticHeight);

}
//for every monster, update its speed and if it collides with the walls, reverse their direction of motion
function createMonsters(){
  for (let i = 0; i < numOfMonsters; i++){
    image(monsterImg, monsterXs[i], monsterYs[i], monsterWidth, monsterHeight);
    
    monsterXs[i] += monsterSpeeds[i];
  if (monsterXs[i] < 0 || monsterXs[i]+monsterWidth > width)
    {
      monsterSpeeds[i] = -monsterSpeeds[i];
    }
  }
}
//if plastic collected, generate a new one on the canvas at a random location and update score
function checkPlasticCollisions(){
  //print(newSeg.getX(), " and ", plasticX);
  if (newSeg.getX() > plasticX && newSeg.getX() < plasticX + plasticWidth && newSeg.getY() > plasticY && newSeg.getY() < plasticY + plasticHeight)
    {
      coinSong.play();
      score += 10;
      plasticX = random(0,width-plasticWidth);
      plasticY = random(0,height-plasticHeight);
      image(plasticImg, plasticX, plasticY, plasticWidth, plasticHeight);
    }
}
//if monster collides collected, generate a new one on the canvas at a random location and decrement life
function checkMonsterCollisions(){
  for (let i = 0; i < numOfMonsters; i++){
    if (newSeg.getX() > monsterXs[i] && newSeg.getX() < monsterXs[i] + monsterWidth && newSeg.getY() > monsterYs[i] && newSeg.getY() < monsterYs[i] + monsterHeight)
    {

      powSong.play();
      newSeg.blink();
      monsterXs[i] = random(0,width-monsterWidth);
      monsterYs[i] = random(0,height-monsterHeight);
      life -=10;
    }
  }
}

//when life reaches zero, end the game
function checkLife(){
  if (life === 0){
    gameState = 'stop';
  }
}

function displayScoreandLife(){
  fill('rgb(138,63,196)')
  textFont(scoreFont);
  textSize(18);
  text('Highscore: ' + highscore + '  Score: '+ score, 10,20);
  text('Life remaining: '+ life, 390,20);
}

function stopPage(){
  background('rgb(100,229,255)');
  image(gameOverImg, 0,0,width,height);
  fill('white');
  textAlign(CENTER);
  textFont(scoreFont);
  textSize(24);
  if (score > highscore)
  {
    print('score is ' + score + ' highscore is ' + highscore);
    
    text('Congratulations. You helped free the ocean of harmful plastic and you just beat your previous highscore!', 0, height/2, width);
    fill('gold')
    text('New Highscore =  ' + score, 10, height*2/3, width);
  }
    else 
    { 
      fill('white');
       text('Congratulations. You helped free the ocean of harmful plastic!',10, height/2, width);
      
       text('Score =  ' + score + '\n\nHighscore = '+ highscore, 10, height*2/3, width);
    }
  fill('red')
  ellipse(width/2, 530,200,80);
  fill('white')
  text('Play Again', 295, 538);
  if (mouseX > width/2-100  && mouseX < width/2+100 && mouseY > 530-40 && mouseY < 530+40){
    fill('gold');
    ellipse(width/2, 530,200,80);
    fill('white')
    text('Play Again', 295, 538);
    if (mouseIsPressed){
      reset = true;
      gameOverSong.stop();
      gameState='start';
    }
  }
}

function resetGame(){
  gameOverSong.stop();
  song.loop();
  life =100;
  if (score > highscore){
    highscore = score;
  }
  score = 0;
  startPlaying=false;
  x1=0;
  x2=width;
  direction='start';
  newSeg.setX(width/2);
  newSeg.setY(height/2);
  
  for (i = numOfMonsters; i > 2;i--)
  {
    monsterSpeeds.splice(i, 1);
    monsterXs.splice(i, 1);
    monsterYs.splice(i, 1);
  }
  numOfMonsters = 3;
  step =3;
  
  for (i =0; i<numOfMonsters; i++)
    {
      monsterSpeeds[i] = random(-2,2);
      monsterXs[i] = random(0,width);
      monsterYs[i] = random(0,height);
    }
}

function increaseSpeed(){
  if(startPlaying === true){
  step +=0.5;
  
  append(monsterXs,random(monsterWidth,width-monsterWidth));
  append(monsterYs,random(monsterHeight,height-monsterHeight));
  append(monsterSpeeds, random(-2,2));
  numOfMonsters+=1;
  for (i =0; i < numOfMonsters; i++ ){
    if (monsterSpeeds[i]<0){
      monsterSpeeds[i]-=0.5;
    }
    else{
      monsterSpeeds[i]+=0.5
      }
  }
  }
}

Problems/Future Improvements

Some problems I mentioned earlier. Additionally, I was not able to stop the ‘game over’ song and it kept playing repeatedly at the same time. I figure out that it was due to the draw function being repeatedly called so I set up a flag to check if the song was already playing.

For the future, I can add coins/life randomly on the canvas so the user can “gain” life as well. Also, I can also make the plastic move as well. Different difficulty levels can also be designed instead of one continuous level with increasing difficulty. Also, the diver’s collision with the monster or plastic is not the most accurate and can be made more accurate for a more visually appealing effect.

As a whole, I really enjoyed revamping the snakes game to make one that I enjoy and to also raise awareness on an issue I care about.

MidTerm Project- “Help Milo!”

Concept

My midterm project, “Help Milo!”, is a high score based game where the players help a cat named Milo collect as many food items as possible. When it came to deciding the kind of character and theme I would adopt for this game, I instantly knew it had to revolve around cats and specifically my pet cat, unsurprisingly named Milo. Initially, my sketch for this project was very abstract as I had created randomly sized platforms that emerged at different instances. But then the idea of creating an almost picture frame view of the game looked better, so I decided to go for a more organized platform design. 

 

In the game, the player uses LEFT and RIGHT arrow keys to move the cat along the platforms that continuously move upwards. Moving the cat to the top of the ladder causes it to climb down so it avoids touching the top of the frame. The player needs to collect as many food items as possible and avoid touching the upper frame or falling on the bottom frame. The score gets updated and high score is also displayed at the end. The game restarts when ENTER is pressed. 

Game Code

For this game, I created classes for both the player, platforms, and bonuses (food item). Through the classes, I was able to append the class objects into arrays for both platforms and bonuses so that the for loop in the draw function could iterate over the arrays for any function. At a specific frameRate, new platform objects and bonuses are created and then when they move beyond the frame, they are removed from the array to avoid lag in the game. 

 

I think I found it difficult to retrieve the attributes of different classes to use them in another class. I struggled to find a way in which I can use the y coordinate of the platform in the player class so that I can limit the movement of the player beyond the platform. Similarly, I wanted to access the coordinates of the bonuses for the player to collect them in a function. I figured out the solution by passing the entire object ‘platform’ or ‘bonus’ as an argument to the function in player. That way, I was able to access the different attributes of the other classes and use it to change the movement of the player. 

update(platform){
 
  //if the player in on the platform and not on the ladder space, then it stays on the platform
  if(this.y >= platform.y && this.y <= platform.y+platformsize && (this.x<platform.spacex || this.x > (platform.spacex+platform.spacewidth))){
    this.y = platform.y;
    
  }
  //if player on ladder space, it falls
  else if(this.x>platform.spacex && this.x < (platform.spacex+platform.spacewidth) && this.y >= platform.y && this.y <= platform.y+platformsize){
    this.rownum = 0;
    this.y+=1;
    this.col = (this.col+1)%4;
  }
  //initially it falls 
  else{
    this.y+=1;
    //this.col = (this.col+1)%4;

  }
}
//to check if the x and y coodinates of player and food is same to catch the food
bonuscatch(bonus){
  if(this.x>bonus.x && this.x < bonus.x+(bonus.size) && this.y>bonus.y && this.y <bonus.y+(bonus.size)){

    caught = true;
    success.play();
  }
}

 

A bug that constantly proved to be an ever present issue was the code placement in and outside the for loop to iterate over the arrays of platforms and bonuses. Due to the usage of a single for loop within the draw() function, it took me time to figure out how to manage iteration over the different arrays at the same time and call the different functions.  I think I found the use of sprite sheets and variable “gamestate” to change the user interface to be the most interesting to figure out.

Improvements

I think for future improvements, I would really like to implement some sort of obstacle in the game. I was able to implement the obstacles but could not edit their frequency because of the for loop and use of .splice() in the obstacle arrays. I would also like to add different levels to the game, add different elements to the levels to make them complex or easier. Creating different levels of the game through a random structure would also be an interesting aspect to develop.

Midterm Project – Yerkebulan Imanbayev

Concept:

With this project, I decided to focus on the classic game of “Whack-A-Mole.” Initially, I was planning to recreate the game, but then after the realization that it will become more of an exercise than a project, I decided to change the concept. I thought of it more realistically and what a game like that might possibly entail – and one of the interpretations of it could be that it is animal abuse. Therefore, I decided to expand on that concept and use the game as a platform to share awareness about animal abuse. That’s why instead of “whacking” a mole, the user has to “whack” a politician who committed or contributed to animal abuse.

Game features:

When the user hits any key to be redirected to the “game” window, they can see the time and the score being displayed. There are two characters in front of them: the mole and the politician. There is only one way to win in this game: to whack the politician 30 times in the span of 20 seconds. However, there are two different ways to lose in this game: the user can accidentally click on the mole – in which case they lose automatically – or they do not manage to whack the politician 30 times in the span of 20 minutes.

Generally, I attempted to make the game design appealing by creating the background image using DALL-E 2. The colors of the buttons and the text were picked so as to ensure legibility and conveyance of the sense of fun, quirky arcade games that we all know and love. The game is also designed to ensure that all buttons have a sound attached to them, including the background sound that is constantly playing unless the user is on the “win” or “loss” window.

Issues:

There were multiple issues I faced when I first started working on the project. Due to a large number of lines in the code, it was very hard for me to keep track of my variables and the functions. That is why my first step in tackling all of those issues was to give my variables a sensible name and making sure to comment on confusing parts of my code.

The next issue I faced was positioning my characters in the ellipses. I did not know how to do that because the function that contained the coordinates for my ellipses was separate from the class that contained the coordinates for my characters. That is when one of my friends suggested using a double array. After the coordinates of the ellipses were placed in the double array, they were called in the class as the coordinates for the characters.

  //creating an array to hold the coordinates of all the ellipses to refer to it later on
  arrayEllipses = [
    [95, 225],
    [95, 325],
    [95, 425],
    [235, 225],
    [235, 325],
    [235, 425],
    [375, 225],
    [375, 325],
    [375, 425],
  ];
}
//pulling out random elements from my array
   this.randomNumberPolitician = int(random(0, 9));
   this.randomNumberMole = int(random(0, 9));
   
   //check if the integer is the same; if so, choose a different hole for the mole
   while (this.randomNumberPolitician == this.randomNumberMole) {
     hoverMole = false;
     this.randomNumberPolitician = int(random(0, 9));
     this.randomNumberMole = int(random(0, 9));
   }

   //changing the position of the politician if the image is clicked
   if (hoverPolitician && mouseIsPressed) {
     this.politicianX = arrayEllipses[this.randomNumberPolitician][0];
     this.politicianY = arrayEllipses[this.randomNumberPolitician][1];
     this.moleX = arrayEllipses[this.randomNumberMole][0];
     this.moleY = arrayEllipses[this.randomNumberMole][1];
     clicks++;
     buttonSound.play();
   }

One of the elements in the code embedded above was my solution for the next issue I faced: because the coordinates assigned to the characters came from the same array, they were inevitably going to be equal at some point, leading to the mole and the politician appearing in the same ellipse. My way of tackling that was creating a “while()” function that would randomize their position again if their coordinates were equal.

This led me to a different issue: at times, when the character clicks on the politician after their coordinates were reorganized (because they matched as mentioned in the paragraph earlier) the code would consider that to be clicking on the mole and would lead to a loss. I minimized the issue to the best of my abilities by decreasing the range of coordinates that would be considered a “click” for the mole. Despite minimizing that, there is still that probability.

Future improvements:

In the future, I would definitely want to improve the issue mentioned in the previous paragraph in a way that would leave no room for the possibility of losing if the politician was clicked. In the same vein, I would also want to create a more efficient way to match the coordinates of the characters to the coordinates of the ellipses. Despite using the double array, the values within the array were hard-coded.

I would also like to make the design of the characters more game-like by having them appear from under the hole as opposed to simply popping up. I believe that it would make the interaction more user-friendly.

Overall, despite certain issues within the code, I am proud of my work as someone who is learning programming for the first time, and I believe I was able to create a game that the user can play, restart, and enjoy.

Midterm – Ghostly Pong

 

Concept

My midterm project, which is called Ghostly Pong, is similar to the Ping Pong game but instead of 1 disk the players will play with 2, and the disks will keep periodically disappearing and reappearing, just like a ghost.

After hearing some valuable feedback during the class on my midterm progress, I decided to change the concept of my game and make it unique of a kind – something that you can’t play in real life. That was when I came up with the idea of “Ghostly Pong” – a Pong game with a disappearing disk. Furthermore, another special feature that I incorporated into my game which is not present in traditional Pong game is the idea of playing with 2 disks simultaneously.

Game features

  • Each player will have a paddle and an individual score counter
  • Players must hit the disk with their corresponding paddles, if they miss the disk, their opponents will score a point
  • Players can move the paddle only vertically (along the initially specified y-axis)
  • Paddles will be in rectangular shape, the disc will be in circular shape
  • The control key for the player 1 will be “W” and “S” keys, for the player 2 – “UP” and “DOWN” keys
  • The first player to score 10 points wins

 

Game design

In my code, I created different classes for Disk, Paddle, and Game objects. Each of these classes contains corresponding functions that aim at displaying the object, updating the coordinates, making the object move, and perform other features unique to the object. Every time a new game starts, new instances of these objects would be created.

Also, my game consists of 4 stages: Welcome page, Instructions page, Game, Game Over page. The draw() function will draw will display different screens based on the stage we’re in. Transitions from one stage to another is usually handled by the mouse click.

//display for a welcome page
function welcomePageDisplay(){
    image(bgWelcome, 0, 0, RES_W, RES_H);
    
    //restore default alignment modes
    rectMode(CORNER)
    textAlign(LEFT)
  
    fill(255)
    textFont("Lakki Reddy", 25) 
    let msg1 = "Welcome to the"
    
    //place all the text in the center of the screen
    let tWidth1 = textWidth(msg1)
    text(msg1, RES_W / 2 - (tWidth1 / 2), RES_H / 9)
  
    textFont("Comforter Brush",60);
    let msg2 = "Ghostly Pong Game"
    let tWidth2 = textWidth(msg2)
    text(msg2, RES_W / 2 - (tWidth2 / 2), RES_H / 4)
  
    textFont("Lakki Reddy",15) 
    let msg3 = "Click anywhere to continue to instructions page"
    let tWidth3 = textWidth(msg3)
    text(msg3, RES_W / 2 - (tWidth3 / 2), 10*RES_H / 11)

}

There are two separate functions for updating the screen when the player scores a goal or when the game is over: continueGame() get called when the disk enters one of the goals, hence in this function I create a new instances of only Disk objects. In the function restartGame(), I create a new instance of the whole Game, which in turn automatically creates new instances of Disk and Paddle objects.

The game will always have a background music, which will also change based on the game theme the user chooses. Moreover, the collision of the disk and the paddle, as well as the goals will be accompanied by different corresponding sounds (this can make the game a bit easier by giving some hints about the location of the disk when it disappears from screen).

P.S. For all the sound and images I used, I created a separate text file called “links” with the links to the websites from where I took those elements.

Problems I ran into

In terms of technical side, the most difficult and time-consuming part of this project was writing the code for checking the collision the paddle with the disk and making it bounce off as in the real life. In order to achieve this goal, I decided to rely on the physics’ law of collision: when the disk hits the surface, it will bounce with the same speed as before in the opposite direction(conservation of impulse) and it will also bounce off with the same angle (angle of incidence = angle of reflection). After a few trials and error , I succeeded in implementing this collision check and bouncing correctly.

The below code is where I implemented this logical check.

//Code for managing Disk bouncing when it touches paddles 
function Disk_Paddle_Contact(paddle, disk){
        
        //check whether the disk contacted the left paddle
        if ((disk.disk_xc - disk.disk_w/2 < paddle.px + paddle.pw/2) && (disk.disk_yc + disk.disk_h/2 > paddle.py - paddle.ph/2) && (disk.disk_yc - disk.disk_h/2 < paddle.py + paddle.ph/2)){
            if(disk.disk_speed_x < 0){
              
                //play the collision sound when disk touches the paddle
                collisionSound.play()
              
                //print(paddle.px,paddle.py)
                disk.disk_speed_x = - disk.disk_speed_x
            }
        }      
        //check whether the disk contacted the right paddle      
        else if ((disk.disk_xc + disk.disk_w/2 > paddle.pxr - paddle.pw/2) && (disk.disk_yc + disk.disk_h/2 > paddle.pyr - paddle.ph/2) && (disk.disk_yc - disk.disk_h/2 < paddle.pyr + paddle.ph/2)){
            if(disk.disk_speed_x > 0){
              
              //play the collision sound when disk touches the paddle
                collisionSound.play()
              
              //print(paddle.pxr,paddle.pyr)
              disk.disk_speed_x = - disk.disk_speed_x
        
            }
        }   
}
//Code for managing Disk bouncing when it touches boundaries 
    Disk_bouncing(){
        //this if statement will be executed if the disk touches right-side boundaries of the table
        if(this.disk_xc + this.disk_w/2 > RES_W){
          
          //this indicates, player 2 missed the disk, so play 1 scores one point
          score_player1 += 1
          goalSound.play()
          continueGame() 
        }
                
         //this if statement will be executed if the disk touches left-side boundaries of the table       
        else if(this.disk_xc - this.disk_w/2 < 0){
          
           //this indicates, player 1 missed the disk, so play 2 scores one point
           score_player2 += 1
           goalSound.play()
           continueGame() 
        }
  
       
        //this if statement will be executed if the disk touches upper or lower boundaries of the table
        if(this.disk_yc + this.disk_h/2 > RES_H || this.disk_yc - this.disk_h/2 < 0){
            if(RES_H/2 + 100 < this.disk_yc  || this.disk_yc < RES_H/2 - 100){
                this.disk_speed_y = - this.disk_speed_y
            }   
        }
      
        this.disk_xc = this.disk_xc + this.disk_speed_x
        this.disk_yc = this.disk_yc + this.disk_speed_y 

     }
}

What I’m particularly proud of

I’m proud of the good technical decision to create different classes for each of the Disk and Paddle objects, as this allowed me to easily create new instances of them, independently of each other, later in the game and to write a separate function on a global scope called ” Disk_Paddle_Contact(paddle, disk)” which takes instances of those classes and calculates whether there was a contact. I’m also proud of the good game design and the designs of the Welcome and Game Over pages.

Future improvements

Some people may find it hard to hit the disk as it constantly keeps disappearing. Therefore I decided to add some sound stimuli in terms of the collision sound and goal sound to help the user recognize when he/she scores a goal. One of the suggestions for future improvements would be making the paddles blink when the disk is nearby, thus sending some sort of a “hint” to the player of where the disk is located.

Another possible suggestion would be adding a volume slider for the game_display(), so that the user could control the intensity of a background music, and make it lower if it is too loud.

Midterm Assignment – Wordlaga!

Concept

As soon as I heard our midterm assignment was to make a game, I had a galaga in mind because I knew that would be challenging to make yet so fun to both make and play it. Plus I love galaga, it’s one my favorite classic games. However, for the purpose of this assignment, I came up with my own variation of the galaga game, the wordlaga.

Wordlaga is a game where instead of the galaga’s bug enemies, I had a set of alphabets that form a word as the enemies. Players, as galaga, must shoot down the letterin the correct order that forms the target word. If the enemy collides with player or if player shoots the wrong letter, game will be over. Player can move the galaga left and right using arrow keys and shoot using the up arrow key to shoot down the enemy. The goal of the game is shoot down as many words as you can and achieve the highest score possible, or finish the game by emptying the preloaded word bank… if you can.

I came up with this idea while having a conversation with a friend about educational games and I remembered that I mostly played/enjoyed galaga when I was young. Since galaga is such a simple game, why not recreate it for a somewhat educational purpose where young children can learn words by shooting down the letter that form the word in correct order? Thus, wordlaga was created.

Code

This 400 lines worth of coding took hours of debugging and consists of many parts that I am proud of. The two particular parts I would like to mention where one is a mechanism that I am proud for and one that caused me a mind-splitting headache trying to make it work. The proud part of my code is the enemy-bullet collision detection.

for(let i = 0; i < enemyArr.length; i++){
    enemyArr[i].display();
    enemyArr[i].updateVel(player);
    for(let j = 0; j < galagaBulletFired.length; j++){
      if(enemyArr[i].checkHit(galagaBulletFired[j])){
        //if player didn't hit the right word, game is over
        if(enemyArr[i].ascii + 97 != correctAnswer[count]){
          wrongChoiceSound.play();
          gameOver = true;
        }
        else{
          //for each enemy killed correctly, player gets 100 score
          destroyedEnemyArr.push(i);
          score += 100;
          enemyKillSound.play();
          usedBulletArr.push(j);
          count++;
        }
      }
      
      //if the bullets leave the screen, elliminate them
      if(galagaBulletFired[j].position.y < -20)
        galagaBulletFired.splice(usedBulletArr[j],1)
    }
    //if player collides with enemy game is over
    if(enemyArr[i].collisionDetection(player)){
      explosionSound.play();
      gameOver = true;
    }
  }

In this code, I have a nested for loop where one iterates through the array that contains the enemy instances and the the nested loop iterates through the array of bullets fired. For each each enemy, if the enemy instance returns true for a simple collision detection, then the index of this instance will be recorded in a separate array. The enemy instance array will be iterated again after the check for the collision, where it will be checked if the index stored earlier matches an instance. If it does, then that element will be spliced. This collision also checks the content of the enemy instance and if the wrong letter is shot down, the player will lose the game. I’m particularly proud of this piece of code because while it was a bit challenging to figure out how to destroy enemies when they are shot down, I came up with this solution relatively quickly and it worked right away in few attempts.

Now for the code that caused me anger equivalent to the consumption of mint chocolate,

if(frameCount % (310 - 10 * stageLvl) == 0){
  moveEnemy(enemyArr, int(random(0, enemyArr.length)))
}

...

function moveEnemy(enemyArr, enemyNum){
  enemyArr[enemyNum].movement(player); 
}

...

//helper function for enemy movement. 
  updateVel(player){
    //if this enemy is chosen to move, it will start moving towards the player by the player position passed
    //the speed of the enemy moving will be based on the stage level
    if(this.isMoving){
      if(this.position.y < 400){
        let distanceBetween = player.position.x - this.position.x;
        this.velX = distanceBetween * 0.01;
        this.velY = enemySpeed;
      }
      //if the enemy went passed the player and is off the screen, make it return to the top of the screen
      else{
        this.position.y = -150;
      }
    }
  }

...

movement(player){
  this.isMoving = true;
  this.updateVel(player);
}

...

  //if the enemy instance is not called to move, its velocity is fixed 0, but if it is, display will update the enemy's position
  display(){
    if(!this.isMoving && this.position.y == 75){
      this.velY = 0;
    }
    this.position.x += this.velX;
    this.position.y += this.velY;
    image(sprite[this.ascii], this.position.x - enemySize/2, this.position.y - enemySize/2, enemySize, enemySize);
  }
}

This code deals with the movement mechanism of the enemy. In wordlaga, the enemy alphabet, after some time, will move towards the player and try to collide with them. This particular function makes that happen where after some pass in framecount, a random enemy from the array of instances will specifically move towards the player, and return to the top of the screen after it misses the player and goes out of the screen. Each step of making this was so troublesome because satisfying a condition for one of the said functions became the cause for a bug for another. For example, in order for the enemy to track the player position, the movement function must be kept called in the draw function since the player position will be constantly updated. However, if the movement function is kept called in the draw function, all the enemies continuously started to move instead of one by one. This problem was fixed by separating the function that called for movement and actually updating the enemy position. The time restriction in calling this function periodically was done by using the frameCount and modulo division. After a total full day worth of time of debugging, I have reached this stage of this code where it works. Honestly I at this point I am not even proud of this code I am just thankful that it works.

Areas for Improvements

While I am satisfied with my finished product, I can definitely see some potential improvements for this game. First of all, there could be a different stage design where the number of letters could increase as the stage level progresses or having a multiple life system where the player gets three lives instead of just one. I could also add the mechanism of enemy shooting bullets at the player to make the game more interesting or add explosion animation to make the game more visually appealing. I could think of couple other minor improvements to perfect this game, but frankly I need more time to be able to make these improvements since I was sick the past week and took a toll in preparation time. Also, while it might sound easy to make these improvements, we never know what kind of headache causing bugs could occur. This is actually the lesson I learned from this project while dealing with the enemy movement function bugs: what I might consider to be simple implementation could be very difficult for the computer to understand. Overall I had fun making this whole project and hopefully one day I’ll come back to improve it.

Week 7: Midterm Project – Endure.io

Concept

You and your friend are space travellers who roam around various planets just for the fun of it. Racing against each other as you explore few planets is a hobby for both of you. One day, however, your spaceship malfunctions and crashes into a nearby planet. Your friend was so far ahead on the race that s/he does not realise you have crashed. You do not get injured in the crash but your spaceship is damage and all the equipments in the spaceship, including your laser gun.

An entire alien civilisation has seen you crash and perceives you as a threat. They are coming to attack you! Defend yourself with a damaged gun that shoots laser on random directions as you collect resources scattered around the planet to repair your ship and hopefully get back to your friend.

Game Dynamics

This is a simple game with simple dynamics:

  1. The player is movable through the arrow keys.
  2. The aliens/minions are not playable characters but follows the player wherever they go.
  3. Resources and Health Regen spawn in random places

I have made the game as simple as possible so that the player is not overwhelmed with all the functionality possible. This is mainly a time based game where the player needs to survive for the longest period of time and collect about 50 resources to win the game. However there is a catch. The resources spawn every 5 or so seconds and you will only have at most two resources dropped at any given moment. Thus, the player can not wait for all the resources to be dropped then collect it, rather the player constantly needs to move around to collect he resources. This is true for the health regeneration power up as well, however, you can have at most three of these at any given moment on screen.

Code I am proud of

The biggest thing that made working on the code easier was dividing the whole code into various different JS files and having them only focus on certain aspect. For instance, I divided the player, minion and weapon class into different JS files to make debugging and understanding the code easier. This also makes the filing structure logical and effective.

The hardest part of the project when either the laser or the minion were to be deleted from the canvas and the game environment after a collision. The problem is simple; when the laser hits the minion:

  1. The minion has to be deleted
  2. The arrow has to be deleted

Despite the simplicity of the problem, it was not easy to overcome. Let’s just look at minions for now. I had to figure out:

  1. what deleting a minion from the game environment meant.
  2. which minion to delete, and how do I know which is the minion in the array that I had to delete

For the first problem, I realised that deleting simply meant that I had to remove the minion from the minions array. This would simply not display a minion object, hence removing it entirely. The next problem I ran into right after implementing the deletion code was that: as the player moves around, the minion that was deleted was not the ones that the laser hit. The deletion of minions appeared to be random. I had my eureka moment, when I realised I had to specify an id for each minion so that I could identify which laser hit (the lasers are also id‘ed) which minion. This way I could delete the specific minion and not break the code.

I decided to id the objects using their indices in the array. This meant that I had to decrease the id of the objects of the subsequent objects in their arrays by 1. This made id reusable and limited to the number of minions in any given instance.

detectLaserHit() {
    for (let i = 0; i < lasers.length; i++) {
      let laser_pos = lasers[i].getLaserPos();
      
      let d = dist(laser_pos.x, laser_pos.y, this.pos.x, this.pos.y);
      
      if (d < this.radius) {

        this.health--;
        if (this.health <= 0) {
          // Notice we delete the minion according to their id and reset the id in the following lines
          minions.splice(this.id, 1);

          // since the minion id is linked to its index in the array, we are decreasing the id of every minion that comes after the minion we killed in the array.
          for (let i = this.id; i < minions.length; i++) {
            minions[i].id -= 1;
          }
        }
        
        lasers[i].sturdiness--; 
        if (lasers[i].sturdiness <= 0) {
          lasers.splice(i, 1);
        }
      }
    }
  }

Problems Encountered:

There are various problems I ran into and here is a list of them:

Note: italics means that I was able to solve them, while bolded text mean I was not.

  1. Sound: I had an error while running the code. The error told me that play() was being called when a new sound file was being loaded. It appeared it was an error with createAudio, which I swapped out with loadSound. Another problem was that my sound was clipping because I put my play function inside the draw function, which was calling the play function multiple times. I had to take play the sound in setup function.
  2. Event called more than once: When suppose we are at time 10 seconds in the game, at this time I call the upgrade_weapons function. This was however being called multiple times in the course of the 10th second. So, I had to use a boolean flag to make sure I was only calling the functions once.
    if (endurance_time % 10 == 0 && upgrade_flipper) {
      upgrade_weapons();
      upgrade_minions();
      drop_health_power_up();
    }
    // stop upgrading when upgraded once
    // flipping the flippler to true once the second has passed.
    if (endurance_time % 10 == 1) {
      upgrade_flipper = true;
    }
  3. Minions movement: The minions move towards the player in a trivial fashion, just changing direction straight towards the player. This creates a line of minions in the x-axis and y-axis relative to the player. I tried adding in randomness to their movement but this. I did randomise the speeds of every minion to minimise this effect, but it is still persistent.

Future improvements:

There are a lot of thing to improve on this game. Firstly the game dynamics itself. I could add in different levels and improve the weapon in different level. I could also implement a controllable weapon where you can roughly aim where to hit (with enough resources, you can repair your weapon). Another improvement could be adding in a boss alien when killed would give me tons of resources. This game could take a multiplayer approach, where my friend finds me and helps me escape the planet.