By now, you should have a good understanding of event-driven programming using the turtle
module. We will finish this chapter by pulling everything together to create a simple video game—our own version of the old Space Invaders game, in which aliens appear in the sky and you must shoot them with a laser cannon before they reach the ground. In our game, however, drones will appear in the sky rather than aliens.
FIGURE 13.4 shows a screenshot of the game in action. In the figure, notice the three main objects that we will implement to develop our video game. At the bottom center of the window is the laser cannon, which is the usual turtle triangle. The drones are the enemies, and the small circles are the bombs that have been shot from the laser cannon. These three things will become our main classes: LaserCannon
, Drone
, and Bomb
. Each of these classes inherits from the Turtle
class, so we can control each element of our game as a Turtle
. The different appearance of each object comes from using the turtle shape
method that allows us to use image files and even shapes you draw yourself to represent a turtle.
The next step is to identify the capabilities of each of our classes. The LaserCannon
must be able to do two things:
▶ Aim at Drone
s.
▶ Fire Bomb
s.
A Bomb
must be able to do three things:
▶ Move in the direction it was fired.
▶ Detect when it hits a Drone
.
▶ Detect when it disappears out of the window.
A Drone
must be able to do two things:
▶ Move from the top of the screen to the bottom.
▶ Detect when it reaches the bottom of the screen.
This high-level design is captured in FIGURE 13.5. As is standard UML practice, the abstract BoundedTurtle
and its two abstract methods, move
and remove
, are italicized. Also as is standard practice with UML, in the Drone
class, the static variable droneList
and the static method getDrones
are underlined.
LaserCannon
ClassLet’s look at the implementation of the LaserCannon
class in more detail (see LISTING 13.9). This class should look somewhat familiar to you after studying the bouncing turtles example from the previous section. The LaserCannon
will serve as our initial turtle, so it will create the window for all the other turtles that come later. In the _ _init_ _
method, we create a turtle and set up three callback functions: aim
, shoot
, and quit
. A mouse click causes the aim
method to be called. The aim
method turns the laser cannon toward the position of the cursor where the mouse was clicked. The shoot
method is called when the user presses the “s” key. The shoot
method is very short: It just creates a new Bomb
. As you will see later in Listing 13.12, the constructor for Bomb
takes care of the rest of the work.
BoundedTurtle
ClassThe BoundedTurtle
class is an abstract class that will be the superclass of both the Drone
and Bomb
classes. The difference between a regular Turtle
and a BoundedTurtle
is that the BoundedTurtle
knows when it is outside the window. When the turtle is outside the window, it disappears.
LISTING 13.10 shows the code for the BoundedTurtle
class. In the outOfBounds
method, the statement xPos, yPos = self.position()
is equivalent to two statements that get the x
and y
coordinates of the turtle separately. The turtle position
method returns a tuple, but the assignment statement unpacks the tuple and assigns the corresponding elements to the variables on the left-hand side. The outOfBounds
method returns True
or False
depending on whether the turtle has gone outside its boundaries.
The BoundedTurtle
class defines two abstract methods: move
and remove
. The Bomb
and Drone
classes will need to define these methods, and Python will use polymorphism to determine which class’s version of the method to call depending on the object used to call the methods.
Drone
ClassThe next class we will implement is the Drone
class. Our drones need to be able to move down the screen, and they need to be able to remove themselves from the game when they are hit by a Bomb
.
As in the bouncing turtle simulation, we will keep track of all drones so that when a bomb moves, we can test whether it has hit any of the drones. We will use a static variable droneList
to keep track of all the drones that are alive.
To keep things simple, we want only one method to add or remove drones from the droneList
. We will perform this task in the Drone
constructor. When a new drone is created, we append the new drone to the list. When a drone is hit by a bomb, we set the _ _alive
instance variable for that drone to False
.
Because the Bomb
class needs to know which drones it can blow up, it needs to get a list of all the “live” drones. To do this, the Drone
class will provide a static method called getDrones
to return a list of live Drone
s. Like static attributes, static methods are independent of objects. Static methods are useful when you have a method that belongs inside a class but does not apply to any instance of the class.
LISTING 13.11 shows the code for the Drone
class. Notice that no self
parameter appears in the formal parameter list of the static method getDrones
. Also notice that we use a new decorator, @staticmethod
, to create a static method. The @staticmethod
decorator tells the Python interpreter that there is no need to pass an instance variable as an implicit parameter to the function getDrones
. Instead, we call the method using the class name, as in Drone.getDrones()
, without needing an instance of the Drone
class.
Drone.getDrones
returns all the live drones by constructing a new list created from the instances of Drone
where _ _alive
is True
.
The other Python construct that is part of the getDrones
method is the list comprehension on line 7. List comprehension provides a concise way to create a list without using a for
loop with an append inside it. SESSION 13.4 illustrates some uses of list comprehensions. The first example shows it is legal to apply Python operators to the variable you are using to create the list.
The getDrones
method on lines 6–7 of Listing 13.11 uses a list comprehension and is equivalent to the getDrones
method shown here:
Notice two important points about the _ _init_ _
method. First, it is the only method allowed to modify the droneList
. This restriction ensures that we avoid the situation in which one callback method could be adding a drone to the list while another is trying to remove a drone from the list. This could happen if a bomb hit a drone at the same time that another drone was being created.
Second, notice that the Drone
’s remove
method sets the value of _ _alive
to False
and hides the turtle. In the constructor, we use a list comprehension to filter out the dead drones every time a new drone is created. This performance improvement keeps the list nice and short.
Also notice the conditional statement on lines 13–14. This statement checks whether the 'Drone.gif'
file has been loaded. If it has been loaded once, it is not necessary to load it again because the addshape
method just loads a list of shapes that works for all the turtles in the same window.
The move
method checks whether the drone is out of bounds. If the drone stays in bounds, the timer is reset so that the drone will move again in 200 milliseconds. If the drone is out of bounds, the drone is removed and the timer is not reset.
The remove
method marks the drone as dead and hides it so it is no longer seen. By setting
_ _alive = False
, the drone stays on the list (until another drone is created), but will not be included in any calculations involving bombs that are shot from the laser cannon.
Bomb
ClassOur final class, Bomb
, is no more complicated than the bouncing turtle. A bomb is created with an initial heading corresponding to the direction in which the cannon is pointing. Once the bomb is created, it continues moving in that direction. If it runs into a drone, the bomb explodes, the drone is marked as dead, and the bomb hides itself and ceases to move. LISTING 13.12 shows the Bomb
class. In the distance
method, we calculate the distance between the bomb and each Drone
using the math.dist
method (lines 24–27).
To put together all the pieces of the game and start things running, we will create one final class, DroneInvaders
, which will be the main application. LISTING 13.13 provides the code for the DroneInvaders
class. The main task of this class is to create a LaserCannon
object and to set up a timer to add drones to the playing field at regular intervals. These tasks are implemented in the play
method.
To play the game, we execute the two commands shown in SESSION 13.5. Of course, these commands should be added to the bottom of the .py
file with all of the class definitions so we can run everything with a single command. At the top of the .py
file, you should include import statements for turtle
, random
, math
, and abc
.
3.138.69.172