PY-1.2-BP5-MQ10

Clear the Drawing Screen

We will make a program where you can draw with your mouse. When you press the 'C' key, the whole drawing screen will become black and clear. 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-BP5/quest-10

What you will see:

  • test_screen_clears_on_c_key_press
  • test_drawing_with_mouse
  • test_no_drawing_when_mouse_not_down

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 when you move the mouse and clear the screen when you press the 'C' 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 circle, you need to tell Pygame where the middle of the circle is, how big it is (its radius), and what color it should be:

import pygame

# ... setup code ...

circle_color = (255, 255, 255) # White
circle_center = (150, 200)
circle_radius = 5

pygame.draw.circle(my_screen, circle_color, circle_center, circle_radius)

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

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

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-BP5/quest-10
  • test_screen_clears_on_c_key_press
  • test_drawing_with_mouse
  • test_no_drawing_when_mouse_not_down

Documentation

Control Panel Documentation

This document provides a reference for concepts and techniques used in building interactive applications with Python, focusing on state management, event handling, drawing, and basic GUI elements.

Managing Program State

Program state refers to the data that changes over time and needs to be remembered between updates or events. This could include things like the current drawing color, the pen thickness, whether a button is pressed, or a score.

State can be managed using individual variables or, for related pieces of data, grouped together.

Organizing State with Dictionaries

As the number of state variables grows, organizing them into a dictionary can make the code cleaner and easier to manage. A dictionary stores data as key-value pairs.

# Example: Using individual variables
current_color = 'black'
pen_size = 1
is_pen_down = True

# Example: Using a dictionary
drawing_state = {
    'color': 'black',
    'pen_size': 1,
    'is_pen_down': True
}

Accessing and modifying values in a dictionary is done using the key:

# Accessing a value
current_level = user_profile['level']

# Modifying a value
user_profile['level'] = user_profile['level'] + 1

# Adding a new key-value pair
user_profile['last_login'] = 'today'

[ASSET: Image showing a simple dictionary structure with keys and values]

Handling Events

Interactive programs respond to user actions like key presses or mouse clicks. This is typically managed through an event loop.

The Event Loop (Pygame)

In Pygame, the main loop includes checking pygame.event.get() which returns a list of events that have occurred since the last check.

import pygame

# Inside the main game loop
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        # Process other events here

Keyboard Input (Pygame)

To react to a key press, check for the pygame.KEYDOWN event type. The specific key pressed is available in event.key. Pygame provides constants for keys (e.g., pygame.K_c, pygame.K_SPACE, pygame.K_UP).

# Inside the event loop
if event.type == pygame.KEYDOWN:
    if event.key == pygame.K_c:
        # Action for 'c' key
        print("C key pressed")
    elif event.key == pygame.K_SPACE:
        # Action for spacebar
        print("Spacebar pressed")

Mouse Input (Pygame)

Mouse events include button presses (pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP) and movement (pygame.MOUSEMOTION). The position of the mouse for these events is stored in event.pos.

To check if a specific mouse button is currently held down (useful for drawing or dragging), use pygame.mouse.get_pressed(). This returns a tuple where index 0 is the left button, 1 is the middle, and 2 is the right.

# Inside the event loop
if event.type == pygame.MOUSEBUTTONDOWN:
    if event.button == 1: # Left button
        click_pos = event.pos
        print(f"Left mouse button clicked at {click_pos}")

# Outside the event loop, inside the main while running: loop
if pygame.mouse.get_pressed()[0]: # Check if left button is held down
    current_mouse_pos = pygame.mouse.get_pos()
    print(f"Left mouse button held down at {current_mouse_pos}")

Turtle Events

The turtle module provides simpler ways to handle events, often by binding functions directly to keys or mouse clicks on turtle objects.

  • screen.onkey(function, key_name): Calls function when key_name is pressed. screen.listen() must be called first.
  • turtle_object.onclick(function): Calls function(x, y) when the turtle object is clicked at screen coordinates (x, y).
  • turtle_object.ondrag(function): Calls function(x, y) when the turtle object is clicked and dragged.
import turtle

def handle_up_arrow():
    print("Up arrow pressed")

def handle_turtle_click(x, y):
    print(f"Turtle clicked at {x}, {y}")

# Setup
screen = turtle.Screen()
my_turtle = turtle.Turtle()

