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)
Rect(100, 150, 40, 40)
100 150 40 40
140 190
(120, 170)
(110, 150) 150right 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)))
True
False
True
FalseThe 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.

robot.png, 40 × 40 pixels
coin.png, 24 × 24 pixelsimport 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)

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 game is the same main loop as before, with its three parts:
- Events only change variables: the arrow keys set
going_upandgoing_down. - 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. Whengame_overisTrue, this part does nothing, so everything stays where it was. - 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.