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.

Leave a Reply