PY-1.2-BP6-MQ50

Make a Drawing Function

We will make a special block of code called a 'function' that draws everything on the screen. This will make our main program cleaner and easier to understand. We will use the pygame library for this.

See If It Works

Let's check if your program is working. We do this by running a special test. Open your terminal and type this command:

pytest module-1.2/blueprint-PY-1.2-BP6/quest-50

What you will see:

  • test_function_existence_and_signature
  • test_draw_scene_internal_logic
  • test_main_loop_calls_draw_scene

These tests will show a red X because your code is not finished yet.

Write Your Code

Open the main.py file. You need to move the code that draws the background and the shape into the draw_scene function. Then, call draw_scene from the main part of your program.

Here's how you can make a function that fills the screen:

import pygame

def fill_screen_with_color(screen_to_fill, color_to_use):
    screen_to_fill.fill(color_to_use)

# In your main program:
# fill_screen_with_color(my_game_screen, (0, 0, 0)) # Fill with black

Here's how you can draw a rectangle:

import pygame

# ... setup code ...

box_color = (0, 0, 255) # Blue
box_x = 250
box_y = 150
box_width = 100
box_height = 100

pygame.draw.rect(my_screen, box_color, (box_x, box_y, box_width, box_height))

Use these examples to help you make your drawing function.

Check Your Work Again

Now that you've written your code, let's check it again. Type this command in your terminal:

pytest module-1.2/blueprint-PY-1.2-BP6/quest-50
  • test_function_existence_and_signature
  • test_draw_scene_internal_logic
  • test_main_loop_calls_draw_scene

Documentation

Blueprint PY-1.2-BP6: The Inspector - Debugging and Refactoring

This document provides a quick reference for key concepts and techniques covered in this blueprint, focusing on finding and fixing errors (debugging) and improving code structure (refactoring).

What is a Bug?

A bug is an error in a computer program that causes it to produce an incorrect or unexpected result, or to behave in an unintended way. Bugs are a normal part of the software development process.

Debugging is the process of finding and resolving defects or problems within a computer program.

A simple diagram showing an input going into a 'buggy' process and producing a wrong output, then going into a 'fixed' process and producing the correct output.

Debugging Methods

Debugging involves investigating your code to understand why it isn't behaving as expected.

Method 1: Print Statement Debugging

Using print() statements is a fundamental technique to inspect the state of your program and understand its execution flow.

  • Purpose: Check the value of variables at specific points, confirm if certain parts of the code are being reached, or track the order of execution.
  • How to use: Insert print() calls with descriptive messages and variable values where you suspect an issue might be occurring.
# Example: Checking a variable's value inside a loop
count = 0
while count < 5:
    print(f"Current count: {count}") # Check the value of count
    # ... other code ...
    count += 1
print("Loop finished.") # Confirm the loop completed

Method 2: The IDE Debugger

Your code editor (like VS Code) has powerful, built-in tools for debugging.

  • Breakpoint: A marker you place on a line of code. When you run the debugger, your program will pause its execution at that exact line.
  • Step Over: A control that executes the currently paused line and immediately pauses again on the next line. This lets you walk through your code one line at a time.
  • Variable Inspector: A panel that shows you the live values of all your variables at the moment the program is paused.

A GIF showing a breakpoint being set in VS Code, the debugger launching, and the 'Step Over' button being clicked to advance line-by-line, with the Variable Inspector panel visible.

What is Refactoring?

Refactoring is the process of restructuring existing computer code without changing its external behavior. The goal is to improve nonfunctional attributes of the software, such as readability, maintainability, and simplicity.

Refactoring is like simplifying an algebraic expression; the value is the same, but the form is cleaner and easier to work with.

Image comparing a messy, unrefactored code snippet with a clean, refactored version that produces the same output.

Refactoring Techniques

Technique 1: Extract Function

