Final Project Final Documentation

Concept and Design:

For my final project, I created a table piano using the ultra sonic sensor and connected it to the P5js interface. The start screen allows users to choose between three modes: free mode, game mode, and record mode. 

In free mode, users can play the piano for as long as they want and try to figure out a tune on their own. Every time a note is pressed on the piano, the P5js interface shows a different picture for each note so that the experience is more immersive. The free mode allows the user to practice their skills and be as creative as they want to be. 

Record mode as the name suggests records and store each key played by the user and the concatenation of these notes then can be played back in the form of a tune. This allows the user to listen back to what they play and make amends to what needs to be changed in the future. This proves to be a good tool for those who like to be precise and perfect.

In game mode, numbers fall from the sky, and the user would have to press the appropriate number on the table piano. The game eventually keeps getting harder as the score increases. The game gets harder in the form of game speed and the spacing between the numbers. Every un-pressed note/number will result in losing a life and after a certain number of lives lost, the game ends. The current score of the user and the high score of the user is also displayed at the same time encouraging the users to get more competitive in a healthy way. The game mode is basically created to add some fun to piano playing and also to build muscle memory of where what keys are and strong reflexes. This would in turn help the user with reading off of music sheets while playing actual pianos. 

Final Product

Interaction Design:

The project is designed in a way that makes it much more interactive as compared to my previous projects in the class. From the hardware connected to Arduino to the software element in P5js, everything is interactive. The main theme of the project is a table piano and users have to use their hands to play keys and notes. This allows the user to get a different feel of the project as compared to simple buttons on a keyboard or keys on a touch screen phone. When it comes to the P5js user interface, it is designed in a way, that gives full control to the user. There are buttons that change color when you hover over it, suggesting that it is a button. There are instruction screens in all three modes and all the screens have back buttons as well that allow the user to go back to the home screen. Furthermore, before recording, stopping while recording, or playing the recorded tone, the user can use interactive buttons provided to control all of these functions. The user can also see their score going up and lives decreasing while playing the game. In the end, the game has an option of being restarted as well.

Description of P5js code:

The P5js code is developed using Object Oriented Programming to keep the work flow easier, manageable, more efficient. Two classes have been created: the noteDrop class and the Game class. The noteDrop class handles the movements and functionality of a single falling note. On the other hand, the game class is where all the game logic is implemented. The objects of the noteDrop class are stored in an array and then the array is used in various ways depending on the game state. 

To keep track of the different game states, variables and boolean values have been used. These variables are updated based on user input and the game logic. Additionally, messages are sent to and received from Arduino to synchronize the game state. This allows for better user experience where the hardware and software work in perfect harmony.

To ensure a smooth flow of the game, I have also implemented a preload function that loads all the necessary assets before the game begins. This helps to prevent any issues with lag or delays during gameplay.

In order to make the code more manageable and easier to understand, the game has been divided into functions and different game states. This also allows for easier editing and maintenance of the codebase. Here is the code:

new p5(); //to counter the issue with random function

//declare global variables
let highscore = -1;
let globalGameSpeed = 1.5;
let gameObject;
let menuBackgroundPicture1; 
let menuBackgroundPicture2; 
let menuBackgroundPicture3;
let menuBackgroundPicture4; 
let endBackgroundPicture;
let recordScreen1;
let recordScreen2;
let recordScreen3;
let recordScreen4;
let recordScreen5;
let freeScreen0;
let freeScreen1;
let freeScreen2;
let freeScreen3;
let freeScreen4;
let freeScreen5;
let freeScreen6;
let freeScreen7;
let freeScreen8;
let freeScreen9;
let freeScreen10;
let freeScreen11;
let freeScreen12;
let freeScreen13;
let freeScreen14;
let freeScreen15;
let freeScreen16;
let gameScreenBG;
let gameInstructions;
let num1;
let num2;
let num3;
let num4;
let num5;
let num6;
let num7;
let num8;
let num9;
let num10;
let num11;
let num12;
let num13;
let num14;
let num15;
let num16;

let playfair_font; //declare the font named playfair
let background_music;

function preload() //preload all the assets
{
  menuBackgroundPicture1 = loadImage("menu_background1.png");
  menuBackgroundPicture2 = loadImage("menu_background2.png");
  menuBackgroundPicture3 = loadImage("menu_background3.png");
  menuBackgroundPicture4 = loadImage("menu_background4.png");
  endBackgroundPicture = loadImage("end_game.png");
  playfair_font = loadFont("playfairDisplay_font.ttf");
  recordScreen1 = loadImage("record_instruction1.png");
  recordScreen2 = loadImage("record_instruction2.png");
  recordScreen3 = loadImage("record_instruction3.png");
  recordScreen4 = loadImage("record_instruction4.png");
  recordScreen5 = loadImage("record_instruction5.png");
  
  freeScreen0 = loadImage("free01.png");
  freeScreen1 = loadImage("free1.png");
  freeScreen2 = loadImage("free2.png");
  freeScreen3 = loadImage("free3.png");
  freeScreen4 = loadImage("free4.png");
  freeScreen5 = loadImage("free5.png");
  freeScreen6 = loadImage("free6.png");
  freeScreen7 = loadImage("free7.png");
  freeScreen8 = loadImage("free8.png");
  freeScreen9 = loadImage("free9.png");
  freeScreen10 = loadImage("free10.png");
  freeScreen11 = loadImage("free11.png");
  freeScreen12 = loadImage("free12.png");
  freeScreen13 = loadImage("free13.png");
  freeScreen14 = loadImage("free14.png");
  freeScreen15 = loadImage("free15.png");
  freeScreen16 = loadImage("free16.png");
  
  gameScreenBG = loadImage("gameModeBG.png");
  gameInstructions = loadImage("gameModeinstructions.png");
  
  num1 = loadImage("num1-removebg-preview.png");
  num2 = loadImage("num2-removebg-preview.png");
  num3 = loadImage("num3-removebg-preview.png");
  num4 = loadImage("num4-removebg-preview.png");
  num5 = loadImage("num5-removebg-preview.png");
  num6 = loadImage("num6-removebg-preview.png");
  num7 = loadImage("num7-removebg-preview.png");
  num8 = loadImage("num8-removebg-preview.png");
  num9 = loadImage("num9-removebg-preview.png");
  num10 = loadImage("num_a-removebg-preview.png");
  num11 = loadImage("num_b-removebg-preview.png");
  num12 = loadImage("num_c-removebg-preview.png");
  num13 = loadImage("num_d-removebg-preview.png");
  num14 = loadImage("num_e-removebg-preview.png");
  num15 = loadImage("num_f-removebg-preview.png");
  num16 = loadImage("num_g-removebg-preview.png");
  
  background_music = loadSound("music2.mp3");
}


class NoteDrop //noteDrop class that commands the movement of each note
{
  constructor(x, y, noteWidth, noteHeight, noteNumber, canvasWidth, canvasHeight) //constructor initializes everything
  {
    this.note_x = x;
    this.note_y = y;
    this.note_height = noteHeight;
    this.note_width = noteWidth;
    this.canvas_width = canvasWidth;
    this.canvas_height = canvasHeight;
    this.visible = false;
    this.note_number = noteNumber;
  }
  
  movement() //movement of the note from top to bottom of the canvas
  {
    this.note_y = this.note_y + globalGameSpeed; //
    if ((this.note_y + this.note_height >= 0) && (this.note_y + this.note_height <= 20)) //as soon as the bottom of the note enters the canvas
    {
      this.visible = true; //make it visible and this allows for catching it
    }
  }
  
  display() //displays the note
  {
    this.movement();
    if (this.visible == true) //only display the note if it is visible
    {
      if (this.note_number == 1) //for each note
      {
        image(num1, this.note_x, this.note_y); //display its appropriate picture
      }
      else if (this.note_number == 2)
      {
        image(num2, this.note_x, this.note_y);
      }
      else if (this.note_number == 3)
      {
        image(num3, this.note_x, this.note_y);
      }
      else if (this.note_number == 4)
      {
        image(num4, this.note_x, this.note_y);
      }
      else if (this.note_number == 5)
      {
        image(num5, this.note_x, this.note_y);
      }
      else if (this.note_number == 6)
      {
        image(num6, this.note_x, this.note_y);
      }
      else if (this.note_number == 7)
      {
        image(num7, this.note_x, this.note_y);
      }
      else if (this.note_number == 8)
      {
        image(num8, this.note_x, this.note_y);
      }
      else if (this.note_number == 9)
      {
        image(num9, this.note_x, this.note_y);
      }
      else if (this.note_number == 10)
      {
        image(num10, this.note_x, this.note_y);
      }
      else if (this.note_number == 11)
      {
        image(num11, this.note_x, this.note_y);
      }
      else if (this.note_number == 12)
      {
        image(num12, this.note_x, this.note_y);
      }
      else if (this.note_number == 13)
      {
        image(num13, this.note_x, this.note_y);
      }
      else if (this.note_number == 14)
      {
        image(num14, this.note_x, this.note_y);
      }
      else if (this.note_number == 15)
      {
        image(num15, this.note_x, this.note_y);
      }
      else if (this.note_number == 16)
      {
        image(num16, this.note_x, this.note_y);
      }
    }
  }
}

class Game //game class contains all the functionality and game attributes
{
  constructor()
  {
    //defines canvas dimensions
    this.canvasWidth = 800;
    this.canvasHeight = 600;
    
    //defines note dimensions
    this.noteWidth = 75;
    this.noteHeight = 75;
    
    this.gameHealth = 10;
    this.gameScore = 0;
    
    //these arrays contain various note numbers. They are two different arrays in order to avoid overlap of notes
    this.notes1 = [1, 2, 3, 4, 5, 6, 7, 8];
    this.notes2 = [9, 10, 11, 12, 13, 14, 15, 16];
    
    this.noteArray = []; //contains the arrays that are displayed
    this.recordArray = []; //contains the array that is recorded (not used since array now recorded in arduino)
    
    //boolean variables to control the game
    this.recordBool = false;
    this.playBool = false;
    this.playingScreen = false;
    this.instructionsBool = true;
    this.backgroundNumber = 0;
    
    this.arrayCounter = 0;
    this.notesInAnArray = 8;
    this.clickCount = 0;
    
    //dimensions of various buttons
    this.menuButtonWidth = 282
    this.menuButtonHeight = 56;
    this.menuButtonX = 38;
    this.menuButton1Y = 357;
    this.menuButton2Y = 445;
    this.menuButton3Y = 533;
    
    this.recordButtonX = 212;
    this.recordButtonY = 369;
    this.recordButtonWidth = 393;
    this.recordButtonHeight = 80;
    
    this.homeButtonX = 22;
    this.homeButtonY = 33;
    this.instructionButtonX = 690;
    this.instructionButtonY = 33;
    this.homeButtonWidth = 95;
    this.homeButtonHeight = 40;
    
    //used to increase game speed
    this.difficultyParameter = 20;
    
    this.gameState = "menuScreen";
    
    this.noteSpacing = 2.5; //this is the random spacing between notes in order to increase difficulty
    
    for (let i = 0; i < this.notesInAnArray; i++) //constructs and initializes note objects
    {
      this.noteArray[i] = new NoteDrop(random(0, this.canvasWidth - this.noteWidth), (i + 1)*(-this.noteHeight * this.noteSpacing), this.noteWidth, this.noteHeight, this.notes1[i], this.canvasWidth, this.canvasHeight);
    }
  }
  
  rainMovement() //rain like movement of notes from the top of canvas to the bottom
  {
    for (let i = 0; i < this.notesInAnArray; i++)
    {
      this.noteArray[i].display();
      fill(0,0,0);
      
      if (this.noteArray[i].note_y > this.canvasHeight && this.noteArray[i].visible == true) //if the note is not caught and goes below canvas
      {
        this.gameHealth = this.gameHealth - 1; //you lose a life
        if (this.gameHealth == 0) //if the lives go to zero
        {
          this.gameState = "endScreen"; //the game ends
        }
        this.noteArray[i].visible = false; //the note also becomes invisible
      }
      
      if (this.noteArray[i].note_y > this.canvasHeight) //as soon as the note goes below the canvas
      {
        this.arraySwitch(i); //switch its number and it should re-emerge at the top of the canvas
      }
    }
  }
  
  arraySwitch(indexOfArray) //this function changes the note numbers in order to make them random and avoid overlap
  {
    if (this.arrayCounter % 2 == 0) //arrayCounter keeps track of what array of notes is being used
    {
      //if array counter is even that means first array is being used and so
      this.noteArray[indexOfArray].note_number = this.notes2[indexOfArray]; //you change the array note numbers and get them from notes2 array
      this.noteArray[indexOfArray].note_y = this.noteArray[this.notesInAnArray -1].note_y - ((indexOfArray + 1) * (this.noteHeight * this.noteSpacing)); //change the y value and ensure all of them are almost equidistant and there is no overlap
      this.noteArray[indexOfArray].note_x = random(0, this.canvasWidth - this.noteWidth); //the x coordinates are random
      this.noteArray[indexOfArray].visible = false; //ensure that the visibility is off
    }
    else
    {
      //same thing here but the notes are taken from the first array instead of the second one
      this.noteArray[indexOfArray].note_number = this.notes1[indexOfArray];
      this.noteArray[indexOfArray].note_y = this.noteArray[this.notesInAnArray -1].note_y - ((indexOfArray + 1) * (this.noteHeight * this.noteSpacing));
      this.noteArray[indexOfArray].note_x = random(0, this.canvasWidth - this.noteWidth);
      this.noteArray[indexOfArray].visible = true;
    }
    
    if (indexOfArray == this.notesInAnArray - 1) //when all the 8 notes in an array have gone beneath the canvas, 
    {
      shuffle(this.notes1, true); //shuffle both arrays and
      shuffle(this.notes2, true);
      this.arrayCounter = this.arrayCounter + 1; //increment the counter to now go for the other array
    }
  }
  
  recordNotes(valueOfNote) //records the value of notes and appends them to the array (not used)
  {
    append(this.recordArray, valueOfNote);
  }
  
  playRecordedNotes() //plays all the notes that are recorded
  {
    let noteString = "102\n"; //sends a message to arduino and allows arduino to know when to play the notes
    writeSerial(noteString);
    
    this.playBool = true; //the game is now in playing mode
    this.recordBool = false; //and not in recording mode
  }
  
  display() //display function of the whole game
  {
    if (this.gameState == "menuScreen") //if the game is in menu mode
    {
      image(menuBackgroundPicture1, 0, 0); //display the menu picture
      //and if the mouse hovers over a button, change the color of the button
      if (mouseX >= this.menuButtonX && mouseX <= this.menuButtonX + this.menuButtonWidth && mouseY >= this.menuButton1Y && mouseY <= this.menuButton1Y + this.menuButtonHeight)
      {
        image(menuBackgroundPicture2, 0, 0);
      }
      else if (mouseX >= this.menuButtonX && mouseX <= this.menuButtonX + this.menuButtonWidth && mouseY >= this.menuButton2Y && mouseY <= this.menuButton2Y + this.menuButtonHeight)
        {
          image(menuBackgroundPicture3, 0, 0);
        }
      else if (mouseX >= this.menuButtonX && mouseX <= this.menuButtonX + this.menuButtonWidth && mouseY >= this.menuButton3Y && mouseY <= this.menuButton3Y + this.menuButtonHeight)
        {
          image(menuBackgroundPicture4, 0, 0);
        }
    }
    else if (this.gameState == "gameScreen") //if game mode is on
    {
      if (this.instructionsBool == true) //see if the instructions page is up
      {
        image(gameInstructions, 0, 0); //provide instructions to the user
      }
      else //else it would be game mode
      {
        image(gameScreenBG, 0, 0); //add the background to the game
        this.rainMovement(); //make sure the notes keep falling
        textSize(15);
        fill(255,255, 255);
        text("SCORE: ", 10, 20); //display score
        text(this.gameScore, 75, 20);
        text("LIVES: ", this.canvasWidth - 87, 20); //display lives
        text(this.gameHealth, this.canvasWidth - 30, 20);
      }
    }
    else if (this.gameState == "freeScreen") //if the game is in free mode
    {
      this.freeModeBackgrounds(); //display different backgrounds for different notes played
    }
    else if (this.gameState == "recordScreen") //if the game is in record mode
    {
      if (this.recordBool == false && this.playBool == false) //see if its the first page
      {
        image(recordScreen1, 0, 0); //then display the "start recording" image
        if (mouseX >= this.recordButtonX && mouseX <= this.recordButtonX + this.recordButtonWidth && mouseY >= this.recordButtonY && mouseY <= this.recordButtonY + this.recordButtonHeight)
        {
          image(recordScreen2, 0, 0); //hovering over the button would change the button color
        }
      }
      else if (this.recordBool == true && this.playBool == false) //if the button is pressed, then start recording
      {
        let valueOfState = "98\n"; //tell arduino that the game is now in recording mode so that it changes state as well
        writeSerial(valueOfState);
        image(recordScreen3, 0, 0); //show image that says stop recording and play
        if (mouseX >= this.recordButtonX && mouseX <= this.recordButtonX + this.recordButtonWidth && mouseY >= this.recordButtonY && mouseY <= this.recordButtonY + this.recordButtonHeight)
        {
          image(recordScreen4, 0, 0); //hovering over the button changes the button color
        }
      }
      else if (this.recordBool == false && this.playBool == true) //when the button is pressed that says stop recording
      {
        image(recordScreen5, 0, 0); //image is display that says playing
        this.playRecordedNotes(); //recorded notes are then played
      }
    }
    else if (this.gameState == "endScreen") //when the lives are lost and game ends
    {
      let stateOfArduino = "103\n"; //tell the arduino that game has ended (sets arduino back to the start position)
      writeSerial(stateOfArduino);
      image(endBackgroundPicture, 0, 0); //show game end image
      if (this.gameScore > highscore) //calculate if you have a highscore
      {
        highscore = this.gameScore; //if yes then change the highscore value to your current score
      }
      fill(255, 255, 255);
      textSize(23);
      text("HIGH SCORE: ", 295, 365); //display high score
      text(highscore, 460, 365);
      text("YOUR SCORE: ", 295, 475); //display your score
      text(this.gameScore, 460, 475);
    }
    else
    {
      background(255, 0, 0);
    }
  }
  
  backgroundVariableChange(keyValue) //changes the background variable based on the data received
  {
    this.backgroundNumber = keyValue;
  }
  
  freeModeBackgrounds() //the background variable is used to display appropriate pictures for each note
  {
    if (this.backgroundNumber == 0) //every note has a different picture associated with it
    {
      image(freeScreen0, 0, 0);
    }
    else if (this.backgroundNumber == 1)
    {
      image(freeScreen1, 0, 0);
    }
    else if (this.backgroundNumber == 2)
    {
      image(freeScreen2, 0, 0);
    }
    else if (this.backgroundNumber == 3)
    {
      image(freeScreen3, 0, 0);
    }
    else if (this.backgroundNumber == 4)
    {
      image(freeScreen4, 0, 0);
    }
    else if (this.backgroundNumber == 5)
    {
      image(freeScreen5, 0, 0);
    }
    else if (this.backgroundNumber == 6)
    {
      image(freeScreen6, 0, 0);
    }
    else if (this.backgroundNumber == 7)
    {
      image(freeScreen7, 0, 0);
    }
    else if (this.backgroundNumber == 8)
    {
      image(freeScreen8, 0, 0);
    }
    else if (this.backgroundNumber == 9)
    {
      image(freeScreen9,0, 0);
    }
    else if (this.backgroundNumber == 10)
    {
      image(freeScreen10, 0, 0);
    }
    else if (this.backgroundNumber == 11)
    {
      image(freeScreen11, 0, 0);
    }
    else if (this.backgroundNumber == 12)
    {
      image(freeScreen12, 0, 0);
    }
    else if (this.backgroundNumber == 13)
    {
      image(freeScreen13, 0, 0);
    }
    else if (this.backgroundNumber == 14)
    {
      image(freeScreen14, 0, 0);
    }
    else if (this.backgroundNumber == 15)
    {
      image(freeScreen15, 0, 0);
    }
    else if (this.backgroundNumber == 16)
    {
      image(freeScreen16, 0, 0);
    }
  }
  
  
  notePressed(keyValue) //this is used in game Mode - keyValue is the key pressed on the table piano sent by arduino to p5js
  {
    for (let i = 0; i < this.notesInAnArray; i++) //in all the arrays
    {
      if (this.noteArray[i].visible == true) //if the note is visible (on screen)
      {
        if (keyValue == this.noteArray[i].note_number) //check if the key pressed matches a note
        {
          this.gameScore = this.gameScore + 1; //increase the game score
          this.noteArray[i].visible = false; //make the visibility false
          if (this.gameScore % this.difficultyParameter == 0 && this.noteSpacing > 1.5) //make the game harder but not very hard
          {
            this.noteSpacing = this.noteSpacing - 0.3; //decrease the distance between the notes
            globalGameSpeed = globalGameSpeed + 0.5; //increase game speed
          }
          break; //avoid checking other keys because already one has been found
        }
      }
    }
  }
}

//setup function
function setup() {
  createCanvas(800, 600); //creates canvas of desired size
  textFont(playfair_font); //use the playfair font to keep everything consistent
}

gameObject = new Game(); //create object of the game class

function draw() { 
  background(220); //arbitary background for debugging
  // if (gameObject.gameState == "menuScreen") //only play the music when the user is in the menu
  // {
    if (!background_music.isPlaying()) //ensure the music keeps looping forever
    {
      background_music.play(); //but also ensures that it doesnt start from the start every time
    }
  // }
  // else //if the game is in any other state
  // {
  //   if (background_music.isPlaying()) //check if the music is playing
  //   {
  //     background_music.stop(); //if yes then stop the music
  //   }
 // }
  gameObject.display(); //displays all the screens
}

function keyTyped() //key typed built in function
{
  
  if (gameObject.gameState == "endScreen") //when the game has ended
  {
      if (key === 'h' || key === 'H') //you can press h to return to home/restart game
      {  
        gameObject = new Game(); //a new object is assigned to the variables and the constructor initializes everything
      }
  }
  
  if (gameObject.gameState == "menuScreen") //if the game is in menu screen
  {
    if (keyCode == ENTER) //use enter to 
    {
      setUpSerial(); //set up serial
    }
  }
  return false;
}

function readSerial(data) //callback function for read serial
{
  if (data != null) //if the data is good
  {
      if (gameObject.gameState == "gameScreen") //if game mode is on
      {
        if (data >= 1 && data <= 16) //and the data is between the range we want (the keys)
        {
          gameObject.notePressed(data); //tell the game that a note has been pressed and which one
        }
      }
      
      if (gameObject.gameState == "recordScreen" && gameObject.recordBool == true) //if the game is in record mode and is recording
      {
        if (data >= 1 && data <= 16) //if any note is pressed
        {
          gameObject.recordNotes(data); //record the notes (not used)
        }
      }
    
    if (gameObject.gameState == "freeScreen") //if the game is in free mode
    {
      if (data >= 1 && data <= 16) //and the data is what we want
      {
        gameObject.backgroundVariableChange(data); //we know a note has been played and so change the backgrounds
      }
    }
  }
}

function mouseClicked() //mouseclicked function
{
  if (gameObject.gameState == "menuScreen") //if the game is in menu screen
    {
      //whatever button is pressed, change the gameState to that specific state
      if (mouseX >= gameObject.menuButtonX && mouseX <= gameObject.menuButtonX + gameObject.menuButtonWidth && mouseY >= gameObject.menuButton1Y && mouseY <= gameObject.menuButton1Y + gameObject.menuButtonHeight)
      {
        gameObject.gameState = "freeScreen";
        let screenState2 = "97\n"; //tell arduino that we are in free mode now
        writeSerial(screenState2);
      }
      else if (mouseX >= gameObject.menuButtonX && mouseX <= gameObject.menuButtonX + gameObject.menuButtonWidth && mouseY >= gameObject.menuButton2Y && mouseY <= gameObject.menuButton2Y + gameObject.menuButtonHeight)
      {
        gameObject.gameState = "gameScreen";
        gameObject.instructionsBool = true;
      }
      else if (mouseX >= gameObject.menuButtonX && mouseX <= gameObject.menuButtonX + gameObject.menuButtonWidth && mouseY >= gameObject.menuButton3Y && mouseY <= gameObject.menuButton3Y + gameObject.menuButtonHeight)
      {
        gameObject.gameState = "recordScreen"; //change the mode to record mode
      }
    }
  
  if (gameObject.gameState == "recordScreen") //if the game is in record mode
  {
    //check to see if the button is pressed or not and then change boolean variables accordingly
    if (mouseX >= gameObject.recordButtonX && mouseX <= gameObject.recordButtonX + gameObject.recordButtonWidth && mouseY >= gameObject.recordButtonY && mouseY <= gameObject.recordButtonY + gameObject.recordButtonHeight && gameObject.recordBool == false && gameObject.playBool == false)
    {
      gameObject.recordBool = true;
      gameObject.clickCount = 0; //this is so that button is not accidentally double clicked
    }
    else if (mouseX >= gameObject.recordButtonX && mouseX <= gameObject.recordButtonX + gameObject.recordButtonWidth && mouseY >= gameObject.recordButtonY && mouseY <= gameObject.recordButtonY + gameObject.recordButtonHeight && gameObject.recordBool == true && gameObject.playBool == false)
    {
      gameObject.clickCount++;
      if (gameObject.clickCount >= 3)
      {
        gameObject.recordBool = false;
        gameObject.playBool = true;
        gameObject.playingScreen = true;
      }
    }
  
    if (mouseX >= gameObject.homeButtonX && mouseX <= gameObject.homeButtonX + gameObject.homeButtonWidth && mouseY >= gameObject.homeButtonY && mouseY <= gameObject.homeButtonY + gameObject.homeButtonHeight && gameObject.playingScreen == true)
    {
      gameObject.playingScreen = false;
      gameObject.gameState = "menuScreen"; //go to the menu state if the home button is pressed
    }
  }
  
  if (gameObject.gameState == "freeScreen") //if the game is in free mode
  {
    if (mouseX >= gameObject.homeButtonX && mouseX <= gameObject.homeButtonX + gameObject.homeButtonWidth && mouseY >= gameObject.homeButtonY && mouseY <= gameObject.homeButtonY + gameObject.homeButtonHeight)
    {
      //if the home button is pressed
      gameObject.backgroundNumber = 0; //reinitalize variable so that instruction screen pops up again
      gameObject.gameState = "menuScreen"; //go back to menu state
      let screenState3 = "104\n"; //tell arduino that we are going back to menu screen
      writeSerial(screenState3);
    }
  }
  
  if (gameObject.gameState == "gameScreen") //if the game is in game mode
  {
    if (gameObject.instructionsBool == true && mouseX >= gameObject.instructionButtonX && mouseX <= gameObject.instructionButtonX + gameObject.homeButtonWidth && mouseY >= gameObject.instructionButtonY && mouseY <= gameObject.instructionButtonY + gameObject.homeButtonHeight)
    {
      //if the instructions button is pressed
      gameObject.instructionsBool = false; //set the boolean to false
      let gameScreenState = "99\n"; //tell arduino that we are in game mode now
      writeSerial(gameScreenState);
    }
    
    if (gameObject.instructionsBool == true && mouseX >= gameObject.homeButtonX && mouseX <= gameObject.homeButtonX + gameObject.homeButtonWidth && mouseY >= gameObject.homeButtonY && mouseY <= gameObject.homeButtonY + gameObject.homeButtonHeight)
    {
      //if the home button is pressed
      gameObject.instructionsBool = true; //ensure the next time instruction page pops up
      gameObject.gameState = "menuScreen"; //go back to menu screen mode
      let stateOfArduino2 = "103\n"; //tell arduino as well that you have gone back to menu screen mode
      writeSerial(stateOfArduino2);
    }
  }
}

remove();

Description of Arduino Code:

The program reads data from an ultrasonic sensor and responds by generating tones through a buzzer and turning on an LED. The program has four different states, menu mode, game mode, record mode, and free mode, which are determined by input from the P5js interface.

In the beginning, the code defines the pins for the ultrasonic sensor, buzzer, buttons, and LED, and initializes several variables used in the program, including pressed, distance, arduinoState, recordCounter, and recordArray.

The first state is menu mode, indicated by arduinoState == 100. In this mode, the program waits for input from the connected P5js program. If it receives the value 99, it changes the state to game mode, if it receives 98, it changes the state to record mode, and if it receives 97, it changes the state to free mode.

In game mode, the program turns on the LED to indicate that the ultrasonic sensor is working, reads sensor data, and generates tones based on the distance measured. It also checks for input from the P5js program and leaves the state if it receives the value 103.

In record mode, the program reads data from the ultrasonic sensor, turns on the LED, and waits for input from the P5js program. If it receives the value 102, it plays back the recorded notes stored in recordArray, resets the state to menu mode, and sets recordCounter to 0.

In free mode, the program reads data from the ultrasonic sensor, turns on the LED, and checks for input from the P5js program. It leaves the state if it receives the value 104.

Finally, the playRecordedNote() function is called in the loop() function to generate tones based on the notes stored in recordArray. The function maps the values in the array to specific frequencies and durations using the tone() function.

All over the place, the Serial.read() function is used to skip over irrelevant data that may be present in the serial. Also the break feature is used excessively to break out of the serial.available() loop once the required value is read. Here is the code:

//Define the pins used for the ultrasonic sensor, buzzer, buttons, and LED
const int pingPin = 2; //Trigger Pin of Ultrasonic Sensor
const int echoPin = 3; //Echo Pin of Ultrasonic Sensor
const int buzzerPin = 8;
const int LEDbutton = 7;

//Initialize variables used in the program
int pressed = 0;
long distance = 0;
int arduinoState = 100;
int recordCounter = 0;
int recordArray[100];

int stateValue = 0; //helps change the state of Arduino (sent by p5js)

//Include the pitches library for generating tones
#include "pitches.h"

void setup()
{
 //Start serial communication at 9600 baud
 Serial.begin(9600);

 //Set the ultrasonic sensor pins as output and input respectively
 pinMode(pingPin, OUTPUT);
 pinMode(echoPin, INPUT);

 //Set the LED pin as an output
 pinMode(LEDbutton, OUTPUT);


 //Turn off the LED initially
 digitalWrite(LEDbutton, LOW);
}

void loop() 
{
  if (arduinoState == 100)  //menu mode
  {
    digitalWrite(LEDbutton, LOW); //this means that the sensor is not working
    while (Serial.available()) //if serial is available
    {
      stateValue = Serial.parseInt(); //then parseInt and see what p5js has sent us
      if (stateValue == 99) //game mode
      {
        arduinoState = 99; //set arduino state to 99 as well which is game mode
        digitalWrite(LEDbutton, LOW);
        break; //we have the state so we can now go to that state       
      }
      else if (stateValue == 98) //record mode
      {
        arduinoState = 98; //set arduino to record mode as well
        digitalWrite(LEDbutton, LOW);
        break; //we have the state so we can leave
      }
      else if (stateValue == 97) //free mode
      {
        arduinoState = 97; //set arduino state to free mode
        digitalWrite(LEDbutton, LOW);
        break; //we have the state so we can leave
      }
    }    
  }
  else if (arduinoState == 99) //game Mode
  {
    sensorReading(); //start the sensor
    digitalWrite(LEDbutton, HIGH); //turn on LED to indicate the sensor is on

    while (Serial.available()) //if serial is available
    {
        int changeOfState = Serial.parseInt(); //use the parseInt functiion
        if (changeOfState == 103) //if the value is 103, this means that the p5js program has left the game Mode
        {
          arduinoState = 100; //so arduino can also leave the game mode state
          break;
        }
        Serial.read(); //keep reading and ignoring the serial values until 103 is reached
    }
  }
  else if (arduinoState == 98)
  {
    digitalWrite(LEDbutton, HIGH); //turn on LED to indicate the sensor is on
    sensorReading(); // start the sensor
    int dataNote;

    while (Serial.available()) //see if serial is available
    {
        int playSound = Serial.parseInt(); //parse the serial for integer
        if (playSound == 102) //if it is 102, we know that p5js wants us to play the recorded sound
        {
          for (int i = 0; i < recordCounter; i++)  //go through the array that has recorded notes
          {
            playRecordedNote(recordArray[i]); //play each note
            recordArray[i] = 0; //then empty that location for the next time
          }
          arduinoState = 100; //go back to state 100 which is menu state
          recordCounter = 0; //initialize the record counter to 0 as well for the next time
          break; //break the loop
        }
        Serial.read();
      }
  }
  else if (arduinoState == 97) //if you are in free mode
  {
    sensorReading(); // start the sensor
    digitalWrite(LEDbutton, HIGH); //turn on LED to indicate the sensor is on

    while (Serial.available()) //check if serial is available
    {
        int stateChange = Serial.parseInt(); //parse the serial for integers
        if (stateChange == 104) //if it is 104
        {
          arduinoState = 100; //p5js has left free mode and is in menu mode so arduino does the same
          break;
        }
        Serial.read(); //keep reading serial to ignore irrelevant information
    }    
  }
}

void playRecordedNote(int valueOfNote) //play recorded notes
{
  if (valueOfNote == 1)
  {
    tone(buzzerPin, NOTE_C4, 400); //for 400ms
  }
  else if (valueOfNote == 2)
  {
    tone(buzzerPin, NOTE_E4, 400);
  }
  else if (valueOfNote == 3)
  {
    tone(buzzerPin, NOTE_G4, 400);
  }
  else if (valueOfNote == 4)
  {
    tone(buzzerPin, NOTE_A4, 400);
  }
  else if (valueOfNote == 5)
  {
    tone(buzzerPin, NOTE_C5, 400);
  }
  else if (valueOfNote == 6)
  {
    tone(buzzerPin, NOTE_D5, 400);
  }
  else if (valueOfNote == 7)
  {
    tone(buzzerPin, NOTE_E5, 400);
  }
  else if (valueOfNote == 8)
  {
    tone(buzzerPin, NOTE_G5, 400);
  }
  else if (valueOfNote == 9)
  {
    tone(buzzerPin, NOTE_A5, 400);
  }
  else if (valueOfNote == 10)
  {
    tone(buzzerPin, NOTE_B5, 400);
  }
  else if (valueOfNote == 11)
  {
    tone(buzzerPin, NOTE_D6, 400);
  }
  else if (valueOfNote == 12)
  {
    tone(buzzerPin, NOTE_E6, 400);
  }
  else if (valueOfNote == 13)
  {
    tone(buzzerPin, NOTE_G6, 400);
  }
  else if (valueOfNote == 14)
  {
    tone(buzzerPin, NOTE_A5, 400);
  }
  else if (valueOfNote == 15)
  {
    tone(buzzerPin, NOTE_B5, 400);
  }
  else if (valueOfNote == 16)
  {
    tone(buzzerPin, NOTE_C5, 400);
  }
  else 
  {
    tone(buzzerPin, NOTE_C5, 400);
  }
  delay(408); // a small delay so it sounds nice
}

//Function to read the ultrasonic sensor and play a tone based on the distance measured
void sensorReading()
{
  //Send a short low pulse
  digitalWrite(pingPin, LOW);
  delay(2); //delay to avoid complications
  digitalWrite(pingPin, HIGH); //sends a high pulse for 10 microseconds
  delay(10);
  digitalWrite(pingPin, LOW);
  distance = pulseIn(echoPin, HIGH); //Measure the duration of the ultrasonic pulse and calculate the distance
  distanceNotes(distance); //play the notes based on the distance
  delay(100);
}

void distanceNotes(long distance) 
{
  if (distance >= 2920) //if the distance is greater than 2920
  {
    noTone(8); //then dont play anything
    Serial.println(0); //send 0 to serial to indicate nothing is being played
    pressed = 0; //reinitialize the key pressed variable to 0 so other keys can be pressed
  }
  else if (distance >= 2800 && pressed == 0) //for each distance, there is a specific note
  {
    tone(buzzerPin, NOTE_C5, 400);
    if (arduinoState == 98) //f the arduino is in record mode
    {
      recordArray[recordCounter] = 16; //record the note number in the array
      recordCounter++; //increment counter for the future
    }
    Serial.println(16); //print the serial and send to p5js for it to be dealt with appropriately
    pressed = 1; //do this to avoid the same note being played repeatedly 
  }
  else if (distance >= 2600 && pressed == 0)
  {
    tone(buzzerPin, NOTE_B5, 400);
    Serial.println(15);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 15;
      recordCounter++;
    }
  }
  else if (distance >= 2420 && pressed == 0)
  {
    tone(buzzerPin, NOTE_A5, 400);
    Serial.println(14);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 14;
      recordCounter++;
    }
  }
  else if (distance >= 2260 && pressed == 0)
  {
    tone(buzzerPin, NOTE_G6, 400);
    Serial.println(13);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 13;
      recordCounter++;
    }
  }
  else if (distance >= 2060 && pressed == 0)
  {
    tone(buzzerPin, NOTE_E6, 400);
    Serial.println(12);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 12;
      recordCounter++;
    }
  }
  else if (distance >= 1960 && pressed == 0)
  {
    tone(buzzerPin, NOTE_D6, 400);
    Serial.println(11);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 11;
      recordCounter++;
    }
  }
  else if (distance >= 1690 && pressed == 0)
  {
    tone(buzzerPin, NOTE_B5, 400);
    Serial.println(10);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 10;
      recordCounter++;
    }
  }
  else if (distance >= 1515 && pressed == 0)
  {
    tone(buzzerPin, NOTE_A5, 400);
    Serial.println(9);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 9;
      recordCounter++;
    }
  }
  else if (distance >= 1345 && pressed == 0)
  {
    tone(buzzerPin, NOTE_G5, 400);
    Serial.println(8);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 8;
      recordCounter++;
    }
  }
  else if (distance >= 1180 && pressed == 0)
  {
    tone(buzzerPin, NOTE_E5, 400);
    Serial.println(7);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 7;
      recordCounter++;
    }
  }
  else if (distance >= 1005 && pressed == 0)
  {
    tone(buzzerPin, NOTE_D5, 400);
    Serial.println(6);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 6;
      recordCounter++;
    }
  }
  else if (distance >= 825 && pressed == 0)
  {
    tone(buzzerPin, NOTE_C5, 400);
    Serial.println(5);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 5;
      recordCounter++;
    }
  }
  else if (distance >= 660 && pressed == 0)
  {
    tone(buzzerPin, NOTE_A4, 400);
    Serial.println(4);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 4;
      recordCounter++;
    }
  }
  else if (distance >= 530 && pressed == 0)
  {
    tone(buzzerPin, NOTE_G4, 400);
    Serial.println(3);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 3;
      recordCounter++;
    }
  }
  else if (distance > 310 && pressed == 0)
  {
    tone(buzzerPin, NOTE_E4, 400);
    Serial.println(2);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 2;
      recordCounter++;
    }
  }
  else if (distance <= 310 && pressed == 0)
  {
    tone(buzzerPin, NOTE_C4, 400);
    Serial.println(1);
    pressed = 1;
    if (arduinoState == 98)
    {
      recordArray[recordCounter] = 1;
      recordCounter++;
    }
  }
}

