PY-1.2-BP2-MQ20

Objective

Let's begin by reviewing our objective.

Create a Pygame script that opens a window. In the event loop, check for a KEYDOWN event. If one occurs, print the specific key that was pressed to the console by accessing the event's key attribute. You must use only the pygame library for this task; do not use or reference the turtle module.

Check Spec

Next, we'll run pytest to see the failing tests. This confirms the engineering specification we need to meet.

Test Results:

  • test_keydown_event_prints_key_attribute

Implement `main.py`

Now, let's build the solution by following the TODO comments in the skeleton code. Each implementation slide covers all TODOs for a single file, referencing the most relevant documentation sections to review for the task.

Step by step checklist:

  1. Iterate through the list of events from pygame.event.get().
  2. For each event, check its 'type' attribute.
  3. If the type is pygame.QUIT, set 'running' to False to exit the loop.
  4. If the type is pygame.KEYDOWN, print the event's 'key' attribute.

The following documentation sections are going to be helpful:

  • The Game Loop and Event Queue
  • Handling Keyboard Events
  • Identifying the Specific Key

Validate

With the code in place, let's run the tests again to validate our work.

Test Results:

  • test_keydown_event_prints_key_attribute All tests passed!

Documentation

Pygame Event Handling and Drawing Cheat Sheet

This document provides a quick reference for key concepts and commands used in Pygame to handle user input and draw shapes.

The Game Loop and Event Queue

Pygame programs typically run inside a main loop. In each iteration (frame), the program checks for events, updates the game state, and draws to the screen.

User actions (like key presses, mouse clicks, or closing the window) generate events. These events are placed in an event queue. You process events by iterating through this queue.

import pygame

# Basic setup (usually done before the loop)
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("My Pygame Window")

running = True
while running:
    # --- Event Handling ---
    # Get all user events from the queue
    for event in pygame.event.get():
        # Check if the user clicked the window's close button
        if event.type == pygame.QUIT:
            running = False
        
        # --- Your event handling code goes here ---
        # Check for key presses, mouse clicks, etc.

    # --- Game State Updates (if any) ---
    # e.g., move a character, update a score

    # --- Drawing ---
    # Clear the screen (optional, but common)
    screen.fill((255, 255, 255)) # Fill with white

    # --- Your drawing code goes here ---
    # Draw shapes, images, etc. based on the current state

    # --- Update Display ---
    # Show the newly drawn frame
    pygame.display.flip() # or pygame.display.update()

# Quit Pygame when the loop ends
pygame.quit()

Event Queue Diagram

Handling Keyboard Events

Keyboard actions generate KEYDOWN (key pressed down) and KEYUP (key released) events.

Detecting a Key Press

To check if any key was pressed down in the current frame, check the event's type:

for event in pygame.event.get():
    if event.type == pygame.KEYDOWN:
        # A key was pressed!
        print("A key was pressed.")

Identifying the Specific Key

When a KEYDOWN or KEYUP event occurs, the event object has a key attribute. This attribute holds an integer value representing the specific key. Pygame provides constants for most keys, starting with pygame.K_.

for event in pygame.event.get():
    if event.type == pygame.KEYDOWN:
        # Print the integer code of the pressed key
        print(f"Key code: {event.key}")

Checking for a Specific Key

Use the event.key attribute in a conditional statement to react to a particular key:

# Example: Reacting to the Spacebar
for event in pygame.event.get():
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_SPACE:
            print("Spacebar pressed!")

# Example: Reacting to the 'A' key
for event in pygame.event.get():
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_a:
            print("'A' key pressed!")

Common key constants include:

  • pygame.K_a, pygame.K_b, ..., pygame.K_z for letter keys.
  • pygame.K_0, pygame.K_1, ..., pygame.K_9 for number keys.
  • pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN for arrow keys.
  • pygame.K_ESCAPE for the Esc key.
  • pygame.K_RETURN for the Enter key.
  • pygame.K_SPACE for the spacebar.

Handling Mouse Events

Mouse actions also generate events.

Inspecting Mouse Attributes

  • pygame.MOUSEMOTION: Occurs when the mouse is moved.
  • pygame.MOUSEBUTTONDOWN: Occurs when a mouse button is pressed.
  • pygame.MOUSEBUTTONUP: Occurs when a mouse button is released.
  • event.pos: A tuple (x, y) with the mouse coordinates. Available for motion and button events.
  • event.button: An integer representing the button. Available for button events (MOUSEBUTTONDOWN, MOUSEBUTTONUP).
    • 1: Left button
    • 2: Middle button
    • 3: Right button
    • 4: Scroll wheel up
    • 5: Scroll wheel down
for event in pygame.event.get():
    # Check for mouse movement
    if event.type == pygame.MOUSEMOTION:
        print(f"Mouse moved to: {event.pos}")

    # Check for a mouse click (button down)
    if event.type == pygame.MOUSEBUTTONDOWN:
        print(f"Mouse button {event.button} pressed at {event.pos}")

