# CS21 Lab 11: SpaceBlaster

### Due Saturday night, April 27 Thursday night, May 2

Make sure all programs are saved to your cs21/labs/11 directory. Files outside this directory will not be graded.

$update21$ cd ~/cs21/labs/11

### Topics for this assignment

• Defining Classes
• Top-Down Design
• Writing test cases

## Overview

So far in this course we have used many different types of objects (Circle, Point, lists, strings, Zipcode, Channel, ...) but you haven't defined your own classes. The goal of this lab is to practice writing your own classes, with constructors, attributes, and methods.

In the file spaceblaster.py, you will implement a game to navigate a starship across space, destroying asteroids along the way. Be careful---hitting an asteroid will damage your starship, and it's the only one you have!

### High-level Specifications:

To complete this assignment, you need to implement two classes: Asteroid and Starship. Asteroids come in different sizes, move in different directions, and at different speeds. The starship comes equipped with a high-powered laser that can blast asteroids to smithereens, but only when the asteroid is directly in front of the starship. The laser is so powerful it can blow through entire asteroids, destroying everything in its path. The player scores one point for each destroyed asteroid; play continues until the starship collides with an asteroid.

• Start with the starship centered in a graphics window (800x800 is a good size, but use setCoords(0,0,100,100) to make it easier to compute positions)
• Control the starship by pressing the Up, Down, Left, or Right keys. Note: the GraphWin class has a method called checkKey() that will tell you what the last key pressed was. checkKey() returns a string containing what the last key pressed was since the last time it was called. If no key had been pressed since the last checkKey() call, it returns the empty string.
• The starship will continue moving in the direction of the last key pressed.
• Asteroids start off screen and move in a straight line. The direction of movement should be random.
• Fire the laser with the f key. When the laser is fired, all asteroids immediately to the right of the starship's laser will be destroyed; the player earns one point for each destroyed asteroid.
• The game ends when the starship collides with an asteroid, or when the user enters q.
• At the end of the game, the game should show the game state (e.g. "Game Over" if the player pressed q, or "You've been hit!" if the starship collides with an asteroid. Also display the player's score.

### Breaking things up into classes

This game may look hard to program at first glance, but by breaking the program up into classes and testing each class incrementally, it will be relatively strightfoward to implement.

## 1. The Asteroid Class

First, write and test the Asteroid class that has the methods shown below. We encourage you to test each method as you write them. For example, write the constructor (__init__), then write some test code to make a graphics window and create an Asteroid object. Make sure its size, color, and position look correct. Then add the move() method, and add more test code to move the asteroid in the right direction.

### Constructor __init__(self, win)

• initialize the color and size of the Asteroid. Make random choices for each.
• initialize the direction of movement dx, dy of the Asteroid. These should be random numbers between [-1,1]. Hint: the uniform(a,b) method in the random library returns a random number between a and b.
• initialize the starting position of the asteroid. Each asteroid should begin just out of the window frame. The starting position should be randomized, and you have some freedom in how you initilize this. However, which side of the screen the asteroid comes from should depend on the direction the asteroid is moving in. For example:
• if dx,dy> 0 then the asteroid can begin to the left or below the window -- otherwise the asteroid will never appear on the window.
• if dx>0 but dy<0 the asteroid should begin either to the left or above the window.
• if dx,dy < 0 the asteroid should begin to the right or above the window.
• if dx<0 but dy>0 the asteroid should begin to the right or below the window.
• In the GraphWin object passed into the constructor, create and draw a Circle object representing the Asteroid, of the radius, color, and starting position initialized above.

### move method: move(self)

• has no parameters (except for self)

• moves the Asteroid dx horizontally and dy vertically.

Note: this move() method will call the Zelle graphics move() method to move the Circle object. It's OK that it has the same name.

### offScreen method: offScreen(self)

• has no parameters (except for self)

• returns True if the Asteroid is completely off the screen. To see if the Asteroid is above the window, get the Y-coordinate of the Circle objects' center, subtract the radius, and see if it is above 100. Similarly, to see if the Asteroid is to the left of the window, get the X-coordinate of the Circle object's center, add the radius, and see if it is below 0. You'll also need to check to see if the Asteroid is below/to the right of the screen.

### destroy method: destroy(self)

• has no parameters (except for self)

• returns nothing, but changes the color of the Circle to signify it has blown up. Also briefly pause to give the user a chance to see the asteroid as it is blown up. Finally, undraw the Circle.

### getBoundary method: getBoundary(self)

• has no parameters (except for self)
• returns a clone of the Asteroid's Circle.

Here is a video showing a simple test of the Asteroid class. It creates an Asteroid and moves it until it is off-screen.

## 2. The Starship Class

Next, write and test the Starship class that has the methods shown below. This class will craete the starship object itself (a rectangle for the main body with two smaller rectangles to represent tail fins), as well as the methods needed to move the starship, fire the laser, and decide if it collides with an Asteroid.

### Constructor __init__(self, win)

First, implement the constructor method __init__(self, win). This method has an input parameter win, a GraphWin object. It should create a Rectangle object for the body of your starship, two smaller rectangle objects to represent tail fins, and draw the rectangles on the screen. In addition, the Starship has a speed and direction. These should be initially zero (the ship is at rest) and ("Right") respectively. Other specifications lie below:

• choose an appropriate size for the body e.g., four by ten.
• The tail fins should be smaller and centered on the upper left and lower left corners of the body.
• When the starship is created, the body should be centered at the center of the graphics window.

### move method: move(self, new_direction)

The move() method moves the Starship a small distance, depending on its current speed and direction. A Starship's direction should be one of "Up", "Down", "Left", "Right". When the move method is called, two steps must be taken. First, update the direction and speed. Second, move the starship in the new direction according to the updated speed.

• Updating direction/speed. The value of the input parameter new_direction should be a string with value either"Up", "Down", "Left", "Right", or "None".

• If the new direction is "None", then do not change the direction or speed.

• If the new direction is the same as the current direction, don't change the direction, but increase the speed by 10%. (This signifies the user is happy w/current direction and chooses to increase speed)

• If the new direction is different from the old direction, update direction to the new direction, and reset the speed to be 1. (changing direction reduces momentum almost to a complete stop!)

• Moving the starship. Once you've updated direction and speed, move the starship in the appropriate direction by an increment of speed. To move the starship, you'll need to call the Zelle Graphics move() method on each Rectangle comprising your starship.

### collide method: collide(self, asteroid)

The collide() method takes an asteroid object as input and returns True if the starship has collided with this asteroid. There could be two reasons why the starship and asteroid collide---either the starship moves into the asteroid, or vice versa. To make things a little simpler, we've created a helper function intersect(rect, circ) in a sb_utils library. The intersect() function has two input parameters: a Rectangle object and a Circle object. It returns True if the two objects intersect. To use this method, first import the cs21s19 library:

from cs21s19 import intersect

The Starship class contains three Rectangles; the Asteroid class is represented with a Circle. To see if the Starship collides with the Asteroid, check to see if any part of the starship intersects with the asteroid.

### fire method: fire(self, asteroids)

The fire() method has one input parameter in addition to self: asteroids, a list of Asteroid objects. The purpose of this method is to handle what happens when the Starship fires its laser gun. To represent the laser, draw a thin red Line from the right side of the Starship to the end of the graphics window.

To determine if an asteroid has been hit by the laser, check to see if two things occur: (i) the center of the asteroid is to the right of the Starship's center, and (ii) the starship's center's y-coordinate is within a radius of the y-coordinate of the asteroid.

If an asteroid does get hit, it is destroyed.

### Testing the Starship class

Use incremental development and testing as you program your Starship class. First, implement the constructor and make sure your Starship appears on the window. Then, implement the move method, save your code, and test it by calling move several times. Does the Starship move in the way you expect? Finally, create some Asteroid objects, and move the Starship until it collides with an Asteroid. Does the starship correctly detect collisions?

Here are some sample test videos of the Starship class:

## 3. Writing the main program

Your main program should create a starship and an initial set of asteroids, and then loop until either the starship collides with an asteroid or the user enters q.

Inside each iteration of the game loop, you should:

• check to see if the user entered a key. If so, either move the Starship, fire the startship's laser, or quit the game, depending on what the user entered. Hint: remember to use the GraphWin method checkKey(), not the python input() function.

• Then, loop through each asteroid. For each asteroid, you should
1. move the asteroid, then (2) check to see if it is destroyed or offscreen. If either happens, remove the asteroid from the list. You can use the pop method to remove an item from the list. e.g. to remove the i-th item from a list ls, call ls.pop(i).

Hint: If you want to loop through a list and remove some elements, it's helpful to loop through starting from the end of the list instead of the front. Otherwise, the pop(i) method will affect the indices of the list while you're looping through them, causing a syntax error.

• pause briefly (how long is up to you, but 0.2 seconds is a good start)
• Finally, each iteration of the while loop should with create a new asteroid with probability 1%. Hint: The random library contains a function random() which returns a value between 0 and 1. It has a one percent chance of being below 0.01.

Once the game ends, print a status message in the GraphWin object describing the user's score and why the game ended; e.g., user quit or collided with an asteroid.

## 4. Extra Challenges

There are many possible extra challenges for this assignment if you're inclined to make this lab truly out of this world. Here are some suggestions:

• Get creative with the design of the starship. Consider making the tail fins from objects other than rectangles, or change the shape of the main body.

• Draw an Asteroid using the Polygon object instead of a circle. How does this change the Asteroid class? How will you see if the asteroid collides with the Starship?

• Enable the Starship to change directon of movement and/or fire the laser to the left instead of always to the right.

• Make the game progressively harder as the game goes on; e.g., consider adding new Asteroids more quickly and/or making the asteroids bigger or faster as the game progresses.

### Submit

Once you are satisfied with your programs, fill out the questionnaire in QUESTIONS-11.txt. Then run handin21 a final time to make sure we have access to the most recent versions of your file.