We will make a program that counts how many times the game updates. This count will show up in the console.
Here is what a blank game window looks like:
We will make a program that counts how many times the game updates. This count will show up in the console.
Here is what a blank game window looks like:
Now, let's run the tests to see what needs fixing.
Run this command in your terminal:
pytest module-1.3/blueprint-4/quest-15
You should see these tests failing:
test_counter_increments_and_prints_each_frame
Now, let's write the code for main.py
.
In main.py
, you will add a counter that goes up each time the game updates. You will also print this number so you can see it.
Here are some general examples of how you might use a counter and print it:
# Example: A simple counter in a loop
count = 0
while count < 5:
print(f"Current count: {count}")
count += 1
# Example: Using a counter in a game loop
# frames_passed = 0
# running = True
# while running:
# # Game logic here
# frames_passed += 1
# print(f"Frames elapsed: {frames_passed}")
# if frames_passed > 100:
# running = False
Now that you've written your code, let's run the tests one more time.
Run this command in your terminal:
pytest module-1.3/blueprint-4/quest-15
You should see all tests passing:
test_counter_increments_and_prints_each_frame
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).
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
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
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.
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.
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
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.
Collision detection and list modification are often tied to other game state changes. For example, when a ball hits a brick:
# 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)
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)
Debugging is the process of finding and fixing errors (bugs) in your code.
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.
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
.
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
.
A visual showing the steps: