Skip to main content

Irate Avians


Getting Started


A. Goals

The purpose of this homework is to do a deeper dive into object-oriented programming.

At the end of the assignment, you will have produced a game called Irate Avians (our take on Angry Birds). Irate Avians has moving targets, targets with multiple hit points, and a limited number of bird launches. We have provided an example video of how your game might look when it is complete here. Each class acts as the blueprint for distinct, crucial parts of our overall game. As you are implementing this assignment, keep in mind that we are having objects interact with each other; they do not exist in isolation. Your bird object will interact with your target objects by flying into and hitting them, and your bird and target objects operate within the arena object which controls the game.


B. Getting Started

You can find the base code files here which are also available through Codio. Read through the classes and function stubs provided, along with the comments. You will be using several different classes to implement the game. Namely:


C. Advice

Work on the program in the order of the directions. Do not try to do things out of order, as that will likely add to your confusion. You will start with game elements like the bird and work backwards to the drawing and the loop.

Compile, run, and debug frequently; make one small change at a time, ensuring that your program works as you expect it to after each change. We will describe checkpoints you should test as you code the program.


1. Class Constructors


We will begin the Irate Avians assignment by implementing the constructor for each of our game components - Bird.java, Target.java, and Arena.java. This will allow us to properly instantiate them by parsing a level-description text file as in previous assignments like NBody.

A. Bird Constructor

The constructor for the Bird class should initialize all of the bird’s instance variables. Its position, radius, and number of throws (how many times the bird can be launched before the game ends) are passed to the constructor. The bird’s velocity components should both be set to zero.


B. Target Constructor

The constructor for the Target class takes in the canvas’s width and height, and the target’s position, radius, velocity components, and number of hit points. These attributes should be initialized accordingly. The Targets in this version of the game move across the screen, and take more than one hit to completely destroy, which is why we will need velocity and hit point variables. We will discuss how to use these variables in later sections of the writeup, just make sure you initialize them in this constructor. Also, initialize hitThisShot to false.


C. Arena Constructor

The Arena class’s constructor is where you will parse the level description file passed as the first argument to IrateAvians.java’s main function. As the comments in the base code note, you will read several pieces of information about the game’s initial state from the input file. The data in the file will be ordered as such:

numTargets width height
bird.numThrowsRemaining
targets[0].xPos targets[0].yPos targets[0].radius targets[0].xVel targets[0].yVel targets[0].hitPoints
targets[1].xPos targets[1].yPos targets[1].radius targets[1].xVel targets[1].yVel targets[1].hitPoints

The first line of information contains data about the Arena. Make sure to call PennDraw.setXscale() and PennDraw.setYscale() with the width and height in the file (from 0 to the specified width or height), in addition to setting Arena’s member variables. The Arena constructor should initialize its bird to the position (1,1), with a radius of 0.25, and with a number of throws equal to the amount indicated in the file. The number given for numTargets will tell you how many lines there will be with target data in the file; you should use this when allocating and instantiating the values of your Target array. Note that the .close() function in the In class closes the input stream for your text file. As well, make sure to set mouseListeningMode and mouseWasPressedLastUpdate to the correct boolean value in your constructor.


2. Draw Functions


Next in this project we will implement the draw() functions of the Bird, Target, and Arena classes. This will let us “visually debug” our constructors to ensure our objects are initialized with the right properties.


A. Drawing the Bird

You should draw the bird so that its body should appear as a circle centered at the bird’s position, with a radius equal to the bird’s radius. Additionally, draw a triangular beak and two circular eyes somewhere on the bird’s body to give it more visual detail. You may add more body parts and details as you wish, but they won’t be worth any additional credit. You should also draw the bird’s remaining throws as text at a position 0.1 units above the bird’s body (not overlapping it).


B. Drawing the Bird’s Launch Velocity

In addition to drawing the bird, you will implement the bird’s drawVelocity() function to pre-visualize the bird’s launch velocity when the player clicks and drags (to be implemented in a future section). All drawVelocity() needs to do is draw a line from the bird’s position to a point equivalent to the bird’s center after a 1 second timestep. HINT: remember that in physics, $p_{new} = p_{old} + velocity * timestep$.


C. Drawing a Target

Each target should be drawn as a circle centered at the target’s position, with a radius equal to the target’s radius. The target’s current hit points should be drawn as text centered at the target’s position. A target’s draw function should only draw anything if the target’s hit points are above zero.


D. Drawing the Arena

The Arena’s drawing function will be a little more involved than the Bird’s or Target’s. The arena is the object in the game engine that stores the bird and targets, so it will invoke their respective draw functions to draw them. Thus, Arena’s draw() function will perform the following operations:


E. Checkpoint

Run IrateAvians.java and give it the command-line argument targetFile1.txt. At this point, you should see the bird and targets being drawn on your screen, but nothing will move and nothing will respond to mouse input (yet).

This should produce the following drawing:

If your drawing does not look like this, try to debug your draw() functions.


3. Updating the Components


A. Updating the Targets

