PY-1.3-BP4-MQ55

Our Goal: Show Words on Screen!

In this lesson, we will learn how to put words on our game screen.

We will make Pygame ready to show text. Then, we will write some words, like 'Hello Pygame!', and make them appear on our game window.

Check Your Work

Before you start coding, let's check how the tests look now.

Run this command in your terminal: pytest module-1.3/blueprint-4/quest-55

You will see that some tests are not passing yet. This is normal because you haven't written the code for them.

Here are the tests that are not passing:

  • test_display_hello_text

Time to Code!

Now, let's write the code in main.py to show words on the screen.

In Pygame, you need to do a few things to show text:

  1. Start the font system: You need to tell Pygame you want to use fonts.
  2. Pick a font: Choose what kind of letters you want to use and how big they should be.
  3. Write your words: Turn your words into something Pygame can draw.
  4. Put it on the screen: Place your words where you want them to go.

Here is an example of how you might show text in Pygame:

import pygame

pygame.font.init() # Step 1

# Step 2: Choose a font and size
my_font = pygame.font.SysFont('Arial', 30)

# Step 3: Write your words
text_surface = my_font.render('My Game Text', True, (255, 255, 255)) # White color

# Step 4: Put it on the screen
screen.blit(text_surface, (100, 50)) # At position (100, 50)

Remember to use the right parts of your code to do these steps.

Check Your Work Again

Now that you've written your code, let's check if it works!

Run this command in your terminal: pytest module-1.3/blueprint-4/quest-55

If your code is correct, the test should now pass.

Here is the test that should pass:

  • test_display_hello_text

Documentation

Game State and Object Management

Game development involves tracking the state of the game (like whether it's running or over) and managing collections of objects (like players, enemies, or bricks).

Basic Game State

A common way to control the main loop of a game is using a boolean variable.

# Example: Controlling a loop
game_is_running = True
while game_is_running:
    # Game logic and drawing happens here
    
    # A condition that might end the game
    if some_condition_is_met:
        game_is_running = False # Set the flag to False to stop the loop

Variables initialized before the game loop maintain their value across each iteration (frame). These are persistent game variables.

# Example: A persistent counter
frame_count = 0 # Initialized before the loop
while True: # Assuming some other way to exit
    frame_count += 1 # Incremented each frame
    # The value of frame_count persists

Collision Detection

In Pygame, the pygame.Rect object has a useful method called colliderect(). This method checks if two rectangles overlap.

# Example: Checking collision between two rectangles
rect1 = pygame.Rect(50, 50, 20, 20)
rect2 = pygame.Rect(60, 60, 20, 20)

if rect1.colliderect(rect2):
    print("Rectangles overlap")

To check for collisions between one object (like a ball) and multiple objects (like bricks), you typically iterate through the list of multiple objects and check collision with the single object inside the loop.

# Example: Checking ball collision with multiple bricks
# Assume 'ball' is an object with a 'rect' attribute
# Assume 'bricks' is a list of objects, each with a 'rect' attribute

# Loop through each brick
# Check if the ball's rectangle collides with the current brick's rectangle

Managing Lists During Iteration

When you have a list of objects (like bricks) and you need to remove objects from that list based on a condition (like a collision) while you are looping through the list, a common issue can arise.

The Problem with Modifying a List While Iterating

If you use a standard for loop to iterate directly over a list and remove items from that same list within the loop, you can encounter unexpected behavior. The iterator keeps track of its position based on the original list's indices. When an item is removed, the list shrinks and the indices of subsequent items shift. The iterator might then skip over the item that moved into the position of the removed item.

# Example: Demonstrating the list modification bug (Conceptual)
my_list = [10, 20, 30, 40, 50]
print(f"Initial list: {my_list}")

# This loop will skip elements
for item in my_list:
    print(f"Checking item: {item}")
    if item % 20 == 0: # If item is 20 or 40
        print(f"Removing item: {item}")
        my_list.remove(item) # Modifying the list during iteration

print(f"Final list: {my_list}")
# Expected: [10, 30, 50]
# Actual: [10, 30, 40, 50] or similar depending on Python version/implementation details
# The item '40' might be skipped because '20' was removed.

Debugging with Print Statements

One way to understand what's happening inside your loop, especially when dealing with issues like the list modification bug, is to use print() statements. By printing the state of your list or variables at different points in the loop, you can observe how they change (or don't change) and identify where the logic deviates from what you expect.

# Example: Using print for debugging (Conceptual)
# Assume 'bricks' is the list being modified
# Assume 'ball' is the colliding object

# Print the list state before the loop
# Loop through bricks:
    # Print the current brick being checked
    # Check for collision:
        # If collision:
            # Print a message indicating removal
            # Remove the brick
    # Print the list state after potential removal (optional, but helpful)
# Print the final list state after the loop

Safe List Modification

To safely remove items from a list while iterating, you should iterate over a copy of the list. This leaves the original list free to be modified without disrupting the iteration process.

A simple way to create a copy (a slice) of a list is using the [:] notation.

# Example: Safely modifying a list during iteration
original_list = [10, 20, 30, 40, 50]
print(f"Initial list: {original_list}")

# Iterate over a copy (slice) of the list
for item in original_list[:]:
    print(f"Checking item: {item}")
    if item % 20 == 0: # If item is 20 or 40
        print(f"Removing item: {item}")
        original_list.remove(item) # Modify the original list

print(f"Final list: {original_list}")
# Expected: [10, 30, 50]
# Actual: [10, 30, 50] - Correct!

In the context of game development, you would iterate over bricks[:] and remove from the bricks list.

Integrating State Changes

Collision detection and list modification are often tied to other game state changes. For example, when a ball hits a brick:

  • The brick is removed (list modification).
  • The ball's direction might reverse (changing ball object's state).
  • The player's score might increase (changing a score variable's state).
# Example: Collision triggering state changes (Conceptual)
# Assume 'ball' object with 'rect' and 'speed_y'
# Assume 'bricks' list
# Assume 'score' variable

# Iterate over a copy of bricks:
    # Check collision:
        # If collision:
            # Remove brick from original list
            # Reverse ball's vertical speed
            # Increase score
            # Stop checking for other collisions this frame (optional, but common)

Displaying Text with Pygame Fonts

Pygame's pygame.font module allows you to render text onto surfaces, which can then be blitted onto the screen.

# Example: Steps for displaying text (Conceptual)
# Assume 'screen' is the main display surface

# 1. Initialize the font module (usually done by pygame.init())

# 2. Create a font object
# font = pygame.font.Font(font_file, size)
# Use None for default font

# 3. Render the text onto a new surface
# text_surface = font.render(text_string, antialias, color)

# 4. Blit the text surface onto the screen
# screen.blit(text_surface, position)

Basic Debugging Techniques

Debugging is the process of finding and fixing errors (bugs) in your code.

  • Understand the Error: Read error messages carefully. They often tell you the type of error and where it occurred.
  • Print Statements: As shown above, printing variable values and program state can help you trace the execution flow and see what your code is doing at specific points.
  • Simplify the Problem: If you have a complex bug, try to isolate the part of the code causing the issue. Can you reproduce the bug with a smaller, simpler example?
  • Use a Debugger: More advanced tools (like pdb in Python or integrated debuggers in IDEs) allow you to pause execution, step through code line by line, and inspect variables.
# Example: Using pdb (Conceptual)
import pdb

def my_buggy_function(data):
    # ... some code ...
    pdb.set_trace() # Program execution will pause here
    # ... more code ...

# When the program hits pdb.set_trace(), you get a (Pdb) prompt.
# You can type variable names to see their values, 'n' to go to the next line, 'c' to continue, etc.

Image/Gif Opportunity: List Modification Bug

A short animation or sequence of images showing a list [A, B, C, D] being iterated, B is removed, and the iterator pointer jumps from checking B to checking D, skipping C.

Image/Gif Opportunity: Safe List Modification

A short animation or sequence of images showing iteration over a copy [A, B, C, D] while removing from the original list [A, B, C, D]. Show B being checked in the copy, B being removed from the original, and the iterator in the copy moving correctly to check C.

Image/Gif Opportunity: Pygame Text Rendering

A visual showing the steps:

  1. A font object being created (conceptual).
  2. The font object rendering text onto a separate surface (show the text surface).
  3. The text surface being blitted onto the main screen surface.