Communication Between Arduino and P5js:

P5js and Arduino are fully connected in this implementation. They communicate with each other to ensure that the program runs smoothly and there is harmony between the software and hardware components of the project. The project has various modes that allows for a greater user experience and so the program is divided in a way that breaks down different modes in game states. This goes for both P5js and Arduino. P5js sends data to Arduino that allows for mode changes in the Arduino program. Without P5js communicating the state it is on, Arduino would have never been able to sync with the program. P5js also tells Arduino when to start recording, stop recording, and then when to play the recorded tune. It also sends data to Arduino in order to communicate when to turn the sensor on or off. Arduino, on the other hand, in the game mode calculates the distance using its ultrasonic sensor and then evaluates what note each distance corresponds to and then finally sends it to P5js interface. The interface then uses the data to either play the game and “catch the notes.” Furthermore, in the free mode as well, Arduino filters out the bad data and then sends only the right notes to P5js in order to be displayed using various images. 

Reflection and Improvement

I’m particularly proud of the user interface that I have designed for the project. I put a lot of thought and effort into making it user-friendly and intuitive, while also allowing for complete freedom and interactivity. The buttons and other interactive features really make the interface come alive, and I think users will find it enjoyable to use.

In addition to the user interface, I am also really proud of how I tackled the game states in the project. Initially, I was worried that I wouldn’t be able to get everything done in time, but through lots of user testing and feedback, I was able to create a code that I’m actually really proud of. The game states add a level of complexity and depth to the project, and I think users will appreciate the extra challenge.

Finally, I’m really happy with the arraySwitch function that I developed for the project. It was a difficult task to create two different arrays with different types of notes and then switch between them every time one array was exhausted. But in the end, it was definitely worth it because it ensures that the same note won’t come up twice in a row, which makes the game more interesting and challenging. 

I am very proud of the end result of my project, but there are still areas that I can improve on. For instance, I expected the ultrasonic sensor to be more accurate, but I was disappointed with the level of precision it provided. In addition, using the ultrasonic sensor made it difficult for users to play notes quickly, as the hand gestures required were awkward and unlike those used for a normal piano. In the future, I will need to explore alternative sensor options to improve user experience.

Another area for improvement would be to incorporate more buttons or LEDs on the Arduino side of the project to make it more interactive. This would enhance the overall experience for users and make it more engaging. Finally, time management was a significant challenge for me during this project, and as a result, I was unable to 3D print or craft a box for the Arduino. As a result, the aesthetics of the final product (hardware) were what I had in mind. In the future, I will need to better manage my time to ensure that I have enough time to complete all aspects of the project, including the aesthetics.

User Testing

According to my friend’s testing of my project, they had some initial confusion with the user interface, as there were no back buttons and some buttons didn’t respond when hovered over. Additionally, they had trouble understanding when to start playing the notes and what each mode was meant for. However, through incorporating their feedback, I was able to make improvements to the instructions screen and provide clearer information on the interface, which ultimately led to a more interactive and controllable experience. My friend found the P5js portion of the program to be smooth and bug-free, which is working well.

On the other hand, my friend had issues with the sensor as it registered a note even while their hand was still moving in front of it. I had to explain to them that only one finger/hand should be used, and it needed to move out of the way of the sensor for the next note to be played. To make this clearer to future users, I could include instructions on the interface or have a printed paper with the instructions. However, the best solution would be to use a different sensor altogether to avoid these issues in the future.

Week 12 – Final_Project_Last_Final

Concept and Design:

For my final project, I will be creating a table piano using the ultra sonic sensor and would be connecting it to the P5js interface. The start screen will allow users to choose between three modes: free mode, game mode, and record mode.

In free mode, users can play the piano for as long as they want and try to figure out a tune on their own. Record mode will be an extension of free mode, where each key played by the user will be stored within P5.js and can be played back by pressing a button.

In game mode, notes will fall from the sky, and the user would have to press the appropriate note on the table piano. The game will eventually keep getting harder as more and more notes fall. Every un-pressed note will result in losing a life and after a certain number of lives lost, the game will end.

[optional] If I get enough time, I might add another feature to the game where users will be presented with a pre-set tune, including its sound, the keys to be played, and the order in which they should be played. The goal will be for the user to memorize the tune/keys and then play it on the piano. Correctly played tunes will earn points, while pressing the wrong key will result in a buzzer sound and the need to start over. Users will have the option to replay the tune or skip to a new one.

Arduino:

The Arduino will mainly have the ultrasonic sensor, LEDs, buttons, and a buzzer. First, Arduino will receive which mode the program is in right now i.e. free mode, record mode, or game mode. This will light up the appropriate LED in the physical setup. After that the ultrasonic sensor will allow the user to “press keys” on the table piano. Varying distance would correspond to different keys on the table. Arduino would send the values of the ultrasonic sensor (distance) to the P5js interface. These values will then be processed by the P5js program and appropriate action will be taken. Furthermore, in the free and record mode, every time a key will be pressed, Arduino would send that specific key to the P5 program to be displayed on the screen. In record mode, when the play button would be hit, the P5 program would send signals to the Arduino program to play the notes stored in the array. 

P5 Program:

The P5js program will mainly have the user interface. This program will allow the user to choose between different modes and would act as the screen for the communication done by Arduino. Once the user decides the game mode, the P5 program would send in this information to the Arduino program so that the ultrasonic sensor and buzzer can be switched on or off appropriately. This program will then receive the notes/distances from Arduino and will process it in a way to recognize the notes and then display the appropriate picture/note on the screen. The program will match the notes received from Arduino during the game mode with the ones it is displaying in order to award the user with scores. Finally when the user will exhaust all of their lives, P5js would send a signal to Arduino, that will in turn play a “game ended” themed sound.

Final Project Proposal

Concept

I am considering expanding my sound project for the final project by creating a table piano with enhanced features. The piano will be based on either an ultrasonic sensor or a pressure sensor. The concept is to integrate P5.js code as a game. The start screen will allow users to choose between three modes: free mode, game mode, and record mode.

In free mode, users can play the piano for as long as they want and try to figure out a tune on their own. Record mode will be an extension of free mode, where each key played by the user will be stored within P5.js and can be played back by pressing a button.

In game mode, users will be presented with a pre-set tune, including its sound, the keys to be played, and the order in which they should be played. The goal will be for the user to memorize the tune/keys and then play it on the piano. Correctly played tunes will earn points, while pressing the wrong key will result in a buzzer sound and the need to start over. Users will have the option to replay the tune or skip to a new one.

To add more challenge, game mode may have different difficulty levels such as easy, medium, and hard, with tunes of varying lengths and number of keys. The fastest player in each category will have their name displayed on the screen throughout the exhibition, motivating others to beat the record.

Week 11 – Serial Communication (In-Class Exercises)

Exercise 1:

During the exercise, we had to use an Arduino with an analog sensor to manipulate a feature in P5js based on the readings. We opted for an ultrasonic sensor to achieve this, where the data obtained was utilized to shift an ellipse along its horizontal axis in P5js.

CODE FOR P5JS

//declare variables for the dimensions and coordiantes of ellipse
let ellipseXcord;
let ellipseYcord;
let ellipseRadius;

function setup() 
{
  createCanvas(500, 500); //create canvas
  background(100);
  textDisplay();
  //set the initial ellipse to be in the centre
  ellipseXcord = width/2;
  ellipseYcord = height/2;
  ellipseRadius = height/10;
}

//displays this text in the starting
function textDisplay()
{
  text("PRESS ANY KEY TO START SERIAL PORT", width/2 - 109, height/2 - 5);
}

function draw() 
{
  background(220);
  if (serialActive) //if the serial is active
  {
    ellipse(ellipseXcord, ellipseYcord, ellipseRadius, ellipseRadius); //then keep changing the coordinates of ellipse bases on ellipseXcord
  }
  else //if the serial is not active
  {
    textDisplay(); //then display the text with instructions
  }
}