The first thing we’re going to do in this section is actually add a little code to Arena’s update function. Go ahead and iterate over the array of Targets and call update() on each Target. You should pass timeStep to each invocation of target.update(). We will implement the rest of arena.update() later in this section. We do this so that our code actually calls target.update() somewhere so we can test whether our implementation of it works after this step.

Now, we will actually implement target.update(). This function should do the following:

  1. Increment the target’s x position by its x velocity times the input time step.
  2. Increment the target’s y position by its y velocity times the input time step.
  3. Check whether the target’s x position is less than zero or greater than the Arena’s width, accounting for the target’s radius in this calculation. The target should be completely offscreen to the left or right for this check to evaluate to true; you should not just check the target’s center. If the target is offscreen horizontally, then you should set the target’s xPos such that it wraps around to the other side of the screen, also accounting for the target’s radius. For example, if the target’s radius were 0.5 and its x position were 10.5, it would be offscreen to the right. So, that target’s position would be set to -0.5 in order to wrap it around to the left side of the screen, while making it just offscreen so it doesn’t visually teleport, but smoothly move offscreen then onscreen again. Refer to the video provided in the base code for a visualization of this occurring.
  4. Check the target’s y position against 0 and the Arena’s height, accounting for radius. Then, perform the same behavior as you did for the x axis check above, but for the y axis.

B. Checkpoint: Moving Targets

Run IrateAvians.java from the terminal, and give it the command-line argument targetFile1.txt. If you’ve correctly implemented target.update(), you will see the targets move like they do in the example video of Irate Avians.


C. Updating the Bird

Implementing bird.update() will be fairly brief. This function should simply increment the bird’s x and y position by its x or y velocity multiplied by the input time step. Then, it should update the bird’s y velocity by subtracting from it the value 0.25 * timeStep to represent gravity dragging the bird down over time. We won’t be able to test this function until we’ve completed arena.update(), however.


D. Updating the Arena Part 1: Mouse-listening mode

arena.update() is a slightly more involved function than the other two updates. You’ve already implemented the portion that updates each Target. Now you will implement the portion that handles mouse-listening mode, and the portion that handles bird-flight mode. We have provided comments inside Arena.update() that outline what to do, but we will further explain the process here. When the game is in mouse-listening mode, it has to handle two possible states. The first is when the player is currently pressing a mouse button, indicated by PennDraw.mousePressed() returning true. In this state, the Arena should set mouseWasPressedLastUpdate to true, and invoke bird.setVelocityFromMousePos(). We will implement that function in the next section. The second state is when the player has just released the mouse button, indicated by PennDraw.mousePressed() returning false and mouseWasPressedLastUpdate being true. In this state, mouseWasPressedLastUpdate should be set to false, mouseListeningMode should be set to false, and the bird should decrement its throw counter. To decrement the bird’s throws, you should implement bird.decrementThrows().


E. Setting the Bird’s Initial Velocity

We will take a detour in our code for a moment in order to implement bird.setVelocityFromMousePos(), which will allow us to update the bird’s initial velocity in mouse-listening mode. This function should get the mouse x position and y position, and compute the vector from that position to the bird’s current position. Then, it should set the bird’s current velocity to equal that vector (remember, a vector has an x and y component).

xVel <- bird's X position - mouse's x position
yVel <- bird's Y position - mouse's y position

F. Checkpoint: Mouse-Listening Mode

If you run your program with the same command-line argument as before, you should now be able to click and drag, and a line representing your bird’s initial velocity should be drawn when you do so. Once you release the mouse, the game will become non-interactive as you have not yet implemented bird-flight mode in arena.update(). For this checkpoint to work, make sure that mouseListeningMode is set to true in the Arena constructor.


G. Updating the Arena Part 2: Bird-flight mode

The comments in arena.update() indicate where you should handle bird-flight mode: in the else statement provided in the base code of arena.update(). This portion of the function should do three things (some of which are functions we have not yet implemented):

  1. Call the bird’s update function
  2. Iterate over the array of Targets and call bird.testAndHandleCollision() on each Target. The eventual result of this will be to have the bird check if it currently overlaps each target, and decrease that target’s hit points by 1 if it does overlap.
  3. Check if the bird is offscreen by calling arena.birdIsOffscreen(), and if that evaluates to true, iterate over each Target, and if the target is hit this round, decrease its health and reset its hit value to false for the next round. Finally, you should be calling bird.reset() and setting mouseListeningMode to true. The eventual result of this will be to reset the game back to mouse-listening mode when the bird flies offscreen.

H. Handling Bird-Target Collision

First, we will implement bird.distance(), which is a helper function that computes the distance between two points (x1, y1) and (x2, y2). You will use the Pythagorean Theorem to implement this function to find the distance between two points. This function will be called from bird.testAndHandleCollision().

We will now implement bird.testAndHandleCollision(). The first thing this function should do is check if the input Target has more than zero hit points. If it does, then the bird is allowed to test for intersection against it, as the target still exists in the playing arena. The bird should now check if the distance between its center and the target’s center is less than the sum of its radius and the target’s radius. If this is true, then you should set the target’s hitThisShot value by calling the setter in the Target class.


