Build a Dodge the Blocks Game

Build a 2D arcade game in Python where you dodge falling blocks.

Introduction

30 Second Summary

Games are everywhere on your phone and computer, but have you ever wondered how they actually work under the hood? Every game runs on the same core pattern: a loop that checks for input, updates the world, and draws the next frame.

In this project, you will build a "Dodge the Blocks" arcade game using Python and Pygame. You will create a player that moves with arrow keys, spawn falling enemies, detect collisions, and track your score.

What You'll Build

A 2D arcade game where you control a blue rectangle at the bottom of the screen, dodging red enemy blocks that fall from random positions at the top.

By the end of this project, you'll have:

  • A game loop that handles player input, updates game state, and renders graphics at 60 frames per second.
  • A player character that moves left and right with arrow keys and stays within screen boundaries.
  • A scoring system and collision detection that ends the game when an enemy block hits you, displaying a Game Over screen with your final score.
  • Secret Mission: Add difficulty scaling so enemies speed up over time, plus a restart feature.

Are there any prerequisites?

Basic Python knowledge is all you need. If you are comfortable with variables, loops, conditionals, and functions, you are ready to go. No game development experience required.

Create the Game Window and Game Loop

Pygame is installed and your blank file is ready. Now it's time to build the foundation of every game: the window and the game loop.

A game loop is a continuous cycle that keeps your game alive. Each pass through the loop checks for player input, updates the game state, and redraws the screen. Without it, the window would flash open and immediately close.

In this step, get ready to:
  • Set up Pygame with a display window and color constants.
  • Write a game loop that keeps the window open and responsive.
  • Run the file to confirm the window appears and closes cleanly.
Set up Pygame and define your constants

Before anything appears on screen, you need to initialize Pygame, define the window size, and choose colors. Constants keep these values in one place so they are easy to tweak later.

  • In dodge_game.py, add the following imports, initialization, and constants:
import pygame
import random

# Initialize pygame
pygame.init()

# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (50, 120, 220)
RED = (220, 50, 50)
DARK_GRAY = (30, 30, 30)

What does this code do?

  • pygame.init() starts all of Pygame's internal modules (display, sound, fonts). You must call this before using any Pygame feature.
  • SCREEN_WIDTH and SCREEN_HEIGHT define your game window at 800 pixels wide by 600 pixels tall.
  • Each color is an RGB tuple. DARK_GRAY will be the background, BLUE is for the player, and RED is for enemies.

Next, create the actual window, clock, and font objects that Pygame needs at runtime.

  • Add the following code directly below your color constants in dodge_game.py:
# Create the game window
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Dodge the Blocks")
clock = pygame.time.Clock()

# Font for score display
font = pygame.font.Font(None, 36)
game_over_font = pygame.font.Font(None, 72)

What does this code do?

  • pygame.display.set_mode() creates the game window surface at your specified dimensions.
  • pygame.display.set_caption() sets the text in the window's title bar.
  • pygame.time.Clock() creates a clock you will use to control how fast the game runs.
  • pygame.font.Font(None, 36) loads Pygame's built-in default font at size 36. Passing None means no external font file is needed.
  • Save dodge_game.py.

Seeing a 'No module named pygame' error?

Make sure you installed Pygame in the same Python interpreter PyCharm is using. Check your interpreter in the bottom-right corner of PyCharm. It should match the one where you ran pip install pygame.

help me fix a pygame import error in PyCharm

Write the game loop

The game loop is what makes a game feel alive. Every frame, it checks whether the player closed the window, redraws the screen, and pauses just long enough to run at a smooth 60 frames per second.

  • Add the following code at the bottom of dodge_game.py, below the font lines:
# Game state
running = True

# Game loop
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(DARK_GRAY)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

What does this code do?

  • running = True acts as the on/off switch for the loop. Setting it to False ends the game.
  • pygame.event.get() reads all pending events (mouse clicks, key presses, window close). Without this call, the OS considers the window unresponsive.
  • pygame.QUIT fires when the player clicks the window's X button.
  • screen.fill(DARK_GRAY) erases the previous frame by painting the entire screen one color.
  • pygame.display.flip() pushes the freshly drawn frame to the actual display.
  • clock.tick(60) pauses just enough to cap the loop at 60 iterations per second (60 FPS).
  • pygame.quit() shuts down Pygame cleanly after the loop ends.