function readSerial(data) //call back function
{
  if (data != null) //if the data received is not null
    {
      ellipseXcord = map(data, 300, 1500, ellipseRadius, width - ellipseRadius); //map the value of the data and then update the variable
      //do it with ellipse radius because dont want half the circle off the screen
    }
}

function keyPressed() //if any key is pressed, then set up serial
{
  setUpSerial();
}

CODE FOR ARDUINO

const int pingPin = 2; //Trigger Pin of Ultrasonic Sensor
const int echoPin = 3; //Echo Pin of Ultrasonic Sensor
long distance = 0; //will contain the distance of the object

void setup() {
  //Start serial communication at 9600 baud
  Serial.begin(9600);

  //Set the ultrasonic sensor pins as output and input respectively
  pinMode(pingPin, OUTPUT);
  pinMode(echoPin, INPUT);
}

void loop() {
  //Send a short low pulse
  digitalWrite(pingPin, LOW);
  delay(2); //delay to avoid complications
  digitalWrite(pingPin, HIGH); //sends a high pulse for 10 microseconds
  delay(10);
  digitalWrite(pingPin, LOW); //turn off the ping pin
  distance = pulseIn(echoPin, HIGH); //Measure the duration of the ultrasonic pulse and calculate the distance
  Serial.println(distance); //print it in the serial (P5js)
}

EXERCISE 2

In this particular exercise, the primary objective was to control the LEDs on an Arduino board through P5js code. We utilized the left and right arrow keys, which functioned as input devices to alter the dimness of the bulb. The code starts off with the LED at its maximum brightness but as the right arrow key is pressed, the brightness keeps decreasing. When the left arrow key is pressed, the brightness starts increasing again.

CODE FOR P5JS

let dimnessCounter = 0; //this will control the brightness and dimness of the LED

function setup() 
{
  createCanvas(400, 400);  //create canvas
}


function textDisplay() //display text in the starting
{
  text("PRESS SPACE TO START SERIAL PORT", width/2 - 109, height/2 - 5);
}

function draw() 
{
  
  background(100);  //grey background
  
  if (serialActive) //if serial is active
  {
    text("CONNECTED!", width/2 - 27, height/2 - 5); //tell the user that it is connected
    text("PRESS RIGHT ARROW TO LOWER BRIGHTNESS!", width/2 - 130, height/2 + 15); //give instructions on how to control brightness
  }
  else
  {
    textDisplay(); //display instructions on how to start serial is not active
  }
}


function keyPressed() //built in function
{ 
  if (key == " ") //if space is pressed then
  {
    setUpSerial(); //setup the serial
  }
  else if (keyCode == LEFT_ARROW) //if left arrow pressed
  {
    if (dimnessCounter != 0) //check if brightness is not already at the lowest
    {
      dimnessCounter = dimnessCounter - 50; //if not then decrease by 50
    }
  }
  else if (keyCode == RIGHT_ARROW) //for the right key
  {
    if (dimnessCounter != 250) //check if dimness not at the maximum
    {
      dimnessCounter = dimnessCounter + 50; //increase dimness by 50
    }
  }
    
}

//callback function
function readSerial(data) 
{
    let sendToArduino = dimnessCounter + "\n"; //add the next line to dimness counter
    writeSerial(sendToArduino); //write serial and send to arduino
}

CODE FOR ARDUINO

//declare variables
const int LED_PIN = 5;
int dimness = 0; //contains the dimness fothe LED

void setup() 
{
  Serial.begin(9600); // Start serial communication at 9600 baud

  pinMode(LED_PIN, OUTPUT); //declare pin to be output

  while (Serial.available() <= 0) 
  {
    Serial.println("CONNECTION ESTABLISHED"); // send a starting message
  }
}
void loop() 
{
  //wait for p5 to do something
    while (Serial.available()) //when serial is available
    {
      dimness = Serial.parseInt(); //parse the dimness from the serial to the variable
      Serial.println(dimness); //checking purposes

      if (Serial.read() == '\n') //since the "\n" would be present in the serial, read that and
      {
        analogWrite(LED_PIN, dimness); //write it to the LED
      }
    }
}

EXERCISE 3

In this exercise, we were tasked with creating an interactive p5js program that simulates a bouncing ball, which changes direction based on a variable called ‘wind.’ The ball bounces on the floor and we needed to detect when it hit the floor, so we could turn an LED on. For the second part of this exercise, we used an analog sensor (ultrasonic sensor) to control the wind variable. We set a default value in which the ball would stay in the middle of the screen. When the object is moved to the right from the center, the ball would move to the right, and when moved to the left, the ball would move to the left.

CODE FOR P5JS

//declare variables
let velocity;
let gravity;
let position;
let acceleration;
let wind;
let drag = 0.99;
let mass = 50;
let LEDvalue = 1401;
let goRight = 0;
let goLeft = 0;
let first

function setup() {
  createCanvas(1000, 360); //create canvas
  noFill();
  position = createVector(width/2, 0);
  velocity = createVector(0,0);
  acceleration = createVector(0,0);
  gravity = createVector(0, 0.5*mass);
  wind = createVector(0,0);
}

function textDisplay()
{
  text("PRESS SPACE TO START SERIAL PORT", width/2 - 109, height/2 - 5); //display the appropriate text in the start
}

function draw() {
  background(255);
  if (serialActive) //if the serial is active
  {
    applyForce(wind);
    applyForce(gravity);
    velocity.add(acceleration);
    velocity.mult(drag);
    position.add(velocity);
    acceleration.mult(0);
    ellipse(position.x,position.y,mass,mass);
    if (position.y > height-mass/2)  //if the ball touches the bottom
    {
      velocity.y *= -0.9;  // A little dampening when hitting the bottom
      position.y = height-mass/2;
      LEDvalue = 1401; //take LED value to be 1401 because we dont want the value (1) to be lurking in the serial and then affecting the wind values

    }
    else
    {
      LEDvalue = 1400; //when the LED is off
    }
    
  }
  else
  {
    fill(0);
    textDisplay();
  }
}

function applyForce(force){
  // Newton's 2nd law: F = M * A
  // or A = F / M
  let f = p5.Vector.div(force, mass);
  acceleration.add(f);
}

function keyPressed(){
  if (key==' '){ //if space is pressed, then serial is set up
    setUpSerial();
  }
  
  if (keyCode == DOWN_ARROW) //if down arrow is pressed
  {
    //mass etc is changed
    mass=random(15,80);
    position.y=-mass;
    velocity.mult(0);
  }
}

function readSerial(data) //call back function
{
  let sendToArduino = LEDvalue + "\n"; //sends value of LED to arduino with \n added
  writeSerial(sendToArduino); //write to arduino
  
  
  if (data != null) //if the data is not null and something is received
  {
    console.log(data);
    if (data > 1450) //if the distance is greater than 1450, then
    {
      wind.x = 1; //the ball/wind goes right
    }
    else if (data < 1350) //if the distance is less than 1350
    {
      wind.x = -1; //the ball/wind goes left
    }
  } 
}

CODE FOR ARDUINO

//declare variables
const int LED_PIN = 4;
int LEDvalue = 0; //will contain whether or not the LED should be on or off
int distance = 0; //will contain the distance by ultrasonic sensor
const int pingPin = 2; //Trigger Pin of Ultrasonic Sensor
const int echoPin = 3; //Echo Pin of Ultrasonic Sensor

void setup() 
{
  Serial.begin(9600); // Start serial communication at 9600 baud

  pinMode(LED_PIN, OUTPUT); //pin mode is output

  //Set the ultrasonic sensor pins as output and input respectively
  pinMode(pingPin, OUTPUT);
  pinMode(echoPin, INPUT);

  while (Serial.available() <= 0) 
  {
    Serial.println(1400); //connection establishment. 1400 so that the wind values do not change
  }
}

void loop() 
{
  //wait for p5js
    while (Serial.available()) 
    {
      sensorReading(); //reads data from the sensor

      LEDvalue = Serial.parseInt(); //parsing from the serial written data from p5js

      if (LEDvalue == 1400) //if the LED value is 1400
      {
        digitalWrite(LED_PIN, LOW); //then turn off the LED
      }
      else if (LEDvalue == 1401) //if the LED value is 1401
      {
        digitalWrite(LED_PIN, HIGH); //then turn on the LED
      }
    }
}

//Function to read the ultrasonic sensor and measure distance
void sensorReading()
{
  //Send a short low pulse
  digitalWrite(pingPin, LOW);
  delay(2); //delay to avoid complications
  digitalWrite(pingPin, HIGH); //sends a high pulse for 10 microseconds
  delay(10);
  digitalWrite(pingPin, LOW); //turn off the ping pin
  distance = pulseIn(echoPin, HIGH); //Measure the duration of the ultrasonic pulse and calculate the distance
  Serial.println(distance); //print the serial from distance
}

VIDEO

Week 10 – Table Piano

Concept:

Zaid and I collaborated on a project and aimed to push our limits. Our goal was to create a table piano using an ultrasonic sensor, a sensor we had never used before. As we reminisced about our experiences with musical instruments, we recognized the hassle the school music band encountered carrying bulky and expensive pianos around. To tackle this issue, we came up with a solution: a table piano. We attached the sensor and the Arduino to the table and marked lines on the table using a scale and a marker. A fixed point was established at the end to indicate the piano’s length. We divided the piano into seven notes and utilized the Serial Monitor to determine the distance for each “Piano Key.” Afterwards, we researched online and discovered the notes that play “Jingle Bells” and assigned them to the keys in the table piano. To enhance the piano’s functionality, we included two buttons – one to switch the sensor on and off and the other to play “Jingle Bells” automatically. The first button controlled the sensor and simultaneously controlled the LED as well. This was done to allow the user to know when the sensor is on and when it is off. The second button was implemented as an auto-tune feature to enable users to listen to the tune first and then practice the keys themselves to replicate the tune. The good thing about this piano is that you can add or remove as many keys as you want and practice on specific keys too. Additionally, it is beginner-friendly and portable.

