Homework 8: Piper Game Part 2 Due Friday December 1, 11:59PM EST
This is a two part homework assignment. You’ll be working on the second part of the “Piper Game” this week (that’s what’s written on this page). Before you start this, you should be finished with homework 7. If you struggled with homework 7 and need the solution to start this homework, you may email me and I will send it to you after the due date for homework 7.
I am not providing a detailed recommended timeline for this assignment, as I expect that students will have varied schedules and some may choose to work on this during the break while others will not. However, you will be in very good shape to complete the assignment if you have completed the Player
section prior to the break, the Clam
section by Monday November 27th, and the Wave
section by Wednesday, November 29th. This will give you some time to debug any issues that come up regarding how everything is working together.
Background
In this homework you will be implementing the “Piper Game” using Object-Oriented Programming (OOP) techniques. The sand piper must race the clock to collect as many clams on the beach without getting wet. A screencast of a run of the game is shown below.
Getting Started
Start with your homework 7 code, and copy the code below in the bottom of the play_game
function (it is intentionally indented as it will be in your function). You’ll also need the images that you downloaded for homework 7: the piper and the clam. These images must be saved to the same directory as your program file.
# Initialize the pygame engine
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 14)
clock = pygame.time.Clock()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Piper's adventures")
time = 0
score = 0
# Main game loop
while time < max_time:
# Obtain any user inputs
event = pygame.event.poll()
if event.type == pygame.QUIT:
break
# Screen origin (0, 0) is the upper-left
# TODO: handle key presses
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
pass
elif event.key == pygame.K_LEFT:
pass
elif event.key == pygame.K_UP:
pass
elif event.key == pygame.K_DOWN:
pass
# TODO: Determine if Piper gathered more clams
# TODO: Update the position of the wave
# TODO: When the wave has reached its peak create new clams
# TODO: If the piper touched the wave the game is over...
# Draw all of the game elements
screen.fill([255, 255, 255])
# Render the current time and score
text = font.render('Time = ' + str(round(max_time - time, 1)), True, (0, 0, 0))
screen.blit(text, (10, 0.95 * SCREEN_HEIGHT))
text = font.render('Score = ' + str(score), True, (0, 0, 0))
screen.blit(text, (10, 0.90 * SCREEN_HEIGHT))
# Render next frame
pygame.display.update()
clock.tick(FPS)
# Update game time by advancing time for each frame
time += 1.0 / FPS
print('Game over!')
print('Score =', score)
pygame.display.quit()
pygame.quit()
Part 1: Player Updates
Updating __init__
The player will appear on screen as the piper image you previously downloaded. To do so create an image
instance variable in the Player
class assigned the value returned by the pygame.image.load
function. Why an instance variable? Since the image is created in one method (__init__
) and used in another render
, we need to store that image as an instance variable that persists between those method calls. Use the pygame.transform.scale
function to resize the image instance variable to match the size of its rectangle. Note that like functions on strings, pygame.transform.scale
does not modify its argument, it returns a new image. Since Player
inherits from Entity
it can access the rect
instance variable via self.rect
.
Implementing render
Player
should implement a render
method that has two parameters, self
and the PyGame display created in the play_game
function. It should use the blit
method on that display to draw its image
instance variable at the current location of the player’s rectangle.
You might want to use the inherited get_x()
and get_y()
methods from the Entity
class to get the location of the piper. The inputs to the blit
method are the image you want to render and the pair of coordinates (represented as a tuple) reprenting the location.
Some basic properties of the player’s __init__
and render
methods are tested by the autograder. I recommend submitting now to check your work.
Render your piper
Now go to the play_game
function. Invoke the render
method on the Player
that you created for homework 7, with screen
as the argument in the section with the comment “Draw all of the game elements”. Note that the order matters, we want to draw the background first, then the clams, then the piper, then the wave (so everything is properly layered), so make sure to render the Player
after the screen.fill
method. You should now be able to see the piper on the screen!
Moving around your piper
Next modify the event handling conditional to shift the piper based on the player’s key presses. Each key press should shift the piper by the amount specified in the STEP
constant. With that modification you should now be able to move the piper around the screen!
For full credit for this section, use methods from Entity
rather than updating the instance variables directly in the play_game
function.
Part 2: Clam
Updating __init__
Similar to Player
, the clam should appear as the image you downloaded earlier, loaded into an image
instance variable and scaled to match the size of its rectangle.
Implementing render
and rendering your clams
Similar to Player
implement a render
method to draw the clam image on the display at the current location of the clam’s rectangle.
Then, inside the game loop, write a loop that renders all of the clams that you created for homework 7. You should render those clams after the background but before the wave (so they are “covered” by the wave).
Some basic properties of the clam’s __init__
and render
methods are tested by the autograder. I recommend submitting now to check your work.
Gathering clams
The objective of the player is to gather clams. If the piper overlaps (collides) with a clam, that clam is collected. Inside your game loop implement another loop to check if the piper overlaps any of the clams (your collide
method in Entity
will be helpful here!). If so, increment the score by 1.
When the piper collects a clam, that clam should disappear. One way to do so is to add a boolean instance variable to the Clam
class that specifies whether that clam is visible
, and thus should be drawn (and is eligible to be collected). Modify the Clam.render
method to only draw the clam if visible and modify your “collection” loop to only collect visible clams. When a clam is collected it should be made invisible.
You should now be able to move the piper around the screen collecting all the clams (and increase your score accordingly!).
Part 3: Wave
Implementing render
and rendering your wave
Wave
should implement a render
method that has two parameters, self
and the PyGame display created in the play_game
function. It can use the pygame.draw.rect
function to draw its rectangle on the display. The first argument to draw
will be the display, the second the color ((0, 0, 255)
for blue) and the third the rectangle to draw.
Invoke your wave’s render
method with screen
as the argument in the section with the comment “Draw all of the game elements”. Make sure to render the wave last so it is “on top” of all the other elements.
Some basic properties of the wave’s render
method are tested by the autograder. I recommend submitting now to check your work.
Moving the wave
The wave will move back and forth in time (like a real wave!). You should model the x-coordinate of the left-side of the wave as
$x(t)=0.75\cdot w - 0.25\cdot w\cdot\sin(t)$
where t is the time
variable in the game loop and w is the SCREEN_WIDTH
. With this expression the left edge of the wave should oscillate between 0.5*SCREEN_WIDTH
and SCREEN_WIDTH
, i.e., the right half of the screen. Implement the above expression in the game loop to set the x-coordinate of the wave object. With this implemented, you should now be able to watch the wave oscillate back and forth!
Use math.sin
from the math
module to compute the sine of a number!
Game end condition
The piper does not like to get hit by a wave and so it is game over if the piper touches the water. Add a conditional to check if the piper has collided with the wave, and if so, terminate the game loop early (you can either use break
or use a boolean variable and add to the condition in the while
loop).
Washing up new clams
Every time the wave washes into shore it brings a new group of randomly distributed clams (i.e., the clams regenerate). Implement a conditional that when the wave is near its left-most terminus, i.e. the x-coordinate is less than 0.51*SCREEN_WIDTH
(recall our discussion of the imprecision of floating point values as to why we don’t check if the x-coordinate is equal to 0.5*SCREEN_WIDTH
), you replace your previous clams (some of which may have been collected, and some not) with a new group of clams (i.e., NUM_CLAMS
new clams). You could do so by overwriting the current list of clams.
Submitting
Submit your hw8.py
file on gradescope!
Most of this assignment is not autograded. You will need to test your code and make sure that it works properly on your computer by playing the game!