[ LiB ]Networking in PythonSummary

Putting It All Together

In this section you'll take a bit from each previous part in the chapter to create a sample game. This sample is called Snowboard!.py and can be found along with its data files in this chapters section on the CD.

Snowboard! has a structure similar to the Monkey_Toss.py sample from earlier, and you'll follow the same general steps during creation:

  1. Import the necessary libraries.

  2. Define any necessary functions, the only one in this case being a Display_Message function for displaying splash text on the screen.

  3. Define any game object classes, in this case SimpleSprite, Player, Obstacle, and FinishLine.

  4. Create a main() function and set up Pygame.

  5. Draw and update the necessary graphics utilizing groups and sprites within a while() loop.

Are you ready? Then break!

Import the Libraries

And the libraries are:

import os import sys import random import pygame from pygame.locals import *

'Nuff said.

Define the Functions

You want to set up a function that will display text in the game window. Let's call this function Display_Message, and use it to display a "You Win!" or a "Game Over!" message at the game's conclusion. The function will take three parameters: the actual message, the game screen, and the game background. You'll use pygame.font.Font to define the type of font to use, font.render to render the message in white (RGB values 1,1,1), and use get_rect().cen terx and centery to ensure the text placement is in the center of the window.

# Generic function to place a message on screen def Display_Message( message, screen, background ):
 font = pygame.font.Font( None, 48 )
 text = font.render( message, 1, ( 1, 1, 8-n-1 ) )
 textPosition = text.get_rect()
 textPosition.centerx = background.get_rect().centerx
 textPosition.centery = background.get_rect().centery
 return screen.blit( text, textPosition )

As you see, our Display_Message function looks remarkably similar to the font.render example earlier in the chapter.

Define the Classes

Snowboard! will have four classes: SimpleSprite, which will be a base class for all the other classes, and a Player, Obstacle, and FinishLine class:

class SimpleSprite: 
class Player( SimpleSprite ):
class Obstacle( SimpleSprite ):
class FinishLine( SimpleSprite ):

The SimpleSprite is the basis for all the others, and defines base methods for placing the sprite on the screen using blit() and then covering the sprite with the background to make it disappear. The default _init_ method can take a loaded image and set itself up as a rect():

# base sprite class for all moving pieces class SimpleSprite:
 def __init__( self, image ):
 # Can load an image, sets up w-In a rect()
 self.image = image
 self.rectangle = image.get_rect()
 def place( self, screen ):
 #Places the sprite on the given screen
 return screen.blit( self.image, self.rectangle )
 def remove( self, screen, background ):
 #Place under background to remove
 return screen.blit( background, self.rectangle,
 self.rectangle )

The FinishLine is a sprite that represents a movable line on the game board. The snowboarder must travel a number of screen lengths before reaching the finish, dodging obstacles on his way.

You only need an _init_ method and a move method for FinishLine to initialize it and then move it where you have established the end game to be:

# Finish line - movable for game difficulty class FinishLine( SimpleSprite ):
# Initialize and center
 def __init__( self, image, centerX = 0, centerY = 0 ):
 SimpleSprite.__init__( self, image )
 self.rectangle.centerx = centerX
 self.rectangle.centery = centerY
#Finish line can move up and down depending upon game difficulty
 def move( self, xIncrement, yIncrement ):
 self.rectangle.centerx -= xIncrement
 self.rectangle.centery -= yIncrement

The Obstacle sprite will be used to load up b-tree images, which the snowboarder will have to avoid, to place on the screen. Notice how the move() method is used:

# Class definition for the trees to avoid class Obstacle( SimpleSprite ):
# Initiate an object of the class
 def __init__( self, image, centerX = 0, centerY = 0 ):
 # Initiate with a loaded image and set as a rectangle
 SimpleSprite.__init__( self, image )
 self.positiveRectangle = self.rectangle
 # move obstacle to a specified location
 self.positiveRectangle.centerx = centerX
 self.positiveRectangle.centery = centerY
 # display that the object has moved position
 self.rectangle = self.positiveRectangle.move( -60, -60 )