Code and Circuit:

As we built-up upon previous class knowledge of how circuits and Arduino code works, we found the implementing process to be relatively easy. The code and circuit looks like this:

//Define the pins used for the ultrasonic sensor, buzzer, buttons, and LED
const int pingPin = 2; //Trigger Pin of Ultrasonic Sensor
const int echoPin = 3; //Echo Pin of Ultrasonic Sensor
const int buzzerPin = 8;
const int redButton = A3;
const int yellowButton = A2;
const int LEDbutton = 7;

//Initialize variables used in the program
int pressed = 0;
long distance = 0;
int redPosition = 0;
int yellowPosition = 0;
int redCount = 1;
int yellowCount = 1;

//Include the pitches library for generating tones
#include "pitches.h"

void setup()
{
 //Start serial communication at 9600 baud
 Serial.begin(9600);

 //Set the ultrasonic sensor pins as output and input respectively
 pinMode(pingPin, OUTPUT);
 pinMode(echoPin, INPUT);

 //Set the button pins as inputs and the LED pin as an output
 pinMode(redButton, INPUT);
 pinMode(yellowButton, INPUT);
 pinMode(LEDbutton, OUTPUT);

 //Turn off the LED initially
 digitalWrite(LEDbutton, LOW);
}

void loop() 
{
  //Read the positions of the red and yellow buttons
  redPosition = digitalRead(redButton);
  delay(100); //add delay to avoid double reading accidentally
  yellowPosition = digitalRead(yellowButton);
  delay(100);

  //Increment the appropriate button count if a button is pressed
  if (redPosition == HIGH)
  {
    redCount++;
  }

  if (yellowPosition == HIGH)
  {
    yellowCount++;
  }

  //Play Jingle Bells and turn off the LED if both button counts are even (both buttons pressed)
  if (redCount % 2 == 0 && yellowCount % 2 == 0)
  {
    digitalWrite(LEDbutton, LOW); //LED turned off to tell that sensor turned off
    jingleBells();
  }
  else if (redCount % 2 == 0) //if only the red button is pressed and the count is even
  {
    sensorReading(); //then start the sensor
    digitalWrite(LEDbutton, HIGH); //turn on LED to indicate the sensor is on
  }
  else if (yellowCount % 2 == 0) //if yellow button is pressed and is even
  {
    jingleBells(); //play Jingle Bells
    digitalWrite(LEDbutton, LOW); //turn off LED to indicate sensor is off
  }
  else
  {
    digitalWrite(LEDbutton, LOW); //if none of the buttons were pressed and no counter was even
    noTone(8); //play nothing
  }
}

//Function to read the ultrasonic sensor and play a tone based on the distance measured
void sensorReading()
{
  //Send a short low pulse
  digitalWrite(pingPin, LOW);
  delay(2); //delay to avoid complications
  digitalWrite(pingPin, HIGH); //sends a high pulse for 10 microseconds
  delay(10);
  digitalWrite(pingPin, LOW);
  distance = pulseIn(echoPin, HIGH); //Measure the duration of the ultrasonic pulse and calculate the distance
  distanceNotes(distance); //play the notes based on the distance
  delay(408);
}

//function that plays jingle bells automatically
void jingleBells()
{
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_G4, 400);
  delay(408);
  tone(buzzerPin, NOTE_C4, 400);
  delay(408);
  tone(buzzerPin, NOTE_D4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_F4, 400);
  delay(408);
  tone(buzzerPin, NOTE_F4, 400);
  delay(408);
  tone(buzzerPin, NOTE_F4, 400);
  delay(408);
  tone(buzzerPin, NOTE_F4, 400);
  delay(408);
  tone(buzzerPin, NOTE_F4, 400);
  delay(408);
  tone(buzzerPin, NOTE_F4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_D4, 400);
  delay(408);
  tone(buzzerPin, NOTE_D4, 400);
  delay(408);
  tone(buzzerPin, NOTE_E4, 400);
  delay(408);
  tone(buzzerPin, NOTE_D4, 400);
  delay(408);
  tone(buzzerPin, NOTE_G4, 400);
  delay(408);
  yellowCount++; //yellow count increments to stop the tune from playing again (not on loop)
}

void distanceNotes(long distance) 
{
  if (distance >= 1750) //if the distance is greater than 1750
  {
    noTone(8); //then dont play anything
    pressed = 0; //reinitialize the key pressed variable to 0 so other keys can be pressed
  }
  else if (distance >= 1520 && pressed == 0) //for each distance, there is a specific note
  {
    tone(buzzerPin, NOTE_E4, 400);
    pressed = 1; //do this to avoid the same note being played repeatedly 
  }
  else if (distance >= 1220 && pressed == 0)
  {
    tone(buzzerPin, NOTE_G4, 400);
    pressed = 1;
  }
  else if (distance >= 960 && pressed == 0)
  {
    tone(buzzerPin, NOTE_C4, 400);
    pressed = 1;
  }
  else if (distance >= 800 && pressed == 0)
  {
    tone(buzzerPin, NOTE_D4, 400);
    pressed = 1;
  }
  else if (distance >= 350 && pressed == 0)
  {
    tone(buzzerPin, NOTE_F4, 400);
    pressed = 1;
  }
  else if (distance < 350 && pressed == 0)
  {
    tone(buzzerPin, NOTE_B0, 400);
    pressed = 1;
  }
}

Final Product:

The final circuit and implementation looks like this:

Future Improvements:

For the future, we would want to get better ranged sensors to increase the length of the piano. Moreover, an idea that came to our minds a bit too late was allowing the user to press a button and then record what they have played. Then pressing another button would play the tune they have played for them to fully analyze their progress. In the future, we would want to add other tunes that the user could access using different buttons for practice. Additionally, the user accessing different preset keys could also be a possible improvement. Apart from all of this, the Jingle Bells function could have been implemented better using an array and a for-loop. 

 

Week 9 – Annoyance Avoidance

Concept:

While brainstorming with my roommate about what to make for this week’s assignment, my suitemate arrived and expressed his frustration about our friends constantly knocking on our door when we’re not there. He complained about the disturbance, particularly during exams and at night. While apologizing to him, an idea popped into my head – a solution that would inform our friends about our availability. The concept involves two LEDs outside our room representing my roommate and me, which will turn on when we’re in the room and off when we’re not. If neither of us are in the room, we can press the two buttons together before leaving, causing the lights to flicker, indicating that we are not available. This solution could potentially address the problem for our friends and our suitemate.

Implementation:

To help navigate the board, I first drew the circuit diagram so that I would know what component would go where. To make my life easier, I used the red wire for 5V, black wire for GND, green wire for the green button, yellow wire for yellow button and the two white wires for the two LEDs. The circuit diagram looks like this:

I did run into a few problems with the code where pushing the button for a relatively longer period would reverse the effect that I wanted. I then added delay in certain places to resolve this issue. The code looks like this:

//globally declaring the three variables. Setting them to 1 so that all the lights are off
int yellowCount = 1;
int greenCount = 1;
int doubleCount = 1;

