Midterm project || airplane game

As semester goes to the middle, we have our last coding assignment before the spring break. Goal of the assignment  is to build a game in processing using all our previous knowledges (insert sound, images and so on)

We already created a game when learned class structure as a part of object oriented programming (third week assignment),  as i spend a lot of time on visual part, i decided to build my game based on the previous assignment. I add all the physics and the game works now as a true game

When you start the game the main screen with the rules appear

So, you have 3 levels of the game, each of them has different properties

  • easy

As this mode is running the amount of the clouds is not high so you can easily manage the plane. To win you have to pick up only 2 coins

  • medium

Medium mode is a bit harder to play because of the

  • hard

To win in this mode you have to pick up the biggest amount of the coins and the amount of the clouds is the biggest, so this mode is really for professionals .

Win screen shows you beautiful fireworks

In case the plane touches the cloud you get this screen

After the game is finished you can restart the game pressing the space bar on the keyboard. By the way all the operations in the game managed by keyboard. For example, level choice and plane management using “wasd” letters).

At the beginning i was thinking about the  process of the crashing the plane. Quite often circles are used to create a condition  to end the game, however in this particular situation the shape of the cloud is not really a circle, because it is based on rectangle. Solution of this problem is to use 2 circles for the cloud. To be honest i thought that this approach would not guarantee the , but when you play it gives really very sensitive clouds and plane so the game is not as simple as can seems to be. The same approach i used to pick up coins. After you catch the coin the score goes up and game finishes when the corresponding amount of coins is reached.

The final result you can see on this video demonstrating the game process

 

As for my personal experience, i am happy that i had an opportunity to finish the game and bring it to life.

Press the link to see the full code:

midtermVarya

 

Drowning Diver-MIDTERM [week 7]

My midterm game has been very much inspired by flappy birds. It follows very much the same concept, the character has to jump and go through the columns not touching them, otherwise, it falls and fails to win the game.  With this game, however, my character was a scuba diver discovering the underwater world, if it gets hit by a column he will drown. There’s no such purpose to what the game does, the main goal is to go as far as possible and collect scores.

The Main Character is a ScobaDiver and the obstacles are the columns. The Main Character is controlled by mousePressed() and jumps on a y-axis, measured by gravity instantiated by vy each time. Even though the game might not have distinct levels, speed increases each time score goes more than 5, 10, and 20. The game keeps track of previous scores and displays the best and the current score, while the user is playing the game.

The interface of the game functions by clicking the game window to start the game, and press the spacebar to restart the game. The picture of the character is animated by sprite sheet and makes it seem as if the diver is swimming underwater while passes by moving background and columns.

https://github.com/zharmakhan-zn/zharmakhan-zn.github.io/tree/main/midterm

 

PImage wall;
PImage character;
PImage columnt,columnb;
int wallx;
int wally;
int chx; 
float chy;
float g;
float Vchy;
int[] columnX, columnY;
int gameCondition;
int score,highScore;

PImage[]sprites;
int direction=1;
int step=0;
int frames_x;
int frames_y;
int speed=3;

import processing.sound.*;
SoundFile file;

void setup(){
size(400,400);
wall=loadImage("underwater.png");
character=loadImage("scuba.png");

sprites= new PImage[8];
int img_w= character.width/8;
int img_h= character.height/1; //animate spritesheet

int index=0;
for(int frames_y=0; frames_y<1; frames_y++){
for(int frames_x=0; frames_x<8; frames_x++){
sprites[index]= character.get(frames_x*img_w,frames_y*img_h,img_w, img_h);
index++;
}
}

columnb=loadImage("pipetop.png");
columnt=loadImage("pipebottom.png");

file= new SoundFile(this, "buble.wav"); //uploaded wav file

chx=50;
chy=50;
g=0.5;

columnX=new int[5];
columnY=new int[columnX.length]; //set columns

for (int i=0; i<columnX.length; i++){
columnX[i]=width+150*i;
columnY[i]= (int)random(-50,0);
}
gameCondition=-1;
}

void draw(){
if (gameCondition==-1){
startScreen();
}
else if (gameCondition==0){
moveBackground();


columnSet();

characterControl();
score();
}
else{

restartScreen();

}
if(mousePressed){
file.stop();
file.play();
Vchy=-5;
direction=1;
frames_x+=speed;

if (frameCount%speed==0) {
step = (step+1) % 8;
//gameCondition=1;
}
}

}
void score(){
if(score>highScore){
highScore=score;
}
textSize(15);
fill(160,160,160,200);
rect(width-100,10,85,60,5);
fill(0);

text("SCORE:"+ score, width-85,30);
text("BEST:"+ highScore, width-85,60);
}
void startScreen(){
image(wall,0,0);
textSize(40);
text("DROWNING DIVER",0+20, height/2);
textSize(20);
text("click to start",width/2-60, height/2+60);
if (mousePressed){
chy=height/2;
gameCondition=0;
}
}

void restartScreen(){
image(wall,0,0);
textSize(40);
text("YOU DROWNED", 60,height/2);
textSize(20);
text("score:"+score, 160, height/2+50);
text("click SPACE to restart", 0+100,height/2+100);
if (keyPressed){
if(key==' '){
chx=50; 
chy=height/2;
for (int i=0; i<columnX.length; i++){
columnX[i]=width+150*i;
columnY[i]= (int)random(-50,0);
}
gameCondition=0;
score=0;
}
}
}


void characterControl(){
scale(direction,1);

image(sprites[step],chx,chy,50,100);
chy=chy+Vchy;
Vchy=Vchy+g;
if (chy>height || chy<0){
gameCondition=1;
}

}
void moveBackground(){
image(wall, wallx,wally);
image(wall, wallx+wall.width,wally);
wallx= wallx-1; 
if(wallx<-wall.width){
wallx=0;
}
}

void columnSet(){
for (int i=0; i<columnX.length; i++){
image(columnt,columnX[i],columnY[i]+300,50,180);
image(columnb,columnX[i],columnY[i],50,180);
columnX[i]-=2;
if(score>5){
columnX[i]--; //speed up when score more than 5
}
if(score>10){
columnX[i]--; //speed up when score more than 10
}
if(score>20){
columnX[i]--; //speed up when score more than 20
}
if (columnX[i]<-200){
columnX[i]=width;
}
//character collision with columns
if (chx> columnX[i]-50 && chx<columnX[i] +50){
if (!(chy> columnY[i] +150 && chy<columnY[i] +(300-40))){
gameCondition=1;
}
else if(chx==columnX[i] || columnX[i]+1==chx)
{
score++;
}
if(gameCondition==1 && gameCondition==-1){
score=0;
}

}
}
}

The Snake Game – Midterm

IDEA:

One of my favorite games I played back in my childhood was the snake game. I used to play it on the old Nokia phones which was a very basic version of it. I thought recreating the snake game would be a nice aim for the midterm. However, since I did not want to replicate it, but make my own version of it so, I got creative with it and added my own final touches on it to -in a way- make it my own.

^The form I tried replicating^

 

PROCESS:

I started with creating the main menu

 

and wrote an array list that will store the x and y values of the snake then, making the snake’s body that is generated through a for loop. Changed the frame count too, to make the snake faster after each 5 points.

 

In order to grow the snake’s body, I wrote a bunch of code that makes up the fruit of the snake and randomly generates it across the window. I also added a sound effect to play when the snake takes a bit from the food.

 

After that, a user would like to view their score instead of counting the squares that make up the snake’s body, so I added a text function that displays the score which is the size of the snake.

 

 

In order for this game to end, the snake must die. So, I created an if statement that checks of the snake’s position is on the window’s parameter. Then a for loop to check if the snake bit itself. Then an option to play again if the user wanted to.

 

 

WHY IT IS BETTER THAN THE ORIGINAL + FEATURES:

1)Different visuals and sounds

 

2)You can read your score

 

3)You can play over and over again

 

4)Night mode – in a way

 

5) snake speeds up every 5 points making it more challenging

 

DEMO:

 

 

CODE:

// main menu variables
String gameState;


// to make the snake's body
ArrayList<Integer> x = new ArrayList<Integer>(), y = new ArrayList<Integer>();

//image variable
PImage fruit;

//global variables
int w=30, h=30, blockSize=20, dir=2, foodX=15, foodY=15, speedNum = 8, fc = 255, life = 30, score;

//font variable
PFont f; 

//coordinates for x and y
int[] Xdir={0, 0, 1, -1};
int[] Ydir={1, -1, 0, 0}; 

//game condition 
boolean gameOver=false;

  
import processing.sound.*;
SoundFile foodSound;
SoundFile mainMenuSound;





void setup(){

  size(600, 600);
  gameState = "START";
  // font initializer
  f = createFont("Georgia", 35);
  //snake's starting position 
  x.add(0); y.add(15); 

  foodSound = new SoundFile(this, "0.aif");
  
  
  fruit = loadImage("apple.png");





}


void draw() {
  background(0);
  if (gameState == "START") {
    startGame();
  } else if (gameState == "PLAY") {
    playGame();
  } else if (gameState == "LOSE") {
    loseGame();
  }
}