# Bind events
screen.onkey(handle_up_arrow, "Up")
my_turtle.onclick(handle_turtle_click)

screen.listen() # Start listening for events

Drawing Basics

Both Pygame and Turtle provide ways to draw on a window.

Pygame Drawing

  • screen.fill(color): Fills the entire screen surface with a single color.
  • pygame.draw.circle(surface, color, center_pos, radius, width=0): Draws a circle. width=0 fills the circle.
  • pygame.draw.line(surface, color, start_pos, end_pos, width): Draws a line.
  • pygame.draw.rect(surface, color, rect_tuple_or_object, width=0): Draws a rectangle. width=0 fills the rectangle.
  • pygame.display.flip(): Updates the entire screen to show what has been drawn.
import pygame

# Assuming screen is already created
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)

screen.fill(WHITE) # Clear to white
pygame.draw.circle(screen, RED, (100, 100), 20) # Draw a red circle
pygame.draw.line(screen, BLACK, (0, 0), (800, 600), 5) # Draw a black line
pygame.display.flip() # Show the drawing

Turtle Drawing

  • turtle.Turtle(): Creates a new turtle object.
  • turtle.Screen(): Gets the drawing window (screen).
  • t.forward(distance), t.backward(distance): Move the turtle.
  • t.left(degrees), t.right(degrees): Turn the turtle.
  • t.penup(), t.pendown(): Lift or lower the pen (stop/start drawing lines).
  • t.goto(x, y): Move the turtle to a specific position.
  • t.clear(): Deletes the turtle's drawings from the screen.
  • t.home(): Moves the turtle to the center (0,0) and sets its direction to default.
  • t.pencolor(color_name_or_tuple): Sets the color of the line drawn.
  • t.pensize(width): Sets the thickness of the line.
  • t.speed(speed_value): Sets the drawing speed (0 is fastest).
  • t.hideturtle(), t.showturtle(): Hide or show the turtle icon.
  • t.write(text, align, font): Write text on the screen at the turtle's position.
  • screen.bgcolor(color_name_or_tuple): Sets the screen's background color.
  • screen.mainloop() or turtle.done(): Keeps the window open and responsive to events.
import turtle

screen = turtle.Screen()
screen.bgcolor("lightblue") # Set background color

t = turtle.Turtle()
t.pencolor("darkgreen") # Set pen color
t.pensize(5) # Set thickness
t.speed(1) # Set speed

t.penup()
t.goto(-100, 0) # Move without drawing
t.pendown()

t.forward(200) # Draw a line
t.left(90)
t.forward(100)

t.penup()
t.goto(0, 50)
t.write("Hello!", align="center", font=("Arial", 16, "normal"))

turtle.done() # Keep window open

[ASSET: Image showing a simple turtle drawing]

Collision Detection

Determining if two graphical objects interact (like a mouse click hitting a button) is called collision detection.

Point-in-Rectangle Logic

To check if a point (px, py) is inside a rectangle defined by its top-left corner (rx, ry), width rw, and height rh, you can use inequalities:

  • The point is horizontally inside if px >= rx AND px < rx + rw.
  • The point is vertically inside if py >= ry AND py < ry + rh.

The point is inside the rectangle only if both horizontal and vertical conditions are true. This convention includes the top and left edges but excludes the bottom and right edges.

def is_point_in_rect(point, rect):
    px, py = point
    rx, ry, rw, rh = rect

    # Check horizontal bounds
    x_match = (px >= rx) and (px < rx + rw)

    # Check vertical bounds
    y_match = (py >= ry) and (py < ry + rh)

    # Return True only if both are true
    return x_match and y_match

# Example usage:
button_area = (50, 50, 100, 40) # x=50, y=50, width=100, height=40
click_pos = (75, 60)

if is_point_in_rect(click_pos, button_area):
    print("Clicked inside the button area.")

[ASSET: A diagram showing a rectangle on a 2D grid with points inside and outside, highlighting inclusive/exclusive edges.]

Pygame Rect.collidepoint()

Pygame's Rect objects have a built-in method collidepoint(x, y) or collidepoint((x, y)) that performs the point-in-rectangle check using the same inclusive/exclusive edge convention.

import pygame

# Assuming a Pygame Rect object exists
button_rect = pygame.Rect(50, 50, 100, 40) # x=50, y=50, width=100, height=40

