PY-1.2-BP4-MQ60

Add an Eraser

We will add a new tool to our drawing program: an eraser! When you press the 'E' key, everything you have drawn will disappear. We will use the pygame library for this.

Missing Image

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-BP4/quest-60

What you will see:

  • test_drawing_on_mouse_motion
  • test_erase_on_e_key_press
  • test_no_drawing_if_mouse_not_down
  • test_app_quits_on_quit_event

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 add code to make your program draw lines when you move your mouse and clear the screen when you press a key.

Your program will run in a loop, checking for things like mouse clicks, mouse movement, and key presses.

Here's how you can check for different events:

import pygame

# Inside your game loop:
for event in pygame.event.get():
    if event.type == pygame.QUIT:
        # This means the user wants to close the window
        pass
    elif event.type == pygame.MOUSEBUTTONDOWN:
        # This happens when you click the mouse button down
        print("Mouse button pressed!")
    elif event.type == pygame.MOUSEBUTTONUP:
        # This happens when you let go of the mouse button
        print("Mouse button released!")
    elif event.type == pygame.MOUSEMOTION:
        # This happens when you move the mouse
        print(f"Mouse moved to {event.pos}")
    elif event.type == pygame.KEYDOWN:
        # This happens when you press a key down
        if event.key == pygame.K_a:
            print("You pressed the 'a' key!")

To draw a line, you need a starting point and an ending point, a color, and a thickness:

import pygame

# ... setup code ...

line_color = (0, 0, 0) # Black
start_point = (10, 10)
end_point = (50, 50)
line_thickness = 2

pygame.draw.line(my_screen, line_color, start_point, end_point, line_thickness)

To clear the screen, you can fill it with a color:

# Inside your game loop, when you want to clear:
my_screen.fill((255, 255, 255)) # Fills with white

Remember to update the display after drawing:

import pygame
# ... drawing code ...
pygame.display.flip()

Use these examples to help you make your drawing program.

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-BP4/quest-60
  • test_drawing_on_mouse_motion
  • test_erase_on_e_key_press
  • test_no_drawing_if_mouse_not_down
  • test_app_quits_on_quit_event

Documentation

Blueprint PY-1.2-BP4 Documentation

This document provides a reference for key concepts and functions used in the micro-quests for Blueprint PY-1.2-BP4.


Pygame Basics

Initialization and Window

Before using Pygame functions, the library must be initialized. A display window (screen) is then created.

import pygame

# Initialize Pygame
pygame.init()

# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# Create the screen surface
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

# Set the window title
pygame.display.set_caption("My Pygame Window")

Updating the Display

After drawing, you must update the display to make your changes visible. It's common to fill the background first to clear the previous frame.

# Inside the main game loop, after drawing commands:
screen.fill((255, 255, 255)) # Fill with white
# ... drawing commands ...
pygame.display.flip() # Make the drawn content visible

Drawing Shapes (pygame.draw)

The pygame.draw module provides functions for drawing simple shapes onto a Surface (like your screen). The first argument is always the surface to draw on.

Examples of Pygame drawing primitives (rect, circle, line, polygon)

  • Rectangle: pygame.draw.rect(surface, color, (x, y, width, height)) or pygame.draw.rect(surface, color, rect_object)
    • color is an RGB tuple (e.g., (255, 0, 0) for red).
    • (x, y, width, height) is a tuple defining the top-left corner, width, and height.
  • Circle: pygame.draw.circle(surface, color, (center_x, center_y), radius)
    • (center_x, center_y) is a tuple for the circle's center point.
    • radius is the circle's radius in pixels.
  • Lines: pygame.draw.lines(surface, color, closed, list_of_points, thickness)
    • closed is True or False. If True, it connects the last point to the first.
    • list_of_points is a list of coordinate tuples, e.g., [(10, 20), (50, 60), (80, 30)].
    • thickness is the line width in pixels.
  • Polygon: pygame.draw.polygon(surface, color, list_of_points)
    • list_of_points is a list of coordinate tuples defining the vertices of the polygon. The points are automatically connected, and the shape is filled.
# Example drawing calls:
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

# Draw a red rectangle
pygame.draw.rect(screen, RED, (100, 100, 50, 80))

# Draw a green circle
pygame.draw.circle(screen, GREEN, (400, 300), 25)

# Draw blue lines
points = [(50, 50), (150, 50), (100, 150)]
pygame.draw.lines(screen, BLUE, False, points, 2)

The pygame.Rect Object

The pygame.Rect object is a fundamental Pygame class for storing and manipulating rectangular areas. It holds integer coordinates for the top-left corner, width, and height. It also provides convenient attributes for other corners, the center, and dimensions, which are calculated automatically.

Pygame Rect attributes

A Rect object can be created using pygame.Rect(left, top, width, height).

# Example Rect creation and attributes:
my_rect = pygame.Rect(100, 150, 200, 80)

print(f"x: {my_rect.x}")
print(f"y: {my_rect.y}")
print(f"width: {my_rect.width}")
print(f"height: {my_rect.height}")
print(f"center: {my_rect.center}")
print(f"topleft: {my_rect.topleft}")
print(f"bottomright: {my_rect.bottomright}")

# You can also draw a Rect object directly
# pygame.draw.rect(screen, BLUE, my_rect)

Core Programming Patterns

The Game Loop (Input, Update, Render)

Most real-time applications, including games, run on a continuous loop that processes three main phases:

  1. Handle Input: Check for user actions (key presses, mouse clicks, window closing).
  2. Update State: Change game variables (like position, score, health) based on input or game logic.
  3. Render: Draw the current state of the game to the screen.

Input -> Update -> Render cycle

# Basic structure inside the main game loop:
running = True
while running:
    # 1. Handle Input
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        # Handle other events (key presses, mouse clicks)

    # 2. Update State
    # Check key states for continuous movement
    # Update player position, check collisions, update scores, etc.

    # 3. Render
    screen.fill(BACKGROUND_COLOR) # Clear previous frame
    # Draw game objects (player, enemies, score, etc.)
    pygame.display.flip() # Show the newly drawn frame

Managing State with Variables and Lists

Game state refers to all the variables that describe the current condition of the game (e.g., player position, score, whether an object is active, a list of items).

Lists for Storing State

Python lists are useful for keeping track of a sequence of game states, such as the history of positions for a drawing trail.

# Initialize an empty list
trail_points = []

# Add a position to the list
current_position = (100, 100)
trail_points.append(current_position)

# Add another position
new_position = (110, 105)
trail_points.append(new_position)

print(trail_points) # Output: [(100, 100), (110, 105)]

# You can then use this list with drawing functions like pygame.draw.lines().
# if len(trail_points) > 1:
#     pygame.draw.lines(screen, BLUE, False, trail_points, 2)

# To clear a list:
# trail_points.clear()
# or
# trail_points = []

Boolean Flags

A boolean variable (True or False) can act as a "flag" to control program behavior. Toggling a boolean means flipping its value (True becomes False, False becomes True).

Boolean Flag Example

# Global flag
is_active = True

def toggle_active():
    global is_active # Use global to modify the variable outside the function
    is_active = not is_active # Toggles the value
    print(f"Is active: {is_active}")

# Example usage:
# toggle_active() # is_active becomes False
# toggle_active() # is_active becomes True

You can use an if statement to make code run only when a flag is True.

# Inside a function or loop:
if is_active:
    # Code here only runs if is_active is True
    print("Action performed because active.")
else:
    # Code here runs if is_active is False
    print("Action skipped because inactive.")

Cycling through Lists

You can use an index variable and the modulo operator (%) to cycle through items in a list, looping back to the beginning when you reach the end.

items = ['apple', 'banana', 'cherry']
current_index = 0

def get_next_item():
    global current_index
    # Calculate the next index, wrapping around using modulo
    current_index = (current_index + 1) % len(items)
    # Return the item at the new index
    return items[current_index]

# Example usage:
# print(get_next_item()) # Prints 'banana', index becomes 1
# print(get_next_item()) # Prints 'cherry', index becomes 2
# print(get_next_item()) # Prints 'apple', index becomes 0 (wraps around)

Pygame Input Handling

Event-Based Input (pygame.event.get())

The pygame.event.get() function retrieves events from Pygame's event queue. This is typically done once per frame inside the main game loop. You then iterate through the list of events to check their type.

# Inside the main game loop:
for event in pygame.event.get():
    # Check event.type here
    pass

Checking for QUIT

The pygame.QUIT event is generated when the user clicks the close button on the window. Checking for this event is essential for allowing the user to exit the application gracefully.

# Inside the event loop:
if event.type == pygame.QUIT:
    running = False # Set a flag to exit the main game loop

Handling KEYDOWN

The pygame.KEYDOWN event is generated once when a key is pressed down. The event.key attribute contains the constant representing the specific key that was pressed (e.g., pygame.K_LEFT, pygame.K_SPACE, pygame.K_c).

# Inside the event loop:
if event.type == pygame.KEYDOWN:
    if event.key == pygame.K_LEFT:
        # Code to handle left arrow key press
        pass
    elif event.key == pygame.K_SPACE:
        # Code to handle spacebar press
        pass
    # Add more elif checks for other keys

State-Based Input (pygame.key.get_pressed())

The pygame.key.get_pressed() function returns a sequence (like a list or tuple) containing the state of every keyboard key. Each item in the sequence is True if the corresponding key is currently held down, and False otherwise. This is useful for continuous actions like movement, as you can check the key's state every frame.

# Inside the main game loop, but OUTSIDE the event loop:
keys = pygame.key.get_pressed()

if keys[pygame.K_LEFT]:
    # Code to move left continuously while the key is held
    pass
if keys[pygame.K_RIGHT]:
    # Code to move right continuously while the key is held
    pass
# Use separate 'if' statements for each direction to allow diagonal movement

Turtle Graphics Basics

Initialization and Screen

You need a Screen object to represent the drawing window and a Turtle object (often called a "stylus" or "pen") to do the drawing.

Turtle Core Structure

import turtle

# Get the screen object
screen = turtle.Screen()
screen.title("My Turtle Window")

# Create a turtle object
stylus = turtle.Turtle()

Turtle Control Commands

The turtle object has methods to control its movement, drawing state, and appearance.

Turtle Control commands list

  • Movement:
    • stylus.forward(distance): Move forward by distance pixels.
    • stylus.backward(distance): Move backward.
    • stylus.left(degrees): Turn left by degrees.
    • stylus.right(degrees): Turn right by degrees.\
    • stylus.goto(x, y): Move directly to the specified coordinates.
  • Drawing:
    • stylus.penup(): Lift the pen up (stop drawing when moving).
    • stylus.pendown(): Put the pen down (start drawing when moving).
    • stylus.pencolor("color_name"): Set the color of the line drawn. Can use color names (like "red", "blue") or hex codes.
    • stylus.shape("shape_name"): Change the appearance of the turtle cursor (e.g., "arrow", "turtle", "circle", "square", "triangle", "classic").
  • Resetting:
    • stylus.clear(): Erase the turtle's drawings from the screen, but the turtle's position and state remain unchanged.
    • stylus.reset(): Erase drawings and reset the turtle to its initial state (center, facing right, pen down).
# Example Turtle commands:
stylus.pencolor("green")
stylus.forward(100)
stylus.left(90)
stylus.penup()
stylus.forward(50)
stylus.pendown()
stylus.pencolor("purple")
stylus.circle(30)
stylus.clear() # Erase everything drawn so far

Event Handling (Binding Functions)

Turtle Graphics uses a simple event binding system. You define functions (handlers) and tell the screen which event should trigger which function.

Turtle Core Structure diagram

# Define a function to be called on an event
def handle_click(x, y):
    print(f"Clicked at ({x}, {y})")

# Tell the screen to start listening for events
screen.listen()

# Bind the left mouse click to the handler function
screen.onclick(handle_click)

# Bind a key press to a handler function
# The second argument is the key name (e.g., "space", "Up", "Down", "Left", "Right", "c")
def handle_space_press():
    print("Space was pressed!")

screen.onkey(handle_space_press, "space")

# Bind mouse drag to a handler function
# The handler receives the x, y coordinates where the mouse is dragged
def handle_drag(x, y):
    print(f"Dragging at ({x}, {y})")
    # stylus.goto(x, y) # Example: move the turtle to the drag position

turtle.ondrag(handle_drag)