void startGame(){
  
  textAlign(CENTER);
  textSize(18);
  textFont(f);
  fill(255);
  text("Click Anywhere to Start the Game!", width/2, height/2 -30);
  textSize(14);
  text("Use the Arrows to move\n Eat the fruits for a higher score \n Do NOT eat yourself or escape the walls of the game\n You have 1 life", width/2, height/2);
  //look for the click
  if (mousePressed == true) {
    gameState = "PLAY";
  }
}




void playGame(){
  
  
  
  
   //snake's body color
  fill(3, 252, 169); 
  
  //snake's body
  for (int i = 0; i < x.size(); i++){
    
    rect(x.get(i)*blockSize, y.get(i)*blockSize, blockSize, blockSize);
    }
    
    
    if (!gameOver) { 
    // food
    imageMode(CENTER);
    image(fruit, foodX*blockSize+10, foodY*blockSize+10, fruit.width*0.03, fruit.height*0.03);
    // score on player screen
    textAlign(LEFT); 
    textFont(f);
    textSize(25);
    fill(255);
    text("score : " + x.size(), 30, 30, width - 20, 50);
    
    //add more blocks to the snake
    if (frameCount%speedNum==0) { 
      x.add(0, x.get(0) + Xdir[dir]); 
      y.add(0, y.get(0) + Ydir[dir]);
    
    
      // snake die if it touches the window
      if (x.get(0) < 0 || y.get(0) < 0 || x.get(0) >= w || y.get(0) >= h) {
        
        gameOver = true;
    }
    //  snake die if it touches itself
    for (int i=1; i<x.size(); i++) 
        if (x.get(0)==x.get(i)&&y.get(0)==y.get(i)) gameOver = true; 
        
        // eating food 
        if (x.get(0)==foodX && y.get(0)==foodY) { 
         if (x.size() %5==0 && speedNum>=2) speedNum-=1;  // speed increases every 5 points 
          foodSound.play();
         // generates new food position
        foodX = (int)random(0, w);
        foodY = (int)random(0, h);
       
 
      } else {
        // so that the snake size doesnt become infinite
        x.remove(x.size()-1); 
        y.remove(y.size()-1);
      }
    }
  }
  
  
  else {
   
    loseGame();
  }
 

}


void loseGame(){

  // game is over screen
    fill(255); 
    textSize(30); 
    textFont(f);
    textAlign(CENTER); 
    text("GAME OVER \n Your Score is: "+ x.size() +"\n Press ENTER", width/2, height/3+30);
    
    keyPressed();
  
}


void keyPressed() { 
  int newDir=keyCode == DOWN? 0:(keyCode == UP?1:(keyCode == RIGHT?2:(keyCode == LEFT?3:-1)));
  if (newDir != -1) dir = newDir;
  
  if (keyCode == ENTER && gameOver) { 
    x.clear(); 
    y.clear(); 
    x.add(0);  
    y.add(15);
    dir = 2;
    speedNum = 8;
    gameOver = false;
  }
}

The link to download the zip folder with the code:

https://github.com/ShaikhaAlshehhi/Midterm-SnakeGameFinal

CONCLUSION:

I really had fun experimenting and testing this project. It did add to my coding career and inspired me. As a next step for this project, I would like to implement a life system where the snake would get injured instead of dying and could heal from it.

Midterm: F1 Racing Simulator

Overview: 

This was definitely the hardest game I have made in Processing. I decided to be a bit ambitious and make a full on racing game. The result I came up with is a 2D style game where a player can try to set the fastest lap around a course as possible.

 

Workflow:

I started off by making a menu where a player could select a team. I decided to take all the F1 2020 teams and car liveries and make them options. To do so I loaded up the team names, logos, and cars into arrays so that a single index could select all of these attributes of a single team. I made a team class to hold all these various data points and within the team selection menu let users use the arrow keys to scroll through the options. I decided to make it look like one was active so drew the menu tinted and then the current teams logo not tinted to make it look highlighted.

Next I moved on to making a car drive around a track. This was the hardest part by far. The car physics weren’t too bad, but since I wanted the car to stay stationary and the track to pan, like in most games, I had to sort out how to move the entire track. At first I wanted to do Yas Marina Circuit. I downloaded an image from Google and began playing around

I could drive the car around Yas Marina but to make the map look not pixelated I had to increase the image size by about 20 times. This meant huge files and a picture tens of thousands of pixels wide. I then tried breaking up the image into a 20×20 tiles so only 4 map tiles had to be drawn at once but even this was too much for processing to handle and caused out of memory errors. With Processing’s limitations I decided to ditch such a large track and make my own.


I created a track with striped curbs and sand on the corners much like real circuits. I also added grass, a finish line and starting grid, and water to keep players from cutting across or going off the map.

I could then adjust the car physics with this new map by detecting what surface the car was on. If the car was on the track or curbs it was fine and it could have full speed or acceleration. If the car was on the grass or sand it would decelerate much faster with a capped max speed. If a player put the car into the water they would lose and the car would crash!

Next up I added some important game mechanics such as a lap timer and a timer to save the best lap time. It was tricky to detect when players crossed the start line. I also had to make sure drivers couldn’t go backwards over the start line to cheat the system and make it look like a faster lap when they had not gone all the way around.

Sound was probably the hardest part. In normal racing games you would take an engine loop sound and adjust the pitch and fade in and out acceleration and deceleration tracks. I had a really hard time with this so decided to stick to a single engine loop to get the engine sound effect. The only way to adjust pitch in processing is through the rate which had some limitations. It sounds a little digitalized but overall not bad.

Finally I wrapped it all up with a main menu, credits, and controls to help the player navigate the game.

Gameplay:

When a user loads the game they will be greeted with the main menu. All menus are navigated with the arrow keys, enter, or backspace. I imagined console like controls so the mouse is never used in this game.

From the main menu, users can access the controls, the credits, or start the game itself

 

With the game started, users can now use the arrow keys to select which team they want (have to go with Ferrari here).

Once they press enter, they will be loaded onto the starting grid on the track. Once they cross the Start/Finish line, the lap timer will reset and begin actively counting their lap.

Users can control the car by pressing the up arrow for gas, the down arrow for brakes, and the left or right arrows to turn left or right. The car will speed up when given throttle, slowly decelerate when not given any gas, and decelerate quickly when braking. The car will also decelerate much quicker on sand or grass and can’t go very quick. Avoid the water or the game ends as you won’t drive very far in the marina! At any time the user can go back to the main menu with backspace.

The goal of the game is to set the quickest lap time possible. My best is 38 seconds, can you beat me?

Here is a short video showing some gameplay (the quality isn’t the greatest as I had a lot of recording software issues)

 

Here are the game files

F1Game

Cards Royale – The Ultimate Card Game

(Disclaimer – Please read my previous post on Midterm Progress – Almost a Card Game before as this one builds upon that.)

Yes, I finally made a card game that I can play by my rules!

For my Intro to IM midterm project, I decided to make a 2 player card game – Cards Royale. It is inspired by many card games I have played all these years with my family and friends. I must admit that I was very skeptical about this idea initially and thought of changing it multiple times, but there was something in me which did not let me chose any other game to build. My motivation for a card game came about when I was ardently searching for my favorite card game – Judgement – online, so that I could play with my family from afar. Playing cards together has been my family’s little tradition since childhood and I have learned over some 15 trick-making card games till now. Thus, not having been able to find my favorite game online, it felt like the perfect opportunity to try making a computer version of it myself! Hence, I started in this journey – yes, I would call it a journey because it really was one.

Why I was skeptical about this game was because there are many things to keep in mind in a card game and I am a person who does a task with utmost perfection and details; I cannot leave it unfinished in between – especially not a card game. Hence, I wasn’t sure if I could do justice to the card game keeping all the game rules, table rules, strategy, visual appearance to make it seem realistic, in mind. Nevertheless, this was not an excuse. I decided to take on this challenge and make a card game as true as the original card games I have played since childhood. This motivation of mine guided a majority of the design choice decisions, rules, user experience, interactivity, etc. which I describe in this documentation.

DESIGN CHOICE

My game consists basically of 3 screens – a home screen, an instructions screen, and a game screen. I first started with the game screen because that is the main part and, honestly, I had planned to go ahead with this idea only if I felt feasible and good about it after having tried my hands on trying to code the game screen for a considerable amount of time and effort.

I first sketched out a basic layout of how my objects of the game should be placed on the screen (see the previous document for visuals). The placement was very important as I designed it in accordance with table rules – players seated opposite each other, the deck of cards on one side and tricks on the other, close to the person who made it, and cards played in the center of the table.

Next, I had to decide the theme and color scheme. I want to keep it royal and posh, something of a luxury to capture our annual festivity spirit of Diwali. Plus, if you go to any card playing area, this is a common theme you find. So, I decided to go with a royal maroon patterned background with textured green table cloth (which is a screenshot of a snooker table image by the way), complemented with the colors yellow and red.

