This code example should teach nearly nothing new but instead put all the wisdom from the previous steps together to create a little game. In this catch-the-thief-game up to 2 players control sprites1) (the Pygame snake and the babytux bird) and are tasked to help the police (blue circle with white “P”) to catch a thief (red triangle with transparent “T”). To make the game interesting (and difficult), the police moves toward the middle position between the both players (this position is marked with a black cross). The thief moves randomly.
All sprites are controlled indirectly, like ships in water (or spaceships in space): The player point the mouse or press the cursor - keys <key>←</key> <key>→</key> <key>↑</key> <key>↓</key> in the direction the sprite should move. The sprite will eventually move into this direction but will continue to do so even if it gets a new order. Bouncing off the screen edge will change the direction. If no orders are given, all sprites except the thief will eventually come to an halt because of friction. The player can use an emergency break and force his sprite to halt by pressing <key>ENTER</key> or pressing the left mouse button. Instead of the cursor keys, the player can also use <key>w</key>, <key>a</key>, <key>s</key>, <key>s</key>, <key>CTRL</key>.
If no second player is present, one skilled player can try to control both sprites with mouse & keyboard.
In the code, those pygame surfaces are called sprites. Please note that pygame has it's own, very sophisticated pygame.Sprite class (see the next steps) but those pygame sprites do not appear in this code example.
This code example define many functions with def. Most important are the last two lines, basically the only lines outside a function:
if __name__ == "__main__": play_the_game()
Those lines control if the game is started directly or imported from another program (like a program that manages a highscore list). If the programm is started directly, the internal variable __name__ has the value "__main__". In this case, the game start itself by calling the play_the_game() function.
Because the loaded background image is larger than the pygame display screen resolution, the background image is resized using this command:
background = pygame.transform.scale(background, (screen.get_width(), screen.get_height()))
After defining some useful functions for later use the game creates several pygame surfaces: screen and background and several sprites: bird, snake, police, cross, thief where each sprite get the variables x, y, dx, dy with the spritename as prefix. X and Y control the position while dx and dy control the movement speed.
Inside the mainloop, all sprites are cleaned, updated (player commands are computed here) and finally blittedn on the screen again. The mainloop switch into a special GameOver - mode after the playtime runs out. While in the GameOver mode, no sprites are displayed and only the score is rendered for the players information.
The cleanblit function calls and a lot of headache could all be ignored and instead a simple line of
#screen.blit(background, (0,0)) # not GameOver
would do the job. However, framerate would drop when using large screen resolutions.
Before blitting (drawing) the sprites, a check is made if the police sprite is near enough at the thief sprite to credit the players with a score point. There are some code lines to see if the pygame sound mixer is currently busy and if he is silent the spring.wav sound is played. Also because at each catching of the thief the screen is filled with a random colour, the code line checks if this frame (mainloop cycle) is the first of “normal” activity (no thief is catched) right after an “catch” frame. If yes, the old original background is blitted on the screen.
catch_in_last_frame = catch_in_this_frame # save old catch info catch_in_this_frame = False if (distx < police.get_width() /2) and (disty < police.get_height()/2): catch_in_this_frame = True points += seconds screen.fill(randomcolour()) if not pygame.mixer.get_busy(): spring.play() # only play this sound if mixer is silent at the moment else: # no catch this time if catch_in_last_frame: screen.blit(background, (0,0)) # restore backgrounnd
<note>trivia: This games is inspired by the Austrian tv cult series Kottan. As the content and visuals of this series are -sadly- neither public domain nor creative-commons licensed i use free graphics. But on your own computer, you can replace the sprite graphics with some characters of your favorite TV show.</note>
To run this example you need:
|013_catch_the_thief.py|| || Download the whole Archive with all files from Github:
| wien.jpg ||
| snake.gif ||
| babytux.png |
from wikimedia commons
| spring.wav |
from Battle of Wesnoth
| time_is_up_game_over.ogg |
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/013_catch_the_thief.py
click reload in your browser if you see no code here: