Week 6: Midterm Project (Sounds Like NYUAD)

Remind me to never create a sound-based game again.

This project was a roller coaster, there were some days where I would take the entire day just to debug one tiny thing, and some days where I could make a huge advancement with little resistance. Thankfully, I am happy to where I got.

Trying to make sense/ organzie/ my old code

Since I already had a python based code, starting the game was easy. Myfirst few elements, the image, the map, the player were easy to achieve, but it changed fast. When I got to the borders, I had to redo a large aspect of what it used to be. I refined them, which was time-consuming because of how I had to keep track of X and Y values of the borders since I individually drew them. Then figuring out the movement of the player in relation to the borders was a nightmare. It was nothing related to my previous code, and after spending the whole day on it, I resorted to pencil and paper, trying to figure out how to code the movement. Here’s my sad sketch:

After that celebratory dance, I was on to other elements, like the cats. This proved to be relatively easy, since I had coded this kind of thing before. You hit a cat, you die. It was simple. It was so easy, as a matter of fact, I thought it would be great if I would add another element, humans. I thought “they’re so simple, and I could just make it so that if I hit them, I lose points and time, I don’t die.” After spending a few hours sketching 5 illustrations of humans, and adding it to my code, I realised I had misjudged how ‘easy’ it was. My issue was that I had my code so that whenthe distance between the player and the object is less than an amount, the score decreases. The issue with this is that it would continuously decrease until the object is out of bounds when I just wanted it to decrease once. Eventually, I did it in a way where once it hits the object, a boolean is triggered to true, you lose the points, and then after 1 second, the boolean goes back to false, and you can hit it again. (you probably shouldn’t though).

Other elements I was excited to add were things like adding a score, where it was calculated based on your time (the faster the better) and each sound you find, and it tells you your score at the end. (A very small thing I added was changing the score to green when it’s positive, red when it’s negative). Another thing was also adding a tally on the top left of the screen, to see how many sounds left to find, as playing it I realized I was always wondering how many I had left.

Another element I added (although it doesn’t look as nice as I wanted it to), was having the location you are at glow in gold for a second to indicate that you found the sound in that spot. I also made a ‘ding’ sound to be heard when you get a point, so that you know it happened. I changed the main players’ face to my off-brand illustration of the NYUAD falcon, which I’m not a fan of, but someone pointed out to me that with the addition of humans, the player cannot be sure which one they are controlling.

The sound was tricky, because there were so many bugs that kept showing up. Java only allows sound in the main program, not in classes, so I had to keep experimenting and trying to see how to fix these issues. For example, having the sounds in an array didn’t work. Often times the sounds would overlap because of the draw loop triggered over and over again, especially when mixed with restarting, the sounds would layer over each other. My main solution was to make sure that when the game runs, all the sounds are playing but on mute, so when a location is triggered, the sound unmutes, instead of just playing at a random tempo. Also made sure for the song to keep playing when you win, so you can hear your music, but to stop playing when you die.

Moving on to the restart, instruction, and game over screens. These where also a struggle to figure out, because instead of having most of my initiations in a class like I would normally do, I had it in the main page to correlate with the sounds. Eventually, I figured it out, having functions for each screen, and then debugging for a while. I also had to redesign a few elements for the screens.

int gameScreen = 0;
PImage dead, won, instructions;
boolean trigger = false;
Game game;
Creature creature;
Cats[] cats;
Humans[] humans;
Borders[] horizentalBorders;
Borders[] verticalBorders;
Locations[] locations;
//SoundFile[] sounds;
SoundFile backgroundSound, meow, ding, baraha, alarm, dining, library, knock, palms, theatre;

void setup() {
  //This trigger is just fir the cat meow
  trigger = false;
  creature = new Creature (50, 450, 10);
  game = new Game (1024, 786, 585);
  cats = new Cats[3];
  humans = new Humans[3];
  //for displaying the cats and humans, in random places and speeds
  for ( int g = 0; g< 3; g++) {
    cats[g] = new Cats (int(random(200, 900)), int(random(200, 600)), 15, 70, 70, 200, 800);
    humans[g] = new Humans (int(random(200, 900)), int(random(200, 900)), 15, 70, 70, 200, 800);
  }
  //Creating the locations for the sound to br triggered. These dont change
  locations = new Locations[7];
  locations[0] = new Locations (250, 270, 100); // library
  locations[1] = new Locations ( 250, 470, 100); // Palms
  locations[2] = new Locations (400, 310, 50); //baraha
  locations[3] = new Locations (790, 250, 100); //theatre
  locations[4] = new Locations (950, 400, 100); //dining
  locations[5] = new Locations (800, 620, 200); // knock
  locations[6] = new Locations (570, 570, 200); // alarm
  size(1024, 768);
  background(255, 255, 255);
  //borders of the map, where you cannot enter from
  horizentalBorders = new Borders[14];
  verticalBorders = new Borders[13];
  horizentalBorders[1] = new Borders (444, 286, 575, 286); //h
  horizentalBorders[2] = new Borders (442, 346, 520, 346); //h 
  horizentalBorders[3] = new Borders (402, 470, 502, 470); //h
  horizentalBorders[4] = new Borders (590, 472, 712, 472); //h
  horizentalBorders[5] = new Borders (754, 493, 812, 493); //h
  horizentalBorders[6] = new Borders (70, 175, 440, 175); // h
  horizentalBorders[7] = new Borders (70, 375, 422, 375); //h
  horizentalBorders[8] = new Borders (310, 725, 970, 725); //h
  horizentalBorders[9] = new Borders (530, 527, 586, 527); //h 
  horizentalBorders[10] = new Borders (536, 595, 586, 595); // h
  horizentalBorders[11] = new Borders (350, 614, 396, 614); //h
  horizentalBorders[12] = new Borders (754, 602, 808, 602); // h
  horizentalBorders[0] = new Borders (756, 640, 808, 640); // h
  verticalBorders[12] = new Borders (440, 175, 440, 270); //h
  verticalBorders[1] = new Borders (540, 346, 540, 455); //v
  verticalBorders[2] = new Borders (582, 286, 582, 456);// v
  verticalBorders[3] = new Borders (70, 175, 70, 375); //v
  verticalBorders[4] = new Borders (310, 572, 310, 725); //v
  verticalBorders[5] = new Borders (970, 525, 970, 725);  // v
  verticalBorders[6] = new Borders (775, 675, 775, 720); // v
  verticalBorders[7] = new Borders (800, 675, 800, 720); //v 
  verticalBorders[8] = new Borders (586, 527, 586, 595); // v
  verticalBorders[9] = new Borders (536, 529, 536, 595); //v
  verticalBorders[10] = new Borders (396, 614, 396, 664); //v
  verticalBorders[11] = new Borders (808, 602, 808, 640); // v
  verticalBorders[0] = new Borders (756, 602, 756, 640); // v
  //if you die- page
  dead = loadImage("sorry.png");
  //if you win - page
  won = loadImage("finalpage.png");
  //instructional page
  instructions = loadImage("instructions.png");
  //loading the sounds of the song
  backgroundSound = new SoundFile(this, "OriginalBacktrack.mp3");
  baraha = new SoundFile(this, "CardSwipe.mp3");
  knock= new SoundFile(this, "Knock_RA.mp3");
  theatre = new SoundFile(this, "SneezeCough.mp3");
  library = new SoundFile(this, "Typing_clicking.mp3"); 
  palms = new SoundFile(this, "Birds.mp3");
  dining = new SoundFile(this, "Didyoudothereading.mp3");   
  alarm = new SoundFile(this, "iPhoneAlarm.mp3"); 
  meow = new SoundFile(this, "meow.mp3");
  ding = new SoundFile(this, "Ding.mp3");

//playing all the sounds in loop for easier restart (less bugs), all on mute except background, 
// this is so that whenever they are triggered they are still on tempo for the song
  backgroundSound.play();
  baraha.loop();
  baraha.amp(0.0);
  knock.loop();
  knock.amp(0.0);
  theatre.loop();
  theatre.amp(0.0);
  library.loop();
  library.amp(0.0);
  dining.loop();
  dining.amp(0.0);
  alarm.loop();
  alarm.amp(0.0);
  meow.amp(0.5);
  //restart();
}

void sound() {
  // locating each location for their respective sound, because an array of sounds did not work
  if ( locations[0].trigger == true) {
    library.amp(1.0);
    println("library");
  }
  if ( locations[1].trigger == true) {
    palms.amp(1.0);
  }
  if ( locations[2].trigger == true) {
    baraha.amp(1.0);
  }
  if ( locations[3].trigger == true) {
    theatre.amp(1.0);
  }
  if ( locations[4].trigger == true) {
    dining.amp(1.0);
  }
  if ( locations[5].trigger == true) {
    knock.amp(1.0);
  }
  if ( locations[6].trigger == true) {
    alarm.amp(1.0);
  }
}

void initScreen() {
  //initial screen, instruction page
  image(instructions, 0, 0);
  text("Click anywhere to begin ! ", 300, 700);
}

void restart() {
  // upon restart
  // this was a response to a cat bug
  trigger = false;
  creature = new Creature (50, 450, 10);
  game = new Game (1024, 786, 585);
  cats = new Cats[3];
  humans = new Humans[3];
  for ( int g = 0; g< 3; g++) {
    cats[g] = new Cats (int(random(200, 900)), int(random(200, 600)), 15, 70, 70, 200, 800);
    humans[g] = new Humans (int(random(200, 900)), int(random(200, 900)), 15, 70, 70, 200, 900);
  }
  locations = new Locations[7];
  locations[0] = new Locations (250, 270, 100); // library
  locations[1] = new Locations ( 250, 470, 100); // Palms
  locations[2] = new Locations (400, 310, 50); //baraha
  locations[3] = new Locations (790, 250, 100); //theatre
  locations[4] = new Locations (950, 400, 100); //dining
  locations[5] = new Locations (800, 620, 200); // knock
  locations[6] = new Locations (570, 570, 200); // alarm

  backgroundSound.loop();
  baraha.loop();
  baraha.amp(0.0);
  knock.loop();
  knock.amp(0.0);
  theatre.loop();
  theatre.amp(0.0);
  library.loop();
  library.amp(0.0);
  dining.loop();
  dining.amp(0.0);
  alarm.loop();
  alarm.amp(0.0);
  //meow.loop();
  meow.amp(0.5);
}

void draw () {
  // to make it easier which game scteen we are on
  if (gameScreen == 0) {
    // instructions page
    initScreen();
  } else if (gameScreen == 1) {
    // the game
    gameScreen();
  } else if (gameScreen == 2) {
    // if you lose
    gameOverScreen();
  } else if (gameScreen == 3) {
    // if you win
    gameWinScreen();
  }
}

void gameScreen() {
  game.display();
  creature.display();
  for ( int g = 0; g< 3; g++) {
    cats[g].display();
    humans[g].display();
  }
  for ( int n = 0; n < 12; n ++) {
    horizentalBorders[n].displayBorders();
  }
  for ( int m = 0; m < 13; m ++) {
    verticalBorders[m].displayBorders();
  }
  for ( int l = 0; l <7; l ++) {
    // checking if the locations are triggered
    locations[l].display();
    locations[l].trigger();
  }
  if (game.count <= 0) {
    // if the time runs our, you lose
    creature.alive = false; 
    creature.win = false;
  }
  if (creature. alive == false && creature.win == true) {
    // if you win
    gameScreen=3 ;
    gameWinScreen();
  }
  if (creature. alive == false && creature.win == false) {
    // if you lose
    gameScreen=2 ;
    gameOverScreen();
  }
}

void gameOverScreen() {
  // lost screen
  image (dead, 187, 59);
}
void gameWinScreen() {
  // won screen, dsplaying score as well
  image (won, 187, 59);
  text("Your Score: " + int(game.scorecount), 420, 420);
}

void mouseClicked() {
  if ( gameScreen == 0) {
    gameScreen= 1;
  }
  if (gameScreen == 2 || gameScreen == 3) {
    restart();
    gameScreen= 1;
    println("restart");
  }
}
// class borders for the map 
class Borders{
  float x, y, x2, y2;
  
  Borders (int _x, int _y, int _x2, int _y2 ){
    x = _x;
    y = _y;
    x2 = _x2;
    y2 = _y2;
    
  
  }
  
  void displayBorders(){
  //stroke(200);
  noStroke();
  line(x,y,x2,y2);
  }
}
// creature cats

class Cats {
  float x, y, r, w, h, x1, x2;
  float vy = 0;
  float vx = 0;
  boolean alive = true;
  PImage img; 

  Cats (float _x, float _y, int _r, int _w, int _h, int _x1, int  _x2) {
    x = _x;
    y = _y;
    r = _r;
    vy = 0;
    vx = random(1, 3);
    w = _w;
    h = _h;
    x1 = _x1;
    x2 = _x2;   
    // picking a random image of the 3 cat versions
    img = loadImage("cat" + str(int(random(1, 4))) + ".png");
  }

  void display() {
    update();
    noFill();
    noStroke();
    image( img, x-r, y-r, 50, 50);
    circle(x, y, r*2);
  }

  void update() {
  // movement of the cats 
    y += vy;
    x += vx; 
    
    // for the cats to move back and forth
    if (x < x1) {
      vx *= -1;
    } else if ( x > x2) {
      vx *= -1;
    }
  }

  float distance(int tarX, int tarY) {
    // to calculate the distance between the cat and another object
    return (pow( (pow(( x - tarX), 2) + pow((y - tarY), 2)), 0.5));
  }

  boolean trigger() {
    return false;
  }
}
//Clas for player 
class Creature {
  int x, y, r;
  float vy = 0;
  float vx = 0;
  int directionH = 0; 
  int numloc = 0;
  boolean alive = true;
  boolean win = false;
  boolean borderCheckY = true;
  boolean borderCheckX = true;
  PImage img, green;
  boolean leftKey = false;
  boolean rightKey = false;
  boolean upKey = false;
  boolean downKey = false;
  int previousTime = 0;
  float timePassed = 1000;


  Creature (int _x, int _y, int _r) {
    x = _x;
    y = _y;
    r = _r;
    vy = 0;
    vx = 0;
    img = loadImage("player.png");
  }

  void sound() {
    // function to equate each sound to its respective location, play the sound upon trigger
    if ( locations[0].trigger == true) {
      library.amp(1.0);
    }
    if ( locations[1].trigger == true) {
      palms.amp(1.0);
    }
    if ( locations[2].trigger == true) {
      baraha.amp(1.0);
    }
    if ( locations[3].trigger == true) {
      theatre.amp(1.0);
    }
    if ( locations[4].trigger == true) {
      dining.amp(1.0);
    }
    if ( locations[5].trigger == true) {
      knock.amp(1.0);
    }
    if ( locations[6].trigger == true) {
      alarm.amp(1.0);
    }
  }

  void display() {
    update();
    noFill();
    noStroke();
    image( img, x-r, y-r, 50, 50);
    circle(x, y, r*2);
  }

  void update() {
    keyPressed();

    y += vy;
    x += vx;

    // for movement of the player and keys + game borders
    if ( leftKey == true && x>=0) {
      directionH = 1;
      vx = -1;
    } else if ( rightKey == true && x + r <= 1024) {
      vx = 1;
      directionH = 0;
    } else if ( downKey == true && y +r <= 784) {
      vy = 1;
    } else if ( upKey == true && y >= 0  ) {
      vy = -1;
    } else { 
      vx = 0;
      vy = 0;
    }

    // checking for collision of map borders
    collisionY();
    collisionX();

    for ( int g = 0; g< 3; g++) {
      if (cats[g].distance(x, y) <= r +cats[g].r ) {
        // when player hits a cat
        if (trigger == false) {
          // mouse sound effect + you die, and the sounds stop
          meow.play();
          palms.amp(0.0);
          baraha.amp(0.0);
          library.amp(0.0);
          theatre.amp(0.0);
          dining.amp(0.0);
          knock.amp(0.0);
          alarm.amp(0.0);
          println("meow");
          trigger = true;
          alive = false;
          win = false;
        }
      }
      // if you hit a human, your score goes down, and you lose 10 seconds off your timer
      if (humans[g].distance(x, y) <= r +humans[g].r) {
        if (humans[g].trigger == false) {
          game.count -=10;
          game.scorecount-=20;
          previousTime = millis();
          humans[g].trigger = true;
          game.display();
        }
        if ( millis() > previousTime + timePassed) {
          previousTime= millis();
          humans[g].trigger = false;
        }
      }
    }

    // probably one of the most important parts. Code to see if you hit a location
    for ( int o = 0; o< 7; o++) {
      if (locations[o].trigger == false) {
        if (locations[o].distance(x, y) <= 20) {
          locations[o].trigger = true; 
          if (tally.size() >0) {
            // adds to your score
            numloc +=1;
            game.scorecount += ((120 - (millis()/1000))*creature.numloc);
            println(game.scorecount);
            // removes a string on top left to see how many you have left
            tally.remove(0);
            locations[o].display2();
            // ding as an indicator that you got one
            ding.play();
            sound();
            //image (green, height, width);
            println(tally);
          }
        }
        // check for a win. If the string length == 0, you won
      } else if (tally.size() == 0) {
        win = true;
        alive = false;
      }
    }
  }
  // checking to see if you hit the horizontal borders. makes you stop moving
  void collisionY() {
    for (int i = 0; i <13; i++ ) {
      if ( vy + y + r >= horizentalBorders[i].y && 
        vx + x + r >= horizentalBorders[i].x &&
        x + vx +r <= horizentalBorders[i].x2 && 
        vy + y + r <= horizentalBorders[i].y2) {
        vy = 0;
      }
    }
  }
  // collision with vertical borders.
  void collisionX() {
    for (int j = 0; j < 11; j++) {
      if ( vx + x + (r*2) <= verticalBorders[j].x && 
        vx + x + (r*2) >= verticalBorders[j].x2 &&
        y + vy + r >= verticalBorders[j].y && 
        vy + y + r <= verticalBorders[j].y2) {
        //println("stop3");
        vx = 0;
      }
    }
  }

  // equating key pressed with players movements
  void keyPressed() {
    if (keyPressed && keyCode == LEFT ) {
      leftKey = true;
    } else {
      leftKey = false;
    }
    if (keyPressed && keyCode == RIGHT) { //&& x+r <= game.w
      rightKey = true;
    } else {
      rightKey = false;
    }
    if (keyPressed && keyCode == UP) {
      upKey = true;
    } else {
      upKey = false;
    }
    if (keyPressed && keyCode == DOWN) { // && y+r <= game.h
      downKey = true;
    } else {
      downKey = false;
    }
  }



  float distanceCreature(int tarX, int tarY) {
    return (pow( (pow(( x - tarX), 2) + pow((y - tarY), 2)), 0.5));
  }
}
import processing.sound.*;
PImage img;
StringList tally;

class Game {
  float w, h, g;
  int count = 120;
  float scorecount = 0;

  Game(int _w, int _h, int _g) {
    w = _w;
    h = _h;
    g = _g;
    img = loadImage("map.jpg");
    tally = new StringList("I", "I", "I", "I", "I", "I", "I" );
  }

  void display () {
    // map image
    image (img, 0, 0);
    fill (0, 0, 0);
    textSize(25);
    // 3d effect on timer
    text("Time Remaining: " + count, 365, 50);
    fill(255, 255, 255);
    textSize(25);
    text("Time Remaining: " + count, 363, 48 );
    fill (255, 255, 255);
    // displaying the tally on the left
    for (int i = 0; i <tally.size(); i++) {
      text (tally.get(i), 50+(i*10), 50);
    }
    fill(150, 17, 17);
    for (int i = 0; i <tally.size(); i++) {
      text (tally.get(i), 50+(i*10), 50);
    }
    fill (255, 255, 255);
    text ("Score: " + int(scorecount), 850, 50);
    // displaying score, with 3d effect and also green if positive, red if negative
    if (scorecount <=0) {
      fill(150, 17, 17);
      text ("Score: " + int(scorecount), 849, 49);
    } else if (scorecount >=0) {
      fill(17, 150, 17);
      text ("Score: " + int(scorecount), 849, 49);
    }

    // for decreasing time
    if (creature.alive == true) {
      if (frameCount % 60 == 0) {
        //println (frameCount);
        count   -=1;
      }
      if (count <= 0) {
        creature.win=false;
        creature.alive = false;
      }
    }
  }
}
// creature cats

class Humans {
  float x, y, r, w, h, y1, y2;
  float vy = 0;
  float vx = 0;
  boolean alive = true;
  PImage img; 
  boolean trigger = false;

  Humans (float _x, float _y, int _r, int _w, int _h, int _y1, int  _y2) {
    x = _x;
    y = _y;
    r = _r;
    vx = 0;
    vy = random(1, 3);
    w = _w;
    h = _h;
    y1 = _y1;
    y2 = _y2;   
    img = loadImage("human" + str(int(random(1, 6))) + ".png");
  }

  void display() {
    update();
    noFill();
    noStroke();
    image( img, x-r, y-r, 50, 50);
    circle(x, y, r*2);
  }

  void update() {

    y += vy;
    x += vx; 

    if (y < y1) {
      vy *= -1;
    } else if ( y > y2) {
      vy *= -1;
    }
  }

  float distance(int tarX, int tarY) {
    return (pow( (pow(( x - tarX), 2) + pow((y - tarY), 2)), 0.5));
  }

  boolean trigger() {
    return false;
  }
}

 

Zip:IntroMidtermProject

Finally, here is the final game (That i am very bad at playing, sorry its so frustrating to watch):

Claustrophobia – Midterm

original game concept

My game is based on a nightmare I used to have when I was a child.
I’m very claustrophobic, so I used to have a recurring nightmare where I would be in a room with a cake. The cake would get progressively bigger until it left me with no space for myself. My only way out was to eat it.
This is what I wanted the gameplay to be.
I would be able to move around, jump, and crouch to avoid the cake’s hits (candles). If it gets me, it gets bigger. If I get close to it and press the spacebar, I can eat a small chunk out of it. The game will end either if the cake takes up the whole room, or if I eat it first.

Changes made/ Updated version

Due to time constraints, and many many details I was not expecting to cause issues within my program, I had to change the concept slightly, and give up one aspect of the game.
So now, this is the concept of my game:

        • It starts off similar. Me. Cake. In room.
        • The cake can still shoot at me and get bigger if it hits me.
        • I can still move around, jump and crouch to avoid hits.

HOWEVER, I gave up the part where I can eat the cake. So now, instead of eating it whole to win the game, another condition needs to be met:

        • I have to last 1 minute and 30 seconds without the cake taking up the whole space.
        • A timer is set up to take care of this.
        • The cake will take up the whole space when it hits me 14 times. So as long as I have lives remaining (initial lives = 14), and 1 minute and 30 seconds have passed, I win.

aesthetic of the game

The game is supposed to be a bit humorous, in the sense that it makes you think that this is going to be a horror game, dealing with claustrophobia and nightmares, while in reality, it is quite light and silly since it is a childhood nightmare of mine.

The vibe of the start screen is very creepy, accompanied by equally creepy music. But when you press play, the whole vibe changes. The images are a lot more colorful, and the music becomes fun and bouncy.



remaining issue

I ran into multiple bugs and issues while working on this assignment. Most were handled. However, there is one I can’t seem to fix.
When the cake size grows bigger, and the image in the code is updated to grow in size, the program begins to run slower the bigger the image gets.
I’ve tried many approaches to fixing this(img.resize(), scale(), …) but none seem to do the trick.
I kept the cake visualized by a rectangle for the game to be able to run smoothly, but I still intend to look for a way around this as I find myself very intrigued by what else could possibly be causing this and how to fix it.

classes and tabs

I have six tabs for this program. One for the main, 4 for classes (Game, Person, Hit, Cake), and one more tab containing the display functions for each screen (Start screen, Instructions, Winning screen, Losing screen).
I have illustrated and explained the main and basic ideas by commenting throughout my code.

This is the Main tab: 

import processing.sound.*;
SoundFile gamemusic,startmusic;
SoundFile pew, hit , scream,sleep;
PImage image1,image2,image3,bg,candle;
PImage creepy1, creepy2,title,title2,instruc,sleeping,screaming;
PImage[][] sprites;
Game g;
Person me;
Cake cake;
ArrayList<Hit> hits;
float ground ;
int timer, timeleft, timestart,seconds,minutes;
int temptime;

//variables to detect which screen we are on
int startMenu =1;
int howTo = 2;
int playGame = 3;
int gameState;

void setup() {
  size(1400, 800);
  gameState = startMenu; //start at start screen
  temptime = 90;
  timer = timeleft = 90000; // set timer for 1 minute 30 seconds
  timestart = 0;
  ground = height - 50;
  g = new Game(); //new instance of our whole game
  me = new Person(); //new person
  cake = new Cake(); //new cake
  hits = new ArrayList<Hit>(); //new hits array
  //load all images
  image1 = loadImage("me.png");
  sprites = new PImage[3][6]; 
  for (int y=0; y < 3; y++) {
    for (int x=0; x< 6; x++) {
      sprites[y][x] = image1.get(x*(image1.width/6), y*(image1.height/3), me.characterW, me.characterH);
    }
  }
  image2 = loadImage("me3.png");
  image3 = loadImage("cakee.png");
  candle = loadImage("candle.png");
  creepy1 = loadImage("creepy1.jpg");
  creepy2 = loadImage("creepy2.jpg");
  title = loadImage("title.png");
  title2 = loadImage("title2.png");
  instruc = loadImage("instruc.png");
  sleeping = loadImage("sleep2.jpg");
  screaming = loadImage("scream.jpg");
  bg = loadImage("bg1.jpg");
  //load all sounds, loop them and set their volumes to 0
  pew = new SoundFile (this,"pew.mp3");
  hit = new SoundFile (this,"gothit2.mp3");
  scream = new SoundFile (this,"scream.mp3");
  scream.loop(); scream.amp(0);
  gamemusic = new SoundFile (this,"gamemusic.mp3");
  gamemusic.loop(); gamemusic.amp(0);
  startmusic = new SoundFile (this,"startmusic.mp3");
  startmusic.loop(); startmusic.amp(0);
  sleep = new SoundFile (this,"sleep.mp3");
  sleep.loop(); sleep.amp(0);
}

void draw() {
  
  if (gameState == startMenu){
    startmusic.amp(1); //play start music
    image(creepy1,0,0);
    image(title, 200,140);
    image(title2, 400,300);
    displayStart(); //display start screen
    //make new instances of everthing to restart everything
    g = new Game(); 
    cake = new Cake();
    me = new Person();
    hits = new ArrayList<Hit>();
    gamemusic.amp(0); //mute other sounds
    scream.amp(0);
    sleep.amp(0);
  }
  else if (gameState == howTo){ 
    image(creepy2,0,0);
    image(instruc,500,100);
    displayHow(); //display instructions screen
    gamemusic.amp(0);
    scream.amp(0);
    sleep.amp(0);
    startmusic.amp(1); //play music for this screen
  }
  else if (gameState == playGame){
    if (g.won == false && g.lost == false) //if the game is still going
    {
      //display the game and play its music
      image(bg, 0, 0);
      gamemusic.amp(0.5);
      startmusic.amp(0);
      scream.amp(0);
      sleep.amp(0);
      temptime = minutes* 60 + seconds;
      g.coolingTime++;
      g.displayGame();
    }
    else if (g.lost == true) //if you lose, display lost screen
    {
      image(screaming,0,0);
      gamemusic.amp(0);
      startmusic.amp(0);
      sleep.amp(0);
      scream.amp(1);
      lostGame();
    } else if (g.won == true) //if you win display winning screen
    {
      image(sleeping,0,0);
      gamemusic.amp(0);
      startmusic.amp(0);
      scream.amp(0);
      sleep.amp(1);
      wonGame();
    }
  }

}
//mouse pressed functions to make the buttons that switch between screen
void mousePressed(){
  if (gameState == startMenu){
       if (475<mouseX && mouseX<475+450 && 620<mouseY && mouseY<620+100){
        gameState = howTo;
      }
  
      if (475<mouseX && mouseX<475+450 && 500<mouseY && mouseY <500+100){
        gameState = playGame;
        timestart = millis();
      }
  }
  else if (gameState == howTo){ 
      if (475<mouseX && mouseX<475+450 && 650<mouseY&& mouseY<650+100){
        gameState = startMenu;
      }
  }
  else if (gameState == playGame && (g.won || g.lost)){
    if (475<mouseX && mouseX<475+450 && 650<mouseY&& mouseY<650+100){
      gameState= startMenu;
    }
  }
}

 

This is the Game Class:
This is where an instance of the actual gameplay is defined using all other instances from other classes. It only has a displayGame() method which combines everything that needs to be done when you press START.

class Game
{
  boolean won ;
  boolean lost;
  int coolingTime, hitsCount, biggerCount, lives;

  Game()
  {
    hitsCount = 0;
    biggerCount = 0;
    coolingTime = 50;
    won = false;
    lost = false;
  }

  void displayGame()
  {
    timeleft =  timer - (millis() - timestart);
    seconds = (timeleft/1000) % 60;
    minutes = (timeleft/1000) / 60;
    //game
    
    me.jump();
    me.move();
    me.drawPerson();
    //for each hit draw it, move it and check it it collides with character
    for (int i=0; i<hitsCount; i++)
    {
      hits.get(i).updateHit();
      hits.get(i).drawHit();
      if ((me.hitPerson(hits.get(i))== true)) //if there is a collision
      {
        hits.get(i).y=900; //make hit disappear from view
        cake.bigger(me); //make cake bigger
        biggerCount++; //add to this counter which gives us how many times you've been hit
        if (hit.isPlaying()) {
          hit.stop();
        }
        hit.play(); //play hit sound if not already playing
      }
      if (hits.get(i).y == 900 || hits.get(i).x<0) 
      {
        hits.remove(i); //to keep hits array small, remove any hit that is no longer needed
        // i.e if it disappeared from screen or if it already hit the character
        hitsCount--;  //and reduce this counter by 1
      }
    }


    if (coolingTime >= 100) { //add a new hit to hit array after some time
      hits.add( new Hit(random(550, ground-20)));
      hitsCount+=1; //increment hit counter
      if (pew.isPlaying()) { 
        pew.stop();
      }
      pew.play(); //play shooting sound "pew pew" if it's not already playing
      coolingTime = int(random(50));
    }
    //draw and move the cake
    cake.drawCake();
    cake.moveCake();
    if (biggerCount == 14) //detect loss
    {
      lost = true;
    }
    if (biggerCount <14 && seconds == 0 && minutes == 0) //detect win
    {
      won = true;
    }
    //display timer and number of remaining lives
    if(seconds <= 5 && minutes == 0) //make timer show up bigger in the center of the screen
    //when you have 5 minutes left
    {
      fill (255,220);
      rect(width/2-250,height/3-70,500,200);
      fill(0);
      textSize(70);
      textAlign(CENTER);
      lives = 14 - biggerCount;
      text("Timer: "+seconds, width/2, height/3+10);
      text("Lives: "+ str(lives), width/2, height/3+100);
    }
    else{
      fill (255,190);
      rect(10,10,220,100);
      fill(0);
      textSize(35);
      textAlign(LEFT);
      lives = 14 - biggerCount;
      if(seconds < 10)
       text("Timer: "+ minutes+":0"+seconds, 20, 50);
      else
        text("Timer: "+ minutes+":"+seconds, 20, 50);
      
      text("Lives: "+ str(lives), 42, 90);
    }
  }

}

 

This is the Cake class:
All methods related to cake are defined here : drawCake(), moveCake(), bigger() (the last one is to make the cake bigger).

class Cake
{
  float cakeW, cakeH;
  float cakeX, cakeY,speed;
  float s;
  Cake(){
    speed = 0.5;
    cakeW = 300;
    cakeH = 200;
    cakeX = width -cakeW - 50;
    cakeY = ground-cakeH;
    s=1;
  }
  
  void drawCake()
  {
    noStroke();
    fill(0,150);
    rect(cakeX,cakeY, cakeW, cakeH);
    //image(image3,cakeX,cakeY,cakeW, cakeH);
  }
  void moveCake() // to slightly move the cake to the left and right
  {
    cakeX -= speed;
    if (cakeX > width -cakeW - 25 || cakeX< width -cakeW - 75)
    {
      speed *= -1;
    } 
  }
  void bigger(Person thisMe) // to make the cake bigger everytime you are hit
  {
    //change size
    cakeH *= 1.1;
    cakeW *= 1.1;
    //push back x and y accordingly
    cakeX = width -cakeW - 50;
    cakeY = ground-cakeH;
    //upadte the x of the character to not let it be displayed afte the border of the cake
    float d =dist(thisMe.characterX,thisMe.characterY,cakeX,thisMe.characterY);
    if ( d< thisMe.characterW)
    {
      thisMe.characterX -= (thisMe.characterW -d) ;
    }

  }

}

 

This is the Hit class:
It has drawHit and updateHit methods.
An ArrayList of hits is defined in the Main Tab to contain all hits.

class Hit
{
  float x,y, speed;
  int w,h;
  
  Hit(float thisY)
  {
    w= 50;
    h = 6;
    //setting speed for every hit depending on timer
    if (90-temptime <= 20) {speed = 8;}
    else if (90-temptime > 20 && 90-temptime<= 30 ) {speed = 9;}
    else if (90-temptime > 30 && 90-temptime <= 40 ) {speed = 10;}
    else if (90-temptime > 40 && 90-temptime <= 50 ) {speed = 11;}
    else if (90-temptime > 50) {speed = 13;}
    x = cake.cakeX;
    y = thisY;
  }
  