When I was first designing my home screen, I wasn’t very satisfied with how it looked. It seemed fine but it didn’t quite catch the vibe I was going for. It felt like I needed the heading to be creative. Hence, I decided to create my own logo in Adobe Draw which would be just perfect for my game (the text is also handwritten and not a font – some of my friends thought otherwise 🙂 ).

MINUTE DETAILS

My drawing endeavor didn’t just finish here. Once I started adding sounds, I felt the need to add a mute button (I personally was a little fed up hearing the sounds over and over again every time I made an edit and ran the code again, plus it was essential for user experience – giving them more flexibility and control). While working on the mute button, I couldn’t find a similar mute and unmute button in yellow color which would compliment my home button. I saw many designs online but they just didn’t fit in. So, I decided to sketch that as well (see below). This was simplistic; fitting just fine and not overcrowding my screen.

I also faced some challenges initially with my mute button as I was using mousePressed with a toggle boolean variable which made the mute button switch rapidly again and again if the button was pressed for a long time (see video below). I was able to fix this by replacing it with mouseClicked() function.

This actually brought me to consider another interactivity choice. Often in card games, undo is not allowed (a table rule which states that once a card has been taken out of the hand – irrespective of whether it has played on the table or not – it cannot be put back in hand and must be played). So, while playing a card game online, it often requires you to press hard enough on the trackpad to play a card. This is done to avoid a card being played by an unintentional tap on the trackpad (I believe this is specific only to laptops). Hence, to implement this idea in my game, I changed my mousePressed keyword for menu option buttons, mute/unmute button, home button, etc. to a mouseClick so that they would work on just a tap, while I kept the playing cards action as mousePressed so that the user would have to press on the trackpad to actually play – distinguishing it from other actions and requiring careful thought.

Another detail I focused on was arranging the cards in hand in sorted order. The first thing you do after picking up cards in your hand is arrange them by suit and number. This makes it easier to track your cards and keep a count to play efficiently. I used bubble sort to arrange them in order.

I also decided to have the cards in hand flip depending on whose turn it is. Initially, cards for both the players were open. This was okay since both of them would be playing on the same screen so it is quite likely that they see each other’s cards (so it wouldn’t really. ake sense ti hide them). However, in an actual game, you are not supposed to see the other person’s cards. Hence, to give that feeling, I decide to close the cards for the player whose turn it wasn’t, to avoid this as much as possible and also motivate them to not see by maybe closing their eyes or turning the laptop. I also closed the cards for both players when each had played and it was time to see who made the trick. This allows both of them to see what card has been played and take a note of it before it is taken off-screen, without having a glimpse of each other’s cards.

THE HOME BUTTON

Keeping user experience in mind, I felt the need to add a home button in case a user wanted to go back to see instructions in between the game or restart the game. There were a few things to consider here –

  • If the user clicked the home button in between the game to see the instructions screen maybe, then they should be able to go back and resume their game from there. Or if they misunderstood some instructions and wanted to restart, they should have that option too. Hence this called for a need to have a pop-up screen to provide this choice menu.
  • This option should not be provided when the start game is clicked for the first time.
  • Neither should it be provided if the user clicked home button instead of the ‘play again’ button once the game was over. So it called for special if conditions to implement this.

The started boolean variable I created for this purpose proved to be useful in other parts of my code as well.

ANIMATION

Now, to make the game look more realistic and seem like it was actually being played on a table, I decided to add a physical dealing of cards effect along with collecting a trick and keeping it aside effect. This animation proved harder than I imagined (more details can be found about the dealing effect in the previous post).

The trick collecting effect was added to solve 2 purposes. One is mentioned above and the other was to fix the error I was getting last time (as mentioned in the previous blog post). What was happening is that if one played a card which was lower in rank than their previous one, it would not show on the table because I display the cards in sequential order, so the higher-ranked card would be displayed on top of the lower-ranked card.

As it can be seen in the video above, as you play cards from left to right, each new card is played on top of the other. However, if you go back and play a card from the left of the previously played card (the 10 of clubs or 5 of diamonds in the above video for example), they are not visible on the screen when played.

To fix this problem, it was necessary to make the cards disappear before the next one is played. However, it wasn’t that easy. If I made a card invisible after it is played, it would become invisible as soon as it was played and it wouldn’t be possible to see what card was played (see below).

Hence, to fix this, I decided it would be a nice touch to have the cards go sideways into the trick pile and then make them disappear. However, it still didn’t quite fix the problem. I still felt the need to have the 2 cards be displayed on the table for a while before they are collected into a trick. (See below – trick one being made without any pause). I decided to add a delay after the cards are being played to fix this problem. However, using the delay() function didn’t quite work because it was pausing at the wrong moment – before the second card is played instead of after the second card is played. (See below – trick 2 being made with a delay at the wrong time).

I used the millis() function instead of delay() to fix this as I got to know that delay() cannot be used to delay something inside a draw() loop, rather it is used after a draw() loop is finished and before the next one starts. It took some time for me to be able to understand millis() and implement the pause at the correct time. Once I got it, it proved to be very useful and I also used it to add a pause at the beginning of a deal so that the deal isn’t started immediately once a user clicks start game. However, it takes some 0.5 seconds to start so that the user can adjust to the new screen setting of a playing table. (This delay does not occur if you continue a game, only comes when you start a new game or restart the previous game.)

SOUND EFFECTS

Now adding sound effects was crucial to make it a more realistic experience. I wanted to add sounds of dealing cards, flipping cards, collecting tricks, etc. However, I didn’t get any free good quality playing cards sound on the internet. So, I decided to record them on my own. I used audacity to record sounds while I dealt, flipped, or played cards, and made tricks. I edited each of those, extracting the best single sound I could get. I used the noise reduction feature to remove some of the background noise and some other effects like pitch, speed to make it sound comfortable and not very shrieky.

The sound of flipping cards was important for another reason. Since I decided to hide the cards of players when it was not their turn, it was important to indicate somehow that a player has played a card and now it’s the other person’s turn. So, I decided to add the flip sound which would indicate that a player has played their card and the cards have been flipped (closing the previous player’s cards and opening the other player’s) so that the next person can now play.

I also found a piece of good background music (a casino jazz piece) to add to my game which would make it more lively. I also added cheering in the end which I again found online.

TEST RUN

So, testing my game by making people play was very important for the development of my game at various stages. I have made more than 21 people play this game till now and got great insights on how people understood my game, use the interface and navigate the playing arena. This helped me improve my user interactivity and design.

My instructions page changed a couple of times based on what experience people have had playing cards and it helped me break it down to very simple words so that a person who has never played cards before is still able to understand, play and enjoy the game. My end screen also saw many stages of development based on the feedback I got from various people. I decided to go with the most popular one. I even focused on small details like increasing the size of the text indicating whose turn it is to play and changing the volume of the sound effects so that none overpowers the other.

Once the logic of the game was coded, I made more and more people play which helped me become aware of many bugs and glitches in my code which I was able to fix in time.

This was also a great confidence booster. The entire weekend went by me building this game and playing it with my friends (and family on zoom). It was a great success! People really enjoyed it and wanted to play more (which is great for me!) and I was happy to see that those who never played cards before could also understand and play it, thoroughly enjoying it, while those who have had experience with cards, appreciated the tiny details I incorporated to make it as realistic to an actual game as possible.

THE FINAL WORKING VERSION

Here is a tutorial of my game –

I am really happy with what I have built and now am confident that I can expand it to more players, add more variations, and maybe put it online so that many can play virtually!

CODE

The complete code can be found here: Shreya-IM-Midterm-Final

 

[Week 7] Midterm project – Minehunter

For the midterm, as per the last update, I work on the game Minehunter.

All the code and the files can be found here. Do be mindful of the folders in which the files are organized. The program will not run if the files are not in the correct folders with correct names. A zip file can also be found at the end of the post.

The game includes an 8×8 game board containing 8 mines scattered randomly across the board. The goal for the player is to uncover all of these hidden mines while maximizing their points. The player can move their character around the board using arrow keys (the current position is highlighted for better visibility). If the player thinks a cell has a mine, they can flag that cell by navigating to the cell then pressing the ‘F’ button. If the player changes their mind, they can press the ‘F’ button again to un-flag the cell. Otherwise, if they thinks a cell is safe, they can reveal the cell using the space bar. Once a safe cell is revealed, the cell shows a number representing how many of its neighbor cells have mines. ‘Neighbor cells’ are defined to be adjacent cells horizontally, vertically, and diagonally. These numbers are visible only when the player is in that cell. Each safe cell uncovered earns the player 2 points. There is also a Hint button that the player can click on to uncover a random safe cell, which costs 10 points.

The player either wins by successfully flagging all cells with mines, or loses by revealing a cell with a mine.

My main goal for this game is to increase player interactions. The rules of the game requires the player to navigate their character across the game board consistently to reveal or flag a cell and to revisit revealed cells, since numbers of neighbor mines are only visible for the current cell. More player movements are also required by the two goals of the game: to flag all mines, and to achieve higher points by revealing more safe cells.

Here is a short demo of my game (excuse my bad playing for the sake of the demo):