Mouse Event Demo (Console Output)

Conditional Logic with Mouse Buttons

Use the event.button attribute to perform different actions based on which mouse button was clicked:

for event in pygame.event.get():
    if event.type == pygame.MOUSEBUTTONDOWN:
        if event.button == 1: # Left click
            print("Left button clicked!")
        elif event.button == 3: # Right click
            print("Right button clicked!")
        else: # Other buttons (middle, scroll wheel)
            print(f"Other button ({event.button}) clicked.")

Core Python Concepts in Action

Pygame event handling and game logic rely heavily on fundamental Python concepts.

  • Boolean State Management: Using True/False variables to track states (like whether a light is on or off, or if a circle should be drawn). Toggling a boolean variable is done with variable = not variable.

    # Simulates flipping a light switch
    is_light_on = False
    # ... user types 'flip' ...
    is_light_on = not is_light_on # Toggles from False to True, or True to False
    
  • Stateful Loops (Accumulators): Using a loop (like while True:) that continues until a condition is met (like a 'quit' command or a running = False flag). Variables inside or outside the loop can accumulate or change state over time.

    # Simulates keeping score
    total_score = 0
    while True:
        user_input = input("Enter points or 'q': ")
        if user_input.lower() == 'q':
            break # Exit the loop
        try:
            points = int(user_input)
            total_score += points # Add to the running total
            print(f"Current score: {total_score}")
        except ValueError:
            print("Invalid input.")
    print(f"Final score: {total_score}")
    
  • Complex Conditional Logic: Using if/elif/else structures to handle multiple distinct conditions or ranges of values.

    # Simulates assigning a grade based on a score
    score = 85 # Example score
    if score >= 90:
        grade = 'A'
    elif score >= 80:
        grade = 'B'
    elif score >= 70:
        grade = 'C'
    elif score >= 60:
        grade = 'D'
    else:
        grade = 'F'
    print(f"The grade is: {grade}")
    
  • Error Handling (try/except): Using try and except blocks to gracefully handle potential errors, such as trying to convert non-numeric input to an integer.

    Try Except Explanation

Drawing Shapes

The pygame.draw module provides functions for drawing common shapes onto a Surface (like your screen object).

Pygame Draw Module

  • Drawing a Rectangle: pygame.draw.rect(surface, color, rect, width=0)

    • surface: The Surface to draw on (e.g., screen).
    • color: The color (R, G, B tuple).
    • rect: A pygame.Rect object or a tuple (x, y, width, height) defining the rectangle's position and size.
    • width: If 0 (default), fills the rectangle. If > 0, draws the outline of the specified width.
    # Example: Draw a blue rectangle outline
    pygame.draw.rect(screen, (0, 0, 255), (100, 100, 200, 100), 5)
    
  • Drawing a Circle: pygame.draw.circle(surface, color, center_pos, radius, width=0)

    • surface: The Surface to draw on.
    • color: The color.
    • center_pos: A tuple (x, y) for the center of the circle.
    • radius: The radius of the circle.
    • width: If 0 (default), fills the circle. If > 0, draws the outline.
    # Example: Draw a filled green circle
    pygame.draw.circle(screen, (0, 255, 0), (400, 300), 50)
    
  • Drawing a Line: pygame.draw.line(surface, color, start_pos, end_pos, width=1)

    • surface: The Surface to draw on.
    • color: The color.
    • start_pos: A tuple (x, y) for the start point.
    • end_pos: A tuple (x, y) for the end point.
    • width: The thickness of the line.
    # Example: Draw a thick red line
    pygame.draw.line(screen, (255, 0, 0), (50, 50), (750, 550), 10)
    

Connecting Events to Drawing

You can use information from events (like event.pos or event.button) to determine what or where to draw.

Drawing on Click

To draw a shape where the user clicks, get the position from event.pos and pass it to a drawing function:

# In the event loop:
for event in pygame.event.get():
    if event.type == pygame.MOUSEBUTTONDOWN:
        # Draw a red circle exactly where the user clicked
        click_position = event.pos
        pygame.draw.circle(screen, (255, 0, 0), click_position, 10)

Draw on Click

Preserving State Between Events

Sometimes you need to remember information from one event (e.g., a mouse click position) and use it later when another event occurs (e.g., a key press). To do this, store the information in a variable that exists outside the event loop, typically initialized before the while running: loop starts.

# Variable outside the loop to store state
last_click_pos = None

while running:
    for event in pygame.event.get():
        if event.type == pygame.MOUSEBUTTONDOWN:
            # Store the position from this event
            last_click_pos = event.pos
            print(f"Stored position: {last_click_pos}")

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_d: # Example: 'D' key
                # Use the stored position from a previous event
                if last_click_pos:
                    print(f"Drawing at stored position: {last_click_pos}")
                    # Example: Draw something at last_click_pos
                    # pygame.draw.circle(screen, (0, 0, 0), last_click_pos, 5)

Preserving State