void setup() {
  //setting pinmodes. 8 and 13 as output since they are connected to the LEDs
  pinMode(8, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(A2, INPUT); //A1 and A2 as input for the switches
  pinMode(A1, INPUT);
}

void loop() {
  int yellowSwitch = digitalRead(A2); //read the yellow switch
  delay(100); 
  int greenSwitch = digitalRead(A1);
  delay(100);

  if (greenSwitch == HIGH && yellowSwitch == HIGH) //when both the switches are pressed together
  {
    doubleCount = doubleCount + 1; //increment this counter

  }
  else
  {
    if (yellowSwitch == HIGH) //for individual switches, increment their respective counters
    {
      yellowCount = yellowCount + 1;
    }

    if (greenSwitch == HIGH)
    {
      greenCount = greenCount + 1;
    }
  }

  if (doubleCount % 2 == 0) //if the counter is even
  {
    digitalWrite(8, HIGH);  //high for one
    digitalWrite(13, LOW); //low for the other
    delay(150); //wait
    digitalWrite(8, LOW); //then invert the voltages
    digitalWrite(13, HIGH); //to give off a flickering effect
    delay(150);

    yellowSwitch = digitalRead(A2); //read the two again
    greenSwitch = digitalRead(A1);

    if (yellowSwitch == HIGH) //if one of them is pressed
    {
      doubleCount = doubleCount + 1; //then this values goes to odd and then the flickering stops
    }

    if (greenSwitch == HIGH)
    {
      doubleCount = doubleCount + 1;
    }

  }
  else
  {
    if (yellowCount % 2 == 0) //if the yellow is pressed again and the count is even
    {
      digitalWrite(8, HIGH); //turn on the led
    }
    else
    {
      digitalWrite(8, LOW); //otherwise turn it off
    }

    if (greenCount % 2 == 0) //same for the green button
    {
      digitalWrite(13, HIGH);
    }
    else
    {
      digitalWrite(13, LOW);
    }
  }
}

The final implementation looks like this:

Further Improvements:

Instead of LEDs, an LCD or a display could be used that would display messages as “BOTH IN ROOM”, “MOEEZ IN ROOM”, or “NONE IN ROOM” etc. Moreover, the code could use some refinement as well with the delay function. Adding a security layer such as a password using a keypad so that only me and my roommate can update it can also help increase its accuracy.

Unusual Switch ft. New Roommates

Idea:

Since our two new roommates (whose names are yet to be determined, but for now we call them Pikachu and Purple Banana) arrived, we have been showcasing them on our social media. When I learned about the task, I was immediately aware that I needed to incorporate both of them into the project somehow. I came up with the idea of using Pikachu’s movable ears to create a switch that could be turned on and off, and thankfully, it proved successful in the end. Here are my two new roommates, chilling together:

Concept:

By attaching two coins to the underside of the ears in a specific manner, I established a mechanism such that when the ears were lifted, the coins would not make contact with each other. However, as the ears returned to their resting position, the coins would come into contact, closing the circuit and ultimately causing the bulb to light up. The open and closed switch looks like this:

The final product is converted into a GIF and it looks like this:

Improvements:

As a result of time constraints and other academic obligations, I was unable to experiment further with the project. In the future, I intend to commence the assignment much earlier to allow for more exploration. Additionally, the setup proved to be rather unstable as the wires kept becoming disconnected. To improve this, I plan on using better tape and aluminum foil (the one that covers the yogurt jar does not work apparently).

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.

Week 5: Midterm Progress – Crack The Egg

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.

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,” “somewhat bad,” and “rotten.” The player must avoid cracking the “normal” eggs while single tapping to crack the “somewhat bad” eggs and double tapping to crack the “rotten” eggs. As the game progresses, the conveyor belts will increase in speed, making it increasingly difficult for the player to inspect the eggs. Every “normal” egg that passes through uncracked adds to the player’s score. However, if the player cracks a “normal” egg or fails to crack a “bad” or “rotten” egg, they will lose one of their three lives. The player will tap the eggs using their mouse.

Code & Progress:

Once I had determined the game I wanted to create and the functionalities it required, I established a task sequence. First, I had to design a start page for the game, then develop a “Game” class and integrate the start page. I, then planned on adding interactive buttons and verifying their functionality. Next, I wanted to progress to the conveyer belt and the “Egg” class, and finally incorporating them into the “Game” class. Currently, I have constructed the background, launched the game class, and implemented a few initial features for user interactivity. While I have created placeholders for some items, such as buttons, I have verified that they all operate correctly. The progress looks somewhat like this as of now:

//game class will contain everything from user interactivity to the score keeping and life lost etc. Will (probably) be the super class to the egg class
class Game
{
  constructor()
  {
    //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
    this.player_lives = 3; //will keep track of the lives for the player and will be displayes as well
    this.speed = 10; //will control the speed with which 
    this.egg_array = []; //this array will contains objects of egg class using new Egg() and the .push method
    
    //controls what is shown to the user at a given time
    this.menu_screen = true;
    this.start_screen = false;
    this.instruction_screen = false;
    
    //will be removed for the start window but will stay for instructions window
    this.back_button_x = 10;
    this.back_button_y = 10;
    this.back_button_width = 75;
    this.back_button_height = 50;
    
  }
  
  //displays different types of screens to the user
  screenDisplay()
  {
    if (this.menu_screen == true)
    {
      image(startmenu_display_picture, 0,0);
      fill("#541675");
      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);
      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)
      {
        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), 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)/8), this.instruction_button_y + (this.button_height/1.3));
   }
    //if the start button is clicked
    else if (this.start_screen == true)
    {
      //change the background and show under construction text but will call other functions when game is implemented
        background("gold");
        textSize(18);
        fill(255, 255, 255);
        text("UNDER CONSTRUCTION", this.canvas_width/6.5, this.canvas_height/2);
      
      //place holder for the back button that will be used to come back to the menu screen but will probably be removed in the start screen
      back_button.resize(this.back_button_width, this.back_button_height); //resize image to fit the screen size
      image(back_button, this.back_button_x, this.back_button_y);
    }
    //if the instruction button is clicked then this if statement would run to show the instruction screen
    else if (this.instruction_screen == true)
    {
      //changes background for now but is basically a placeholder for an image with all the instructions
      background("gold");
      textSize(18);
      fill(255, 255, 255);
      text("UNDER CONSTRUCTION", this.canvas_width/6.5, this.canvas_height/2);
      
      //placeholder for back button. Image is resized to fit the desired dimensions. the picture will change for a better/formal picture of the back button
      back_button.resize(this.back_button_width, this.back_button_height);
      image(back_button, this.back_button_x, this.back_button_y);
    }
  }
}

This is all that I could have done without first creating an “Egg” class. Creating an “Egg” class will ensure that the eggs move from bottom of the canvas to the top on the three conveyer belts, change color/get cracked when tapped, and respawn at the bottom of the canvas. Apart from this, the restart button after game ends and the audios for game background and egg cracking will also be added.

Challenging Parts:

Incorporating the “Egg” class into the “Game” class is proving to be a significant challenge for me. While I am aware that I must use an array in the “Game” class to store the objects of the “Egg” class, I am struggling with egg positioning. I am uncertain how to create a loop for the eggs to appear one after the other without overlapping each other. I aim to ensure that the distance between each egg is identical. Additionally, I am unclear on how the eggs that move beyond the canvas will return to their starting position behind the canvas and at what y-coordinate. Similarly, my concerns apply to the dividers I wish to employ between the conveyer belts, as they will also loop around. I am hopeful that if I can solve one problem, the solution may help me with the other. At present, I am narrowing my focus to resolving the issue with the eggs alone. Instead of three conveyer belts, I will use one conveyer belt (one array) and see if I can find a solution for one belt first. Finding it for one belt will help solve it for the other two belts and the dividers. I plan to devote two days to this issue, and if I cannot find a solution, I will turn to Youtube tutorials for guidance and inspiration.

Week 4 – Neon Sign

Inspiration:

I had a particularly challenging time with this assignment because I struggled to determine what to make for this topic. Despite scouring the internet for inspiration, I found examples that were either beyond my coding abilities or too basic. I tried multiple projects but was not satisfied because they weren’t creative enough. However, my fortunes changed when I received a Snapchat memory today, reminding me of a quote a friend wrote on my dorm’s whiteboard exactly one year ago. This quote has always been a source of inspiration for me to stay focused on my work and avoid procrastination, and it remains one of my favorite quotes. The Snapchat memory was:

I needed to come up with a unique way to showcase this text, which is when my roommate’s suggestion came in handy. While discussing ways to decorate our room, my roommate proposed the idea of using neon lights. The moment he mentioned this, I realized that adding a neon light effect to the quote could work.

Coding Process:

To begin with, I searched online for neon signs that can be used as room décor and was drawn to those with a cursive font. Hence, I opted for a cursive font and explored multiple websites until I found the “Havelberg” font. Following this, I researched how to add and implement this font using p5js. Once I gained this knowledge, I created a preliminary layout of the project, outlining where everything should be placed. The final skeleton looked somewhat like this:

Initially, I encountered challenges in making the project appear more visually appealing and imaginative. To tackle this, I got the idea of incorporating electricity that would flow through the wires and illuminate the sign upon pressing the “on” button. However, as I progressed, I realized that the end result was not as aesthetically pleasing as I had hoped. As a solution, I decided to remove the grey background and start with a blank canvas of darkness. The code for the electricity flowing looked like this:

if (on_button == true) {
    if (!sound.isPlaying()) {
      sound.play(); //the electric static voice
    }
    fill("gold"); //for the wires
    noStroke();
    //since upspeed is in negative values that is why we subtract
    if (height + upspeed > uplimit) {
      upspeed = upspeed + vertical_speed;
      rect(360, 400, wire_width, upspeed); //the first part of the wire and looks like electricity is travelling up
    } else {
      rect(360, 400, wire_width, upspeed); //when it has reached its limit, then it stops and stays there
      if (width + leftspeed > leftlimit) {
        //as soon as the wire part 1 has reached its designated limit, now we start moving to the left
        leftspeed = leftspeed + horizontal_speed; //horizontal speed is the same as upspeed so that it looks uniform
        rect(360, uplimit, leftspeed, wire_width); //leftspeed keeps updating so it looks like it is moving to the left
      } else {
        rect(360, uplimit, leftspeed, wire_width); //same as above
        if (height + upspeed2 > uplimit2) {
          upspeed2 = upspeed2 + vertical_speed; //upspeed2 is different from upspeed since the starting and ending points are different
          rect(360 + leftspeed, uplimit, wire_width, upspeed2);
        } else {
          rect(360 + leftspeed, uplimit, wire_width, upspeed2);
          stroke(57, 255, 20);
          strokeWeight(4);
          noFill(); //make rectangle after the last wire reaches the rectangle
          rect(25, 25, 350, 175, 20); //rectangle but with curved edges
          strokeWeight(1);
          fill(57, 255, 20, sin(frameCount * 0.1) * 255); //fills the text and adds a flicker effect to it
          text("you didnt come this far", 45, 95);
          text("just to come this far", 70, 145);
        }
      }
    }
  }

After adding the button, audio, and the mouseClicked() feature, the final product looked like this:

Reflection:

Despite feeling proud of the code I developed to create the electricity flow, I did not derive much personal satisfaction from this assignment. I felt that my creative potential was hindered and that the piece could have been more visually appealing if I had managed to let my creativity flow more freely. Unfortunately, I was experiencing a creative block that prevented me from fully enjoying the project. However, I plan to revisit this piece at a later date and explore ways to enhance its aesthetics. Apart from the creative aspect of it, I would want to add multiple neon signs in the future that take on random quotes from a csv file.