# Inside the event loop, when a MOUSEBUTTONDOWN event occurs:
if event.type == pygame.MOUSEBUTTONDOWN:
    click_pos = event.pos # Get the mouse position from the event
    if button_rect.collidepoint(click_pos):
        print("Button was clicked!")
        # Perform button action here

Adding Randomness

The random module introduces unpredictability, useful for games, simulations, or creative tools.

  • import random: Imports the module.
  • random.randint(a, b): Returns a random integer N such that a <= N <= b. Both a and b are included.
import random

# Get a random number between 1 and 10 (inclusive)
secret_number = random.randint(1, 10)
print(f"Random number: {secret_number}")

# Generate a random RGB color tuple
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
random_color = (r, g, b)
print(f"Random color: {random_color}")

[ASSET: Image or GIF related to randomness, e.g., dice roll or random color generation]

Specific Techniques & Patterns

Cycling Through a List

To cycle through a sequence of options (like colors or shapes) stored in a list, use an index variable and the modulo operator (%). The modulo operator gives the remainder of a division, which is useful for wrapping around. index % list_length will always result in a valid index within the list's bounds (0 to list_length - 1).

colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] # Red, Green, Blue
color_index = 0

# To move to the next color:
color_index = (color_index + 1) % len(colors)

# Get the current color using the index:
current_color = colors[color_index]

print(f"New index: {color_index}, New color: {current_color}")

[ASSET: Diagram showing an index moving through a list and wrapping around]

Numerical State with Limits

For state variables that are numbers (like size or speed), you often need to prevent them from going below a minimum or above a maximum value. if statements or the built-in min() and max() functions can enforce these limits.

line_thickness = 5
MIN_THICKNESS = 1
MAX_THICKNESS = 20

# Increase thickness, applying max limit
line_thickness += 1
if line_thickness > MAX_THICKNESS:
    line_thickness = MAX_THICKNESS
# Or using min(): line_thickness = min(MAX_THICKNESS, line_thickness + 1)


# Decrease thickness, applying min limit
line_thickness -= 1
if line_thickness < MIN_THICKNESS:
    line_thickness = MIN_THICKNESS
# Or using max(): line_thickness = max(MIN_THICKNESS, line_thickness - 1)

print(f"Current thickness: {line_thickness}")

[ASSET: Diagram showing a number line with min/max boundaries]

Creating GUI Elements (Drawing)

Simple GUI elements like buttons can be drawn using basic shapes and text.

Using Turtle: Draw a rectangle outline and then write text inside it. Remember to use penup() and pendown() correctly.

import turtle

def draw_rectangle(t, x, y, width, height):
    t.penup()
    t.goto(x, y)
    t.pendown()
    for _ in range(2):
        t.forward(width)
        t.left(90)
        t.forward(height)
        t.left(90)

def draw_button_label(t, x, y, text):
    t.penup()
    t.goto(x, y) # Position for text (often center)
    t.write(text, align="center", font=("Arial", 12, "normal"))

# Example:
screen = turtle.Screen()
t = turtle.Turtle()
t.hideturtle()
t.speed(0)

button_x, button_y = -60, -25 # Bottom-left corner of a 120x50 button centered at (0,0)
button_width, button_height = 120, 50
draw_rectangle(t, button_x, button_y, button_width, button_height)
draw_button_label(t, 0, 5, "Clear") # Text centered at (0,0), adjusted slightly up

turtle.done()

[ASSET: Image showing a simple drawn button]

Styling GUI Elements

Colors can be applied to screen backgrounds, drawing tools, and GUI elements to create a visual theme.

  • Turtle: screen.bgcolor(), turtle.pencolor(), turtle.fillcolor(), turtle.color(pencolor, fillcolor).
  • Pygame: Colors are passed as arguments to drawing functions (pygame.draw.circle(screen, color, ...), screen.fill(color)).
import turtle

screen = turtle.Screen()
screen.bgcolor("darkslategray") # Set screen background

drawing_turtle = turtle.Turtle()
drawing_turtle.pencolor("lightcyan") # Set drawing line color

button_turtle = turtle.Turtle()
button_turtle.color("gold", "darkgoldenrod") # Set button outline and fill color

# In Pygame:
# screen.fill((30, 30, 50)) # Dark blue background
# pygame.draw.circle(screen, (200, 50, 50), mouse_pos, 10) # Red drawing color

[ASSET: Visual representation of themed GUI elements]