PY-1.2-BP7-MQ20

Draw and Clear

We will make a program where you can draw lines with the arrow keys. If you press the 'C' key, the screen will become white and clear, but your drawing tool will stay in the same spot. 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-BP7/quest-20

What you will see:

  • test_drawing_clearing_and_pen_position_logic
  • test_immediate_quit

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 make the drawing tool move with the arrow keys and clear the screen when you press the 'C' key.

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.KEYDOWN:
        # This happens when you press a key down
        if event.key == pygame.K_a:
            print("You pressed the 'a' key!")

To draw a small square, you can use pygame.draw.rect():

import pygame

# ... setup code ...

box_color = (0, 0, 0) # Black
box_x = 100
box_y = 100
box_size = 5

pygame.draw.rect(my_screen, box_color, (box_x, box_y, box_size, box_size))

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

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-BP7/quest-20
  • test_drawing_clearing_and_pen_position_logic
  • test_immediate_quit

Documentation

Pygame and Turtle Documentation

This document provides a reference for creating graphical applications using the Pygame and Turtle libraries in Python.

Pygame Fundamentals

Pygame applications typically follow a structure called the game loop. This loop runs continuously, handling events, updating the program's state, and drawing to the screen.

The Game Loop

The core of a Pygame application is the while running: loop.

running = True
while running:
    # 1. Event Handling
    # Process user input (keyboard, mouse, window close)

    # 2. Update State
    # Change positions, scores, etc. (often based on continuous input)

    # 3. Drawing
    # Clear the screen or draw shapes/images

    # 4. Update Display
    # Show the drawn content on the screen

    # 5. Control Frame Rate
    # Limit how fast the loop runs

Initialization and Setup

Before the game loop, Pygame needs to be initialized, and the display window must be created.

import pygame
import sys # Often needed for sys.exit()

# Constants (like screen dimensions, colors) are usually defined here

def run_game():
    pygame.init() # Initialize Pygame modules

    # Create the display surface (the window)
    screen_width = 800
    screen_height = 600
    screen = pygame.display.set_mode((screen_width, screen_height))

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

    # Game state variables (position, color, etc.) are initialized here

    # Fill the background initially (can be done once or every frame)
    background_color = (0, 0, 0) # Black
    screen.fill(background_color)

    # Clock for controlling frame rate
    clock = pygame.time.Clock()

    running = True
    while running:
        # ... game loop ...
        clock.tick(60) # Limit to 60 frames per second

    # Clean up Pygame resources
    pygame.quit()
    # sys.exit() # Use this in a real application to close the process

Drawing Shapes and Backgrounds

Drawing happens on the screen surface. Shapes are drawn using pygame.draw.

# Inside the game loop, after updating state

# To clear the screen each frame (for non-trail drawing)
screen.fill(BACKGROUND_COLOR)

# Draw a circle
# pygame.draw.circle(surface, color, center_position, radius)
pygame.draw.circle(screen, (255, 255, 255), (x, y), 10) # White circle at (x, y) with radius 10

# Draw a line
# pygame.draw.line(surface, color, start_pos, end_pos, width)
pygame.draw.line(screen, (0, 255, 0), (x1, y1), (x2, y2), 5) # Green line from (x1, y1) to (x2, y2) with width 5

Updating the Display

After drawing, pygame.display.flip() or pygame.display.update() makes the changes visible on the screen.

# Inside the game loop, after all drawing commands
pygame.display.flip() # Updates the full screen
# pygame.display.update() # Can update specific areas, flip() is simpler

Handling User Input (Pygame)

User input comes in two main forms: events (single occurrences like a key press or mouse click) and continuous state (whether a key is currently held down).

Event Queue (Single Actions)

The for event in pygame.event.get(): loop processes events that have occurred since the last frame. This is suitable for actions that should happen once per press, like toggling a state or clearing the screen.

# Inside the 'for event in pygame.event.get():' loop
if event.type == pygame.QUIT:
    running = False # Stop the game loop

elif event.type == pygame.KEYDOWN:
    # A key was pressed down
    if event.key == pygame.K_c:
        # The 'c' key was pressed
        screen.fill(BACKGROUND_COLOR) # Example: Clear screen

elif event.type == pygame.MOUSEBUTTONDOWN:
    # A mouse button was pressed down
    if event.button == 1: # 1 is the left mouse button
        print("Left mouse button clicked at", event.pos) # event.pos is the mouse coordinates

elif event.type == pygame.MOUSEBUTTONUP:
    # A mouse button was released
    if event.button == 1:
        print("Left mouse button released")

elif event.type == pygame.MOUSEMOTION:
    # The mouse was moved
    print("Mouse moved to", event.pos)

Continuous Input (pygame.key.get_pressed())

To check which keys are currently being held down on every frame, use pygame.key.get_pressed(). This is suitable for smooth, continuous movement.

# Inside the main while loop, but OUTSIDE the 'for event in pygame.event.get():' loop

keys = pygame.key.get_pressed() # Get the state of all keys

move_speed = 5
x, y = 100, 100 # Example position variables

if keys[pygame.K_LEFT]:
    x -= move_speed # Move left if left arrow is held
if keys[pygame.K_RIGHT]:
    x += move_speed # Move right if right arrow is held
if keys[pygame.K_UP]:
    y -= move_speed # Move up if up arrow is held
if keys[pygame.K_DOWN]:
    y += move_speed # Move down if down arrow is held

# After checking keys, use the updated x, y for drawing

A dot moving smoothly in response to held-down arrow keys

Managing Program State (Pygame)

State variables track the current condition of your program (e.g., position, color, whether drawing is active).

Tracking Position

Variables like x, y, dot_x, pen_x store the coordinates of an object. Update these variables based on input.

# Initial state
x, y = SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2

# Update based on input (example using get_pressed)
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
    x -= MOVE_SPEED
# ... other movement checks ...

# Use the updated position for drawing
pygame.draw.circle(screen, COLOR, (x, y), RADIUS)

Tracking Current Color

A variable can hold the current color to be used for drawing.

# Initial state
current_color = (255, 0, 0) # Start with Red

# Update based on input (example using KEYDOWN event)
if event.type == pygame.KEYDOWN:
    if event.key == pygame.K_g:
        current_color = (0, 255, 0) # Change to Green

# Use the current color for drawing
pygame.draw.circle(screen, current_color, (x, y), RADIUS)

Cycling Through a List

To cycle through a predefined list of values (like colors), use an index variable and the modulo operator (%).

# Initial state
color_list = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] # Red, Green, Blue
color_index = 0
current_color = color_list[color_index]

# Update based on input (example using KEYDOWN event for spacebar)
if event.type == pygame.KEYDOWN:
    if event.key == pygame.K_SPACE:
        # Increment index and wrap around using modulo
        color_index = (color_index + 1) % len(color_list)
        # Update the current color variable
        current_color = color_list[color_index]

# Use current_color for drawing

Drawing color changing through a list when spacebar is pressed

Boolean Toggles (On/Off Switch)

A boolean variable (True or False) can track an on/off state, like whether the pen is currently drawing.

# Initial state
is_drawing = True # Pen starts 'down'

# Toggle the state based on input (example using KEYDOWN event for 'p')
if event.type == pygame.KEYDOWN:
    if event.key == pygame.K_p:
        is_drawing = not is_drawing # Flip the boolean value

# Perform an action conditionally based on the state
if is_drawing:
    # Draw only if is_drawing is True
    pygame.draw.circle(screen, COLOR, (x, y), RADIUS)

Cursor moving, then 'p' pressed, cursor moves without drawing, 'p' pressed again, cursor draws

Turtle Graphics (Event-Driven)

The turtle library uses a different approach than Pygame's continuous loop. You define functions (called handlers) for specific actions and then tell the screen to run those functions when certain events (like key presses) occur.

The Turtle Model (Handlers, Listeners)

Instead of a while loop, Turtle waits for events and calls the functions you've assigned to them.

import turtle

# Global variables for the pen and screen are common
pen = turtle.Turtle()
screen = turtle.Screen()

# Handler functions are defined here

def move_forward():
    # This function will be called when a specific key is pressed
    pen.forward(20)

# The main function sets up the listeners
def main():
    screen.listen() # Tell the screen to listen for events
    screen.onkey(fun=move_forward, key="Up") # Bind the 'Up' arrow key to move_forward function

# Start the Turtle event loop
if __name__ == "__main__":
    main()
    screen.mainloop() # Keep the window open and responsive to events

Diagram showing keys triggering handler functions

Initialization

Create the Turtle object (the pen) and the Screen object.

import turtle

pen = turtle.Turtle()
screen = turtle.Screen()

# Optional setup
screen.setup(width=600, height=500)
screen.bgcolor("white")
pen.speed("fastest") # Adjust drawing speed

Basic Movement and Turning

The pen object has methods for movement and changing direction.

# Inside a handler function
pen.forward(distance)
pen.backward(distance)
pen.left(angle) # Turn counter-clockwise
pen.right(angle) # Turn clockwise

Pen State (Up/Down)

Control whether the turtle draws as it moves.

# Inside a handler function
pen.penup() # Lift the pen (stop drawing)
pen.pendown() # Put the pen down (start drawing)

# Check the current state
if pen.isdown():
    print("Pen is down")

Setting Color

Change the color of the line the turtle draws.

# Inside a handler function
pen.color("red") # Set color by name
pen.color((0, 255, 0)) # Set color by RGB tuple

Resetting Position (home)

Move the turtle back to the center of the screen (0,0) without clearing the drawing.

# Inside a handler function
pen.home() # Go back to (0,0) and face the default direction (usually East)

Binding Keys to Functions (onkey)

Connect a specific key press to a handler function.

# Inside the main setup function
screen.onkey(fun=my_function, key="a") # Call my_function when 'a' is pressed
screen.onkey(fun=another_function, key="space") # Call another_function when spacebar is pressed
screen.onkey(fun=move_forward, key="Up") # Call move_forward when Up arrow is pressed

Starting the Event Loop (mainloop)

This command starts the Turtle graphics window and begins listening for events. It must be the last line in your main execution block (if __name__ == "__main__":).

# At the end of your script
screen.mainloop()

Using Global Variables

Handler functions need access to the pen and screen objects, which are typically created outside the functions. If a handler needs to modify a global variable (like a color index), it must use the global keyword.

# Global state
color_index = 0
COLORS = ["red", "blue"]
pen = turtle.Turtle()

def cycle_color():
    global color_index # Declare intent to modify the global variable
    color_index = (color_index + 1) % len(COLORS)
    pen.color(COLORS[color_index])

def main():
    screen.listen()
    screen.onkey(fun=cycle_color, key="c")
    # ... other bindings ...

# ... rest of the script ...