Breakdown of classes

Minehunter

This is the class for the entire game session. The most important attributes of this class are the various ArrayLists of PVectors. They store the coordinates of various cells for different purposes as specified by the names of the ArrayLists. I choose to use ArrayLists extensively because they support a wide range of functions suitable for my intentions: add() and size() can be used to quickly add new elements to the arrays and get their sizes without me having to manually keep track of array sizes; contains() can be used to check whether a certain object is included in an array instead of iterating through each element and making comparison and similarly with remove() for removing an object without knowing the specific index; clear() can be used to quickly remove all elements from the arrays, which is useful for resetting the game.

The Minehunter class also contains a range of functions for displaying the various screens, including the welcome screen, instruction screen, as well as win/loss screen. The core operation of these displaying functions is changing the global variable screen to reflect the current screen state, which is used in the switch statement in the main program to display the correct screen. Additionally, the class has a number of functions for different player actions like planting flags, revealing cells, getting hints, etc. and for resetting the game.

**Player**

This is the class for the player which displays and moves the player’s character around the game board. The class is also used to display the animated character in the welcome screen which moves automatically instead of waiting for keyboard signals like the player’s character during the game. The implementation of the Player class reuses the code from the lecture on images and sprite sheets.

**Reward**

This is a simple class for keeping track, updating, and displaying the player’s points.

Problems

My main issue is with the design of the game. I wanted to make the graphics more in the style of 8-bit games, but using 8-bit arts as the background for the game board, for some reason, slows down the game significantly. I left the function for displaying the background in the code but I didn’t use it, since I tried it on my laptop and the character’s movement slowed down as if the frame rate were changed. So in the end I settled with a amalgamation of 8-bit design and flat design.

Another problem I had was with the choice of fonts. The majority of texts in the game use a pixel game style font, but I figured the font does not look very legible when the texts are packed, or when uppercase and lowercase letters stand next to each other. So for the instruction screen, I used a different font for the sake of legibility.

One of the most important requirements for the program is to be able to store coordinates in the form (i, j). I initially intended to use the tuple data type, but apparently Processing does not support tuples in Java (it does in Python). My second plan was encoding the coordinates into strings of the form "ij" using the formula i*10+j and later on reverse the encoding using the / and % operations. Then there came the lecture on PVector which was exactly what I needed, and so I switched to use PVectors throughout my program. It was really useful and saved me a lot of time and effort.

References

The implementation of the Player class reuses the code from the lecture on images and sprite sheets.

Some of the underlying game logics and operations are reused from an assignment I did for another course.

Files

Minehunter

Uni Sufferers – Midterm Project

For the midterm project, we were supposed to make a game, and I decided to expand on the game which I created for my week 3 assignment and named it “Uni Sufferers”.  The main principle of this game is very similar to my Week 3 game, and the basic rules can be seen in the image below (screenshot of one of the displays of the game).

Concept:

Almost everyone we know around us has been greatly affected by the change in education systems since the beginning of the pandemic. Virtual classes have led to people having to take 5am classes (due to difference in time zones), having lower attention spans, facing anxiety and having to get through tens of assignments every week. In between all of this, we often fail to realize the importance of taking care of our mental health. This game is an NYUAD-themed game built around the idea of the player helping Faiza the Falcon (our beloved mascot) avoid multiple obstacles in order to save her mental health. These obstacles are 5am zoom classes, class discussions, assignments and anxiety, all of which behave in different ways. The last level (level 11) is about helping Faiza achieve the 4.0 GPA by avoiding certain distractions once she is able to save her mental health.

Main features:

All of the obstacles in the game behave in very different ways.

  1. The clocks in the first few levels only move horizontally between the corners of the grid. They have end points, and once they touch one of the end points, the direction of their velocity is reversed and they continue moving in the opposite direction.
  2. The class discussions behave in a very interesting way. They pop up at random locations on the screen, stay there for a while and then they disappear and move to a completely different random location.
  3. Anxiety remained my favorite throughout the game (I promise it made me very anxious as I tried to make it work on the very last day). Anxiety constantly follows Faiza as Faiza moves around the screen, but with a lesser speed. This means that Faiza can not stay at the same location for a long period of time because the moving anxiety will chase her down.
  4. Assignments are shot toward’s Faiza’s coordinates at a certain point in time. They lock Faiza’s coordinates and are then fired with a certain velocity.
  5. Distractions represent different objects like facebook, instagram, playstation and youtube and all of them behave in the same way. They are instantiated with random velocities within a certain range and they rebound off the walls as they hit them.

Main Difficulties:

  1. One of the main difficulties I faced was the repetition of blocks of code. Many of the classes had attributes and methods which were repetitive and so I learnt how to make use of inheritance in Java. This allowed me to have basic attributes and methods which all other classes can inherit from.
  2. I found it hard to figure out how to make new discussions appear on the screen one at a time at different locations. To counter this, I made a discussions_list of size 1 which holds one discussion at a time and after a certain period of time (using frameCount%), the old discussion would be replaced with a new one.
  3. Perhaps the most difficult part for me was to figure out the functionalities of assignments and anxiety. I countered each one of them in the following ways:

a) Since I intended to make anxiety follow Faiza at all times, I decided to make a dummy within the Anxiety class which would allow everything to be dealt with within the Anxiety class. This dummy would not be visible but would have the same x and y coordinates (target_x and target_y), same radius (target_r) and would move with key strokes the same as Faiza did. Then I created variables called difference_x and difference_y which recorded the difference in the x and y coordinates of the anxiety and the dummy. By using trigonometric identities, I could then figure out the direction in which anxiety had to move in order to follow the dummy (and hence, Faiza). Since Faiza’s (and hence the dummy’s) coordinates were constantly changing, it seemed like anxiety is constantly following Faiza.

b) Making the assignments work was a little easier after I figured out how to make anxiety work. This again had target coordinates which were those of Faiza. Whenever an object of the Assignment class was instantiated, it would lock the coordinates of Faiza at that time, then make use of trigonometry to be shot at that position with a certain velocity.

Final thoughts:

I don’t think I have ever worked harder for any project, any class. I don’t even know how many all-nighters I have pulled off to try to make this work, but I am super proud of how much I have learnt and more importantly, of how I have made this work.

This was so much more than learning about the features of classes. This was about learning how to reflect images, how to implement parallax backgrounds (you can see the background moving as Faiza moves in the x-direction). Honestly, when I started this I didn’t even know not all backgrounds could be implemented as parallax backgrounds –  they had to be symmetric for them to look nice, otherwise it’s just a broken image. Additionally, this project was about learning how to make sprites on my own using https://www.piskelapp.com/ (I made most of them on my own, including that of Faiza, assignments, class discussions, anxiety, GPA, brain and parties). I have learnt about working with collision detection and with counters to display real-time scores as the game progresses. It also helped me learn how to use key presses and mouse clicks to start (and restart) the game and control movements of certain objects.

I have learnt a lot from this process and enjoyed it way more than I thought I would.

Attached below is a video which shows me playing the game once.

Below are also the code and the zip file for the assignment (I am sorry for having one long block of code, I’m just more comfortable working that way).

import java.lang.Math;
import processing.sound.*;
SoundFile intro_song, background_song;

String audioName = "intro.mp3";
String audioName2 = "background.mp3";
String path, path2;

Game game;

// creating arrays for distractions and assignments which are used later
// also creating an array for the possible positions where assignments are launched
Distractions[] distractions;
Discussion[] discussion_list;
Discussion[] discussion_list2;
Assignment[] assignments;
Assignment[] assignments2;
Assignment[] assignments3;
int[][] position_list;

// Creating a Creature class which stores all the basic attributes, including the creature's position, radius, initial x and y velocities, and image attributes. 
// Each 'creature' will inherit certain attributes from this class and its display function where necessary.
class Creature {
  float posX, posY, radius, velocityX, velocityY, imgwidth, imgheight;
  PImage sprite_image;
  int num_frames, frame;
  String directionX;

  Creature(float x, float y, float r, String image_name, float img_w, float img_h, int number_frames) {
    posX = x;
    posY = y;
    radius = r;
    velocityX = 0;
    velocityY = 0;
    directionX = "right";
    sprite_image = loadImage(image_name);
    imgwidth = img_w;
    imgheight = img_h;
    num_frames = number_frames;
    frame = 0;
  }

  // This function displays the image of each creature and inverts it according to the creature's direction of motion. It calls the update function of that specific creature.
  void display() {
    update();
    if (directionX == "right") {
      image(sprite_image, float(int(posX - imgwidth/2)), float(int(posY - imgheight/2)), imgwidth, imgheight, int(frame * imgwidth), 0, int((frame + 1) * imgwidth), int(imgheight));
    } else if (directionX == "left") {
      image(sprite_image, float(int(posX - imgwidth/2)), float(int(posY - imgheight/2)), imgwidth, imgheight, int((frame + 1) * imgwidth), 0, int(frame * imgwidth), int(imgheight));
    }
  }

  void update() {
  }
}