Why fill the screen every frame?

Games redraw everything from scratch each frame. If you skip the fill, old frames pile up and moving objects leave trails behind them. Filling first gives you a clean canvas for each new frame.

  • Save dodge_game.py.
Run your game
  • Right-click anywhere inside the dodge_game.py editor and select Run 'dodge_game' from the context menu.

An 800x600 window with a dark gray background should appear. The title bar reads "Dodge the Blocks". Click the window's X button to close it.

Window not appearing?

  • Check that your while running loop is not indented under anything. It should be at the top level of the file.
  • Make sure you spelled pygame.display.flip() correctly. A typo here means the window opens but stays blank.
  • If the window opens and immediately closes, confirm running is set to True before the loop starts.

help me debug my Pygame window not appearing

✔️ Awesome, I've got everything!

Great. Double check you have saved dodge_game.py and the window closes without errors in the Run tool window.

ⓧ I'd like to double check the full code

Here is the complete dodge_game.py so far:

import pygame
import random

# Initialize pygame
pygame.init()

# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (50, 120, 220)
RED = (220, 50, 50)
DARK_GRAY = (30, 30, 30)

# Create the game window
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Dodge the Blocks")
clock = pygame.time.Clock()

# Font for score display
font = pygame.font.Font(None, 36)
game_over_font = pygame.font.Font(None, 72)

# Game state
running = True

# Game loop
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(DARK_GRAY)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

You have a live game window running at 60 FPS. Next up, you will add a player character that moves with the arrow keys.

Add the Player

Your game window is up and running with a solid dark background and a clean event loop. Now it is time to put something on screen that the player can actually control.

In this step, you will create a blue rectangle that represents the player and wire up the arrow keys to move it left and right. This is the foundation of every action game: an object the player controls in real time.

In this step, get ready to:
  • Define the player as a rectangle positioned at the bottom of the screen.
  • Add keyboard input so the player moves left and right with arrow keys.
  • Add boundary checks so the player cannot leave the screen.
Create the player rectangle

In Pygame, every game object needs a position and a size. A pygame.Rect gives you both in one object, plus built-in collision detection you will use later.

  • In dodge_game.py, find the line that says game_over_font = pygame.font.Font(None, 72).
  • Add the following code below it, before the # Game state comment:
# Player settings
PLAYER_SIZE = 50
PLAYER_SPEED = 7

# Enemy settings
ENEMY_SIZE = 50
ENEMY_SPEED = 5
SPAWN_INTERVAL = 1000

# Create player
player = pygame.Rect(
    SCREEN_WIDTH // 2 - PLAYER_SIZE // 2,
    SCREEN_HEIGHT - PLAYER_SIZE - 10,
    PLAYER_SIZE,
    PLAYER_SIZE
)

What does this code do?

  • PLAYER_SIZE and PLAYER_SPEED define how big the player square is and how fast it moves per frame.
  • The enemy settings (ENEMY_SIZE, ENEMY_SPEED, SPAWN_INTERVAL) are constants you will use in the next step when you add falling enemies.
  • pygame.Rect() creates a rectangle at the bottom-center of the screen. The math SCREEN_WIDTH // 2 - PLAYER_SIZE // 2 centers the player horizontally.

Now you need to draw this rectangle on screen each frame. The player should appear after the background fills but before the display flips.

  • Inside the game loop, find this line:
    screen.fill(DARK_GRAY)
    pygame.display.flip()
  • Add this line between screen.fill(DARK_GRAY) and pygame.display.flip(), so the section looks like this:
    screen.fill(DARK_GRAY)
    pygame.draw.rect(screen, BLUE, player)
    pygame.display.flip()

Why fill before drawing?

screen.fill(DARK_GRAY) wipes the entire screen each frame, like erasing a whiteboard. Then pygame.draw.rect() paints the player on the clean surface. Without the fill, old frames would smear together.

  • Save dodge_game.py and run the file.
  • You should see a blue square sitting at the bottom-center of the screen. It does not move yet.

Don't see the blue square?

  • Make sure pygame.draw.rect(screen, BLUE, player) is placed after screen.fill(DARK_GRAY) and before pygame.display.flip(). Drawing after the flip means the frame already displayed without your player.
  • Check that the player variable is defined above the game loop, not inside it. Defining it inside the loop would recreate it every frame.

Help me figure out why my player rectangle isn't appearing on screen.

Add keyboard movement

A stationary square is not much of a game. You need the player to respond to held-down arrow keys every single frame, not just on a single key press event.

  • In the game loop, find the closing line of the event-handling block (running = False inside the for event loop).
  • Add the following movement code below the event loop and above screen.fill(DARK_GRAY):
    # Player movement
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        player.x -= PLAYER_SPEED
    if keys[pygame.K_RIGHT]:
        player.x += PLAYER_SPEED

    # Keep player on screen
    if player.left < 0:
        player.left = 0
    if player.right > SCREEN_WIDTH:
        player.right = SCREEN_WIDTH

What does this code do?

  • pygame.key.get_pressed() returns a snapshot of every key on the keyboard. If the left or right arrow is held down, the player moves by PLAYER_SPEED pixels that frame.
  • The boundary checks use player.left and player.right (built-in Rect properties) to stop the player from sliding off either edge of the screen.

Why get_pressed instead of KEYDOWN events?

pygame.key.get_pressed() checks whether a key is currently held down. This runs every frame, giving smooth continuous movement.

A KEYDOWN event only fires once when you first press the key. That would make the player jump one step and stop, requiring repeated tapping to move.

  • Save dodge_game.py and run the file.
  • Hold the left and right arrow keys. The blue square should glide smoothly in that direction and stop at the screen edges.

Player not moving or moving only once?

  • Make sure the movement code is inside the while running loop but outside the for event loop. If it is inside the event loop, it only runs when events fire.
  • Check your indentation. The keys = pygame.key.get_pressed() line should be at the same indent level as screen.fill(DARK_GRAY) (one level inside while running).
  • If the player moves off-screen, confirm your boundary checks use player.left and player.right (not player.x).

Help me debug why my player movement is not working in Pygame.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete dodge_game.py file at this point:

import pygame
import random

# Initialize pygame
pygame.init()

# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (50, 120, 220)
RED = (220, 50, 50)
DARK_GRAY = (30, 30, 30)

# Create the game window
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Dodge the Blocks")
clock = pygame.time.Clock()

# Font for score display
font = pygame.font.Font(None, 36)
game_over_font = pygame.font.Font(None, 72)

# Player settings
PLAYER_SIZE = 50
PLAYER_SPEED = 7

# Enemy settings
ENEMY_SIZE = 50
ENEMY_SPEED = 5
SPAWN_INTERVAL = 1000

# Create player
player = pygame.Rect(
    SCREEN_WIDTH // 2 - PLAYER_SIZE // 2,
    SCREEN_HEIGHT - PLAYER_SIZE - 10,
    PLAYER_SIZE,
    PLAYER_SIZE
)

# Game state
running = True

# Game loop
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Player movement
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        player.x -= PLAYER_SPEED
    if keys[pygame.K_RIGHT]:
        player.x += PLAYER_SPEED

    # Keep player on screen
    if player.left < 0:
        player.left = 0
    if player.right > SCREEN_WIDTH:
        player.right = SCREEN_WIDTH

    screen.fill(DARK_GRAY)
    pygame.draw.rect(screen, BLUE, player)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Your player is alive and responsive. Next up, you will spawn enemy blocks that fall from the sky, giving your player something to dodge.

Spawn Falling Enemies

Your player can move left and right, but there is nothing to dodge yet. A game about dodging needs things to dodge.

In this step, you will create red enemy blocks that spawn at the top of the screen at random horizontal positions and fall downward. Pygame's timer system handles the spawning on a regular interval, so enemies keep appearing without you writing manual timing logic.

In this step, get ready to:
  • Set up a timed event that spawns enemies at regular intervals.
  • Move enemies down the screen and clean up any that leave the window.
  • Draw enemies as red rectangles each frame.
Set up the spawn timer and enemies list

Games often need things to happen on a schedule. Pygame lets you create custom timer events that fire at a set interval, which is perfect for spawning new enemies every second.

  • In dodge_game.py, find the SPAWN_INTERVAL = 1000 line.
  • Add the following code directly below it to create a custom spawn event and start the timer:
# Custom event for spawning enemies
SPAWN_ENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(SPAWN_ENEMY, SPAWN_INTERVAL)
  • Next, find the # Game state section and add an empty enemies list above running = True, so it looks like this:
# Game state
enemies = []
running = True

What does this code do?

pygame.USEREVENT + 1 creates a unique event ID that Pygame will fire on a schedule. Think of it as defining a new type of alarm.

pygame.time.set_timer(SPAWN_ENEMY, SPAWN_INTERVAL) tells Pygame to fire that alarm every 1000 milliseconds (once per second). Each time it fires, we will create a new enemy.

  • Save dodge_game.py.

Seeing a red underline on SPAWN_ENEMY?

Make sure the two new lines are placed after SPAWN_INTERVAL = 1000 and before the # Create player section. The timer must be set before the game loop starts.

Help me debug my spawn timer setup.

Now you need to handle the spawn event inside the game loop so that a new enemy actually gets created each time the timer fires.

  • Inside your game loop, find the event handling block. Locate this line:
        if event.type == pygame.QUIT:
            running = False
  • Add the following code directly below it (still inside the for event loop):
        if event.type == SPAWN_ENEMY:
            # Create enemy at random x position, just above the screen
            enemy_x = random.randint(0, SCREEN_WIDTH - ENEMY_SIZE)
            enemy = pygame.Rect(enemy_x, -ENEMY_SIZE, ENEMY_SIZE, ENEMY_SIZE)
            enemies.append(enemy)

What does this code do?

  • random.randint(0, SCREEN_WIDTH - ENEMY_SIZE) picks a random x coordinate so each enemy appears at a different horizontal position.
  • The y position is set to -ENEMY_SIZE so the enemy starts just above the visible window and slides in smoothly.
  • enemies.append(enemy) adds the new Rect to our list so we can track and update it every frame.
  • Save dodge_game.py.
  • Run the file. You will not see enemies on screen yet (they spawn but we have not drawn them), but the program should still run without errors.

Getting a NameError for enemies or SPAWN_ENEMY?

Check that enemies = [] is defined before the game loop starts. Also confirm that SPAWN_ENEMY = pygame.USEREVENT + 1 is above the game loop, not inside it.

Help me fix my NameError in dodge_game.py.

Move and draw enemies each frame

Enemies exist in memory now, but they need to move downward and appear on screen. You also need to clean up enemies that fall past the bottom so the list does not grow forever.

  • In the game loop, find the boundary check section that ends with:
    if player.right > SCREEN_WIDTH:
        player.right = SCREEN_WIDTH
  • Add the following code directly below those boundary checks to move enemies and remove off-screen ones:
    # Move enemies
    for enemy in enemies:
        enemy.y += ENEMY_SPEED

    # Remove enemies that are off screen
    enemies = [enemy for enemy in enemies if enemy.top < SCREEN_HEIGHT]
  • Now find the line where you draw the player:
    pygame.draw.rect(screen, BLUE, player)
  • Add this code directly below it to draw all enemies as red rectangles:
    for enemy in enemies:
        pygame.draw.rect(screen, RED, enemy)

What does this code do?

  • enemy.y += ENEMY_SPEED pushes each enemy 5 pixels lower every frame, creating the falling motion.
  • The list comprehension keeps only enemies whose top edge is still above the bottom of the screen. Once they fall off, they are removed from memory.
  • pygame.draw.rect(screen, RED, enemy) paints each enemy as a filled red rectangle every frame.
  • Save dodge_game.py and run the file.
  • You should see red blocks spawning at the top of the window from random positions and falling steadily downward. New ones appear roughly every second. They disappear after passing the bottom edge.

Red blocks not appearing?

Make sure the enemy drawing loop is placed after screen.fill(DARK_GRAY) and after the player draw line. Drawing before the fill would erase them immediately.

Also check that the movement code (enemy.y += ENEMY_SPEED) is inside the while running loop but not inside the for event loop. It needs to run every frame, not just when an event fires.

Help me figure out why my enemies are not showing up.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file.

ⓧ I'd like to double check the full code

Here is the complete dodge_game.py file at this point:

import pygame
import random

# Initialize pygame
pygame.init()

# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (50, 120, 220)
RED = (220, 50, 50)
DARK_GRAY = (30, 30, 30)

# Create the game window
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Dodge the Blocks")
clock = pygame.time.Clock()

# Font for score display
font = pygame.font.Font(None, 36)
game_over_font = pygame.font.Font(None, 72)

# Player settings
PLAYER_SIZE = 50
PLAYER_SPEED = 7

# Enemy settings
ENEMY_SIZE = 50
ENEMY_SPEED = 5
SPAWN_INTERVAL = 1000

# Custom event for spawning enemies
SPAWN_ENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(SPAWN_ENEMY, SPAWN_INTERVAL)

# Create player
player = pygame.Rect(
    SCREEN_WIDTH // 2 - PLAYER_SIZE // 2,
    SCREEN_HEIGHT - PLAYER_SIZE - 10,
    PLAYER_SIZE,
    PLAYER_SIZE
)

# Game state
enemies = []
running = True

# Game loop
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == SPAWN_ENEMY:
            enemy_x = random.randint(0, SCREEN_WIDTH - ENEMY_SIZE)
            enemy = pygame.Rect(enemy_x, -ENEMY_SIZE, ENEMY_SIZE, ENEMY_SIZE)
            enemies.append(enemy)

    # Player movement
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        player.x -= PLAYER_SPEED
    if keys[pygame.K_RIGHT]:
        player.x += PLAYER_SPEED

    # Keep player on screen
    if player.left < 0:
        player.left = 0
    if player.right > SCREEN_WIDTH:
        player.right = SCREEN_WIDTH

    # Move enemies
    for enemy in enemies:
        enemy.y += ENEMY_SPEED

    # Remove enemies that are off screen
    enemies = [enemy for enemy in enemies if enemy.top < SCREEN_HEIGHT]

    screen.fill(DARK_GRAY)
    pygame.draw.rect(screen, BLUE, player)
    for enemy in enemies:
        pygame.draw.rect(screen, RED, enemy)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Your game now has danger. Red blocks rain from the sky and your player can weave between them. Next up, you will add collision detection so getting hit actually means game over, plus a score counter to track your survival.

Add Collision Detection and Scoring

Your game has a player dodging enemies, but right now there are no consequences for getting hit and no way to track how well you're doing.

In this step, you'll add two core game mechanics: a score that counts up every frame you survive, and collision detection that ends the game when an enemy touches your player.

In this step, get ready to:
  • Add a score counter that increases every frame.
  • Detect collisions between the player and enemies.
  • Display a Game Over screen with your final score.
Add the score variable and display

The simplest way to measure survival in this game is to count frames. Since the game runs at 60 FPS, every frame the player stays alive adds 1 to their score.

  • In dodge_game.py, find the # Game state section near line 43.
  • Find this code:
# Game state
enemies = []
running = True
  • Add score = 0 between enemies = [] and running = True, so the section now looks like this:
# Game state
enemies = []
score = 0
running = True

Now you need to increment the score each frame and render it on screen. You'll also organize the drawing section of your game loop with comments.

  • Inside your game loop, find the existing drawing code that starts with screen.fill(DARK_GRAY) and ends with clock.tick(60).
  • Replace that entire block with the following code:
    # Update score
    score += 1

    # Draw everything
    screen.fill(DARK_GRAY)
    pygame.draw.rect(screen, BLUE, player)
    for enemy in enemies:
        pygame.draw.rect(screen, RED, enemy)

    # Draw score
    score_text = font.render(f"Score: {score}", True, WHITE)
    screen.blit(score_text, (10, 10))

    # Update display
    pygame.display.flip()
    clock.tick(60)

What does this code do?

  • score += 1 adds 1 to the score every frame. At 60 FPS, the score climbs fast.
  • font.render() creates a text Surface from the score string. The True argument enables anti-aliasing (smooth edges).
  • screen.blit(score_text, (10, 10)) draws the text Surface onto the screen at the top-left corner.
  • While you're in the game loop, add a # Handle events comment on the line directly above for event in pygame.event.get(): to keep your code organized.
  • Save dodge_game.py and run the file.
  • You should see the score counting up rapidly in white text at the top-left corner of the window.

Score not showing up?

  • Make sure the # Draw score section comes AFTER pygame.draw.rect() calls but BEFORE pygame.display.flip(). Drawing order matters.
  • Check that score = 0 is defined OUTSIDE the game loop (in the game state section). If it's inside the loop, it resets to 0 every frame.

Why isn't my score text appearing in the Pygame window?

Detect collisions with enemies

Right now, enemy blocks pass straight through the player with no effect. Pygame's Rect objects have a built-in colliderect() method that checks whether two rectangles overlap.

  • In your game loop, find the line that says # Update score.
  • Add the following collision detection code directly ABOVE # Update score:
    # Check for collisions
    for enemy in enemies:
        if player.colliderect(enemy):
            running = False

What does this code do?

  • The loop checks every enemy in the list against the player rectangle.
  • player.colliderect(enemy) returns True if any part of the player rectangle overlaps with the enemy rectangle.
  • Setting running = False breaks out of the game loop on the next iteration, ending the game.
  • Save dodge_game.py and run the file.
  • Let a red block hit your player. The game window should close immediately when a collision happens.

Game not ending on collision?

  • Make sure the collision check is indented inside the while running: loop (one level of indentation from the loop).
  • Check that the collision code is placed AFTER the enemy movement code and BEFORE the # Update score section.

Why isn't my collision detection working in Pygame?

Display the Game Over screen

When the player gets hit, the game loop ends and the program currently just calls pygame.quit(). Instead of closing immediately, you'll show the player their final score on a Game Over screen.

  • Scroll to the bottom of your file and find the pygame.quit() line after the game loop.
  • Replace pygame.quit() with the following Game Over screen code:
# Game Over screen
screen.fill(DARK_GRAY)
game_over_text = game_over_font.render("GAME OVER", True, RED)
score_text = font.render(f"Final Score: {score}", True, WHITE)
screen.blit(game_over_text, (SCREEN_WIDTH // 2 - game_over_text.get_width() // 2, SCREEN_HEIGHT // 2 - 50))
screen.blit(score_text, (SCREEN_WIDTH // 2 - score_text.get_width() // 2, SCREEN_HEIGHT // 2 + 30))
pygame.display.flip()

# Wait for user to close window
waiting = True
while waiting:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            waiting = False

pygame.quit()

What does this code do?

  • screen.fill(DARK_GRAY) clears the gameplay graphics and prepares a blank canvas for the Game Over screen.
  • game_over_font.render() renders the large "GAME OVER" text in red. get_width() measures the text width so you can center it on screen.
  • The while waiting: loop keeps the window open after the game ends so the player can see their score. Closing the window sets waiting = False and exits cleanly.
  • Save dodge_game.py and run the file.
  • Play the game and intentionally let an enemy hit you. You should see a dark screen with "GAME OVER" in red and your final score in white, centered in the window.
  • Close the window by clicking the X button to confirm the waiting loop works correctly.

Game Over screen not appearing?

  • Make sure the Game Over code is OUTSIDE the while running: loop (no indentation). It should run after the game loop finishes.
  • Check that pygame.display.flip() is called after blitting both text surfaces. Without it, nothing gets drawn to the screen.
  • If the window closes instantly, make sure you have the while waiting: loop after pygame.display.flip(). Without it, the program reaches pygame.quit() immediately.

Help me debug why my Pygame Game Over screen isn't showing after the game loop ends.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

import pygame
import random

# Initialize pygame
pygame.init()

# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (50, 120, 220)
RED = (220, 50, 50)
DARK_GRAY = (30, 30, 30)

# Create the game window
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Dodge the Blocks")
clock = pygame.time.Clock()

# Font for score display
font = pygame.font.Font(None, 36)
game_over_font = pygame.font.Font(None, 72)

# Player settings
PLAYER_SIZE = 50
PLAYER_SPEED = 7

# Enemy settings
ENEMY_SIZE = 50
ENEMY_SPEED = 5
SPAWN_INTERVAL = 1000

# Custom event for spawning enemies
SPAWN_ENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(SPAWN_ENEMY, SPAWN_INTERVAL)

# Create player
player = pygame.Rect(
    SCREEN_WIDTH // 2 - PLAYER_SIZE // 2,
    SCREEN_HEIGHT - PLAYER_SIZE - 10,
    PLAYER_SIZE,
    PLAYER_SIZE
)

# Game state
enemies = []
score = 0
running = True

# Game loop
while running:
    # Handle events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == SPAWN_ENEMY:
            enemy_x = random.randint(0, SCREEN_WIDTH - ENEMY_SIZE)
            enemy = pygame.Rect(enemy_x, -ENEMY_SIZE, ENEMY_SIZE, ENEMY_SIZE)
            enemies.append(enemy)

    # Player movement
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        player.x -= PLAYER_SPEED
    if keys[pygame.K_RIGHT]:
        player.x += PLAYER_SPEED

    # Keep player on screen
    if player.left < 0:
        player.left = 0
    if player.right > SCREEN_WIDTH:
        player.right = SCREEN_WIDTH

    # Move enemies
    for enemy in enemies:
        enemy.y += ENEMY_SPEED

    # Remove enemies that are off screen
    enemies = [enemy for enemy in enemies if enemy.top < SCREEN_HEIGHT]

    # Check for collisions
    for enemy in enemies:
        if player.colliderect(enemy):
            running = False

    # Update score
    score += 1

    # Draw everything
    screen.fill(DARK_GRAY)
    pygame.draw.rect(screen, BLUE, player)
    for enemy in enemies:
        pygame.draw.rect(screen, RED, enemy)

    # Draw score
    score_text = font.render(f"Score: {score}", True, WHITE)
    screen.blit(score_text, (10, 10))

    # Update display
    pygame.display.flip()
    clock.tick(60)

# Game Over screen
screen.fill(DARK_GRAY)
game_over_text = game_over_font.render("GAME OVER", True, RED)
score_text = font.render(f"Final Score: {score}", True, WHITE)
screen.blit(game_over_text, (SCREEN_WIDTH // 2 - game_over_text.get_width() // 2, SCREEN_HEIGHT // 2 - 50))
screen.blit(score_text, (SCREEN_WIDTH // 2 - score_text.get_width() // 2, SCREEN_HEIGHT // 2 + 30))
pygame.display.flip()

# Wait for user to close window
waiting = True
while waiting:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            waiting = False

pygame.quit()

Your game is fully playable. You can dodge enemies, track your score, and see a proper Game Over screen when you get hit. Next up, you'll take on a Secret Mission to make the game progressively harder the longer you survive.

Secret mission

Add Difficulty Scaling and Restart

Your game works, but it plays the same way every time. Real arcade games get harder the longer you survive, keeping you on the edge of your seat. In this secret mission, you'll add progressive difficulty that speeds up enemies over time, plus a restart feature so players can jump back in without restarting the program.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your game files and the Pygame library installed, or remove them.

Resources you used:

  • dodge_game.py file on your local machine.
  • Pygame library installed via pip.

Keep everything running

No action needed. Choose this if you want to keep playing your game or continue building on it.

  • Your dodge_game.py file stays wherever you saved it in PyCharm.
  • Pygame stays installed and ready for your next game project.

Pause - I'll come back to this later

Since this project is entirely local with no running processes, there is nothing to pause. Your file and Pygame installation will be waiting for you whenever you come back.

  • No running servers or services to stop.
  • Your game file and Pygame library remain on disk until you choose to delete them.

Delete - I don't want to use this again

Remove all project files and the Pygame library to start fresh.

  • Delete dodge_game.py by right-clicking it in PyCharm's Project panel and selecting Delete. Confirm the deletion when prompted.
  • Uninstall Pygame by running this command in PyCharm's terminal:
pip uninstall pygame
  • Type y when prompted to confirm the uninstall.

Nice Work!

Nice Work!

Nice work! You just built a fully playable arcade game from scratch using Python and Pygame.

What started as a blank file is now an interactive game with a player you control, enemies that fall from above, real-time scoring, and a proper game over screen.

You've learned how to:

  • Build a game loop that processes events, updates state, and renders graphics at 60 FPS using Pygame's Clock.
  • Handle keyboard input for player movement with boundary constraints, and spawn timed enemies using custom events.
  • Implement collision detection between game objects and render dynamic text like scores and game over screens using Pygame's font module.
  • Secret Mission: Added progressive difficulty scaling that increases enemy speed and spawn rate over time, plus a full restart system so players can jump back in without relaunching the program.

Ready to quiz yourself?