Python Arena
Step 3 of 3

A small game

Games ask one question all the time: do these two things touch? Does the robot touch the coin, does the monster touch the robot? pygame has a type for the question: pygame.Rect, a rectangle with a position and a size.

import pygame

robot = pygame.Rect(100, 150, 40, 40)
print(robot)
print(robot.x, robot.y, robot.width, robot.height)
print(robot.right, robot.bottom)
print(robot.center)
robot.x += 10
print(robot.topleft, robot.right)
Output
Rect(100, 150, 40, 40)
100 150 40 40
140 190
(120, 170)
(110, 150) 150

right is x + width, the first x past the rectangle (it covers x from 100 to 139), and bottom the same for y. center is its middle point. Change x or y and the rectangle moves: right, center and the rest follow. You can also set them: robot.center = (160, 120) moves the rectangle so that its center is there.

An image gives a rectangle of its own size with get_rect: robot_image.get_rect(topleft=(100, 150)) is the same as pygame.Rect(100, 150, 40, 40) for the 40 x 40 robot. And blit takes a rectangle as the position, using its top-left corner: window.blit(robot_image, robot).

Touching

a.colliderect(b) tells whether the rectangles a and b overlap, and a.collidepoint(point) whether the point is inside a. That's the click test of the previous module, ready-made.

import pygame

robot = pygame.Rect(100, 150, 40, 40)
coin = pygame.Rect(130, 130, 24, 24)
wall = pygame.Rect(140, 150, 40, 40)
print(robot.colliderect(coin))
print(robot.colliderect(wall))
print(robot.collidepoint((120, 160)))
print(robot.collidepoint((140, 160)))
Output
True
False
True
False

The coin's corner overlaps the robot's top-right corner. The wall starts at x = 140, right where the robot ends, so they're side by side but don't overlap.

Putting it together

Here is a whole game. Coins fly in from the left edge of the window, one at a time, and the player moves the robot up and down with the arrow keys to catch them. Each catch is a point, and the next coin starts from the left. A coin that gets past the robot, all the way out of the window on the right, costs one of the player's three lives, and then the next coin comes. When the last life is gone, the game is over: nothing moves any more, and the window says so until it's closed. A real game would choose each coin's height with random; this one takes them in turn from a list.

The image robot.png
robot.png, 40 × 40 pixels
The image coin.png
coin.png, 24 × 24 pixels
import pygame

pygame.init()
window = pygame.display.set_mode((320, 240))
pygame.display.set_caption("Coin Catcher")
robot_image = pygame.image.load("robot.png")
coin_image = pygame.image.load("coin.png")
font = pygame.font.SysFont("Arial", 24)

robot = robot_image.get_rect(topleft=(270, 100))
coin_ys = [60, 170, 110, 40, 200]
next_y = 0
coin = coin_image.get_rect(topleft=(0, coin_ys[0]))
score = 0
lives = 3
game_over = False
going_up = False
going_down = False
clock = pygame.time.Clock()

while True:
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_UP:
                going_up = True
            if event.key == pygame.K_DOWN:
                going_down = True
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_UP:
                going_up = False
            if event.key == pygame.K_DOWN:
                going_down = False
        if event.type == pygame.QUIT:
            exit()

    if not game_over:
        if going_up and robot.top > 0:
            robot.y -= 4
        if going_down and robot.bottom < 240:
            robot.y += 4
        coin.x += 5
        if robot.colliderect(coin):
            score += 1
            next_y = (next_y + 1) % len(coin_ys)
            coin.topleft = (0, coin_ys[next_y])
        elif coin.left >= 320:
            lives -= 1
            if lives == 0:
                game_over = True
            else:
                next_y = (next_y + 1) % len(coin_ys)
                coin.topleft = (0, coin_ys[next_y])

    window.fill((0, 0, 0))
    window.blit(robot_image, robot)
    window.blit(coin_image, coin)
    score_text = font.render(f"Score: {score}", True, (255, 255, 255))
    window.blit(score_text, (10, 10))
    lives_text = font.render(f"Lives: {lives}", True, (255, 255, 255))
    window.blit(lives_text, (130, 10))
    if game_over:
        text = font.render("Game over!", True, (255, 80, 80))
        window.blit(text, ((320 - text.get_width()) // 2, 100))
    pygame.display.flip()
    clock.tick(60)
Coin Catcher320 × 240
The program's window after 360 frames
The window as text
fill (0, 0, 0)
blit robot.png at (270, 168)
blit coin.png at (320, 60)
blit text "Score: 3" at (10, 10)
blit text "Lives: 0" at (130, 10)
blit text "Game over!" at (115, 100)
The window after 360 frames (After 10 frames: the up arrow is pressed. After 18 frames: the up arrow is let go. After 60 frames: the down arrow is pressed. After 85 frames: the down arrow is let go.).

The game is the same main loop as before, with its three parts:

  1. Events only change variables: the arrow keys set going_up and going_down.
  2. Updating applies the game's rules to its state, the variables that describe the game: the robot moves (but not out of the window), the coin flies on, a catch adds a point and starts the next coin, and a missed coin takes a life. It then starts the next coin, or, when that was the last life, sets game_over. When game_over is True, this part does nothing, so everything stays where it was.
  3. Drawing shows the state as it is now, including the score, the lives and, at the end, the words "Game over!".

Keeping the parts apart makes a game much easier to change. A new rule goes in the updating part, and the drawing part draws whatever the state is.

Your task

Challenge 1 of 3

What does this program print? Type each line it prints.

Challenge 2

Collect the coins with clicks. Each coin is a pygame.Rect, and a click on a coin removes it from the list. The text at the bottom tells how many coins are left. Fill in the blanks: the rectangle method that tells whether a point is inside, and the function that gives the length of the list.

Challenge 3

Write a dodging game. The robot stands at the bottom of the window, at (140, 200), and a monster falls toward it. The player moves the robot with the arrow keys, 4 pixels per frame while the key is held, and the robot's x stays between 0 and 280.

The monster starts at the top (y = 0) and falls 5 pixels per frame. Its x comes from the list [40, 200, 120, 260, 0]: the first monster falls at x = 40, the next one at 200, and so on, back to the start of the list after the last one. When a monster's y reaches 240 or more, it has been dodged: the count of dodged monsters grows by one, and the next monster starts at the top.

When the robot and the monster touch (their rectangles overlap), the game is over: from then on nothing moves, and the window shows Game over! too, until it's closed.

In each frame: handle the events, then (unless the game is over) move the robot, move the monster, check for a dodged monster and check for touching. Then draw: fill the window black, draw the robot, the monster, the text Dodged: 2 (with the real count) at (10, 10), and, when the game is over, the text Game over! centered across the window with its top at y = 100. Use the starter code's font for both texts.