Adding Levels, Scorekeeping, and Theme Music


The game needs just a few more things to feel complete. For my final pass, I add levels—meaning that when a player destroys all of the asteroids on the screen, a new, more plentiful batch appears. I also add scorekeeping functionality and tense theme music to round out the game experience.

The Astrocrash08 Program

In addition to levels, scorekeeping, and theme music, I add some code that may be less obvious to the player, but is still important to complete the program. Figure 12.15 shows off my final version of the game.

click to expand
Figure 12.15: The final touches let the game continue as long as the player's Astrocrash skills allow.

Adding an Asteroid Class Variable

I make a few changes in the Asteroid class, all related to adding levels to the game. In order to change levels, the program needs to know when all of the asteroids on the current level are destroyed. So, I keep track of the total number of asteroids with a new class variable, total, which I define at the beginning of the class:

    total = 0 

Updating Asteroid's Constructor Method

In the constructor, I add a line to increment Asteroid.total:

         Asteroid.total += 1 

Updating Asteroid's die() Method

I make a few additions to Asteroid's die() method. First, I decrement Asteroid.total:

         Asteroid.total -= 1 

Next, I invoke Game's static method increase_score(), which increases the player's score based on the size of the asteroid (smaller asteroids are worth more than larger ones):

         Game.increase_score(30 / self.size) 

Game is a new class that contains a few class variables and static methods for game-wide information and functionality. I explain this new class later in the chapter.

Toward the end of Asteroid's die() method, I test Asteroid.total to see if all the asteroids have been destroyed. If so, I invoke Game's static method next_level(), which creates a new group of asteroids.

         # if all asteroids are gone, create next level         if not Asteroid.total:             Game.next_level(self.screen) 

Adding a Ship Class Variable

I make several additions to the Ship class. I create a class constant, VELOCITY_MAX, which I use to limit the maximum velocity of the player's ship:

     VELOCITY_MAX = 3 

Updating Ship's moved() Method

In Ship's moved() method, I cap the individual velocity components of a Ship object, dx and dy, using the class constant MAX_VELOCITY:

             # cap velocity in each direction             if new_dx < -Ship.VELOCITY_MAX:                 new_dx = -Ship.VELOCITY_MAX             if new_dx > Ship.VELOCITY_MAX:                 new_dx = Ship.VELOCITY_MAX             if new_dy < -Ship.VELOCITY_MAX:                 new_dy = -Ship.VELOCITY_MAX             if new_dy > Ship.VELOCITY_MAX:                 new_dy = Ship.VELOCITY_MAX 

I cap the ship's speed to avoid several potential problems, including the ship running into its own missiles.

Adding Ship's die() Method

When the player's ship is destroyed, the game is over. I add a die() method to Ship that invokes Collider's die() method and the new Ship game_over() method:

    def die(self):        """ Destroy ship and end the game. """        self.game_over()        Collider.die(self) 

Adding Ship's game_over() Method

I add the new game_over() method, which displays the message "Game Over" in the middle of the screen in big, red letters for about five seconds. After that, the game ends and the graphics screen closes.

    def game_over(self):        """ End the game. """        # show 'ame Over' for 250 mainloop cycles (at 50 fps that's 5 seconds)        games.Message(screen = self.screen,                       x = SCREEN_WIDTH/2, y = SCREEN_HEIGHT/2,                       text = "Game Over", size = 90, color = color.red,                       lifetime = 250, after_death = self.screen.quit) 

The Game Class

The Game class is a new class that handles certain game-wide functionality such as creating the player's ship, creating the object for the player's score, increasing the player's score, and creating new levels of asteroids.

The first thing I do in the class is define two class variables:

 class Game(object):     """ The game itself. """     level = 1     sound = games.load_sound("level.wav") 

level is the current game level. sound is for the sound effect that indicates the player has reached a new level.

Next, I write the static method create_ship(), which creates a Ship object on the screen passed to screen at the coordinates passed to x and y:

     def create_ship(screen, x, y):         Game.ship = Ship(screen = screen, x = x, y = y)     create_ship = staticmethod(create_ship) 

Next, I define the static method create_score(), which creates two Game class variables, score_value and score_text. score_value represents the value of the player's score. score_text is a Text object that is displayed on the graphics screen to show the player's score.

     def create_score(screen):         Game.score_value = 0         Game.score_text = games.Text(screen = screen, x = 550, y = 20,                                   text = "Score: "+ str(Game.score_value),                                   size = 25, color = color.white)     create_score = staticmethod(create_score) 

Hopefully, the player's score won't remain zero for too long. So, I define another static method, increase score(), which updates the player's score:

     def increase_score(points):         """ Increase a player's score. """         Game.score_value += points         Game.score_text.set_text("Score: "+ str(Game.score_value))     increase_score = staticmethod(increase_score) 

I add to Game.score_value the number of points passed to the method. Then, I update Game.score_text so that the new score is displayed.

The last method I define in the class is the static method next_level(), which creates the next level full of asteroids. To begin the method, I define a constant, BUFFER, which represents the amount of space I need to preserve around the player's ship when I create new asteroids. By setting the constant to 200, I'm saying that any new asteroid must be created at least 200 pixels away from the player's ship. I do this so that a new asteroid isn't created right on top of the player's ship.

     def next_level(screen):         """ Create the next level. """         # amount of space around ship to preserve when creating asteroids         BUFFER = 200 

Next, I play the sound for advancing to the next level. I don't play the sound for the first level, which is the start of the game.

         # play new level sound (except at first level)         if Game.level > 1:             Game.sound.play() 

Next, I create the asteroids for the new level. Each level starts with the number of asteroids equal to the level number. So, the first level starts with only one asteroid, the second with two, and so on.

         # create new asteroids         for i in range(Game.level):             # pick random x and y at least BUFFER distance from ship's x and y             while True:                 x = random.randrange(SCREEN_WIDTH)                 y = random.randrange(SCREEN_HEIGHT)                 x_dist = abs(Game.ship.get_xpos() - x)                 y_dist = abs(Game.ship.get_ypos() - y)                 if x_dist + y_dist > BUFFER:                     break             # create the asteroid             Asteroid(screen = screen, x = x, y = y, size = 3) 

The real work of the method is to generate random x-and y-coordinates that put a new asteroid at least BUFFER pixels from the player's ship. I create an intentional infinite loop with while True. In the loop, I generate random x- and y-coordinates for the next asteroid. Then, I calculate the distance between the randomly generated x-coordinate and the x-coordinate of the player's ship by using the built-in abs() method, which returns the absolute value of a number. I do the same for the y-coordinate. Then I add the two distances together. If the result is greater than BUFFER, I've found a safe location for a new asteroid and break out of the loop. Otherwise, the loop continues and I try with another randomly generated point on the graphics screen. Once I generate a new point that's far enough away from the ship, I create a large asteroid at that location.

Finally, I display the level number the player has reached and increment the game level:

       # display level number       games.Message(screen = screen,                      x = SCREEN_WIDTH / 2, y = 50,                      text = "Level "+ str(Game.level),                      size = 40, color = color.yellow, lifetime = 150)       Game.level += 1    next_level = staticmethod(next_level) 

The main() Function

I replace the main part of the program with a new function, main(). I begin the function by loading the nebula background image onto the graphics screen:

 # main def main():     my_screen = THE_SCREEN     nebula_image = games.load_image("nebula.jpg")     my_screen.set_background(nebula_image) 

Then, I load and loop the game's theme music:

     games.load_music("theme.mid")     games.play_music(-1) 

By passing -1 to games.play_music(), I ensure the theme will loop forever, giving the game an never-ending soundtrack.

Then, I cut all of the code that created the asteroids and the player's ship. I replace it with code that invokes Game's static methods to create the player's ship, the player's score, and the first level of asteroids:

     Game.create_ship(screen = my_screen, x = SCREEN_WIDTH / 2, y = SCREEN_HEIGHT / 2)     Game.create_score(screen = my_screen)     Game.next_level(screen = my_screen) 

Starting the Program

Last, but not least, I kick off the whole program by calling main():

 # start program main() 




Python Programming for the Absolute Beginner
Python Programming for the Absolute Beginner, 3rd Edition
ISBN: 1435455002
EAN: 2147483647
Year: 2003
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net