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.

Piper game

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!