  void drawHit()
  {
    fill(0);
    noStroke();
    //rect(x,y,w,h);
    image(candle,x,y,w,h);
  }
  
  void updateHit() //moving the hit
  {
    x-= speed; 
  }
}

 

This is the Person class:
It has 4 methods: jump , move, drawPerson, and hitPerson(this one is to check for collision with hits).

class Person
{
  int characterW;
  int characterH;
  int direction;
  int step;
  float xspeed;
  float yspeed;
  float characterX;
  float characterY ;
  boolean crouching; //boolean to check if player is crouching or not
  boolean jumping; //boolean to check if player is already jumping
  
  Person()
  {
    characterW=100;
    characterH=150;
    direction = 2;
    step = 0;
    xspeed= 5;
    yspeed = 0;
    characterX= 0;
    characterY= ground-characterH; 
    crouching = false;
    jumping = false;
  }
  
  void jump(){ //function to make character jump
    characterY += yspeed;
    
    if (characterY + characterH > ground) { //check if character is back on the ground
      //set variables to initial state
      characterY = ground - characterH; 
      yspeed = 0;
      jumping = false;
    }
    else { //if character is in the air
      yspeed ++; //bring him back to the ground (gravity)
    }
  }
  void move(){ //function to move the character with the arrow keys
    if (keyPressed && keyCode == RIGHT){
        direction = 0;
        if(characterX + characterW < cake.cakeX){
          characterX += xspeed;
        }
    }
    else if (keyPressed && keyCode == LEFT){
        direction=1;
        if (characterX > 0){
          characterX -= xspeed;
        }
    }
    else if (!keyPressed){
      direction = 2;
    }
    else if (keyPressed && keyCode == UP && !me.jumping) { // only jump if you're not already jumping
      me.yspeed = -15;
      me.jumping = true;
    }
     
    if (keyPressed && keyCode == DOWN){ //if crouching, update character height and y
      crouching = true;
      characterW=140;
      characterH = 93;
      characterY= ground-characterH; 
    }
    else {crouching = false; characterW = 100; characterH = 150;} //if not, reset them to original
    
    if (frameCount%xspeed==0) { //iterate through sprites
        step = (step+1) % 6;
    }
  }
  
  void drawPerson()
  {
    if (!crouching){
      //rect(characterX, characterY, characterW, characterH);
      image(sprites[direction][step],characterX,characterY);
    }
    else if(crouching) {
      //rect(characterX, characterY, characterW, characterH);
      image(image2,characterX,characterY);
    }
  }
  boolean hitPerson(Hit h) //checking collision with a hit
  {
    if((h.x>characterX +characterW) && dist(h.x+h.w/2,h.y+h.h/2,characterX+characterW/2,characterY+characterH/2) <= characterW/2+h.w/2)
    {
      return true; // means there is a collision from front or back
    }
    if((h.x<=characterX +characterW) && dist(h.x+h.w/2,h.y+h.h/2,characterX+characterW/2,characterY+characterH/2) <= characterH/2+h.h/2)
    {
      return true; // means there is a collision from top or bottom
    }
    else {return false;} // if not, there is no collision
  }
}

 

And finally! This is the tab for the screen display functions:

//function to display losing screen
void lostGame(){
  fill(255);
  textSize(30);
  textAlign(CENTER);
  text("OH NO! YOU LOST!", width/2,600);
  fill(255);
  stroke(0);
  strokeWeight(4);
  rect(475,650,450,100);
  fill(0);
  text("GO BACK TO MAIN MENU",475+450/2,650+100/2);
  if (475<mouseX&& mouseX<475+450 && 650<mouseY&& mouseY<650+100){
      stroke(255);
      strokeWeight(4);
      fill(0);
      rect(475,650,450,100); 
      fill(255);
      text("GO BACK TO MAIN MENU",475+450/2,650+100/2);
   }
}
//function to display winning screen
void wonGame(){
  fill(255);
  textSize(30);
  textAlign(CENTER);
  text("YAY! YOU WON!", width/2,600);
  fill(255);
  stroke(0);
  strokeWeight(4);
  rect(475,650,450,100);
  fill(0);
  text("GO BACK TO MAIN MENU",475+450/2,650+100/2);
  if (475<mouseX&& mouseX<475+450 && 650<mouseY&& mouseY<650+100){
      stroke(255);
      strokeWeight(4);
      fill(0);
      rect(475,650,450,100); 
      fill(255);
      text("GO BACK TO MAIN MENU",475+450/2,650+100/2);
   }
}
//function to display start screen
void displayStart(){
  textSize(30);
  textAlign(CENTER);
  fill(64);
  stroke(0);
  strokeWeight(4);
  rect(475,500,450,100);
  rect(475,620,450,100);
  fill(0);
  text("START GAME",475+450/2,500+100/2);
  text("HOW TO PLAY",475+450/2,620+100/2);
  
   if (475<mouseX&& mouseX<475+450 && 500<mouseY&& mouseY<500+100){
      stroke(255);
      strokeWeight(4);
      fill(0);
      rect(475,500,450,100); 
      fill(255);
      text("START GAME",475+450/2,500+100/2);
   }
                
   if (475<mouseX && mouseX<475+450 && 620<mouseY && mouseY<620+100){
      stroke(255);
      strokeWeight(4);
      fill(0);
      rect(475,620,450,100);
      fill(255);
      text("HOW TO PLAY",475+450/2,620+100/2);
   }
}
//function to display instructions screen
void displayHow(){
  fill(255,200);
  noStroke();
  rect(300,200,800,400);
  fill(0);
  textSize(20);
  textAlign(CENTER);
  text("Well hello sleepy head, welcome to your nightmare.", width/2,240);
  text("In this room, you will face a terrible monster.", width/2,270);
  text("Avoid its hits. OR ELSE...", width/2,300);
  text("It'll get bigger and have you face your claustrophobia!", width/2,330);
  text("If you manage to survive for 90seconds, ", width/2,360);
  text("(which, as we know,is hours in the dream world)", width/2,390);
  text("the nightmare will be over, and you'll sleep peacefully.", width/2,420);
  text("If the monster takes over the entire room before the time is over", width/2,450);
  text("(by hitting you 14 TIMES), your sleep will be interrupted.", width/2,480);
  text("Try to win to stay peacefully asleep.", width/2,510);
  text("Be careful!! The hits get progressively faster with time.", width/2,540);
  text("Move, jump and crouch with the arrow keys. Good luck.", width/2,570);
  fill(64);
  stroke(0);
  strokeWeight(4);
  rect(475,650,450,100);
  fill(0);
  text("GO BACK",475+450/2,650+100/2);
  if (475<mouseX&& mouseX<475+450 && 650<mouseY&& mouseY<650+100){
      stroke(255);
      strokeWeight(4);
      fill(0);
      rect(475,650,450,100); 
      fill(255);
      text("GO BACK",475+450/2,650+100/2);
   }
}

Final game
(has a rectangle instead of cake image)

If you want to try the game out, here is the .zip file!
midtermGame

UPDATE

I fixed the image resizing problem with an off-screen buffer using PGraphics.
This is the working version of the game.
fixedGame

Fight The Pandemic

From my last about the midterm until now, time for submission I had to change a few things. The biggest change was how the game would operate. Initially the plan was to have the shooter in the middle of the screen and have a pointer that would change location based on the location of the mouse in the window, however I changed this to have the shooter moving from side to side as a not to have the user/player switch between the mouse and the keyboard for now the player can play the entirely from the keyboard.

Objective of the game

Try to get as many points as possible while keeping the number of cases as low as possible within 2 minutes/

Game Operation

The keys used for the operation of the game are:

1,2,3,m, space bar and the left and right arrow keys.

The space bar will shoot the fighting tool that is currently selected, this can seen in the shooter icon. Keys 1,2 and 3 can be used to switch between the fighting tools when you’re in “fight” mode. Keys 1,2 and 3 can be used to buy more fighting tools when you are in buy mode. You can switch modes by pressing the m button.

The difference between the fighting tool is number of points you get when you shoot the virus down and the speed of the fighting tool.

Speed of Handwash < Speed of face mask < Speed of face shield.

Shooting the virus down with a handwash will get you 1 points, shooting it down with facemask will get you 3 points and the shield will get you 5 points.

You can use the points to buy more tools. Buying more handwash will cost 1 point, face masks cost 2 points/mask, and face shields cost 3 points/shield.

The number of cases will go up if the virus goes below the window.

 

import processing.sound.*;

//create new game
Game game;

// arrayList to store corona virus images
ArrayList <CoronaVirus> coronaList;
ArrayList <Fighter> activeFighters;

//sound files
SoundFile error;
SoundFile scaryBg;
SoundFile collision;
SoundFile newCase;

//image
PImage virus;

// misc vars
String errorMessage = "";

void setup() {
  size(1280, 720);
  game = new Game(); //init game
  coronaList = new ArrayList<CoronaVirus>(0);
  activeFighters = new ArrayList<Fighter>(0);
  for (int i=0; i<4; i++) {
    coronaList.add(new CoronaVirus());
  }
  virus = loadImage("virus.png");
  error = new SoundFile(this, "error.mp3");
  scaryBg = new SoundFile(this, "scaryBg.mp3");
  collision = new SoundFile(this, "collide.mp3");
  newCase = new SoundFile(this, "dead.mp3");
}

void draw() {
  if (game.state==-2) {
    startScreen();
  }
  else if (game.state==0 || game.state==1) {
    background(211, 211, 211); // clear background to avoid trails
    game.drawGameStats(); // show the game statistics
    game.shooter(); // draw the shooter
    displayError(); //display error messages
    displayCost(); // display cost of buying
    playBGMusic(); // play background music
    updateCorona(); // update the position and display the virus
    moveFighters(); //move the fighting tools on the screen
    checkCollisions(); //check for collision
    checkCoronaPosition(); // check for off screen viruses
    checkFighterPosition(); // check for off screen fighting tool
    newViruses(); // generate new viruses
    game.moveShooter(); // move the shooter
    game.updateTime(); // countdown time
  }
  else if (game.state==-1) {
    gameOver();
  }
}
import processing.sound.*;

//create new game
Game game;

// arrayList to store corona virus images
ArrayList <CoronaVirus> coronaList;
ArrayList <Fighter> activeFighters;

//sound files
SoundFile error;
SoundFile scaryBg;
SoundFile collision;
SoundFile newCase;

//image
PImage virus;

// misc vars
String errorMessage = "";

void setup() {
  size(1280, 720);
  game = new Game(); //init game
  coronaList = new ArrayList<CoronaVirus>(0);
  activeFighters = new ArrayList<Fighter>(0);
  for (int i=0; i<4; i++) {
    coronaList.add(new CoronaVirus());
  }
  virus = loadImage("virus.png");
  error = new SoundFile(this, "error.mp3");
  scaryBg = new SoundFile(this, "scaryBg.mp3");
  collision = new SoundFile(this, "collide.mp3");
  newCase = new SoundFile(this, "dead.mp3");
}

void draw() {
  if (game.state==-2) {
    startScreen();
  }
  else if (game.state==0 || game.state==1) {
    background(211, 211, 211); // clear background to avoid trails
    game.drawGameStats(); // show the game statistics
    game.shooter(); // draw the shooter
    displayError(); //display error messages
    displayCost(); // display cost of buying
    playBGMusic(); // play background music
    updateCorona(); // update the position and display the virus
    moveFighters(); //move the fighting tools on the screen
    checkCollisions(); //check for collision
    checkCoronaPosition(); // check for off screen viruses
    checkFighterPosition(); // check for off screen fighting tool
    newViruses(); // generate new viruses
    game.moveShooter(); // move the shooter
    game.updateTime(); // countdown time
  }
  else if (game.state==-1) {
    gameOver();
  }
}
class Fighter {
  int type = -1; // 0 is handwash, 1 is mask, 2 is shield
  int speed = -1;
  int positionX = -1;
  int positionY = -1;
  PImage img;

  //methods
  //update position
  void updatePosition() {
    positionY -= speed;
    showFighter();
  }
  void fighterImage() {
    //println(type);
    switch(type) {
    case 0: 
      { // handwash
        img = loadImage("handwash.png");
        speed = 3;
        break;
      }
    case 1:
      { // facemask
        img = loadImage("mask.png");
        speed = 5;
        break;
      }
    case 2:
      { // faceshield
        img = loadImage("shield.png");
        speed = 7;
        break;
      }
    }
  }

  void showFighter() {
    image(img, positionX, positionY, 30, 30);
  }
}
class Game {
  // attributes
  int points = 0;
  int cases = 0;
  int state = -2; // 0->shooting | 1-> buying | -1 -> gameOver | -2-> game not started
  int fightingTool = 0; // 0-handwash, 1-mask, 2-shield
  int time = 120;
  PImage maskImg = loadImage("mask.png");
  PImage handwashImg = loadImage("handwash.png");
  PImage shieldImg = loadImage("shield.png");
  int timeDisplay = 0;
  int shooterPosition = width/2;
  int shooterDirection = 0;
  float shooterSpeed = 2;
  
  // fighting tools
  // handwash attr
  int handwash = 20;
  
  // facemask attr
  int masks = 14;
  
  // faceshield attr
  int shields = 6;
  
  

  //methods
  void incrementPoints(int type) {
    if (type == 0) {
      points += 1;
    } else if (type == 1) {
      points += 3;
    } else if (type == 2) {
      points += 5;
    }
  }
  
  void incrementCases() {
    cases+=1;
  }

  void incrementMasks() {
    masks+=1;
  }

  void decrementMasks() {
    masks-=1;
  }

  void incrementShields() {
    shields+=1;
  }

  void decrementShields() {
    shields-=1;
  }

  void incrementHandwash() {
    handwash+=1;
  }

  void decrementHandwash() {
    handwash-=1;
  }
  
  void decrementFighters(){
    switch(fightingTool){
      case 0:{
        decrementHandwash();
        break;
      }
      case 1:{
        decrementMasks();
        break;
      }
      case 2:{
        decrementShields();
        break;
      }
    }
  }

  void changeState() {
    //println("changing state");
    if (state==0) {
      state=1;
    } else if (state==1) {
      state=0;
    }
  }
  void updateTime() {
    if (time==0) {
      state=-1;
    }
    if (frameCount%60==0 && state!=-1) {
      time-=1;
      if (timeDisplay==0) {
        timeDisplay = 1;
      } else if (timeDisplay==1) {
        timeDisplay=0;
      }
    }
    if (points==0 && handwash==0 && masks==0 && shields==0){
      state=-1;
    }
  }

  void drawGameStats() {
    textSize(12);
    color(0, 0, 0);
    imageMode(CENTER);
    
    pushStyle();
    //draw handwash
    image(handwashImg, 70, 30, 30, 30); //150,30,30,30
    text(handwash+" x", 25, 30);//110,30

    // draw mask
    image(maskImg, 150, 30, 30, 30);
    text(masks+" x", 110, 30);

    // draw faceshield
    image(shieldImg, 230, 30, 30, 30);
    text(shields+" x", 190, 30);
    popStyle();
    
    //display time
    pushStyle();
    textSize(30);
    if (timeDisplay==1) {
      fill(0, 0, 0);
      text(time, width-100, height-30);
    } else if (timeDisplay==0) {
      fill(255, 255, 0);
      text(time, width-100, height-30);
    }
    popStyle();

    // game mode
    pushStyle();
    int rectWidth = 100;
    int rectHeight = 30;
    int cornerRadius = 10;
    noFill();
    rect(width/2-rectWidth/2, 20, rectWidth, rectHeight, cornerRadius, cornerRadius, cornerRadius, cornerRadius);
    if (state==0) {
      pushStyle();
      textAlign(CENTER);
      textSize(25);
      fill(255, 255, 0);
      text("FIGHT", width/2, 45);
      popStyle();
    } else if (state==1) { //display buy
      pushStyle();
      textAlign(CENTER);
      textSize(25);
      fill(0, 255, 0);
      text("BUY", width/2, 45);
      popStyle();
    } else if (state==-1) { //display gameover
      pushStyle();
      textAlign(CENTER);
      textSize(18);
      fill(255, 0, 0);
      text("Game Over", width/2, 43);
      popStyle();
    }

    popStyle();

    //display points
    pushStyle();
    fill(0, 0, 0);
    textSize(25);
    text(points+" points", width-300, 45);
    text(cases+" cases", width-150, 45);
    popStyle();
  }
  void drawFightingTool(int x, int y, int imgWidth, int imgHeight) {
    pushStyle();
    imageMode(CENTER);
    if (fightingTool==0) { // draw handwash
      image(handwashImg, x, y, imgWidth, imgHeight);
    } else if (fightingTool == 1) { //draw mask
      image(maskImg, x, y, imgWidth, imgHeight);
    } else if (fightingTool == 2) { //draw shield
      image(shieldImg, x, y, imgWidth, imgHeight);
    }
    popStyle();
  }

  void changeFightingTool(int tool) {
    fightingTool = tool;
    //println(fightingTool);
  }