class Faiza extends Creature {
  boolean move_up, move_down, move_right, move_left;
  boolean alive;
  int breakdown_counter, distraction_counter;

  Faiza(float x, float y, float r, String image_name, float img_w, float img_h, int number_frames) {
    super(x, y, r, image_name, img_w, img_h, number_frames);
    move_up = false;
    move_down = false;
    move_right = false;
    move_left = false;
    // Initially Faiza is set to alive, if Faiza collides with any of the obstacles, then "alive" becomes false and Faiza returns to her original position
    alive = true;
    // creating two counters which record the number of collisions with obstacles (Level 1-10) and distractions (level 11) respectively
    breakdown_counter = 0;
    distraction_counter = 0;
  }

  void update() {
    if (game.level >= 1 && game.level <= 5) {
      // For level 1-5 there are restrictions on the movement of Faiza because of the grid present in these levels
      // Hence, the following if conditions will restrict Faiza's movement by using coordinates of the lines in the grid/maze.
      // The first condition is for when Faiza moves left
      if (move_left == true){
        velocityX = -2;
        directionX = "left";
        if (posX - radius + velocityX < 6) {
          velocityX = 0;
        }
        if (posX - radius > 270 && posX - radius + velocityX < 280 && (posY - radius < 396 || posY + radius > 484)) {
          velocityX = 0;
        }
        if (posX - radius > 650 && posX - radius + velocityX < 660 && posY - radius < 286) {
          velocityX = 0;
        }
        if (posX - radius > 820 && posX - radius + velocityX < 830 && posY + radius > 244) {
          velocityX = 0;
        }
        posX += velocityX;
      }
      
      // the next condition is for when Faiza moves right
      else if (move_right == true) {
        velocityX = 2;
        directionX = "right";
        if (posX + radius < 120 && posX + radius + velocityX > 110 && (posY - radius < 396 || posY + radius > 484)) {
          velocityX = 0;
        }
        if (posX + radius < 750 && posX + radius + velocityX > 740 && posY + radius > 244) {
          velocityX = 0;
        }
        if (posX + radius < 920 && posX + radius + velocityX > 910 && posY - radius < 526) {
          velocityX = 0;
        }
        if (posX + radius > 1018) {
          velocityX = 0;
        }
        posX += velocityX;
      }
      
      // If none of the left and right keys are being pressed, Faiza stops moving horizontally
      else {
        velocityX = 0;
      }
      
      // The condition below is for when Faiza moves upwards
      if (move_up == true) {
        velocityY = -2;
        if (posX + radius < 120 && posY - radius + velocityY < 288) {
          velocityY = 0;
        }
        if (posX + radius >= 120 && posX - radius <= 270 && posY - radius + velocityY < 398) {
          velocityY = 0;
        }
        if (posX - radius > 270 && posX - radius <= 650 && posY - radius + velocityY < 288){
          velocityY = 0;
        }
        if (posX - radius > 650 && posX + radius < 920 && posY - radius + velocityY < 168) {
          velocityY = 0;
        }
        if (posX + radius >= 920 && posY - radius + velocityY < 528){
          velocityY = 0;
        }
        posY += velocityY;
      }
      
      // The condition below is for when Faiza moves downwards
      else if (move_down == true) {
        velocityY = 2;
        if (posX + radius < 120 && posY + radius + velocityY > 613) {
          velocityY = 0;
        }
        if (posX + radius >= 120 && posX - radius <= 270 && posY + radius + velocityY > 482) {
          velocityY = 0;
        }
        if (posX - radius > 270 && posX + radius < 750 && posY + radius + velocityY > 612){
          velocityY = 0;
        }
        if (posX + radius >= 750 && posX - radius <= 820 && posY + radius + velocityY > 242) {
          velocityY = 0;
        }
        if (posX - radius > 820 && posX + radius <= 1024 && posY + radius + velocityY > 612) {
          velocityY = 0;
        }
        posY += velocityY;
      }
      
      // If none of the up and down keys are being pressed, Faiza stops moving vertically
      else {
        velocityY = 0;
      }
    }
    
    else if (game.level >= 6) {
      //The condition below is for when Faiza moves left
      if (move_left == true) {
        velocityX = -2;
        directionX = "left";
        if (posX - radius + velocityX < 6) {
          velocityX = 0;
        }
        posX += velocityX;
      }
  
      //The condition below is for when Faiza moves right
      else if (move_right == true) {
        velocityX = 2;
        directionX = "right";
        if (posX + radius + velocityX > 1018) {
          velocityX = 0;
        }
        posX += velocityX;
      }
  
      //If none of the left and right keys are being pressed, Faiza stops moving horizontally
      else {
        velocityX = 0;
      }        
  
      if (move_up == true) {
        velocityY = -2;
        if (posY - radius + velocityY <= 5) {
          velocityY = 0;
        }
        posY += velocityY;
      }
  
      //The condition below is for when Faiza moves downwards
      else if (move_down == true) {
        velocityY = 2;
        if (posY + radius + velocityY >= 762) {
          velocityY = 0;
        }      
        posY += velocityY;
      }
  
      //If none of the up and down keys are being pressed, Faiza stops moving vertically
      else {
        velocityY = 0;
      }
    }

    // Animating Faiza by continuously iterating over each frame in it's sprite when Faiza is moving
    if ((frameCount%5 == 0) && (velocityX != 0 || velocityY != 0)) {
      frame = (frame + 1) % (num_frames - 1);
    } 
    // If Faiza is not moving, the frame where Faiza is still is displayed
    else if (velocityX == 0 && velocityY == 0) {
      frame = 8;
    }
    
    
    // For all the following blocks of code, "alive" is set to False when Faiza collides with any of these objects, and so Faiza returns to the starting position in that level
    // Also, the breakdown counter is incremented by 1 for every collision
    if (game.level >= 2 && game.level <= 5) {
      if (distance(game.clock) <= radius + game.clock.radius) {
        breakdown_counter += 1;
        alive = false;
      }
    }
    
    if (game.level == 4 || game.level == 5) {
      if (distance(game.clock2) <= radius + game.clock2.radius) {
        breakdown_counter += 1;
        alive = false;
      }
    }
    
    if (game.level == 5) {
      if (distance(game.clock3) <= radius + game.clock3.radius) {
        breakdown_counter += 1;
        alive = false;
      }
    }
    
    if (game.level >= 3 && game.level <= 5) {
      if (distance(discussion_list[0]) <= radius + discussion_list[0].radius) {
        breakdown_counter += 1;
        alive = false;
      }
    }
    
    if (game.level >= 6 && game.level <= 10) {
      if (distance(discussion_list2[0]) <= radius + discussion_list2[0].radius) {
        breakdown_counter += 1;
        alive = false;
        game.anxiety.alive = false;
        game.anxiety2.alive = false;
      }
    }
    
    if (game.level >= 1 && game.level <= 5) {
      if (distance(game.brain) <= radius + game.brain.radius) {
        game.level += 1;
        game.clock = new Clock(310, 330, 32, "clock.png", 66, 66, 4);
        game.clock2 = new Clock(695, 570, 32, "clock.png", 66, 66, 4);
        game.clock3 = new Clock(500, 440, 32, "clock.png", 66, 66, 4);
        alive = false;
      }
    }
    
    if (game.level >= 6 && game.level <= 10) {
      if (distance(game.brain2) <= radius + game.brain2.radius) {
        game.level += 1;
        alive = false;
        game.anxiety.alive = false;
        game.anxiety2.alive = false;
      }
    }

    if (game.level == 11) {
      if (!(posX >= 0 && posX <= 100 && posY >= 530 && posY <= 640)) { 
        for (int i = 0; i < 6; i++) {
          if (distance(distractions[i]) <= radius + distractions[i].radius) {
            distraction_counter += 1;
            alive = false;
          }
        }
      }
      
      // checking collision with the gpa in the last level
      if (distance(game.gpa) <= (radius + game.gpa.radius) && game.level == 11) {
        game.level += 1;
      }
    }
    
    // Incrementing x_shift in the Game class when Faiza in moving in the x-direction to implement parallax effect
    if (posX >= 0) {
      game.x_shift += velocityX;
    }
    
  }

  // this distance method will be used to check for collisions with distractions
  double distance(Creature target) {
    float a = (posX - target.posX);
    float b = (posY - target.posY);
    double c = Math.pow(a, 2);
    double d = Math.pow(b, 2);
    return Math.pow(c + d, 0.5);
  }
}

// Creating the obstacle Clock, which moves sideways within a certain x-range.
// Appears in levels 1-5
class Clock extends Creature {
  // x_left and x_right represent the boundaries of the movement of the clock
  int x_left, x_right, choose_direction;
  
  Clock(float x, float y, float r, String image_name, float img_w, float img_h, int number_frames) {
    super(x, y, r, image_name, img_w, img_h, number_frames);
    x_left = 270;
    x_right = 740;
    velocityX = 3;
    
    // Choosing a random starting direction and multiplying vx by -1 if it's direction is chosen to be left at the beginning.
    choose_direction = int(random(0,2));
    if (choose_direction == 0) {
      velocityX *= -1;
    }
  }
    
    void update() {
      // Animating the clock by continuously iterating over each frame in it's sprite.
      if (frameCount % 12 == 0){
        frame = (frame + 1) % num_frames;
      }
      
      // Making the clock change it's direction if it hits one of the boundaries
      if (posX - radius <= x_left) {
        velocityX *= -1;
        directionX = "right";
      }
      
      if (posX + radius >= x_right) {
        velocityX *= -1;
        directionX = "left";
      }
      
      posX += velocityX;
      posY += velocityY;
      
    }
}

// creating the discussion class which represents random class discussions which pop up anywhere on the screen
// appears in levels 3-10
class Discussion extends Creature {
  
  Discussion(float x, float y, float r, String image_name, float img_w, float img_h, int number_frames) {
    super(x, y, r, image_name, img_w, img_h, number_frames);
  }
  
  void update() {
    // Animating the quiz by continuously iterating over each frame in its sprite.
    if (frameCount % 20 == 0) {
      frame = (frame + 1) % num_frames;
    }
  }
}


// Creating the obstacle Anxiety, which continuously follows Faiza wherever she goes
// appears in levels 6-10
class Anxiety extends Creature {
  float target_x, target_y, target_r, velocity;
  boolean alive;
  boolean move_up, move_down, move_right, move_left;
  float difference_x, difference_y, angle;
  
  Anxiety(float x, float y, float r, String image_name, float img_w, float img_h, int number_frames, float tx, float ty, float tr) {
    super(x, y, r, image_name, img_w, img_h, number_frames);
    
    // Here we make a 'dummy' object which is precisely mapped to Faiza. It has it's separate keystroke functions which perfectly imitate 
    // Faiza's movements. This makes our task easier since everything is now handled within the Anxiety class. The 'dummy' object has of course, been
    // made invisible so that it seems Anxiety is always following Faiza. target_x, target_y, and target_r represent the position and radius of the 'dummy' object respectively.
    target_x = tx;
    target_y = ty;
    target_r = tr;
    
    // All attributes below are very similar to that of Faiza
    // There is also a velocity attribute to set the velocity of anxiety
    alive = true;
    velocity = 1.7;
    move_up = false;
    move_down = false;
    move_right = false;
    move_left = false;
    difference_x = 0;
    difference_y = 0;
    angle = 0;
  }
  
  void update() {
    if (frameCount % 20 == 0) {
      frame = (frame + 1) % num_frames;
    }
    
    
    // all the conditions below have been copies from Faiza's class to exactly imitate Faiza's movements
    if (move_left == true) {
      velocityX = -2;
      directionX = "left";
      if (target_x - target_r + velocityX < 6) {
        velocityX = 0;
      }
      target_x += velocityX;
    }

    else if (move_right == true) {
      velocityX = 2;
      directionX = "right";
      if (target_x + target_r + velocityX > 1018) {
        velocityX = 0;
      }
      target_x += velocityX;
    }

    else {
      velocityX = 0;
    }        

    if (move_up == true) {
      velocityY = -2;
      if (target_y - target_r + velocityY <= 5) {
        velocityY = 0;
      }
      target_y += velocityY;
    }

    else if (move_down == true) {
      velocityY = 2;
      if (target_y + target_r + velocityY >= 762) {
        velocityY = 0;
      }      
      target_y += velocityY;
    }

    else {
      velocityY = 0;
    }
    
    
    // We now calculate difference_x and difference_y for the difference in x and y positions between Anxiety and the 'dummy' respectively,
    // then we calculate angle between them, and increment x and y by velocity*cos(angle) and velocity*sin(angle) respectively
    // the angle constantly changes as Faiza and the dummy move, hence anxiety constantly follows Faiza
    difference_x = target_x - posX;
    difference_y = target_y - posY;
    
    // to avoid zero division error in the angle
    if (difference_x == 0 && difference_y > 0) {
      angle = radians(90);
    }
    if (difference_x == 0 && difference_y < 0) {
      angle = radians(270);
    }
    else {
      angle = atan(difference_y/difference_x);
    }
    
    
    // incrementing x and y positions using the fact that cos(-x) = cos(x) and sin(-x) = -sin(x):
    if (difference_x == 0 && difference_y > 0) {
      posX += velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    if (difference_x == 0 && difference_y < 0) {
      posX += velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    if (difference_x > 0 && difference_y == 0) {
      posX += velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    if (difference_x < 0 && difference_y == 0) {
      posX -= velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    if (difference_x > 0 && difference_y > 0) {
      posX += velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    if (difference_x < 0 && difference_y < 0) {
      posX -= velocity * cos(angle);
      posY -= velocity * sin(angle);
    }
    if (difference_x < 0 && difference_y > 0) {
      posX -= velocity * cos(angle);
      posY -= velocity * sin(angle);
    }
    if (difference_x > 0 && difference_y < 0) {
      posX += velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    
    // Since Anxiety is following the 'dummy', we have to take into account the collision between the dummy and Anxiety
    // and set both the dummy's and Faiza's alive attributes to False.
    if (game.level >= 6 && game.level <= 10) {
      if (distance() <= radius + target_r) {
        alive = false;
        game.faiza.breakdown_counter += 1;
        game.faiza.alive = false;
      }
    }
  }
    
    // defining the distance method for calculating the distance between Anxiety and the dummy  
    double distance() {
    float a = (posX - target_x);
    float b = (posY - target_y);
    double c = Math.pow(a, 2);
    double d = Math.pow(b, 2);
    return Math.pow(c + d, 0.5);
  }
    
    // We slightly modify the display function for Anxiety since we don't want the text beneath Anxiety's image to be inverted when anxiety moves leftwards
    void display() {
      update();
      
      if (directionX == "right" || directionX == "left") {
        image(sprite_image, float(int(posX - imgwidth/2)), float(int(posY - imgheight/2)), imgwidth, imgheight, int(frame * imgwidth), 0, int((frame + 1) * imgwidth), int(imgheight));
      }
    }
  }
        
        
// Creating the obstacle Assignment, which is instantiated randomly at one of the 4 boundaries of the game's screen.
// First, it captures the position of Faiza, then locks those target coordinates (denoted by target_x and target_y) and is then fired towards them.
// Appears in levels 8-10
class Assignment extends Creature {
  float target_x, target_y, velocity;
  float difference_x, difference_y, angle;
  
  Assignment(float x, float y, float r, String image_name, float img_w, float img_h, int number_frames, float tx, float ty) {
    super(x, y, r, image_name, img_w, img_h, number_frames);
    // target_x and target_y represent the coordinates of Faiza
    // we make use of angles and difference in x and y to fire assignments towards faiza with a velocity
    target_x = tx;
    target_y = ty;
    difference_x = target_x - posX;
    difference_y = target_y - posY;
    velocity = 8;
    
    if (difference_x == 0 && difference_y > 0) {
      angle = radians(90);
    }
    if (difference_x == 0 && difference_y < 0) {
      angle = radians(270);
    }
    else {
      angle = atan(difference_y/difference_x);
    }
  }
  
  void update() {
    if (frameCount % 20 == 0) {
      frame = (frame + 1) % num_frames;
    }
    
    // We use the fact that cos(-x) = cos(x) and sin(-x) = -sin(x) and divide the possible directions of movement into cases:
    if (difference_x == 0 && difference_y > 0) {
      posX += velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    if (difference_x == 0 && difference_y < 0) {
      posX += velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    if (difference_x > 0 && difference_y == 0) {
      posX += velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    if (difference_x < 0 && difference_y == 0) {
      posX -= velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    if (difference_x > 0 && difference_y > 0) {
      posX += velocity * cos(angle);
      posY += velocity * sin(angle);
    }
    if (difference_x < 0 && difference_y < 0) {
      posX -= velocity * cos(angle);
      posY -= velocity * sin(angle);
    }
    if (difference_x < 0 && difference_y > 0) {
      posX -= velocity * cos(angle);
      posY -= velocity * sin(angle);
    }
    if (difference_x > 0 && difference_y < 0) {
      posX += velocity * cos(angle);
      posY += velocity * sin(angle);
    }
  }
}
    
// Creating the distractions class which represents different distractions which appear in level 11
class Distractions extends Creature {

  Distractions(float x, float y, float r, String image_name, float img_w, float img_h, int number_frames) {
    super(x, y, r, image_name, img_w, img_h, number_frames);
    // all distractions are instantiated with random speeds within a certain limit
    velocityX = random(2, 5);
    velocityY = -1 * random(2, 5);
  }

  void update() {
    if (frameCount % 12 == 0) {
      frame = (frame + 1) % num_frames;
    }


    // setting conditions for rebounding the distractions when they hit the corners of the screen
    if (posX + radius >= 1024) {
      velocityX *= -1;
    }
    if (posX - radius <= 0) {
      velocityX *= - 1;
    }
    if (posY - radius <= 10) {
      velocityY *= -1;
    }
    if (posY + radius >= 780) {
      velocityY *= -1;
    }

    posX += velocityX;
    posY += velocityY;
  }
}

// Creating the Brain class which appears in levels 1-10 which represents the mental health
class Brain extends Creature {
  Brain(float x, float y, float r, String image_name, float img_w, float img_h, int number_frames) {
    super(x, y, r, image_name, img_w, img_h, number_frames);
  }
  
  void update() {
    if (frameCount % 20 == 0) {
      frame = (frame + 1) % num_frames;
    }
  }
}

// Creating the GPA class which appears in level 11
class GPA extends Creature {

  GPA(float x, float y, float r, String image_name, float img_w, float img_h, int number_frames) {
    super(x, y, r, image_name, img_w, img_h, number_frames);
  }
}

class Game {
  int game_width, game_height;
  Faiza faiza;
  GPA gpa;
  Brain brain, brain2;
  Clock clock, clock2, clock3;
  Anxiety anxiety, anxiety2;
  int level;
  PImage intro_bg, final_bg, over_bg, game_background;
  int x, x_shift, width_right, width_left;
  Discussion discussion, discussion2;
  int rand_int, rand_int2, rand_int3;
  
  Game(int game_wth, int game_hght) {
    level = -1;
    x = 0;
    // x_shift will be used to move the background image when faiza is moving with some x-velocity
    x_shift = 0;
    width_right = 0;
    width_left = 0;
    // rand_int, rand_int2 and rand_int3 are used for random integers which decide the boundary from which assignments are generated
    rand_int = 0;
    rand_int2 = 0;
    rand_int3 = 0;
    game_width = game_wth;
    game_height = game_hght;
    intro_bg = loadImage("start_background.png");
    game_background = loadImage("background1.png");
    final_bg = loadImage("background.png");
    over_bg = loadImage("gameover_background.png");
    
    faiza = new Faiza(34, 585, 27, "faiza.png", 66, 66, 9);
    gpa = new GPA(990, 35, 25, "gpa.png", 70, 56, 1);
    brain = new Brain(980, 570, 30, "brain_waving.png", 85, 85, 2);
    brain2 = new Brain(980, 50, 30, "brain_waving.png", 85, 85, 2);
    clock = new Clock(310, 330, 32, "clock.png", 66, 66, 4);
    clock2 = new Clock(695, 570, 32, "clock.png", 66, 66, 4);
    clock3 = new Clock(500, 440, 32, "clock.png", 66, 66, 4);
    anxiety = new Anxiety(500, 500, 25, "anxiety.png", 66, 66, 3, 34, 585, faiza.radius);
    anxiety2 = new Anxiety(1000, 700, 25, "anxiety.png", 66, 66, 3, 34, 585, faiza.radius);
    
    // instantiating discussions to be added to the array of discussions
    discussion = new Discussion(int(random(300,711)), int(random(300,591)), 25, "Discussions.png", 66, 66, 3);
    discussion2 = new Discussion(int(random(120,995)), int(random(300,591)), 25, "Discussions.png", 66, 66, 3);
    
    discussion_list = new Discussion[1];
    discussion_list2 = new Discussion[1];
    
    discussion_list[0] = discussion;
    discussion_list2[0] = discussion2;
    
    // adding distractions to the array of distractions
    distractions = new Distractions[6];
    distractions[0] =  new Distractions(100, 300, 58, "jake.png", 120, 120, 6);
    distractions[1] =  new Distractions(444, 333, 48, "insta.png", 100, 100, 1);
    distractions[2] =  new Distractions(900, 120, 48, "facebook.png", 100, 100, 1);
    distractions[3] =  new Distractions(887, 635, 48, "netflix.png", 100, 100, 1);
    distractions[4] =  new Distractions(134, 587, 48, "youtube.png", 100, 100, 1);
    distractions[5] =  new Distractions(55, 100, 48, "ps.png", 120, 120, 1);
    
    // having a position_list array which holds possible coordinates of where assignments can be launched
    position_list = new int[4][2];
    position_list[0][0] = int(random(30, 995));
    position_list[0][1] = 30;
    position_list[1][0] = 994;
    position_list[1][1] = int(random(150, 731));
    position_list[2][0] = int(random(30, 995));
    position_list[2][1] = 730;
    position_list[3][0] = 30;
    position_list[3][1] = int(random(150, 401));
    
    assignments = new Assignment[1];
    assignments[0] = new Assignment(position_list[rand_int][0], position_list[rand_int][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
    assignments2 = new Assignment[2];
    assignments2[0] = new Assignment(position_list[rand_int][0], position_list[rand_int][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
    assignments2[1] = new Assignment(position_list[rand_int2][0], position_list[rand_int2][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
    assignments3 = new Assignment[3];
    assignments3[0] = new Assignment(position_list[rand_int][0], position_list[rand_int][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
    assignments3[1] = new Assignment(position_list[rand_int2][0], position_list[rand_int2][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
    assignments3[2] = new Assignment(position_list[rand_int3][0], position_list[rand_int3][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
  }

  void update() {
    // Sending Faiza back to her original position when faiza.alive == False and then setting faiza.alive to True again
    if (faiza.alive == false) {
      faiza.posX = 34;
      faiza.posY = 585;
      faiza.alive = true;
    }
    
    position_list[0][0] = int(random(30, 995));
    position_list[0][1] = 30;
    position_list[1][0] = 994;
    position_list[1][1] = int(random(150, 731));
    position_list[2][0] = int(random(30, 995));
    position_list[2][1] = 730;
    position_list[3][0] = 30;
    position_list[3][1] = int(random(150, 401));
    
    // changing the assignments in each position of the assignments array after certain intervals
    if (level == 8) {
      rand_int = int(random(0,4));
      if (frameCount % 200 == 0) {
        assignments[0] = new Assignment(position_list[rand_int][0], position_list[rand_int][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
      }
    }
    
    else if (level == 9) {
      rand_int = int(random(0,4));
      rand_int2 = int(random(0,4));
      if (frameCount % 200 == 0) {
        assignments2[0] = new Assignment(position_list[rand_int][0], position_list[rand_int][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
      }
      if (frameCount % 201 == 0) {
        assignments2[1] = new Assignment(position_list[rand_int2][0], position_list[rand_int2][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
      }
    }
    
    else if (level == 10) {
      rand_int = int(random(0,4));
      rand_int2 = int(random(0,4));
      rand_int3 = int(random(0,4));
      if (frameCount % 200 == 0) {
        assignments3[0] = new Assignment(position_list[rand_int][0], position_list[rand_int][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
      }
      if (frameCount % 201 == 0) {
        assignments3[1] = new Assignment(position_list[rand_int2][0], position_list[rand_int2][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
      }
      if (frameCount % 202 == 0) {
        assignments3[2] = new Assignment(position_list[rand_int3][0], position_list[rand_int3][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
      }
    }
    
    // Resetting the position of the Anxieties when anxiety.alive or anxiety2.alive are False
    if (anxiety.alive == false || anxiety2.alive == false) {
      anxiety.posX = 500;
      anxiety2.posX = 1000;
      anxiety.target_x = 34;
      anxiety2.target_x = 34;
      anxiety.posY = 500;
      anxiety2.posY = 700;
      anxiety.target_y = 585;
      anxiety2.target_y = 585;
      anxiety.alive = true;
      anxiety2.alive = true;
    }
    
    // adding new discussions to the discussion_lists which are instantiated at newer random positions
    // in later levels, discussions are instantiated more frequently.
    if (frameCount % 300 == 0) {
      discussion = new Discussion(random(300,711), random(300,591), 25, "Discussions.png", 66, 66, 3);
      discussion_list[0] = discussion;
    }
    
    if (frameCount % 150 == 0) {
      discussion2 = new Discussion(random(120, 995), random(130, 739), 25, "Discussions.png", 66, 66, 3);
      discussion_list2[0] = discussion2;
    }
    
    // the following blocks of code check the collisions of assignments with faiza
    if (game.level == 8) {
      if (faiza.distance(assignments[0]) <= faiza.radius + assignments[0].radius) {
        faiza.breakdown_counter += 1;
        faiza.alive = false;
        anxiety.alive = false;
        anxiety2.alive = false;
        rand_int = int(random(0,4));
        assignments[0] = new Assignment(position_list[rand_int][0], position_list[rand_int][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
      }
    }
    
    if (game.level == 9) {
      for (int i = 0; i < 2; i++) {
        if (faiza.distance(assignments2[i]) <= faiza.radius + assignments2[i].radius) {
          faiza.breakdown_counter += 1;
          faiza.alive = false;
          game.anxiety.alive = false;
          game.anxiety2.alive = false;
          rand_int = int(random(0,4));
          rand_int2 = int(random(0,4));
          assignments2[0] = new Assignment(position_list[rand_int][0], position_list[rand_int][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
          assignments2[1] = new Assignment(position_list[rand_int2][0], position_list[rand_int2][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
        }
      }
    }
    
    if (game.level == 10) {
      for (int i = 0; i < 3; i++) {
        if (faiza.distance(assignments3[i]) <= faiza.radius + assignments3[i].radius) {
          faiza.breakdown_counter += 1;
          faiza.alive = false;
          anxiety.alive = false;
          anxiety2.alive = false;
          rand_int = int(random(0,4));
          rand_int2 = int(random(0,4));
          rand_int3 = int(random(0,4));
          assignments3[0] = new Assignment(position_list[rand_int][0], position_list[rand_int][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
          assignments3[1] = new Assignment(position_list[rand_int2][0], position_list[rand_int2][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
          assignments3[2] = new Assignment(position_list[rand_int3][0], position_list[rand_int3][1], 30, "assignment.png", 66, 66, 4, faiza.posX, faiza.posY);
        }
      }
    }
    
  }

  void display() {
    update();

     // displaying the introduction screen
    if (level == -1) {
      image(intro_bg, 0, 0);
      textSize(80);
      text("UNI SUFFERERS", 230, 80);
      textSize(40);
      fill(255, 213, 43);
      text("Press the space bar to proceed", 200, 650);
    }
    
    // displaying the instructions screen
    if (level == 0) {
      image(game_background, 0, 0);
      textSize(50);
      fill(75, 0, 70);
      text("INSTRUCTIONS", 290, 80);
      textSize(30);
      text("1) The player plays the game as Faiza the Falcon", 20, 140);
      text("2) Use the arrow keys to control the movement of Faiza", 20, 180);
      text("3) Help Faiza avoid obstacles and save her mental health", 20, 220);
      text("4) Keep a check on the breakdown counter in the first 10 levels", 20, 260);
      text("5) The obstacles in the first 10 levels are of the following types:", 20, 300);
      text("a) Clocks representing 5am classes move sideways on the screen", 20, 340);
      text("b) Class discussions randomly pop up anywhere on the screen", 20, 380);
      text("c) Anxiety constantly follows the movement of Faiza", 20, 420);
      text("d) Assignments lock Faiza's coordinates and are shot with a velocity", 20, 460);
      text("6) Avoid distractions in the last level and achieve a 4.0 GPA", 20, 500);
      text("7) Once the game is over, click on the screen to play again", 20, 540);
      textSize(40);
      text("Click on the screen to begin playing", 180, 650);
    }
    
    
    // moving the background when faiza is moving in the x direction to give parallax effect
    if (level >= 1 && level <= 10) {
      x = 0;
      x = x_shift;
      
      width_right = x % game_width;
      width_left = game_width - width_right;
      
      image(game_background, 0, 0, width_left, game_height, width_right, 0, game_width, game_height);
      image(game_background, width_left, 0, width_right, game_height, 0, 0, width_right, game_height);
    }

    if (level == 11) {
      image(final_bg, 0, 0);
      //textMode(CENTER);
      textSize(40);
      fill(255, 213, 43);
      text("GET THAT 4.0!", 310, 65);
      textSize(20);
      text(faiza.distraction_counter + " distraction(s)", 860, 740);
    }

    if (level == 12) {
      image(over_bg, 0, 0);
      textSize(150);
      fill(255, 213, 43);
      text("GAME", 270, 220); 
      text("OVER", 290, 350);
      textSize(30);
      text(faiza.breakdown_counter + " breakdowns and " + faiza.distraction_counter + " distraction(s) later,", 240, 550);
      text("you finally got that 4.0 GPA!", 300, 590);
      text("Think you can do better? Click on the", 240, 630);
      text("screen to play again!", 340, 670);
    }
    
    textSize(40);
    fill(75, 0, 70);
    if (level == 1) {
      text("GO SAVE YOUR MENTAL HEALTH!", 180, 70);
    }
    else if (level == 2) {
      text("5 AM CLASSES ARE WAITING!", 210, 70);
    }
    else if (level == 3) {
      text("TOO EASY? LET'S POP THINGS UP!", 170, 70);
    }
    else if (level == 4) {
      text("HAVE MORE OF IT!", 290, 70);
    }
    else if (level == 5) {
      text("AND MORE!", 360, 70);
    }
    else if (level == 6) {
      textSize(35);
      text("SOME THINGS JUST WON'T STOP FOLLOWING YOU", 65, 60);
    }
    else if (level == 7) {
      text("THIS AIN'T GETTING ANY BETTER", 150, 65);
    }
    else if (level == 8) {
      text("WAIT, WHAT? ASSIGNMENT?", 210, 65);
    }
    else if (level == 9) {
      text("WHAT? THERE WERE TWO?", 230, 65);
    }
    else if (level == 10) {
      text("YOU'RE KIDDING ME...", 270, 65);
    }
    
    if (level >= 1 && level <= 10) {
      textSize(20);
      text(faiza.breakdown_counter + " breakdown(s)", 860, 740);
    }
      
    // creating the grid for the first few levels
    if (level >= 1 && level <= 5) {
        stroke(0, 0, 0);
        strokeWeight(7);
        line(0, 100, 1024, 100);
        line(0, 115, 1024, 115);
        strokeWeight(9);
        line(0, 280, 120, 280);
        line(120, 280, 120, 390);
        line(120, 390, 270, 390);
        line(270, 280, 270, 390);
        line(120, 490, 270, 490);
        line(0, 620, 120, 620);
        line(120, 490, 120, 620);
        line(270, 490, 270, 620);
        line(270, 280, 650, 280);
        line(650, 280, 650, 160);
        line(270, 620, 750, 620);
        line(750, 620, 750, 250);
        line(750, 250, 820, 250);
        line(820, 250, 820, 620);
        line(650, 160, 920, 160);
        line(920, 160, 920, 520);
        line(920, 520, 1024, 520);
        line(820, 620, 1024, 620);
    }

    // calling the display function of all objects in the relevant levels
    
    if (level >= 1 && level <= 11) {
      faiza.display();
    }
    
    if (level >= 2 && level <= 5) {
      clock.display();
    }
    
    if (level >= 4 && level <= 5) {
      clock2.display();
    }
    
    if (level == 5) {
      clock3.display();
    }

    if (level >= 3 && level <= 5) {
      discussion_list[0].display();
    }
    
    if (level >= 6 && level <= 10) {
      discussion_list2[0].display();
      brain2.display();
      anxiety.display();
    }
    
    if (level >= 7 && level <= 10) {
      anxiety2.display();
    }
    
    if (level >= 1 && level <= 5) {
      brain.display();
    }
    
    if (level == 8) {
      assignments[0].display();
    }
    
    if (level == 9) {
      assignments2[0].display();
      assignments2[1].display();
    }
    
    if (level == 10) {
      assignments3[0].display();
      assignments3[1].display();
      assignments3[2].display();
    }

    if (level == 11) {
      gpa.display();
      for (int i = 0; i < 6; i++) {
        distractions[i].display();
      }
    }
  }
}


void setup() {
  size(1024, 768);
  game = new Game(1024, 768);
  // setting up the introductory song and the background song
  path = sketchPath(audioName);
  intro_song = new SoundFile(this, path);
  intro_song.loop();
  
  path2 = sketchPath(audioName2);
  background_song = new SoundFile(this, path2);
}

void draw() {
  background(255, 255, 255);
  game.display();
}

// Defining keyPressed and keyReleased for Faiza and the 'dummies' in Anxiety and Anxiety2:
void keyPressed() {
  // displaying the isntructions screen when the space bar is pressed at the intro screen
  if (keyCode == 32 && game.level == -1) {
    game.level = 0;
  }
  if (key == CODED) {
    if (keyCode == RIGHT) {
      game.faiza.move_right = true;
      game.anxiety.move_right = true;
      game.anxiety2.move_right = true;
    }
    if (keyCode == LEFT) {
      game.faiza.move_left = true;
      game.anxiety.move_left = true;
      game.anxiety2.move_left = true;
    }
    if (keyCode == UP) {
      game.faiza.move_up = true;
      game.anxiety.move_up = true;
      game.anxiety2.move_up = true;
    }
    if (keyCode == DOWN) {
      game.faiza.move_down = true;
      game.anxiety.move_down = true;
      game.anxiety2.move_down = true;
    }
  }
}

void keyReleased() {
  if (key == CODED) {
    if (keyCode == RIGHT) {
      game.faiza.move_right = false;
      game.anxiety.move_right = false;
      game.anxiety2.move_right = false;
    }
    if (keyCode == LEFT) {
      game.faiza.move_left = false;
      game.anxiety.move_left = false;
      game.anxiety2.move_left = false;
      
    }
    if (keyCode == UP) {
      game.faiza.move_up = false;
      game.anxiety.move_up = false;
      game.anxiety2.move_up = false;
    }
    if (keyCode == DOWN) {
      game.faiza.move_down = false;
      game.anxiety.move_down = false;
      game.anxiety2.move_down = false;
    }
  }
}

void mouseClicked(){
  // starting the game when the mouse is clicked at the instructions screen
  if (game.level == 0) {
    intro_song.stop();
    background_song.loop();
    game.level = 1;
  }
  // restarting the game when the mouse is clicked at the game over screen
  if (game.level == 12){
    background_song.stop();
    game = new Game(1024, 768);
    intro_song.loop();
  }
}

im_midterm