Blowing Up Asteroids
In this lesson you learn what it takes to blow up an asteroid. For a Photon Torpedo to blow up an asteroid it must "hit" the asteroid. This means that the torpedo must be relatively close to the asteroid. We need to know the distance from the asteroid to the torpedo. This picture helps depict what we need to compute.
The distance between two objects, in this case a torpedo and an asteroid, can be drawn using a right triangle. If the torpedo is at (x1,y1) and the asteroid is at (x2,y2) then the distance between the two can be computed using the Pythagorean Theorem. This states that a^2 + b^2 = c^2. To compute the distance between two points you need to find the length of a and b, then square the two values and add them together. Then take the square root to solve for c. The distance between to points (x1, y1) and (x2, y2) is computed as
distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
So, to see if a torpedo has hit an asteroid we want to know if the distance is small enough that the torpedo and the asteroid have collided. Since both the torpedo and the asteroid have a radius, we can see if the sum of the two radii is greater than the distance between them. If it is, then the torpedo and the asteroid have collided.
Since the collision of two objects needs to be detected for more than just torpedoes and asteroids, we can write a function to check this for us. We'll call the function intersect and it will return True if two objects on the screen intersect and False otherwise.
This code should already be in your program just before the main function.
def intersect(object1,object2): dist = math.sqrt((object1.xcor() - object2.xcor())**2 + \ (object1.ycor() - object2.ycor())**2) radius1 = object1.getRadius() radius2 = object2.getRadius() # The following if statement could be written as # return dist <= radius1+radius2 if dist <= radius1+radius2: return True else: return False
Now, we can call that function to see if and asteroid and a torpedo have intersected (i.e. collided). To do this, for each torpedo we must ask if it collides with any asteroid. We can do this with what are called nested for loops.
This code goes in the play function. Since it uses the deadbullets list, add it at the end of the play function just before call to set the timer.
hitasteroids = [] for bullet in bullets: for asteroid in asteroids: if not asteroid in hitasteroids and \ intersect(bullet,asteroid): deadbullets.append(bullet) hitasteroids.append(asteroid) asteroid.setDX(bullet.getDX() + \ asteroid.getDX()) asteroid.setDY(bullet.getDY() + \ asteroid.getDY())
The code above does two things. It adds the hit asteroids to a list of hit asteroids and the torpedo to a list dead bullets. Then it also sets the dx and dy of the asteroid to a combination of the torpedo and asteroid's dx and dy. This is so the busted up asteroids move in a nice direction when they are created.
When we hit an asteroid two things must happen. The asteroid must bust apart if it is big enough or disappear if it is small and we must score some points. To keep track of the score we can keep a score variable and we'll want to display it. To display the score we need to create a label and put it in the GUI window. Here is the code to do that.
This code should go in the main function right after the frame.pack line.
# The first line below is already in your program!!!! frame.pack(side = tkinter.RIGHT,fill=tkinter.BOTH) # You need to add these lines. scoreVal = tkinter.StringVar()
scoreVal.set("0")
scoreTitle = tkinter.Label(frame,text="Score")
scoreTitle.pack()
scoreFrame = tkinter.Frame(frame,height=2, bd=1, \ relief=tkinter.SUNKEN)
scoreFrame.pack()
score = tkinter.Label(scoreFrame,height=2,width=20,\ textvariable=scoreVal,fg="Yellow",bg="black")
score.pack()
To bust up the asteroids, we must go through the list of hit asteroids and bust them up. This means that if the asteroid is big enough we'll delete the current asteroid and replace it with two smaller asteroids. Otherwise, the asteroid just disappears. At the same time we'll update the score.
When an asteroids breaks apart, the direction of each of the smaller asteroids is made to be perpendicular to the direction of the bullet and original asteroid it hit.
This code goes in the play function after building the list of hit asteroids.
for asteroid in hitasteroids:
try:
asteroids.remove(asteroid)
except:
print("didn't find asteroid in list")
asteroid.ht()
size = asteroid.getSize()
score = int(scoreVal.get())
if size == 3:
score += 20
elif size == 2:
score += 50
elif size == 1:
score += 100
scoreVal.set(str(score))
if asteroid.getSize() > 1:
dx = asteroid.getDX()
dy = asteroid.getDY()
dist = math.sqrt(dx ** 2 + dy ** 2)
normDx = asteroid.getDX() / dist
normDy = asteroid.getDY() / dist
asteroid1 = Asteroid(cv,-normDy,normDx, \ asteroid.xcor(),asteroid.ycor(), \ asteroid.getSize()-1)
asteroid2 = Asteroid(cv,normDy,-normDx, \ asteroid.xcor(),asteroid.ycor(), \ asteroid.getSize()-1)
asteroids.append(asteroid1)
asteroids.append(asteroid2)
If everything has gone well, you are now shooting asteroids and blowing them up. The score updates to reflect the number of points you've accumulated. The only problem is that at this point, the asteroids can't hurt the spaceship.
Have More Time?
It's possible to make the asteroids rotate with just a few lines of code. Rotating asteroids makes the game just a little more realistic and interesting.
Since an asteroid is a turtle, changing the heading of the asteroid has the effect of rotating it. In the __init__ method of the asteroid you can create a random number between 0 and 5 with a call to the random.random() function. This can be stored in the object by writing
self.rotation = random.random() * 5
Then, in the move function you'll want to rotate the asteroid by that amount by calling setheading. To do this you'll have to get the current heading and add the random rotation amount to it.
self.setheading(self.heading()+self.rotation)
What's Next?
Next time we'll make the program keep track of our lives and recognize when the game is over.