I. Checking if the Bird is Offscreen

To implement arena.birdIsOffscreen(), you’ll need to perform three checks.

  1. Is the bird below the screen bottom? This will be true when the bird’s y position plus its radius are less than zero.
  2. Is the bird beyond the left side of the screen? This will be true when the bird’s x position plus its radius are less than zero.
  3. Is the bird beyond the right side of the screen? This will be true when the bird’s x position minus its radius are greater than the arena’s width.

We do not check if the bird is above the top of the screen since the bird is subject to gravity and will fall back down eventually, so the bird is allowed to continue to travel (and not reset) if it goes above the top of the screen.

If any of the three conditions described above are true, then this function should return true. If they are all false, then it should return false. Notably, you should be able to evaluate your function’s return value without using any if statements. If you use any if statements in this function, you will lose points.


J. Checkpoint: Testing Your Gameplay So Far

If you run your game with targetFile1.txt as input, you should be able to launch your bird by clicking and dragging, and have it decrease the number of throws remaining. However, targets don’t lose any hit points yet even when your bird intersects with them.


K. Using the Target’s isHit boolean

Implement target.isHit() and target.setHitThisShot() now. These are basic getters and setters for the isHit field.

We will now update bird.testAndHandleCollision so that it only modifies the input Target if the Bird has not yet collided with it in this bird-launch. To do this, we will make use of the hitThisShot boolean for the Target. The bird should set that Target’s boolean value to true by calling target.setHitThisShot() inside the if statement in your testAndHandleCollisions function.


L. Checkpoint: Testing Your Gameplay Again

When you run your game, you should now be able to properly decrease a Target’s hit points by at most 1 per bird launch. You’re almost done with the project at this point! All you have to do is check whether or not the game should end, which you’ll do in the next section.


4. Updating the Components


If you examine IrateAvians.java, you will see that its while loop continues as long as the Arena’s gameOver() function returns false. You will now implement this function so that your game can end in one of two ways: the player can win, or they can lose.


A. Winning the Game

You will have to check if the player has won the game, which occurs when all targets are broken. Implement arena.didPlayerWin(). This is a helper function that determines if the state of the game’s variables are such that the player has won. This function will return true if and only if the hit points of all Targets have reached 0. In all other scenarios, this function will return false.


B. Losing the Game

You will also have to check if the player has lost the game, which occurs when they are out of bird throws. Implement arena.didPlayerLose(). This is a helper function that returns true if the game is in mouse-listening mode and the bird’s remaining throw count has reached 0. We add the condition that the game must be in mouse-listening mode in order to allow the player to win on their very last throw; if the player lost immediately when the throw count reached 0, they’d never be able to launch their bird for their very last time as the game would immediately end without giving the bird a chance to collide with any remaining targets.


C. Ending the Game

Now that you’ve implemented the two helper functions, go ahead and implement arena.gameOver(). This function should return true when either the win or loss condition is true.

Now you must also implement arena.drawGameCompleteScreen(). This will clear the screen and draw a message that depends on whether the player has won or lost. If the player has won the game, it should draw text on screen that says “You Win!”. If the player has lost, it should draw text that says “You have lost…”.


D. Checkpoint: Testing the Game

Run your game as you have before. If you can reach both the win screen and the lose screen by playing, then congratulations! You have finished coding Irate Avians!


5. Extra Credit


Implement any of these features to earn extra points!

Detailed Drawing (1 points)

Draw the targets with more detail than just a plain PennDraw circle, using additional PennDraw shapes. Using images alone in place of circles will not count for extra credit.

Bouncing Bird (1-2 points)

Have the bird-ball bounce off of targets rather than moving through them. This will require you to reflect the bird’s velocity about the vector between the target’s center and the bird’s center. Feel free to look up how to reflect one vector about another. For an additional point, have a target change its velocity direction to match that of the bird’s bounce velocity, but in the opposite direction.

Force Fields (2 points)

Using PennDraw’s keyboard input capabilities, allow the user to hold the I or O key while clicking to place a force field that either pulls the bird-ball in (I) or pushes it out (O). The bird’s initial velocity should only be updated while the mouse is held when no keyboard key is pressed. Likewise, the user should be able to press the R key to reset the game to mouseListeningMode and reset the bird, should it get caught in the force fields. You should represent force fields as small PennDraw circles; orange for pull-fields and purple for push-fields.

Changing Target Appearance with Health (1 point)

As a target’s HP decreases, it should change appearance. For example, you might change its color, decrease its radius, or have more and more cracks appear on its surface.


6. Readme and Submission


A. Readme

Make sure to fill out the readme provided with the base code before you submit!


B. Submission

You need to upload Arena.java, Bird.java, Target.java, IrateAvians.java, and readme_avians.txt. Remember that your filenames should match the required names exactly, including capitalization. Make sure you submit the .java files, NOT the .class files.

If you used images for any of your drawing, you’ll need to submit those image files as well in order for your program to run correctly.