  void shooter() {
    pushStyle();
    fill(255, 255, 255);
    circle(shooterPosition, height, 100);
    game.drawFightingTool(shooterPosition, height-20, 40, 40);
    popStyle();
  }

  void moveShooter() {
    if (shooterSpeed<=2) {
      shooterDirection=0;
      shooterSpeed=2.1;
    }
    if (shooterSpeed>2) {
      shooterPosition += shooterDirection*shooterSpeed;
    }
  }
}
// to interact with game
void keyPressed() {
  //println(keyCode);
  switch(keyCode) {
  case 32: 
    { // space bar pressed need to create a new fighter add to list and reduce the number from game class
      if (game.fightingTool==0 && game.handwash>0) {//check number of handwash
        Fighter tempFighter = new Fighter();
        tempFighter.type = game.fightingTool;
        tempFighter.positionX = game.shooterPosition;
        tempFighter.positionY = height-30;
        tempFighter.fighterImage();
        activeFighters.add(tempFighter);
        game.decrementFighters();
      } else if (game.fightingTool==1 && game.masks>0) {//check number of masks
        Fighter tempFighter = new Fighter();
        tempFighter.type = game.fightingTool;
        tempFighter.positionX = game.shooterPosition;
        tempFighter.positionY = height-30;
        tempFighter.fighterImage();
        activeFighters.add(tempFighter);
        game.decrementFighters();
      } else if (game.fightingTool==2 && game.shields>0) {//check number of shields
        Fighter tempFighter = new Fighter();
        tempFighter.type = game.fightingTool;
        tempFighter.positionX = game.shooterPosition;
        tempFighter.positionY = height-30;
        tempFighter.fighterImage();
        activeFighters.add(tempFighter);
        game.decrementFighters();
      }
      break;
    }
  case 37:
    { //move left
      game.shooterDirection = -1;
      if (game.shooterSpeed<10) {
        game.shooterSpeed*=1.1;
      }
      break;
    }
  case 39:
    { //move right
      game.shooterDirection = 1;
      if (game.shooterSpeed<10) {
        game.shooterSpeed*=1.1;
      }
      break;
    }
  case 49:
    { //handwash
      if (game.state==0) {
        game.changeFightingTool(0);
      } else if (game.state==1) { //buy more handwash
        if (game.points>=1) { //check if you have enough points
          game.points-=1;
          game.handwash+=1;
        } else {
          triggerErrorSound();
          errorMessage = "Not Enough Points";
        }
      }
      break;
    }
  case 50:
    { //facemask
      if (game.state==0) {
        game.changeFightingTool(1);
      } else if (game.state==1) { //buy more masks
        if (game.points>=2) { //check if you have enough points
          game.points-=2;
          game.masks+=1;
        } else {
          triggerErrorSound();
          errorMessage = "Not Enough Points";
        }
      }
      break;
    }
  case 51: 
    { //faceshield
      if (game.state==0) {
        game.changeFightingTool(2);
      } else if (game.state==1) { //buy more handwash
        if (game.points>=3) { //check if you have enough points
          game.points-=3;
          game.shields+=1;
        } else {
          triggerErrorSound();
          errorMessage = "Not Enough Points";
        }
      }
      break;
    }
  case 77:
    {
      game.changeState();
      break;
    }
  default:
    {
      triggerErrorSound();
      errorMessage = "Invalid Control";
      break;
    }
  }
}

void keyReleased() {
  if (keyCode==37 || keyCode==39) {
    game.shooterSpeed = 0.8;
  }
}

void flashMessage(String msg, color c, int size, int hOffset) {
  pushStyle();
  fill(c);
  textSize(size);
  text(msg, 10, height-hOffset);
  popStyle();
}

void triggerErrorSound() {
  if (!error.isPlaying()) {
    error.play();
  }
}

void updateCorona() {
  for (int i=0; i<coronaList.size(); i++) {
    coronaList.get(i).updatePosition(height);
    coronaList.get(i).dispVirus();
  }
}

void displayError() {
  if (error.isPlaying()) // // display error message
  {
    color errorColor = color(255, 0, 0); // color of error message
    flashMessage(errorMessage, errorColor, 15, 25); // display error message
  }
}

void displayCost() {
  if (game.state==1) { // display cost of buying more items
    String msg = "Handwash 1  Facemask 2  Faceshield 3"; // message string
    color c = color(53, 50, 56); // color of message
    flashMessage(msg, c, 18, 10); // display message
  }
}

void playBGMusic() {
  if (!scaryBg.isPlaying()) { // play bgMusic
    scaryBg.play();
  }
}

void moveFighters() {
  //activeFighters
  for (int i=0; i<activeFighters.size(); i++) {
    activeFighters.get(i).updatePosition();
  }
}

void checkCollisions() {
  //println("checking collision");                        
  // two for loops. Check as virus against each fighter
  // if collision rem both fighter and virus from arraylist
  // increment point accordingly
  // if fighter above screen remove from array list
  // if virus below screen increment cases and rem from arraylist
  for (int i=0; i<coronaList.size(); i++) {
    println(coronaList.size());
    CoronaVirus tC = coronaList.get(i);
    int size = tC.size;
    //println(size);
    for (int j=0; j<activeFighters.size(); j++) {
      Fighter tF = activeFighters.get(j);
      if (tF.positionX>tC.positionX && tF.positionX<tC.positionX+size) { // this mean the fighter is within the image
        if (tF.positionY>tC.positionY && tF.positionY<tC.positionY+size) { // this means the fighter is within the image
          coronaList.remove(i);
          activeFighters.remove(j);
          game.incrementPoints(tF.type);
          if (!collision.isPlaying()){
            collision.play();
          }
          game.points+=1;
        }
      }
    }
  }
}

void checkCoronaPosition(){
  for(int i=0;i<coronaList.size();i++){
    CoronaVirus tC = coronaList.get(i);
    if (tC.positionY>height){
      game.cases+=1;
      if(!newCase.isPlaying()){
        newCase.play();
      }
      coronaList.remove(i);
    }
  }
}

void checkFighterPosition(){
  for(int i=0;i<activeFighters.size();i++){
    Fighter tF = activeFighters.get(i);
    if (tF.positionY<0){
      activeFighters.remove(i);
    }
  }
}

void newViruses(){
  if (frameCount%7==0 && frameCount%9==0 && frameCount%4==0){
    coronaList.add(new CoronaVirus());
  }
}

void gameOver(){
  // this should display the previous game stats
  background(110,125,171);
  int size = 50;
  pushStyle();
  textSize(24);
  int moveItemsHorizontal = 100;
  image(game.handwashImg,width/2-120+moveItemsHorizontal,height/2-240,size,size);
  text(game.handwash,width/2-80+moveItemsHorizontal,height/2-230);
  
  image(game.maskImg,width/2-120+moveItemsHorizontal,height/2-160,size,size);
  text(game.masks,width/2-80+moveItemsHorizontal,height/2-150);
  
  image(game.shieldImg,width/2-120+moveItemsHorizontal,height/2-80,size,size);
  text(game.masks,width/2-80+moveItemsHorizontal,height/2-70);
  
  textSize(32);
  textAlign(CENTER);
  text("Points "+game.points,width/2,height/2+30);
  text("Cases "+game.cases,width/2,height/2+80);
  popStyle();
  restartButton();
  
}

void restartButton(){
  int buttonYPosition = 200;
  int rectWidth = 250;
  int rectHeight = 70;
  pushStyle();
  rectMode(CENTER);
  fill(9,129,74);
  if (mouseX>width/2-rectWidth/2 && mouseX<width/2+rectWidth/2 && mouseY>height/2+buttonYPosition-rectHeight/2 && mouseY<height/2+buttonYPosition+rectHeight/2){
    fill(9,160,74);
    if (mousePressed){
      resetGame();
      fill(9,100,74);
    }
  }
  rect(width/2,height/2+buttonYPosition,rectWidth,rectHeight,30);
  textAlign(CENTER);
  fill(0,0,0);
  textSize(40);
  text("RESTART",width/2,height/2+buttonYPosition+15);
  popStyle();
}

void resetGame(){
  game = new Game();
  coronaList = new ArrayList<CoronaVirus>(0);
  activeFighters = new ArrayList<Fighter>(0);
  game.state = 0;
  for (int i=0; i<4; i++) {
    coronaList.add(new CoronaVirus());
  }
}

void startScreen(){
  background(110,125,171);
  image(virus,20,20,500,500);
  instructions();
  startButton();
}

void startButton(){
  int moveButton = 85;
  int buttonYPosition = 200;
  int rectWidth = 250;
  int rectHeight = 70;
  pushStyle();
  rectMode(CENTER);
  fill(9,129,74);
  if (mouseX>width/2-rectWidth/2 && mouseX<width/2+rectWidth/2 && mouseY>height/2+buttonYPosition-rectHeight/2+moveButton && mouseY<height/2+buttonYPosition+rectHeight/2+moveButton){
    fill(9,160,74);
    if (mousePressed){
      game.state=0;
      fill(9,100,74);
    }
  }
  rect(width/2,height/2+buttonYPosition+moveButton,rectWidth,rectHeight,30);
  textAlign(CENTER);
  fill(0,0,0);
  textSize(40);
  text("START",width/2,height/2+buttonYPosition+15+moveButton);
  popStyle();
}

void instructions(){
  // Title
  pushStyle();
  String title = "Pandemic";
  fill(178,103,0);
  stroke(178,103,0);
  strokeWeight(3);
  textAlign(CENTER);
  textSize(48);
  text(title,width/2,60);
  popStyle();
  
  //objective heading
  pushStyle();
  String objective = "Objective";
  fill(0,0,0);
  textAlign(LEFT);
  textSize(32);
  text(objective,width/2,150);
  popStyle();
  
  //objective text
  pushStyle();
  String objectiveText = "Try to get your points as high as possible while keeping the number of the cases as low as possible";
  fill(0,0,0);
  textAlign(LEFT);
  textSize(18);
  text(objectiveText,width/2+50,180, width/2-70,250);
  popStyle();
  
  //instruction heading
  pushStyle();
  String instruction = "Instructions";
  fill(0,0,0);
  textAlign(LEFT);
  textSize(32);
  text(instruction,width/2,280);
  popStyle();
  
  //objective text
  String line1 = "1. Move the shooter across the screen by using the left and right arrow keys.\n";
  String line2 = "2. The icon shown in the shooter will be what you will be shooting down the virus with.\n";
  String line3 = "3. Shooting the virus down with different tools will yield different points.\n";
  String line4 = "4. The tools which yield greater points are also more expensive to acquire.\n";
  String line5 = "5. You can choose the tool that you’re fighting with by pressing 1,2 or 3. 1 will select the handwash, 2 will select facemask and 3 will select a face shield.\n";
  String line6 = "6. You can acquire more tools by pressing m to go into buying mode and then pressing 1,2 or 3 to acquire more tools. A hand wash will cost 1 point, a face mask will cost 2 points and a face shield will cost 3 points.\n";
  String line7 = "7. Number of cases will increase is the virus goes below the screen.";
  pushStyle();
  String instructionText = line1+line2+line3+line4+line5+line6+line7;
  fill(0,0,0);
  textAlign(LEFT);
  textSize(15);
  text(instructionText,width/2+50,300, width/2-70,700);
  popStyle();
}
Planning Page 1
Planning Page 2
Planning Page 3
Planning Page 4
Planning Page 5
Planning Page 6
Planning Page 7
Planning Page 8
Planning Page 9

 

Aayush Deo Midterm Project

 

David – Midterm

zip file : Midterm_IM

This surely was the project in which I tried my best to incorporate everything we have learned so far. Initially, I wanted to make a game that resembles an already existing game called, “Flappy Bird”, but I diverted my plan to render something different and original to a certain extent. The game’s goal is to fly out of the mysterious cave by avoiding blocks of flying lava. Prior to the instructions of the game, which is fairly simple, I gave a short dystopian background narrative that the player somehow ended up in the cave because things fell apart ever since 2020.

I made the instructions fairly easy. The player will be floating along the cave and the only way to maneuver is to use UP, DOWN, RIGHT (to speed up) keys to avoid the flying lava bombs. If you successfully get out of the cave, you will have to go back to the society, and that requires MONEY. Other people have also attempted to get out of the cave, only to be buried there. So you will pick up the coins that the previous adventurers have left behind and follow their legacy… If you fail, you will become one of them.

I used a few images of caves and rocks to design the game as follow.

Cave1Cave2Rock1 Rock2

To talk more about the functionalities of the game, a player’s health decreases everytime he hits a block of lava. It will keep decreasing until your lives (initally out of 3) have fully depleted. You will then have to restart from the beginning. The placement of coins and lava bombs will be randomly assigned for every level. A very short song from PacMan will be played once you start the game and during the game a player will be able to hear sound effects when he hits a coin or a lava. You will see that there is a flashlight effect on the instructions page that was employed to create a more eerie and dystopian atmosphere.

Here is what it looks like:

The code is as follows:

import ddf.minim.*;

PFont font, font2;

Player player;
ArrayList<Coin> coins;
ArrayList<Bomb> bombs;
boolean [] keys = new boolean[128];
boolean instruction = true;
boolean gameover = true;
PImage photo, cave, rock, rock2;

color player_c = color(100, 200, 200);

int myPlayer = 3;
int myCoins = 0;
float myHealth = 100;

//sound effect
Minim minim;
AudioPlayer sound, sound2, sound3;

void setup(){
  size(800, 400);
  background(0);
  smooth();
  rectMode(CENTER);
  font = createFont("Trattatello",60);
  font2 = createFont("Arial",16);
  
  player = new Player();

  coins = new ArrayList<Coin>();
  for(int i=0; i < 10; i++){
    coins.add(new Coin());
  }

  bombs = new ArrayList<Bomb>();
  for(int i=0; i < 15; i++){
    bombs.add(new Bomb());
  }
  minim = new Minim(this);
  sound = minim.loadFile("1.aif");
  sound2 = minim.loadFile("2.mp3");
  sound3 = minim.loadFile("3.mp3");
  sound.setGain(-5);
  sound2.setGain(-5);
  sound3.setGain(-3);
  sound3.play();
  sound3.rewind();
  
}