If a block of code performs a single, well-defined task, move it into its own function.

  • Purpose: Makes the original code (e.g., a main loop) shorter and easier to read. Allows the extracted logic to be reused elsewhere.
  • How to use:
    1. Identify a block of code that does one thing.
    2. Create a new function.
    3. Move the code block into the new function.
    4. Identify any variables the code block needs from its original context (inputs) and any results it produces (outputs). Define these as function parameters and return values.
    5. Replace the original code block with a call to the new function, passing the necessary inputs.
# Before Refactoring (example)
# ... inside a loop ...
# Calculate area
width = 10
height = 20
area = width * height
print(f"Area: {area}")
# ... rest of loop ...

# After Refactoring (example)
def calculate_area(width, height):
    """Calculates the area of a rectangle."""
    area = width * height
    return area

# ... inside the loop ...
width = 10
height = 20
area = calculate_area(width, height) # Call the new function
print(f"Area: {area}")
# ... rest of loop ...

A diagram showing a large block of code with a smaller section highlighted, and an arrow pointing to a separate function box containing that smaller section, with the original block now showing a call to the new function.

Technique 2: Improve Readability

Write code that is easy for others (and your future self) to understand.

  • Purpose: Reduces the time and effort needed to read, understand, and modify code.
  • How to use:
    • Use clear, descriptive variable and function names (e.g., total_price instead of tp).
    • Break down complex calculations or logic into smaller, named steps using intermediate variables.
# Less readable (example)
# Calculate final price with tax
p = 100
t = p + (p * 0.08)
final = round(t, 2)

# More readable (example)
# Calculate final price with tax
subtotal = 100
tax_rate = 0.08
tax_amount = subtotal * tax_rate
total_price = subtotal + tax_amount
final_price_rounded = round(total_price, 2)

Pygame/Turtle Specifics

Handling Events

In Pygame, the event loop processes user input and system events.

  • Check the event.type attribute to identify the type of event (e.g., pygame.QUIT, pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN).
  • For key events (pygame.KEYDOWN, pygame.KEYUP), use event.key to identify which key was pressed (e.g., pygame.K_r).
  • For mouse button events (pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP), use event.pos to get the mouse coordinates.
# Example Pygame event loop structure
import pygame
# ... setup ...
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN:
            # Check event.key here
            pass
        if event.type == pygame.MOUSEBUTTONDOWN:
            # Access event.pos here
            pass
    # ... drawing and update ...

Drawing Order

In graphics programming, the order in which you draw is crucial. Objects drawn later appear on top of objects drawn earlier.

  • Always draw the background first to clear the screen from the previous frame.
  • Then, draw all other game elements (shapes, sprites, text) on top of the background.
  • Finally, update the display (pygame.display.flip() or pygame.display.update()) to show the completed frame.
# Example Pygame drawing order
# ... inside the game loop ...
# 1. Draw background
screen.fill(BACKGROUND_COLOR)

# 2. Draw game elements
pygame.draw.rect(screen, PLAYER_COLOR, player_rect)
# ... draw other objects ...

# 3. Update display
pygame.display.flip()
# ...

Using Global Variables

Variables defined outside of any function are called global variables. To modify a global variable from inside a function, you must use the global keyword.

  • Reading a global variable inside a function does not require the global keyword.
  • Writing to (assigning a new value to) a global variable does require the global keyword inside the function.
# Example using global
counter = 0 # Global variable

def increment_counter():
    global counter # Declare intent to modify the global 'counter'
    counter += 1

def read_counter():
    # No 'global' needed to just read the value
    print(f"Current counter value: {counter}")

# ...
increment_counter()
read_counter() # Output: Current counter value: 1

Adding Sound with Pygame Mixer

Adding sound effects can make an application more engaging.

  • Initialize: Start the mixer module.
    import pygame
    pygame.mixer.init()
    
  • Load: Load a sound file into a pygame.mixer.Sound object.
    # Assumes 'sound.wav' is in the same directory
    sound_effect = pygame.mixer.Sound("sound.wav")
    
  • Play: Call the .play() method on the sound object when the corresponding action occurs.
    # Example inside a function triggered by an event
    def on_action():
        # ... perform action ...
        sound_effect.play() # Play the sound