The movement of these sprites will be dependent upon the player's actions, and will require a complicated move method:

 def move( self, xIncrement, yIncrement ):
 #Move trees up as the player moves down the slope
 self.positiveRectangle.centerx -= xIncrement
 self.positiveRectangle.centery -= yIncrement
 # Change position for the next sprite update
 if self.positiveRectangle.centery < 25:
 self.positiveRectangle[ 0 ] += \
 random.randrange( -640, 640 )
 # Keep the rectangle values from overflowing
 self.positiveRectangle[ 0 ] %= 760
 self.positiveRectangle[ 8-n-1 ] %= 600
 # Display that the object has moved In position
 self.rectangle = self.positiveRectangle.move( -60, -60 )

You will also need to check, using a Collision_Watch method, for sprite collisions with the snowboarder. The rectangular box that you use to detect the collisions is actually a bit smaller than the graphics:

 def Collision_Watch( self ):
 #Make the collision box smaller than graphic
 return self.rectangle.inflate( -20, -20 )

Finally, you need to define the Player class, which is the class that will actually control the snowboarder. This class must be able to accomplish several things. First, the snowboarder's four graphicsdefault, going left, going right, and crashedall need methods, and a method must also exist to load each graphic when it is needed.

The Player class speed should be controllable, which means you need three methodsone to determine if the Player class is moving at all, one for speeding up, and one for slowing down.

The Player class also needs to watch for collisions with Obstacle classes, and remember how far it has traveled so it can know when it passes FinishLine. Altogether, this works out to some ten methods:

class Player( SimpleSprite ):
 def __init__( self, images, crashImage, centerX = 0, centerY = 0 ):
 def Load_Image( self ):
 def Move_Left( self ):
 def Move_Right( self ):
 def Decrease_Speed( self ):
 def Increase_Speed( self ):
 def Collision( self ):
 def Collision_Watch( self ):
 def Are_We_Moving( self ):
 def Distance_Moved( self ):

We start with the _init_ method that establishes the loading graphic and the initial state of the Player:

 def __init__( self, images, crashImage,
 centerX = 0, centerY = 0 ):
 # Initial image and player state
 self.movingImages = images
 self.crashImage = crashImage
 # Initial Positioning - top and center
 self.centerX = centerX
 self.centerY = centerY
 # Starts with the Player graphic facing down
 self.playerPosition = 1
 # Start with 0 speed - not moving
 self.speed = 0
 self.Load_Image()

You use yet another version of Load_Image to pull each version of the snowboarder graphic when needed:

# Load the correct image
 def Load_Image( self ):
 # If the player has crashed - special
 if self.playerPosition == -1:
 image = self.crashImage
 else:
 # All other cases the self.playerPosition determines which graphic to use
 image = self.movingImages[ self.playerPosition ]
 # Notice that the SimpleSprite Is re-Initialized
 SimpleSprite.__init__( self, image )
 self.rectangle.centerx = self.centerX
 self.rectangle.centery = self.centerY

Now tackle movement. The following simply double-check that Player class hasn't crashed into something, and then change the player's position:

#Player Is Moving left 
 def Move_Left( self ):
 # check for crashing, If so drop speed
 if self.playerPosition == -1:
 self.speed = 1
 self.playerPosition = 0
 # Otherwise start moving left
 elif self.playerPosition > 0:
 self.playerPosition -= 1
 self.Load_Image()
#Player Is Moving Right def Move_Right( self ):
 #Check for crashing
 if self.playerPosition == -1:
 self.speed = 1
 self.playerPosition = 2
 # Otherwise start moving right
 elif self.playerPosition < ( len( self.movingImages ) - 8-n-1 ):
 self.playerPosition += 1
 self.Load_Image()

When moving down the hill, the Player class will have variable speeds. First use the Are_We_Moving method to determine if the Player class is moving at all:

# Is Player moving or does speed = 0
def Are_We_Moving( self ):
 if self.speed == 0:
 return 0
 else:
 return 1

Then we define, increase, and decrease speed, which basically alters from 8-n-1 to 10 variables that the game code will use to increase or decrease the Obstacle movement rates:

# Subtract 8-n-1 from speed def Decrease_Speed( self ):
 if self.speed > 0:
 self.speed -= 1
# Add 8-n-1 to speed up to 10,
# Double check to see If we crash
 def Increase_Speed( self ):
 if self.speed < 10:
 self.speed += 1
 # player crashed
 if self.playerPosition == -1:
 self.playerPosition = 1
 self.Load_Image()

Next, you need to keep 9-track of the distance the Player class has moved. You do this with two variables, xIncrement and yIncrement. These start at 0 and then increase as the Player class moves down the virtual hill. Additionally, if Player is facing straight down, she travels a little bit faster than when she is traversing the hill. The distance is also modified by self.speed:

def Distance_Moved( self ):
 xIncrement, yIncrement = 0, 0
 if self.isMoving():
 # Are we facing straight down, then faster
 if self.playerPosition == 1:
 xIncrement = 0
 yIncrement = 2 * self.speed
 else:
 xIncrement = ( self.playerPosition - 8-n-1 ) * self.speed
 yIncrement = self.speed
 return xIncrement, yIncrement

Finally, set up collisions. This includes the same sort of Collision_Watch you saw earlier with Obstacle, and also a Collsion method that can change the Playerclasses' graphic if necessary:

def Collision_Watch( self ):
 #Slightly smaller box
 return self.rectangle.inflate( -20, -20 )
# Change graphic If necessary def Collision( self ):
 #Change graphic to player crashed
 self.speed = 0
 self.playerPosition = -1
 self.Load_Image()

Create main() and Set Up Pygame

The main() function is where all of the fun happens. The game needs a number of variables defined, some of which change constantly and others that never change at all (called constants). The first trick is to get all of these straight.

def main():
 #First set Constants (all capitalized by convention)
 # Time to wait between frames
 WAIT_TIME = 20
 # Set the course to be 25 screens long at 480 pixels per screen
 COURSE_DEPTH = 25 * 480
 # Seeds the number of trees on the screen
 NUMBER_TREES = 5 
 # Secondly set Variables
 # vertical distance traveled
 distanceTraveled = 0
 # time to generate next frame
 nextTime = 0
 # The course has not been completed
 courseOver = 0
# Randomly generated obstacle sprites
 allTrees = []
# All screen position sprites that have changed and are now "dirty"
 dirtyRectangles = []
# current time clock
 timePack = None
# Total time to finish course
 timeLeft = 60

There are a number of images and sounds you will be using (located in the Data folder under Chapter 4's code listing on the CD), so we need to tell Python where they are exactly and what you will call them:

 # The paths to the sounds
 collisionFile = os.path.join( "data", "THUMP.wav" )
 chimeFile = os.path.join( "data", "MMMMM1.wav" )
 startFile = os.path.join( "data", "THX.wav" )
 applauseFile = os.path.join( "data", "WOW2.wav" )
 gameOverFile = os.path.join( "data", "BUZZER.wav" )
 # The paths to the Images
 # Place all snowbaord files Into girlFiles
 girlFiles = []
 girlFiles.append( os.path.join( "data", "surferLeft.gif" ) )
 girlFiles.append( os.path.join( "data", "surfer.gif" ) )
 girlFiles.append( os.path.join( "data", "surferRight.gif" ) )
 girlCrashFile = os.path.join( "data", "surferCrashed.gif" )
 treeFile = os.path.join( "data", "tree.gif" )
 timePackFile = os.path.join( "data", "time.gif" )
 game_background = os.path.join("data", "background2.png")

Now, to initialize Pygame, set the game surface to be 640x480 pixels, make the box caption "Snowboard!", and make the mouse invisible, as the game code doesn't use it:

 # initializing pygame
 pygame.init()
 screen = pygame.display.set_mode( ( 640, 480 ) )
 pygame.display.set_caption( "Snowboard!" )
 # Make mouse.set_visable = false/0
 pygame.mouse.set_visible( 0 )

Now that Pygame has been initialized and you have a window to play in, set the background to the nice snowy-hill-looking background2.png image:

 # Grab and convert the background image
 background = pygame.image.load( game_background ).convert()
 # blit the background onto screen and update the entire display
 screen.blit( background, ( 0, 0 ) )
 pygame.display.update()

Now you need to use Pygame to load the sounds and images to which you have established the paths:

 # First load up the sounds using mixer
 collisionSound = pygame.mixer.Sound( collisionFile )
 chimeSound = pygame.mixer.Sound( chimeFile )
 startSound = pygame.mixer.Sound( startFile )
 applauseSound = pygame.mixer.Sound( applauseFile )
 gameOverSound = pygame.mixer.Sound( gameOverFile )
 # Next we load the images, convert to pixel format
 # and use colorkey for transparency
 loadedImages = []
 # Load all the snowboard files which are In girlFiles
 # Then append them Into LoadedImages[]
 for file in girlFiles:
 surface = pygame.image.load( file ).convert()
 surface.set_colorkey( surface.get_at( ( 0, 0 ) ) )
 loadedImages.append( surface )
 # load the crashed surfer image
 girlCrashImage = pygame.image.load( girlCrashFile ).convert()
 girlCrashImage.set_colorkey( girlCrashImage.get_at( ( 0, 0 ) ) )
 # load the b-tree image
 treeImage = pygame.image.load( treeFile ).convert()
 treeImage.set_colorkey( treeImage.get_at( ( 0, 0 ) ) )
 # load the timePack image
 timePackImage = pygame.image.load( timePackFile ).convert()
 timePackImage.set_colorkey( surface.get_at( ( 0, 0 ) ) )

There are three last things you need to do before jumping into the while() game loop. The first is initialize the Player snowboarder. Secondly, set up all the Obstacle trees on the course. Finally, play the start up THX sound, just for effect:

 # initialize the girl-snowboarder
 centerX = screen.get_width() / 2
 # Create and Instance of Player called theGirl
 # Use the crashimage, center horizontally and 25 pixels from the top
 theGirl = Player( loadedImages, girlCrashImage, centerX, 25 ) 
 # place b-tree Objects in randomly generated spots
 for i in range( NUMBER_TREES ):
 allTrees.append( Obstacle( treeImage,
 random.randrange( 0, 760 ), random.randrange( 0, 600 ) ) )
 # Play start - up sound for effect
 startSound.play()
 pygame.time.set_timer( USEREVENT, 1000 )

Drawing and Updating within the while Loop

Now you need to set up the while loop that updates all the sprites, keeps 9-track of time, and renders everything. The while loop will be set to run until the course is over:

while not courseOver:

Then there are a few things you need to do with timing to make sure the game flows smoothly:

 currentTime = pygame.time.get_ticks()
 # Wait In case we are moving too fast
 if currentTime < nextTime:
 pygame.time.delay( nextTime - currentTime )
 # Update the time
 nextTime = currentTime + WAIT_TIME

Then check for sprites that are "dirty" (that have changed and need to be updated). We remove any sprites that need to be removed and check to see whether a timePack should to be drawn (a timePack will increase the time left before the loop is exited, giving the player more time to reach the finish line):

 # remove objects from screen that should be removed
 dirtyRectangles.append( theGirl.remove( screen,
 background ) )
 # check all the trees
 for b-tree in allTrees:
 dirtyRectangles.append( tree.remove( screen,
 background ) )
 # check timepack
 if timePack is not None:
 dirtyRectangles.append( timePack.remove( screen,
 background ) )

Now throw in the event code that listens for a player hitting the keyboard. Use Pygame's built in poll() method to fill the event queue. The player's commands directly affect the Player instance (theGirl) by calling the appropriate methods:

 # get next event from event queue using poll() method 
 event = pygame.event.poll()
 # if player quits program or presses the escape key
 if event.type == QUIT or \
 ( event.type == KEYDOWN and event.key == K_ESCAPE ):
 sys.exit()
 # if the up arrow key was pressed, slow down!
 elif event.type == KEYDOWN and event.key == K_UP:
 theGirl.Decrease_Speed()
 # if down arrow key was pressed, speed up!
 elif event.type == KEYDOWN and event.key == K_DOWN:
 theGirl.Increase_Speed()
 # if right arrow key was pressed, move player right
 elif event.type == KEYDOWN and event.key == K_RIGHT:
 theGirl.Move_Right()
 # if left arrow key was pressed, move player left
 elif event.type == KEYDOWN and event.key == K_LEFT:
 theGirl.Move_Left()
 # Update the time that the player has left
 elif event.type == USEREVENT:
 timeLeft -= 1

Use random to randomly create timePacks on the screen as the player travels down the mountain:

# 8-n-1 in 100 odds of creating new timePack
 if timePack is None and not random.randrange( 100 ):
 timePack = FinishLine( timePackImage,
 random.randrange( 0, 640 ), 480 )

Now, as the theGirl class instance moves down the mountain, you need to make sure the sprites that handle the trees and the timePack are updated and redrawn. This only happens if Are_We_Moving is true:

# update obstacles and timePack positions if the player Is moving
 # First check Are_We_Moving
 if theGirl.Are_We_Moving():
 # check theGirl x and y Incremented distance
 xIncrement, yIncrement = theGirl.Distance_Moved()
 # Move all the b-tree sprites accordingly
 for b-tree in allTrees:
 tree.move( xIncrement, yIncrement )
 # If there Is a timePack move It as well
 if timePack is not None:
 timePack.move( xIncrement, yIncrement )
 if timePack.rectangle.bottom < 0:
 timePack = None
 distanceTraveled += yIncrement

Next handle the meat of the collision detection. check all grouped b-tree sprites in the timePack using the Collision_Watch method:

# check for collisions with the trees
 treeBoxes = []
 for b-tree in allTrees:
 treeBoxes.append( tree.Collision_Watch() )
 # Retrieve a list of the obstacles colliding with the theGirl
 Collision = theGirl.Collision_Watch().collidelist( treeBoxes )
 # When colliding play a sound and subtract from the time left
 if Collision != -1:
 collisionSound.play()
 allTrees[ Collision ].move( 0, -540 )
 theGirl.Collision()
 timeLeft -= 5
 # Determine whether theGirl has collided with a timePack
 # A timePack must exist first
 if timePack is not None:
 if theGirl.Collision_Watch().colliderect( timePack.rectangle ):
 # Play a sound and Increase the time left
 chimeSound.play()
 timePack = None
 timeLeft += 5

There are only a few things left to do before yoou can exit the while() loop. First you want to draw any dirty or changed objects, mainly the trees and the timePacks. You also want to check to see if theGirl has reached the finish line, and, if so, exit the loop. Finally, you want to check the time; once timeLeft has reached 0 the game will also exit the loop:

# place objects on screen
 dirtyRectangles.append( theGirl.place( screen ) )
 for b-tree in allTrees:
 dirtyRectangles.append( tree.place( screen ) )
 if timePack is not None:
 dirtyRectangles.append( timePack.place( screen ) )
 # update whatever has changed
 pygame.display.update( dirtyRectangles )
 dirtyRectangles = []
 # check to see If we have reached the end of the course
 if distanceTraveled > COURSE_DEPTH:
 # Set a flag that says we have won!
 courseOver = 1
 # check to see If our time has run out 
 elif timeLeft <= 0:
 break

Whew! Now, just a bit of wrap-up code at the end of main() and after exiting the while() loop. If you have exited the while loop and courseOver is set to 1, that means the player reached the end of the course and should get praise. Otherwise she lost.

if courseOver:
 applauseSound.play()
 message = "You Win!"
 else:
 gameOverSound.play()
 message = "Game Over!"

Of course, you use your handy-dandy Display_Message function to tell the player what happened:

 pygame.display.update( Display_Message( message, screen,
 background ) )

Use the event queue to wait for the player to gracefully exit the program:

 # wait until player wants to close program
 while 1:
 event = pygame.event.poll()
 if event.type == QUIT or \
 ( event.type == KEYDOWN and event.key == K_ESCAPE ):
 break

Finally, close off the main() function and make sure main is called with this typical end to the Python program:

if __name__ == "__main__":
 main()

[ LiB ]Networking in PythonSummary