void draw(){
  if (instruction){
    photo = loadImage("cave.jpg");
    //Using the width and height of the photo for the screen size
    photo.resize(width,height); 
    image(photo,0,0);
    loadPixels();
    photo.loadPixels();
    for(int x= 0; x<width; x++){
      for(int y=0; y<height; y++){
        //location in the pixel array
        int loc = x+y*width;
        float r = red(photo.pixels[loc]);
        float g = green(photo.pixels[loc]);
        float b = blue(photo.pixels[loc]);
        //get distance between the cursor and the pixel
        float d = dist(mouseX,mouseY,x,y);
        float mult = map(d,0,300,1.8,0);
        //the closer the distance, the brighter.
        pixels[loc] = color(r*mult,g*mult,b*mult);
      }
    }
    updatePixels();
    textFont(font);
    fill(#FFFF00);
    text("Cave Escape",width/2-200,height/2-70);
    textFont(font2);
    fill(255);
    text("It's year 2020... Things went south...\nIn fact, so south that you got stuck in a mysterious cave...\nSurvive the cave of HELL with full of GOLD COINS.\nMake your way out through flying lava bombs.\n\nTo maneuver, press → ↑ ↓\nPress ANY KEY to start",width/2-200,height/2-20);
    return;
  }
  
  background(0);
  cave = loadImage("cave2.jpg");
  //Using the width and height of the photo for the screen size
  cave.resize(width,235); 
  image(cave,0,67);
  caveStone();
  
  for (int i = coins.size() -1; i >= 0; i--){
    Coin c = coins.get(i);
    c.display();
    
    if(player.checkCollisionCoins(c)){
      sound2.rewind();
      sound2.play();
      
      myCoins += 10;

      coins.remove(i);
    }
  }
  
  for (int i = bombs.size() -1; i >= 0; i--){
    Bomb b = bombs.get(i);
    b.display();
   
    if(player.checkCollisionBombs(b)){
      sound.rewind();
      sound.play();
      player_c = color(255, 0, 0);
      myHealth -= 1.3;
      if(myHealth <= 0){
        myPlayer -= 1;
        myHealth = 100;
      }
    } 
  }

  player.display();
  player.move();
 
  //score
  textSize(15);
  fill(255);
  text("Player life = " + myPlayer, 60, 30, 255); // show life
  textAlign(LEFT);
  text("Coins = " + myCoins, 10, 55, 255); // show coins gathered 
  textAlign(LEFT);
  text("Health = " + myHealth, 110, 55, 255); 
  
  if(myPlayer == 0){
    setup();
    myCoins = 0;
    myHealth = 100;
    myPlayer = 3;
  } else {
    player_c = color(100, 200, 200);
  }

}


//Player
class Player{
  PVector pos;
  PVector acc;
  PVector vel;
  PVector speed;
  
  Player(){
    pos = new PVector(-5, height/2);
    acc = new PVector(0.8, 0.8);
    vel = new PVector(0,50);
    speed = new PVector(0.9,0);
  }
  
  void display() {
    fill(player_c);
    rect(pos.x, pos.y,30,30, 7);
  }
  
  void move(){
    vel.mult(5);
    vel.limit(10);
    vel = vel.add(acc);
    pos.add(speed);
    
    if(pos.x < -50){
      pos.x = width; 
    }
    if(pos.x > width){
      pos.x = -50;  
      bombs = new ArrayList<Bomb>();
      coins = new ArrayList<Coin>();
      for(int i=0; i < 10; i++){
        coins.add(new Coin());
      }
      for(int i=0; i < 15; i++){
        bombs.add(new Bomb());
      }
    }
    if(pos.y > height - 120 ){
      pos.y = height - 120; 
    }
    if(pos.y < 120){
      pos.y = 120; 
    }
    
    if(keys[RIGHT])
      pos.x += vel.x;
    if(keys[UP])
      pos.y -= vel.y;
    if(keys[DOWN])
      pos.y += vel.y;
  }
  
  boolean checkCollisionBombs(Bomb b){
     if(dist(player.pos.x, player.pos.y, b.pos.x, b.pos.y) <= b.b_size){
        return true; 
      } else {
        return false;
      }
  }

  boolean checkCollisionCoins(Coin c){
    if(dist(player.pos.x, player.pos.y, c.pos.x, c.pos.y) < c.radius){
        return true;
      } else {
        return false;
      }
  }
}

class Coin{
  
  PVector pos;
  PVector acc;
  PVector vel;
  color c;
  float radius;
  
  Coin(){
    pos = new PVector(random(50, width), random(110, height-110));
    acc = new PVector(0,0);
    vel = new PVector(0,0);
    radius = 20;
    c = color(255, 204, 0);
  }
  
  void display(){
    pushMatrix();
    noStroke();
    translate(pos.x, pos.y);
    fill(80);
    ellipse(2,2, radius, radius);
    fill(c);
    ellipse(0,0,radius, radius);
    fill(0);
    textSize(15);
    textAlign(CENTER);
    text("$",0,4);
    popMatrix();
  }
}


class Bomb {
  PVector pos;
  PVector acc;
  PVector vel;
  float b_size = 25;
  color c;
  float direction = random(2,8);
  
  Bomb (){
    pos = new PVector(random(100, width), random(110, height- 110));
    acc = new PVector(0,0);
    vel = new PVector(0,0);
    c = color(255, 0, 0);
  }
  
  void display(){
    pushMatrix();
    translate(pos.x, pos.y);
    fill(c);
    rect(0, 0, b_size, b_size);
    pos.y += direction;
    if (pos.y < 110 || pos.y > height-110){
      direction *= -1;
    }
    popMatrix();
  }
  
}

void keyPressed(){
  if (instruction) {
    instruction = false;
  }
  else if (gameover){
    gameover = false;
  }
  else {
    keys[keyCode] = true;
  }
}

void keyReleased(){
  keys[keyCode] = false;
}



void caveStone(){
  rock = loadImage("base.jpg");
  rock2 = loadImage("base2.jpg");
  rock.resize(30,30);
  rock2.resize(30,30);
  
  for(int i = 0; i <= width; i += 30) {
    image(rock,i,67);
    image(rock,i,302);
  }
  for(int j = 0; j <= width; j += 90) {
    image(rock2,j,67);
    image(rock2,j,302);
  }
}

 

Week 7 – Midterm

Here we are, after Fall-ish break also known as Midterms Week:

Back to the Spelling Game

After a lot of planning, and not much sleep, I managed to achieve the general idea of my game, with a few bugs and missing elements. Although I would have loved to have it as I imagined, I think with the amount of time + midterms that I had, this is a cool result!

Additionally, if you still didn’t notice, I always try my best to avoid making games for our assignments and often opt for the artwork option. The thought of coding a game often scares me, especially when I think of moving between screens and checking for correct moves. So, this was a great challenge for me! And while I feel like I’m a bit more familiar with game states and other elements, I still think I can work on my game-making skills, and will probably end up looking at your codes for help 🙂

what planning looks like for me

List of Classes: 

  • Animals: this is where I load and display the visual for my animal
  • Buttons: where I create the start and replay buttons
  • Letter: Where I create letter blocks for a keypad grid and check for player clicks.

 

 

Steps

  • Make a start button, start screen, and then switch to the gameplay screen that is linked with an animal.

 

  • Make a keypad for each possible animal. This keypad must contain the letters of the animal name in random order, and it must not repeat letters within the alphabet list:

For this bit, I created two functions

  1. WordCharsinKeypad(): fills the characters from the animal name into the keypad randomly (the letters won’t appear next to each other).
  2. fillRemainingKeys(): checks for remaining empty spots in the keypad and fills them with a random letter from the alphabet without repeating letters from that alphabet array.
  • Display the game playing screen, make the keypad interactive, and give feedback to the user by printing on the screen the letters they get correctly:

I initially aimed to provide two types of feedback. One, which is evident in my program, is that the animal is gradually spelled out on the screen and a letter is added when the player picks the correct letter.

The other, however, I struggled with, which is showing a “try again” message when the player presses the wrong letter. For now, the program just doesn’t respond to a wrong letter.

When I tried to apply the second feedback this is what I got:

  • Finally, I wanted to switch between screens, as you can see above, move from the start screen to the gameplay screen, to the replay screen:

For the start and replay, I relied on buttons from my button class, however, for moving from gameplay to replay screen, it only requires the player to guess the complete word. A small issue I have here is that the last letter does not display before moving to the replay screen. I tried to resolve it with delay()  and altering the if condition in the code, but I still haven’t figured it out.

Some Design Choices

  • I was truly excited to design my own animals, I thought it would be a great chance to work on my Adobe Illustrator skills. However, I placed that as my last priority and unfortunately did not have time for that. So I used free PNGs from open source websites.
  • My game is missing sound, this is something that I will work on and practice as I still feel a bit unfamiliar with it
  • I tried to use soft colors with great contrast to appeal to the child’s eye
  • Keeping accessibility in mind, I made sure to use a font that is legible for most kids. This typeface is OpenDyslexic and it is specifically designed to make it easier for anyone with dyslexia to read a text.

Example of a Game Run

Future Developments

For a more developed version of this game, it would probably incorporate the sounds I mentioned in my progress post, my own designs, and levels rather than one run.

Here’s my code!

Main Function

// to load OpenDyslexic Font
PFont f; 
//for final screen
PImage confetti; 
boolean wrongLetter = false;
//startScreen
int screenNumber = 0;
//a string array to pick a random animal each time start is pressed:
String[] animalNames = {"CAT", "OWL", "FROG", "FISH", "PANDA", "MONKEY", "TURTLE", "CHAMELEON"}; 
// string where the random animal picked from array is stored:
String word; 
//character array of all letters in the alphabet:
char alphabet[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; 
//initializing a letter object array for a keypad:
Letter[] keypad = new Letter[12]; 
//initializing the animal object for the animal displayed in the run:
Animals animal4Run; 
//columns for keypad grid display
int cols = 3;
//rows for keypad grid display
int rows =4; 
// a variable that checks the players progress of selecting the correct letters -- e.g. if they have "CA" out of "CAT" it will equal 1 
int playerPosition = 0; 
//initializing a button object for start button
Buttons startButton;
// initializing a button object for replay button
Buttons replayButton; 
//an empty string variable. It will be used to reflect the player's progress in choosing the correct letter
String wordInProg; 

void setup() {
  size(960, 540);
  confetti = loadImage("CONFETTI.png");
  confetti.resize(960, 540);
  //using OpenDyslexic so the font could be readable for most
  f = createFont("OpenDyslexic-Bold.otf", 20);  
  textFont(f);
  //creating the start button object
  startButton = new Buttons(width/2, height/2+100, 100, 70, "Start"); //creating the start button object
  //creating the replay button object
  replayButton = new Buttons(width/2, height/2+200, 100, 70, "Replay"); 
  //setup the word in progress as an empty string, it will fill gradually as the player picks the correct letters.
  wordInProg = "";
  //generates a random index to pick a random animal each time run is clicked
  int indexForRun = (int)random(0, animalNames.length); 
  //sets the word variable using the generated random index
  word = animalNames[indexForRun];
  //creates an animal object using the word
  animal4Run = new Animals(word); 
  //index variable to go through all objects in the keypad array
  int index = 0; 
  for (int c = 0; c < cols; c++) {
    for (int r = 0; r < rows; r++) {
      //using columns and rows for a grid display
      keypad[index] = new Letter('x', c, r); 
      index += 1;
    }
  }

  //function that fills the characters from the animal name into the keypad in random positions
  wordCharsInKeypad(); 
  //function that fills the remaining empty keypad spaces
  fillRemainingKeys();
}

void draw() {
  background(227, 181, 164);
  //switch function to move between game screens
  switch(screenNumber) { 
  // case 0 is the start screen
  case 0:
  //calls setup to randomize a new animal everytime start is pressed
    setup(); 
    fill(232, 95, 92);
    textSize(60);
    text("Spell the Animal Name Game!", width/2, height/2 - 100);
    //display function for start button
    startButton.buttonDisplay();
    //checks if player clicks the start button and moves on to the next game screen
    if (mousePressed && mouseX >= startButton.xLoc - startButton.buttonW/2 && mouseX <= startButton.xLoc + startButton.buttonW/2 && mouseY >= startButton.yLoc - startButton.buttonH/2 && mouseY <= startButton.yLoc + startButton.buttonH/2) {
      screenNumber = 1; 
    }
    break;
  // playing screen
  case 1: 
    fill(156, 255, 250);
    textSize(50);
    text("What is this animal called?", width/2, height/2 - 200);
     //displays the player's progress in choosing the correct letters on the screen
    text(wordInProg, width/2-50, height/2 + 200);
    for (int i = 0; i < keypad.length; i ++) {
      //displays all letter objects in the keypad
      keypad[i].display(); 
      //displays the image of the animal in question
      animal4Run.display(); 
      //loops through all letters to check if 1. they are clicked , 2. if they are the correct letter 
      for (int j = 0; j < keypad.length; j++ ) { 
        textSize(20);
        //calls a function that checks whether the letter is clicked 
        keypad[j].letterIsClicked(); 
        if (keypad[j].letterClicked == true) {
          keypad[j].letterClicked = false;
          //println(">>>", keypad[j].letter, cat.animalName.charAt(playerPosition));
          if (keypad[j].letter != animal4Run.animalName.charAt(playerPosition)) {
            text("try again", width/2 - 300, height/2);} //?????
          //checks if the character clicked in the keypad matches the character that is at the player's position in the animal name
          if (keypad[j].letter == animal4Run.animalName.charAt(playerPosition)) {
            //this condition ensures that it adds the correct letters only for as long as the word in progress is the same length as the animal name, otherwise you would get "CATTTTTTTTTT" 
            if (wordInProg.length() < animal4Run.animalName.length()) {
              //keeps adding to reflect the player's progress
              wordInProg = wordInProg + animal4Run.animalName.charAt(playerPosition);
              //println(wordInProg);
              //println(keypad[j].letter + " " + cat.animalName.charAt(playerPosition));
              //condition: as long as the player position is still less than the length of the animal name, keep adding as the name is not complete yet
              if (playerPosition < animal4Run.animalName.length() -1) {
                playerPosition+= 1;
               //checks if the word is complete to switch to end screen that reflects success and gives replay button
              } else if (playerPosition == animal4Run.animalName.length()-1) {
                screenNumber = 2;
                break;
              }
            }
          }
        }
      }
    }
    break;
   //end screen
  case 2:
  //resets player position for next iteration of the game
    playerPosition = 0;
    for (int k = 0; k < keypad.length; k++ ) {
      //resets any letters clicked
      keypad[k].letterClicked = false; 
    }
    background(227, 181, 164);
    imageMode(CORNER);
    //tint(255);
    //loads confetti image for celebration :)
    image(confetti, 0, 0);
    fill(232, 95, 92);
    textSize(60);
    text("What a Spelling Champ!", width/2, height/2 - 100);
    fill(156, 255, 250);
    text("Press Replay to Spell Again!", width/2, height/2);
    //displaying replay button
    replayButton.buttonDisplay();
    //checks if pressed to go back to start screen
    if (mousePressed && mouseX >= replayButton.xLoc - replayButton.buttonW/2 && mouseX <= replayButton.xLoc + replayButton.buttonW/2 && mouseY >= replayButton.yLoc - replayButton.buttonH/2 && mouseY <= replayButton.yLoc + replayButton.buttonH/2) {
      screenNumber = 0;
    }
  }
}


//function that inputs the characters of the animal name in the keypad
void wordCharsInKeypad() {
  //a boolean to check if all the characters have been input
  boolean complete = false;
  //i variable to loop through all letters in the animal name
  int i = 0;
  while (complete == false) { 
    //generating a random index so letters don't appear in correct order in the keypad
    int randomIndex = (int) random(keypad.length - 1);  
    // condition: if the key is empty, signified by 'x', fill it with this letter, and then move to the next character (to avoid overwriting keys)
    if (keypad[randomIndex].letter == 'x') { 
    
      keypad[randomIndex].letter = animal4Run.animalName.charAt(i); 
      i += 1;
    }
    //if all the letters from the animal name are in the keypad, exit the while loop
    if (i == animal4Run.animalName.length()) { 
      complete = true;
    }
  }
}

//function to fill the rest of the keypad
void fillRemainingKeys() {
 //same boolean and variable concept as before
  boolean complete = false; 
  int j = 0;
  while (complete == false) {
    //this boolean is to check if the chosen letter already exists in the keypad
    boolean duplicated = false;
    //generates a random index to input a random letter from the alphabet
    int randomIndex = (int) random(alphabet.length -1);
    // println(j, keypad[j].letter, alphabet[randomIndex], word.indexOf(alphabet[randomIndex]));
    //if this position in the keypad is empty AND the chosen character is not in the animal name (as that wouldve been input in the prev function, fill it in the keypad)
    if (keypad[j].letter == 'x' && animal4Run.animalName.indexOf(alphabet[randomIndex]) == -1) {
      //for loop that checks if the letter that we are about to input already exists in the keypad to avoid repetition
      for (int k = 0; k < keypad.length; k++) {
        if (keypad[k].letter == alphabet[randomIndex] ) {
          duplicated = true;
          //if it is a duplicate it breaks out of this for loop and restarts to generate a new letter
          break;
        }
      }
      //to continue trying to generate
      if (duplicated) {
        continue;
      }
      // if it isn't a duplicate it inputs it into the keypad
      keypad[j].letter = alphabet[randomIndex];
    }
    //if the letter is in the word, meaning it is already in the keypad, also continue to generate something else
    if (word.indexOf(alphabet[randomIndex]) != -1) {
      continue;
    }
    j+= 1;
    //condition to exit the while loop when the keypad is fully formed
    if (j == keypad.length) {
      complete = true;
    }
  }

  //for (int k = 0; k < keypad.length; k++) {
  //println(k, keypad[k].letter);
  // }
}

Animals Class

class Animals {
  //two vars for animal name and to load the suitable image
  String animalName;
  PImage animalImage;
  
  
  Animals(String tempAnimalName){
    animalName = tempAnimalName;
    //using animal name to load the image
    animalImage = loadImage(animalName+".png");
    animalImage.resize(300,300);
  }
  
  
  void display(){
   fill(0,255,0);
   imageMode(CENTER);
   image(animalImage,width/2-50,height/2);
   //rect(width/2,height/2,100,100);
   //fill(0);
   //textAlign(CENTER,CENTER);
   //textSize(15);
   //text("I am a" + " " + animalName, width/2,height/2);
  }
}

Buttons Class:

  class Buttons {
    int xLoc, yLoc, buttonW, buttonH; 
    String buttonTitle;
    //
    
    
  
    Buttons(int xLocTemp, int yLocTemp, int buttonWTemp, int buttonHTemp, String buttonTitleTemp) {
      xLoc = xLocTemp;
      yLoc = yLocTemp;
      buttonW = buttonWTemp;
      buttonH = buttonHTemp; 
      buttonTitle = buttonTitleTemp;
      
      
    }
  
  
  

  
  
    void buttonDisplay() {
      rectMode(CENTER);
       noStroke();
       fill(200);
       rect(xLoc, yLoc, buttonW, buttonH);
       fill(0);
       textSize(25);
       textAlign(CENTER,CENTER);
       text(buttonTitle, xLoc, yLoc);
      }
  }

Letter Class:

class Letter {
  char letter; 
  int xLoc, yLoc; 
  int blockW = 70;
  int blockH = 70;
  int colNo;
  int rowNo;
  //booleans to check during the game
  boolean letterClicked;
  boolean letterCorrect;


  Letter(char letterTemp, int tempColNo, int tempRowNo) {
    letter = letterTemp;
    colNo = tempColNo;
    rowNo = tempRowNo;
  }


  void display() {
    //as it is a grid object, col and row number are used for x and y coordinates
    rectMode(CORNER);
    fill(0);
    stroke(255);
    rect(700+colNo*70, 150+rowNo*70, blockW, blockH);
    fill(255);
    textSize(20);
    textAlign(CENTER, CENTER);
    text(letter, 735+colNo*70, 185+rowNo*70);
  }
  
  
//the function that checks if the letter is clicked
 void letterIsClicked() {
    if (mousePressed && mouseX >=  700+(colNo)*70  && mouseX <= 700+(colNo)*70 + blockW && mouseY >= 150+(rowNo)*70 && mouseY <= 150+(rowNo)*70+blockH) {
     // println("mouseY: ", mouseY, " - 200+(rowNo-1)*70: ", 200+(rowNo-1)*70, " - 200+(rowNo-1)*70+blockH: ", 200+(rowNo-1)*70+blockH);

      letterClicked = true;
      //println(letter, " Clicked");
    }
  }
  
}

Zip: midtermm_Game

Nathan – Midterm Project

For this project, I continued to want to make a two-player game. The game is competitive in nature, where the two players will rush to grab valuable resources before it’s all taken by the other.

Inspiration

There isn’t any single source of inspirations for this game. It’s 2D interface kind of resembles the Nintendo pokemon games, where the world is a flat surface and players take a birds-eye view of the world. The mode of the game actually reminds me of the lego star wars game, in some ways. Players have to rush to collect resources while dodging dangerous objects.

Game-Specific Information

There are two heroes, a ninja and a warrior that are on a quest together in an underground mine. While resting, candies start to float out of the caves, and the two heroes decide to grab candies to eat. However, there are edible candies and poisonous candies, and only the candy indicator tells the players which candies are edible. In the game, this candy indicator is located at the bottom middle of the screen, as shown:

If a player “eats” an edible candy, one point will go to their score, which shows at their spawn point, at the bottom of the screen. If a player “eats” a poisonous candy, they die and respawn at their spawn point. Once all the edible candies are collected, the player with the most candy wins. If both have the same amount, it is a tie.

When the game initializes, there is a starting screen with the name of the game, “Candy Rush”, as well as the instructions and a “play” button. When hovering the mouse over the play button, the button will be highlighted:

Regarding the sounds, the game has three in total. One is the background music that loops endlessly. The other two are sounds for when the hero eats a poisonous candy and an edible candy.

There are two classes, one for the heroes and one for the candies. The hero tab simply includes its display and move functions. The candy tab includes its display, collision with the heroes, collision with a wall, and movement functions. The main code page, apart from draw and setup, includes four main functions. The reset function is in charge of initializing all the game values. Most of these functions could have gone into setup, but I needed to create a restart function for when the game ends. Thus, I moved most of the value initialization into the reset function, and then called the reset function in setup. This way, I could decide when to “re-setup” my game.

The next function is menu, which is in charge of the starting menu. I run this function first in draw. While running the menu function, when the player clicks “play”, the universal boolean “go” will turn to true, triggering the begin function.

The begin function is the main gameplay, where all the object functions are called. At the end of the begin function, the last function is called, which is checkwin.

Checkwin simply runs through the win conditions constantly and displays the winning screen when the win condition is met.

Images used

Background:

Hero Sprites:

Sounds used

Background Music:

Catching edible candy:

Catching poisonous candy:

 

Full Code

Main Code Page:

PFont font;                              

import processing.sound.*;            //all the sounds in this game
SoundFile eat;
SoundFile die;
SoundFile music;

Ball b1;                              //initializing the two players
Ball b2;
PImage ninja, warrior, bg;
PImage[][] ninjawalk;
PImage[][] warriorwalk;
int ndirection = 0;
int wdirection = 0;
int nstep = 0;
int wstep = 0;
int speed = 5;

Candy[] candy;                        //initializing all the candy
int count;
color chosen;
int livecount = 0;                    //how many edible candy there are

boolean go = false;                   //start game value

void setup() {
  size(1200, 800);
  bg = loadImage("bg.jpg");
  
  //sounds
  eat = new SoundFile(this, "eat.mp3");
  die = new SoundFile(this, "die.mp3");
  music = new SoundFile(this, "music.mp3");
  music.loop();
  
  //slicing the two sprite sheets for player 1 and 2
  ninja = loadImage("ninja.png");
  ninjawalk = new PImage[4][3];
  int w = ninja.width/3;
  int h = ninja.height/4;
  for(int iy = 0; iy < 4; iy++){
    for(int ix = 0; ix<3; ix++){
      ninjawalk[iy][ix] = ninja.get(ix*w, iy *h, w, h);
    }
  }
  warrior = loadImage("warrior.png");
  warriorwalk = new PImage[4][3];
  int ww = warrior.width/3;
  int wh = warrior.height/4;
  for(int wy = 0; wy < 4; wy++){
    for(int wx = 0; wx<3; wx++){
      warriorwalk[wy][wx] = warrior.get(wx*ww, wy *wh, ww, wh);
    }
  }
  //use this function to restart the game
  reset();
}

void draw() {
  menu();
  if (go == true){
    begin();
  }
}

//this function restarts the game from the gaming interface directly
void reset(){
  float unit = 200;
  b1 = new Ball(width/4, height - 20, 1);          //player 1 character
  b2 = new Ball(width*3/4, height - 20, 2);        //player 2 character
  
  int wideCount = width / int(unit);               //calling the candies, initializing different candy colors
  int highCount = (height*3/4) / int(unit);
  count = wideCount * highCount;
  candy = new Candy[count];
  int index = 0;
  color[] colors = new color[6];
  colors[0] = #FF0011;
  colors[1] = #BA00FF;
  colors[2] = #2500FF;
  colors[3] = #00FFE8;
  colors[4] = #00FF01;
  colors[5] = #FFAF00;
  chosen = colors[int(random(0,5))];
  for (int y = 0; y < highCount; y++) {
    for (int x = 0; x < wideCount; x++) {
      candy[index++] = new Candy(x * unit + unit/2, y * unit + unit/2, 20, 20, colors[int(random(0,5))]);
    }
  }
  
  for (Candy candy : candy) {                      //counting how many edible candies there are to know the win condition
    if (chosen == candy.c){
      livecount ++;
    }
  }
  
  for (int i =0; i<count;i++){                     //candy speed initializing
     candy[i].xSpeed = random(-2,2);
     candy[i].ySpeed = random(-2,2);
  }
}

//this is the starting menu
void menu(){
  background(bg);
  fill(255);
  font = createFont("BreatheFire-65pg.ttf", 60);
  textFont(font);
  textAlign(CENTER);
  text("CANDY RUSH \n even heros like candy", width/2, 185);
  font = createFont("Georgia", 40);
  textFont(font, 30);
  text("This is a two player game. Ninja and Warrior wants to take a break \n on their quest, so they are going to catch candy. Some candies are \n poisonous, and only the candy indicator can tell you which candies are edible. \n The first player to catch all the edible candy wins. \n NINJA: WASD, WARRIOR: direction keys", width /2, 370);
  stroke(0);
  fill(0);
  rectMode(CENTER);
  rect(width/2, 680, 200, 72);
  fill(255);
  textFont(font, 60);
  font = createFont("BreatheFire-65pg.ttf", 42);
  textFont(font);
  textAlign(CENTER);
  text("PLAY", width/2, 700);
  if (mouseX>=(width/2 - 100)  & mouseX<=(width/2 + 100) & mouseY>=650 & mouseY<=720){
    stroke(255);
    noFill();
    rect(width/2, 680, 200, 72);
    if (mousePressed){
        go = true;
    }
  }
}

//this is the game starting function. the actual game is ran here
void begin(){
  background(bg);
  textSize(24);
  text(b1.score, width/4, height - 50);
  text(b2.score, width*3/4, height - 50);
  
  //calling candy functions
  for (Candy candy : candy) {
    candy.display();
    candy.collision();
    candy.update();
    candy.checkEdges();
  }
  
  //this is the candy indicator
  stroke(0);
  fill(chosen);
  ellipse(width/2, height - b1.diameter, b1.diameter, b1.diameter);
  
  //calling character functions
  b1.display();
  b1.move();
  b2.display();
  b2.move();
  checkwin();
}

void checkwin(){
  if (livecount == 0){                      //win condition
    for (Candy candy : candy) {
      candy.xSpeed = 0;
      candy.ySpeed = 0;
    }
    rectMode(CENTER);
    if (b1.score > b2.score){
      fill(0);
      rect(width/2, height/2, width, height);
      fill(255, 255, 255);
      textSize(32);
      text("Ninja Wins! \n (Press r to restart)", width/2, height/2); 
    }
    else if (b2.score > b1.score){
      fill(0);
      rect(width/2, height/2, width, height);
      fill(255, 255, 255);
      textSize(32);
      text("Warrior Wins! \n (Press r to restart)", width/2, height/2);
    }
    else if (b2.score == b1.score){
      fill(0);
      rect(width/2, height/2, width, height);
      fill(255, 255, 255);
      textSize(32);
      text("Tie! \n (Press r to restart)", width/2, height/2);
    }
    
    //restart button
    if (keyPressed) {                            
       if (key == 'r' || key == 'R') {
          reset();
       }
    }
  }
}

Player Class Page:

class Ball {

  float x;
  float y;
  float diameter;
  int score;
  int player;
  int w = ninja.width/3;
  int h = ninja.height/4;
  int ww = warrior.width/3;
  int wh = warrior.height/4;

  Ball(float tempX, float tempY, int tempPlayer) {
    x = tempX;
    y = tempY;
    diameter = 45;
    score = 0;
    player = tempPlayer;
  }

  void display() {
    imageMode(CENTER);                                    //displaying player 1
    if (player == 1){
      image(ninjawalk[ndirection][nstep],x,y,w,h);
    }
    if (player == 2){                                     //displaying player 2
      image(warriorwalk[wdirection][wstep],x,y,ww,wh);
    }
  }
  
  void move() {  
    if (player == 1){
      if (keyPressed) {
        if (key == 'w' || key == 'W') {
          y = y - 5;
          ndirection = 0;
        }
        if (key == 'a' || key == 'A') {
          x = x - 5;
          ndirection = 3;
        }
        if (key == 's' || key == 'S') {
          y = y + 5;
          ndirection = 2;
        }
        if (key == 'd' || key == 'D') {
          x = x + 5;
          ndirection = 1;
        }
        if (frameCount%speed == 0) {
          nstep = (nstep + 1) %3;
        }
      }
    }
    
    if (player == 2){
      if (keyPressed) {
        if (keyCode == UP) {
          y = y - 5;
          wdirection = 0;
        }
        if (keyCode == LEFT) {
          x = x - 5;
          wdirection = 3;
        }
        if (keyCode == DOWN) {
          y = y + 5;
          wdirection = 2;
        }
        if (keyCode == RIGHT) {
          x = x + 5;
          wdirection = 1;
        }
        if (frameCount%speed == 0) {
          wstep = (wstep + 1) %3;
        }
      }
    }
  }
}

Candy Class Page:

class Candy {
  float x;
  float y;
  float w;
  float h;
  float xSpeed, ySpeed;
  boolean caught = false;
  color c;

  Candy(float tempX, float tempY, float tempW, float tempH, color tempC) {
    x = tempX;
    y = tempY;
    w = tempW;
    h = tempH;
    c = tempC;
    xSpeed = ySpeed = 0;
  }  

  void display() {

    ellipseMode(CENTER);
    if (caught == false){
      fill(c);
      ellipse(x, y, w, h);
    }
  }
  
  //collision function for when players catch the right and wrong candy
  void collision() {
    if ((b1.x - b1.w *3/4 < x && x < b1.x + b1.w*3/4) & (b1.y - b1.h*3/4 < y && y < b1.y + b1.h*3/4)){
      if (c == chosen){
        x = -50;
        y = -50;
        caught = true;
        b1.score ++;
        livecount --;
        eat.play();
      }
      else if (c != chosen){
        b1.x = width/4;
        b1.y = height - b1.diameter;
        die.play();
      }
    }
    if ((b2.x - b2.w*3/4 < x && x < b2.x + b2.w*3/4) & (b2.y - b2.h*3/4 < y && y < b2.y + b2.h*3/4)){
      if (c == chosen){
        x = -50;
        y = -50;
        caught = true;
        b2.score ++;
        livecount --;
        eat.play();
      }
      else if (c != chosen){
        b2.x = width*3/4;
        b2.y = height - 20;
        die.play();
      }
    }
  }
  
  void update(){
    x += xSpeed;
    y += ySpeed;
  }
  
  void checkEdges() {
    if (y>(height*3)/4) {
      ySpeed = -ySpeed;
    }
    if (y<0) {
      ySpeed = -ySpeed;
    }
    if (x>width) {
      xSpeed = -xSpeed;
    }
    if (x<0) {
      xSpeed = -xSpeed;
    }
  }
}

 

Entire Compressed Code

Midterm

Game Demonstration

Ryan’s Midterm Project

Initial Stage

When I first started thinking about ideas for my Midterm Project, I simply thought of just finishing one of my unfinished Brick Breaker games. However, we were reminded that it should be original, so this first idea was out of the window.

Afterwards, I tried to think of an original and creative idea of mine like the other people by trying to get some inspiration from other games that are currently out there. However, still, nothing came into my mind. So, I decided to at least think of  a theme for my game first, and I managed to settle on the theme of the situation that we are all in together, which is covid 19.  I thought this was a pretty good theme to work with because I was hoping to make a game that can reflect the situation that we’re all in and at the same time raise people’s awareness of this global epidemic. At first, I wrote down a list of ideas that was covid themed while trying to see if it can be applied with the similar mechanics of other games.

However, I wanted to find a way to make this game a bit more casual or comical to alleviate the tension around our world that is caused by this whole epidemic. Coincidentally, the news about Donald Trump getting Covid was released and I thought that it was a perfect opportunity to incorporate him into my game by having the users be able to control him and make him wear a face mask. I also saw this chance to make my game act as a reminder for the public to always to wear a face mask, or else you will “lose” to Covid.

After deciding on my main idea that I’m going to work with, I proceeded to brainstorm on how I could make this a game. After a while, I decided on a game where the user will control a ball, and the main objective of the user is to try to touch and tag all of the other balls that are originally images of Donald Trump not wearing a face mask and change them into images of Donald Trump wearing a face mask by touching all of them within a time limit of 30 seconds.

Sketches of game layout and examples

(these are pictures of my notes and sketches for my game’s layout)

Images Used 

For my images, I simply just searched different png images of Donald Trump online (not that hard to find). But I had to manually crop out some of the background of the second image and convert it to a png file.

Donald Trump just being Donald Trump
Donald Trump wearing a face mask image

Background music 

Link: https://www.youtube.com/watch?v=xy_NKN75Jhw&list=PLya__OBTLMkONuQDu0kHDCrml2xuEINSi&index=1

Main Difficulties

Some of the major difficulties that I had encountered throughout this entire process range from simply just importing an image or sound file to coding for collision detection between the objects, changing of images upon contact, starting the game when the mouse is clicked within a certain shape, telling the game to stop after the timer ends or ending it before the timer when the user has successfully tagged all of the shapes, and many many more.

However, after talking to both Aaron and Jack , I was finally able to resolve a lot of the different problems that I had experienced (too much to even list them all out). But, at the same time, I have also learned a lot throughout this experience about how to code more efficiently or how to streamline a specific function to another if something is pressed or triggered, and all those stuff.

Zip File

IM_Midterm_Project_2

All the code 

import processing.sound.*;

Balls [] balls;//the [] brackets indicate an array; it contains a whole list of NPC balls
userBall myBall;// this calls forward the User Ball class in the name of "myBall"
Timer timer;
PImage Ballsimg;
PImage collidedBallImg;
boolean buttonPressed;
int buttonX, buttonY, buttonW, buttonH;
int amountcollided;//the amount of times that the balls have collided
String S= "You Win! \n Press the mouse to restart";
SoundFile backgroundmusic;
int gameState;
int numberofballs=10;

void setup() {
  size(1280, 800);
  gameState=0;
  //Assume the start button has not been pressed
  buttonPressed = false;

  //Parameters for the start button 
  buttonW=335;
  buttonH=100;
  textSize(buttonH);
  buttonX=(width-buttonW)/2;
  buttonY=(height-buttonH)/2;
  Ballsimg= loadImage("Donald Trump Face.png");
  collidedBallImg= loadImage("Donald Trump wearing Facemask.png");
  balls= new Balls [numberofballs];// this allows it to contain 10 of the NPC Balls; this calls the constructor 
  for (int i=0; i<balls.length; i++) {// as long as i is less than 10, it's going to keep on calling on the "Balls" constructor, basically creating new balls 
    balls[i]= new Balls(Ballsimg, collidedBallImg);
  }
  backgroundmusic= new SoundFile(this, "Instruction BG Music.mp3");
  myBall= new userBall();//calls on the userBall constructor to create the user Ball in the name of "myBall"
}

void draw() {
  background(255);
  //code for instructions and start button
  if (gameState==0) { //if the Start button is pressed, run the game code 
    //timer.showTimer();//shows the timer on screen
    fill(random(144), random(230), random(166));
    String Instructions= "Instructions: \n Use the arrow keys on your keyboard to control and move the ball around the screen. \n The objective of the game is to make all the other Donald Trump balls wear his mask properly, \n and you will do that by tagging or touching all of them within the time limit of 30 seconds. \n You win if you manage to make all the Donald Trump balls wear a mask. \n You lose if there are still remainig Donald Trump balls not wearing a mask when the time ends."; 
    textAlign(CENTER);
    textSize(20);
    text(Instructions, width/2, height/2-200);
    fill(random(255), random(255), random(255));
    rect(buttonX, buttonY+200, buttonW+65, buttonH);
    fill(255, 0, 0);
    textSize(50);
    text("PRESS TO START", buttonX+200, buttonY+200+buttonH-10);
  } else if (gameState==1) {//the gameplay screen with game code
    for (int i=0; i<balls.length; i++) {
      balls[i].drawBalls();//allows all the NPC balls to be drawn on the window
      balls[i].moveBalls();//allows all the NPC balls to move around the window
      balls[i].Ballscheckedge();//allows all the NPC balls to bounce of the edges of the window
    }
    collisiondetection();
    myBall.drawuserBall();
    timer.showTimer();//shows the timer on screen
  } else if (gameState==2) {//win
    //numberofballs+=1;
    background(0);
    fill(random(255), random(255), random(255));
    textAlign(CENTER);
    textSize(80);
    text(S, width/2, height/2);
    if (mousePressed) {
      reset();
    }
  } else if (gameState==3) {//lose
    String G="Time's Up! You Lose! Game Over";
    String R="Press the mouse to restart";
    background(0);
    fill(random(255), random(255), random(255));
    textAlign(CENTER);
    textSize(80);
    text(G, width/2, height/2);
    textSize(50);
    text(R, width/2, height/2+100);
    if (mousePressed) {
      reset();
    }
  }
}

//if the start button is pressed
void mousePressed() {
  if ( mouseX > buttonX && mouseX < buttonX+buttonW+65 && mouseY > buttonY+200 && mouseY < (buttonY+200)+buttonH) {
    //buttonPressed=true;
    gameState=1;
    backgroundmusic.play();
    timer= new Timer();
  }
}
//code to control the user's ball
void keyPressed() { //code for controlling the user's Ball by arrow keys
  if (keyCode== UP && myBall.locy-myBall.radius >=0) myBall.locy-=25;//if the y location of the user's Ball minus the radius of the ellipse is greater or equal to 0, it will move upwards
  else if (keyCode== DOWN && myBall.locy+myBall.radius <=height) myBall.locy+=25;
  else if (keyCode == LEFT && myBall.locx-myBall.radius>=0) myBall.locx-=25;
  else if (keyCode == RIGHT && myBall.locx+myBall.radius<= width) myBall.locx+=25;
}

void collisiondetection() {
  amountcollided=0;
  //putting in aloop for all of the NPC balls 
  for (int i=0; i<balls.length; i++) {
    //if the x and y location distance of any NPC ball and user ball added together is smaller than the radius of the NPC ball and user ball, then it means it has collided
    if (dist(balls[i].locx, balls[i].locy, myBall.locx, myBall.locy)< balls[i].radius + myBall.radius) {
      //println(dist(balls[i].locx, balls[i].locy, myBall.locx, myBall.locy));
      balls[i].iscollided=true; //if it collides, changes the boolean to true, changing the image
    }
    //each time it collides, the boolean is true, making the amountcollided to increment by 1
    if (balls[i].iscollided==true) {
      amountcollided+=1;
    }
  }
  println(amountcollided);
  //the the amount of collisions added up before the timer ends is greater or equal to 20
  if (amountcollided >=balls.length) {//if greater than the amount of balls in the game
    backgroundmusic.stop();
    gameState=2;
  }
}



//this is the state or the code from setup basically in order for the game to be restarted 
void reset() {
  //Assume the start button has not been pressed
  //buttonPressed = false;
  gameState=0;

  //Parameters for the start button 
  buttonW=335;
  buttonH=100;
  textSize(buttonH);
  buttonX=(width-buttonW)/2;
  buttonY=(height-buttonH)/2;
  Ballsimg= loadImage("Donald Trump Face.png");
  collidedBallImg= loadImage("Donald Trump wearing Facemask.png");
  balls= new Balls [numberofballs];// this allows it to contain 10 of the NPC Balls; this calls the constructor 
  for (int i=0; i<balls.length; i++) {// as long as i is less than 10, it's going to keep on calling on the "Balls" constructor, basically creating new balls 
    balls[i]= new Balls(Ballsimg, collidedBallImg);
  }
  backgroundmusic= new SoundFile(this, "Instruction BG Music.mp3");
  myBall= new userBall();//calls on the userBall constructor to create the user Ball in the name of "myBall"
}
class Balls {
  //basic information for building a bouncing ball
  float locx, locy;
  float ballwidth, ballheight;
  //these are the speed for all the balls; make it random to have different speed for each of them
  float xspeed=random(10);
  float yspeed=random(10);
  int radius=25;
  PImage Ballsimg;
  PImage collidedBallImg;
  boolean iscollided=false;
  

  Balls(PImage _img,PImage _collidedBallImg) {// this is my constructor or a setup function for my "Ball" object
   
    locx=random(width); //putting random will let the balls appear at different locations
    locy=random(height);
    ballwidth=2*radius;
    ballheight=2*radius;
    Ballsimg= _img;// this is inserting the image file loaded previously to every array of the NPC bouncing balls
    collidedBallImg= _collidedBallImg;// the donald trump w/ facemask image inserted for every array of NPC
}
  void drawBalls() {
    stroke(255,0,0);
    //ellipse(locx, locy, ballwidth, ballheight);
    imageMode(CENTER);
    if( iscollided==true){// if collided, it'll show this new collided image
      image(collidedBallImg, locx, locy, ballwidth+30, ballheight+20);
    }else{ //if not collided, it'll show this original image 
    image(Ballsimg, locx, locy, ballwidth+30, ballheight+20);
    }
  }
  void moveBalls() {//causes the NPC Balls to move around 
    locx+=xspeed;
    locy+=yspeed;
  }
  void Ballscheckedge() {//allows the Balls to bounce off the edges
    if (locx+radius >=width || locx+radius<=0) {
      xspeed*=-1;
    }
    if (locy+radius >=height || locy+radius<=0) {
      yspeed *=-1;
    }
  }
}
class Timer {
  float locx, locy;
  float timerwidth, timerheight;
  int countdown=30;
  int seconds; 
  float startTime;


  Timer() {// ask about the logic behind this and how to create a countdown timer? 
    textSize(50);

    startTime= millis()/1000 + countdown;
    seconds = int(startTime- millis()/1000);
  }
  void showTimer() {
    if (seconds<0) {
      startTime= millis()/1000 + countdown;//resets the timer and restarts it when less than 0
      //if seconds is zero, display "game over"
      backgroundmusic.stop();
      gameState=3;
      
    } else {
      seconds = int(startTime- millis()/1000);
      fill(255, 0, 0);
      text(seconds, width/2-100, 100);
    }
  }
}
class userBall {
  //pretty much the exact same as the NPC Balls
  float locx, locy;
  float ballwidth, ballheight;
  float xspeed=5;
  float yspeed=5;
  float radius=30;


  userBall() {//the constructor for this userBall object
    locx=width/2;
    locy=height/2;
    ballwidth=2*radius;
    ballheight=2*radius;
  }
  void drawuserBall() {
    fill(0);
    stroke(0, 0, 255);
    ellipse(locx, locy, ballwidth, ballheight);
  }
  
}

FINAL PRODUCT

Midterm – Meera.

When coming up with an idea for this game, I wanted to make a game that tends to the compulsiveness of having things be in order. I started out with just one white box in the center of my screen. I wanted the box to be spinning around itself, once I did that I added two more boxes the same size as the one in the middle. These boxes spun in different orders around themselves and the objective was to strategically make them stop , on command, at a moment they were all aligned. The game was set up with no way of winning, so a different approach was given. The next approach was to have the boxes orbit as though they were in a solar system. Now that the approach was changed the objective was altered too, the objective became that all the boxes must align around a straight horizontal line that went across the middle of the game window. After setting that up, a system of knowing that the boxes were aligned had to be made, and adding a start menu.

desgin notes 1

rotaysqay recoding

solar rotate align recording

boxes start menu and alignment text

 

 

 

Now I had to clean up the idea, I needed a theme and sound and text and better orbiting for my boxes. I started with my theme, I took inspiration from the movement of the boxes and decided on making the theme a solar theme. I put a yellow sun in the middle of my screen and made the boxes into orbiting spheres that circle around the sun. I added an image for my background but it glitched the orbiting spheres, my issue was that I was uploading the image as a pixel rather than just as an image.

 the was how it looked before I fixed it.

 

Once that was done, I working on the orbiting of my spheres. The problem was that only two spheres aligned at a time, it was mostly the outer sphere which was “ ellipse #3” that never aligned well. I messed around with three of the main components of the orbiting: the radian, the speed, and the radius (ring radius, or how far from the center each orbit ellipse is ). When I altered #3’s speed it made a difference but it still didn’t align them enough. I attempted a little with the radius but that did nothing to my alignment, so I ventured off to my final experiment which was the radian. I decreased the number of the radian in #3 and it got very close to the alignment, so I decided to increase the range of acceptance of a coded alignment. Once I did that, the player could finally win. I added text to help the player see that which sphere is aligned once paused.

 

 

Orbit fix

post space aligment clean up

 

Finally, I imported a sound file to the game. At first, I had the sound play when key was pressed, but since I started out with the code “ song.play();” it got chaotic every time the player paused for alignment.  Thus, I had the “ song.play();” moved up to the void setup, but now the sound ended even though the player is still playing. Therefore, I changed it to “ song.loop();” and it finally worked.

Finally, here’s the end result:

 

boolean paused = false;
float r;
float t;
float q;
float rX, rY;
float tX, tY;
float qX, qY;
float rSpeed, tSpeed, qSpeed;
int mode = 0;
PImage img;

//sound stuff
import processing.sound.*;
SoundFile song;

void setup() {
  size (800, 800);
  img = loadImage ("Space copy.jpg");
  rectMode(CENTER);
  r=radians(30);
  t=radians(40);
  q=radians(100);
  rSpeed = radians(1);
  tSpeed = radians(1.5);
  qSpeed = radians(.9);
  song = new SoundFile( this, "UpBeet copy.wav");
song.loop();
song.amp(.05);
}

void draw() {
  image(img, 0, 0);
  // if statment that take us to the start menu
  if (mode==0) {
    background(0, 0, 0);
    textAlign(CENTER);
    textSize(20);
    fill(40, 85, 56);
    text( " Press Space to Play  :)", width/2, height/2);
    textSize(16);
    fill(50, 100, 89);
    text (" align the grey circles as horizontally as possible, press 'p' on your keyboard to align.", width/2, height/2+30);
    drawStartScreen();
  }

  // if statment that take us from the start menu to the game 
  if (mode == 1) {

    // line in the center 
    line(0, height/2, width, height/2);

    //  center ellipse- ellipse#1
    //rotate(r);
    fill(255, 255, 0);

    ellipse(width/2, height/2, 60, 60);

    fill(55, 64, 80);
    float radius = 100;
    rX = cos(r) * radius + width/2;
    rY = sin(r) * radius + height/2;
    ellipse(rX, rY, 30, 30); // x,y,w,h


    //middle ellipse- ellipse#2

    tX = cos(t) * radius*2 + width/2;
    tY = sin(t) * radius*2 + height/2;

    ellipse(tX, tY, 30, 30); // x,y,w,h


    ////outer ellipse- ellipse#3
    qX = cos(q) * radius*3 + width/2;
    qY = sin(q) * radius*3 + height/2;
    ellipse(qX, qY, 30, 30); // x,y,w,h

    if (!paused) {
      r+= rSpeed;
      t+= tSpeed;
      q+= qSpeed;
    }

    // IF STATMENTS TO LET THE PLAYER KNOW HOW THEY'RE DOING : 
    //println(abs(rY-height/2));
    if (paused) {
      if (abs(rY-height/2)<40 ) {
        textSize(30);
        fill(0, 255, 0);
        text("sphere1: good job!", width/2, height/2+50);
      } else {
        textSize(30);
        fill(255, 0, 0);
        text("sphere1: try again..", width/2, height/2+50);
      }
    }
    if (paused) {
      if (abs(tY-height/2)<40) {
        textSize(30);
        fill(0, 255, 0);
        text("sphere2: good job!", width/2, height/2 +80);
      } else {
        textSize(30);
        fill(255, 0, 0);
        text("sphere2: try again..", width/2, height/2+80);
      }
    }

    if (paused) {     
      if (abs(qY-height/2)<40) {
        textSize(30);
        fill(0, 255, 0);
        text("sphere3: good job!", width/2, height/2 +120);
      } else {
        textSize(30);
        fill(255, 0, 0);
        text("sphere3: try again..", width/2, height/2+120);
      }
    }
  }
}

// the drawing for the start menu 
void drawStartScreen() {
}

// the function that created the Start menu
void keyPressed() {
  if (key==' ') {
    if (mode==0) {
      mode=1;
    } else if (mode==1) {
      mode=0;
    }
  }

}

// the function that pauses the game
void keyReleased() {

  if ( key == 'p' ) {

    paused = !paused;
  }
  //img.updatePixels();
  image(img, 0, 20);
}

ZIP:

https://intro.nyuadim.com/wp-content/uploads/2020/10/space_sound_Midterm_.pde_.zip

space_sound

(let me know if it’s still not working) :), updated nov 2

 

Image used for the background :

Midterm Project – Card Memory Game

Idea

When I was a child, I used to play this memory game that comes by default on every Windows 7 machine:

Memory Game, Windows

Hence the inspiration for my project.

Process

October 13, 2020.

Today marks the first day of my *official* work on the midterm project. I came up with an idea last week – the Card Memory Game. For now, I am doing some UI research to see what type of interface I would like to create for my staring page. I haven’t started my work yet, but I know for sure that I want to include some buttons and sounds. Today I also implemented a function for my button to check if it is being hovered over:

// check if the mouse is over the button
  boolean isHover(){
    if(mouseX <= rectX+rectWidth/2 
      && mouseX >= rectX - rectWidth/2
      && mouseY <= rectY + rectHeight/2
      && mouseY >= rectY - rectHeight/2)
      return true;
     else
       return false;
  }

October 14, 2020.

Keeping up the consistency! Worked on the Welcome UI scene, included some text instruction placeholders, built the button, and figured out how to move between the scenes (has some bugs, will improve next time).

October 16, 2020.

Fixed the function to switch between the scenes, designed the cards and made the cover for them. Created a function to load the card images into the array and display them in the second scene.

Making cards in Figma. Emoji design credits: imagiLabs.com

October 17, 2020

D e b u g g i n g

Debugging improper card deck display

<…>

October 26th, 2020.

Well, a lot of time has passed since my last update, but I was fixing the code here and there in the meanwhile. A few challenges during those few days was fixing the score to display on the screen properly (which I got fixed, yay!), displaying and shuffling the card deck properly (resolved!), storing the card flips (not resolved), and working out the complete logic of the game.

Final Notes

I believe it is worth mentioning that I got tremendous help along the way from Professor Aaron (thank you!), Professor Discord and Professor Google (xD). There were many things that I had to look up online, for example, the list shuffle method.

Does my program work perfectly now? No. Did I learn something new in the meanwhile? YES. Before embarking on this assignment, I had no clue how to switch between the game scenes, use lists and manipulate Java arrays dynamically. While my game still cannot count the score and flip the cards the way it is supposed to, I have learned that asking for help and needing some time to build a good code is normal and does not equal failing.

Demo

I have done the majority of the code, but have a tiny hidden bug somewhere that crashes the whole game. I suspect that happens somewhere between passing each card’s value and comparing selected cards, but I will investigate this further and am determined to make it work! Unfortunately, this might not happen on time for the showcase, but it will happen in the next few steps. For now, enjoy the code & demo:

// v0.4 24.10.2020
// importing sound
  import processing.sound.*;
  SoundFile click;

// v0.3 21.10.2020
// moving everything into class
  int margin = 3;
  IntList imageIndex = new IntList();
  int[] flippedCard;

// v0.2 17.10.2020
// Outputting the card deck

// v0.1 16.10.2020
// Creating different scenes + images

  // set the scene
  int scene, score;
  int numCards = 20;
  //PImage[] cards = new PImage[numCards];
  Card[] cards = new Card[numCards];
  PImage[] images = new PImage[numCards];
  PImage cardCover;

// v0.1 14.10.2020
// Creating the text instructions

  PFont font;
  String instructions_1, instructions_2, instructions_3, instructions_4, btn_start, btn_quit;
  int fontSize = 50;

// v0. 13.10.2020
// Creating a button

  // defining colors as [r,g,b] arrays
  int[] pink = {242, 170, 224};
  int[] green = {51, 225, 142};
  int[] orange = {248, 163, 15};
  int[] blue = {103, 212, 239};
  int[] yellow = {255, 244, 108};
  
  // defining shape coordinates
  float rectX, rectY, rectWidth, rectHeight;
  
  void setup(){
    size(1100, 800);
    rectMode(CENTER);
    click = new SoundFile(this, "clickSound.wav"); 
    scene = 0;
    
    // setting the button coordinates
    rectX = width/2;
    rectY = height/2;
    rectWidth = 170;
    rectHeight = 80;
    
    // setting the text
    push();
      fontSize = 40;
      instructions_1 = "Welcome!";
      instructions_2 = "You are presented a deck of cards.";
      instructions_3 = "Your task is to flip and match every one of them.";
    pop();
    
    btn_start = "START";
    btn_quit = "QUIT";
    font = createFont("VT323-Regular", fontSize);
    textFont(font);
    
    // setting flipped cards to null
    flippedCard = new int[2];
    score = 0;
    
    // load cards once
    loadCards();
  }
  
  void draw(){
    background(green[0], green[1], green[2]);
    // display scenes
    if (scene == 0){
      displayMenu();
    }
    if (scene == 1){
      playScene();
      //displayScore();
    }
    if (scene == 2){
      playWin();
    }
  }
    
  // display the UI scene
  void displayMenu(){
    
    // create a button
    push();
      noStroke();
      if(isHover())
        fill(yellow[0]-20,yellow[1]-20,yellow[2]-20);
      else
        fill(yellow[0],yellow[1],yellow[2]);
      rect(rectX, rectY, rectWidth, rectHeight);
    pop();
    
    // place instructions
    textAlign(CENTER);
    fill(0);
    text(instructions_1, width/2, height/6);
    text(instructions_2, width/2, height/6+80);
    text(instructions_3, width/2, height/6+120);
    text(btn_start, width/2, height/2+12.5);
    push();
      textSize(22);
      text("Intro to IM, Fall 2020", width/2, 3*height/4);
    pop();
  }
  
  // check if the mouse is over the button
  boolean isHover(){
    if(mouseX <= rectX+rectWidth/2 
      && mouseX >= rectX - rectWidth/2
      && mouseY <= rectY + rectHeight/2
      && mouseY >= rectY - rectHeight/2 && mousePressed){
      scene = 1;
      return true;
    }
     else
       return false;
  }
  
  // display scene 1
  void playScene(){
    background(blue[0], blue[1], blue[2]);
    
    // show the cards
    for (int i =0; i<numCards; i++){
      cards[i].displayCards();
    }
    push();
      textSize(60);
      text("Score: " + score, width-200, height/2);
    pop();
  }
  
  // load cards
  void loadCards(){
    
    // store all the cards in the image array
    for (int i = 0; i < numCards; i ++) {
      images[i] = loadImage(i+".png");
      imageIndex.append(i);
    }
    
    // initialize counters
    int counter = 0;
    int index;
    
    // initialize a deck matrix
    for (int i = 0; i < 5; i++) { //i = x
      for (int j = 0; j < 4; j++) {//j = y
        index = i + j * 5; // width is 5
        cards[index] = new Card(i*(margin+images[i].width), j*(margin+images[i].height), images[imageIndex.get(counter)], index);
        counter++;
      }
    }

    // shuffle images
    imageIndex.shuffle(); 
  }
  
  // flip the cards
  void mousePressed(){
    
     // add sound
     click.play();
    
     for (int i = 0; i<numCards; i++){
       cards[i].flipCard();

        if (flippedCard[0] == -1){
          flippedCard[0] = cards[i].value;
        }
        
        else{
          flippedCard[1] = cards[i].value;
        }
        // compare flipped cards
        if (flippedCard[0] != -1 && flippedCard[1] != -1){
          if (flippedCard[0] == (flippedCard[1] - 10) || flippedCard[0] == (flippedCard[1] + 10)){
            score++;
             // check for win
             if (score == numCards/2){
               scene = 2;
            }
          // flip cards back if they don't match
          else {
            cards[i].isFlipped = false;
          }
          // reset the storage array
            flippedCard[0] = -1;
            flippedCard[1] = -1;
          }
        }
      }
    }
  
  // display the win scene
  void playWin(){
     background(pink[0], pink[1], pink[2]);
     text("You have matched them all!", width/2, height/2-200);
     push();
      noStroke();
      if(isHover())
        fill(yellow[0]-20,yellow[1]-20,yellow[2]-20);
      else
        fill(yellow[0],yellow[1],yellow[2]);
      rect(rectX, rectY, 2*rectWidth, rectHeight);
    pop();
    text("Play Again?", rectX, rectY+10);
    score = 0;
    // reset the cards
    for (int i = 0; i < numCards; i++){
      cards[i].isFlipped = false;
    }
  }

class Card {
  PImage card;
  PImage cover;
  int locX, locY, value;
  int cardWidth = 135;
  int cardHeight = 200;
  boolean isFlipped = false;
  int numFlipped = 0;
  boolean isMatched = false;
  
  Card(int _locX, int _locY, PImage _card, int _value){
    locX = _locX;
    locY = _locY;
    card = _card;
    cover = loadImage("cover.png");
    value = _value;
  }
  
  void displayCards() {
    // display cover if not flipped
    if (!isFlipped && !isMatched){
      image(cover, locX, locY);
    }
    else {
      image(card, locX, locY);
    }
  }
  
  // debugging
  void displayValue(){
    print(value);
  }
  
  void flipCard(){
    if(mouseX <= locX+card.width 
      && mouseX >= locX
      && mouseY <= locY + card.height
      && mouseY >= locY){
       isFlipped = !isFlipped;
       
       // count every time the card is flipped
       numFlipped++;
       
       // reset the counter when 2 cards are selected
       if (numFlipped == 3){
         isFlipped = !isFlipped;
         numFlipped = 0;
       }
    }
  }
}

Zip file: amina_midterm

Midterm: MEMO game

Edit: I added a sound effect when flipping a card.

After some thinking, I decided to create a MEMO game for my midterm, a classic game with the goal to find matching pairs of cards. I though this would be a relatively easy way to incorporate different images into my project but it turned out more difficult than I expected as a lot of functionality is needed. This was challenging but thanks to support from Aaron, Jack, and various contributors to online forums, I managed to create a final product that I am happy with despite there being room for improvement and functionality I decided not to include in favor of healthy sleeping habits.

I first created a wireframe of the design I intended in Processing.
During the process, there were some scary moments.
I tested functionalities in different ways with print() functions and by placing simpler objects before placing colors for my cards.
First success: Images placed at the right locations.
All images added and placed.

When two clicked cards match, they remain face up and the player can try to find more matching pairs. They can also click the redo button which I incorporated into the title to return to the first screen and restart the game.

MEMO game screen recording

Intrigued? You can download the game here and play yourself.

Here is the code for the main tab:

//Game screens: 0: Initial Screen, 1: Game Screen

//for timer
int time;
int wait = 1000;

int gameScreen = 0;

PImage redo;
PImage abudhabi;
PImage berlin;
PImage clock;
PImage paris;
PImage people;
PImage stairs;
PImage tram;
PImage water;

Card[] cards;

IntList types; //to shuffle 16 cards of 8 different types
int showing; //to count how many cards are showing

int rows = 4;
int columns = 4;
int cardsize = 150;
int innerpadding = 15;
int outermargin = 20;
int cardstotal = (rows * columns);

void setup() {
  size(685, 855); //window size

  //load images
  redo = loadImage("redo.png");
  abudhabi = loadImage("abudhabi.jpg");
  berlin = loadImage("berlin.jpg");
  clock = loadImage("clock.jpg");
  paris = loadImage("paris.jpg");
  people = loadImage("people.jpg");
  stairs = loadImage("stairs.jpg");
  tram = loadImage("tram.jpg");
  water = loadImage("water.jpg");

  //add types of cards to IntList
  types = new IntList();
  types.append(0);
  types.append(0);
  types.append(1);
  types.append(1);
  types.append(2);
  types.append(2);
  types.append(3);
  types.append(3);
  types.append(4);
  types.append(4);
  types.append(5);
  types.append(5);
  types.append(6);
  types.append(6);
  types.append(7);
  types.append(7);
  types.shuffle();

  //create cards
  cards = new Card [16];

  int cardsinrow = 0;
  int cardsincolumn = 0;

  showing = 0; //keep track of how many cards are face up
  for (int i = 0; i < cards.length; i++) {
    cards[i] = new Card((outermargin + (cardsinrow * (cardsize + innerpadding))), (outermargin + (cardsincolumn * (cardsize + innerpadding))), types.get(i), i);
    cardsinrow++;
    if (cardsinrow >= columns) { // "Line break" for the cards
      cardsinrow = 0;
      cardsincolumn++;
    }
    //println(types.get(i));
  }
}

void draw() {
  if (gameScreen == 0) {
    initScreen(); //code in "initScreen" tab
  }

  if (gameScreen == 1) {
    //memoScreen(); //ideally simplify here and move code to "memoScreen" tab
    background(255);
    noStroke();
    //memo title
    pushMatrix();
    rectMode(CORNER);
    fill(0);
    rect(outermargin+innerpadding*0+cardsize*0, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
    rect(outermargin+innerpadding*1+cardsize*1, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
    rect(outermargin+innerpadding*2+cardsize*2, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
    fill(255);
    triangle(20, 20, 170, 20, 95, 80);
    rect(outermargin+innerpadding+cardsize+60, outermargin+40, 150-60, 20);
    rect(outermargin+innerpadding+cardsize+60, outermargin+cardsize-40-20, 150-60, 20);
    triangle(20+cardsize*2+innerpadding*2, 20, 170+cardsize*2+innerpadding*2, 20, 95+cardsize*2+innerpadding*2, 80);
    ellipseMode(CORNER);
    fill(0);
    ellipse(width-outermargin-cardsize, 20, cardsize, cardsize);
    popMatrix();

    //memo cards
    pushMatrix();
    imageMode(CORNER);
    translate(0, outermargin+cardsize); //to place cards below title
    //to store type and id of card that is flipped
    int[] type = {-1, -1};
    int[] ids = {-1, -1};
    int index = 0;

    for (int i=0; i < cards.length; i++) {
      cards[i].display();

      if (cards[i].show && !cards[i].matched) {
        type[index] = cards[i].type;
        ids[index] = cards[i].id;
        index++;
        //println(cards[i].id+": "+cards[i].type); //functionality testing
      }
    }
    if (type[0] != -1 && type[1] != -1) {
      if (type[0]==type[1]) {
        cards[ids[0]].matched = true;
        cards[ids[1]].matched = true;
        showing = 0;
      } else {
        if (millis() - time >= wait) {
          println("time passed");//functionality testing
          cards[ids[0]].show = false;
          cards[ids[1]].show = false;
          time = millis();//update the stored time
        }
        showing = 0;
      }
    }
    popMatrix();

    //redo symbol up right. when clicked on memoScreen return to initScreen.
    imageMode(CENTER);
    image(redo, 594, 94, 75, 75);
    //when redo image clicked, return to initScreen and reset
    if (mouseX >= width-outermargin-cardsize && mouseX <= width-outermargin && mouseY >= outermargin && mouseY <= outermargin+cardsize) {
      ellipseMode(CORNER);
      fill(0, 0, 0, 50);
      ellipse(width-outermargin-cardsize, 20, cardsize, cardsize);
      if (mousePressed == true) {
        gameScreen = 0;
        types.shuffle();
        for (int i=0; i<cards.length; i++) {
          cards[i].type = types.get(i);
          cards[i].matched = false;
          cards[i].show = false;
        }
      } else {
        return;
      }
    }
  }
}

void mousePressed() {
  if (gameScreen == 1) {
    for ( int t=0; t < cards.length; t++) {
      cards[t].mouseClick();
      time = millis();
    }
  }
}

Card class:

public class Card {

  int card1id, card2id, card1type, card2type;

  int cardposx;
  int cardposy;
  int type; //
  int id;
  boolean show;
  boolean pickCard;
  boolean matched;

  Card(int cardposx_, int cardposy_, int type_, int id_) { //add images
    cardposx = cardposx_;
    cardposy = cardposy_;
    type = type_; //to later assign images to different types
    id = id_; //will use t for now
    show = false; //meaning card does not show the image
    pickCard = true; //meaning card is able to be picked
    matched = false; //meaning card has not matched with another card yet
  }

  void display() {
    // ln(cardposx, cardposy, type, id); //testing
    if (!pickCard) { //card cannot be picked
      return;
    }
    if (!show) { //display blue rect as backside if cards do not show
      fill(0, 0, 255, 190); //card color slightly transparent to become fully blue when mouse hovers over
      if ( mouseOverCard() && showing < 2) {
        fill(0, 0, 255, 255); //stronger blue w/o transparency when mouse hovers over card
      }
      rect(cardposx, cardposy, cardsize, cardsize);
    } else {
      if (type == 0) { //change id to type after testing
        image(abudhabi, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 1) {
        image(berlin, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 2) {
        image(clock, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 3) {
        image(paris, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 4) {
        image(people, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 5) {
        image(stairs, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 6) { 
        image(tram, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 7) {
        image(water, cardposx, cardposy, cardsize, cardsize);
      }
    }
  }

  boolean mouseOverCard() { //to determine when mouse hovers over a card
    //println(cardposx, cardposy, type, id); //testing
    if ( !pickCard ) {
      return( false );
    }
    return( mouseX >= cardposx && mouseX < cardposx+cardsize && mouseY >= cardposy+outermargin+cardsize && mouseY < cardposy+cardsize+outermargin+cardsize ); //need to add +outermargin+cardsize to Y location to account for translation of location under title
  }
  void mouseClick() {
    //time = millis();//store the current time
    //println(time);
    if ( !pickCard ) {
      return;
    } //nothing happens if card cannot be clicked
    if ( mouseOverCard() && !show && showing < 2) { // no more than two cards can be picked. only cards that do not show image already can be clicked //add && showing < 2
      show = !show;
      showing++;
      //println(showing); //functionality testing
    }
    if ( showing == 16) { //change this code
      gameScreen = 2;
    }
  }
  boolean sameType() { //check whether picked cards match meaning they have the same type
    return true; //just for testing
  }
}

First screen:

void initScreen() {
  // code of initial screen
  pushMatrix();
  background(0);

  //memo title
  fill(255);
  rectMode(CORNER);
  rect(outermargin+innerpadding*0+cardsize*0, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
  rect(outermargin+innerpadding*1+cardsize*1, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
  rect(outermargin+innerpadding*2+cardsize*2, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
  ellipseMode(CORNER);
  ellipse(width-outermargin-cardsize, 20, cardsize, cardsize);
  fill(0);
  triangle(20, 20, 170, 20, 95, 80);
  rect(outermargin+innerpadding+cardsize+60, outermargin+40, 150-60, 20);
  rect(outermargin+innerpadding+cardsize+60, outermargin+cardsize-40-20, 150-60, 20);
  triangle(20+cardsize*2+innerpadding*2, 20, 170+cardsize*2+innerpadding*2, 20, 95+cardsize*2+innerpadding*2, 80);

  //game instructions
  textAlign(CENTER);
  textSize(50);
  fill(255);
  text("Find the matching pairs.", width/2, height/2-100);

  //example rects & "Play" button
  rectMode(CENTER);
  fill(0, 0, 255, 190);
  rect(width/2+50, height/2, 50, 50);
  rect(width/2-50, height/2, 50, 50);
  if (gameScreen == 0 && mouseX >= 270 && mouseX <= 420 && mouseY >= 408 && mouseY <= 458) {
    fill(0, 0, 255);
    rect(width/2+50, height/2, 50, 50);
    rect(width/2-50, height/2, 50, 50);
    if (mousePressed == true) { //mouseX >= 270 && mouseX <= 420 && mouseY >= 408 && mouseY <= 458 &&
      gameScreen = 1;
    }
  }
  fill(255);
  textSize(30);
  text("PL", width/2-50, height/2+13);
  text("AY", width/2+50, height/2+13);
  fill(0, 0, 255);
  text("=", width/2, height/2+13);


  //game instructions continued
  fill(255);
  textSize(20);
  text("Click to flip a card. Click another card.", width/2, height/2+100);
  text("Do that again and again.", width/2, height/2+135);
  text("Eventually you'll find all pairs.", width/2, height/2+170);
  text("Inshallah.", width/2, height/2+205);
  text("Ready? Click P L  A Y.", width/2, height/2+275);
  popMatrix();
}

Find an overview of my media sources here.