Bringing Objects to Life: The Bouncing Ball
The Static Ball
This micro-quest focuses on defining a class and creating an object from it.
Core Concept: Class Definition and __init__
- A class is a blueprint for creating objects. It defines the attributes (data) and methods (functions) that objects of that class will have.
- The
__init__
method is a special method called when you create a new object (an instance) of the class. It's used to set up the initial state of the object by assigning values to its attributes.
self
refers to the instance of the class itself. Inside __init__
and other methods, you use self.
to access or modify the object's attributes.
Objective: Create a Ball
class with x
, y
, radius
, and color
attributes, instantiate it, and draw it statically using Pygame.
Key Points:
- Define the
Ball
class using class Ball:
.
- Define the
__init__
method inside the class: def __init__(self, x, y, radius, color):
.
- Inside
__init__
, assign the input parameters to instance attributes: self.x = x
, self.y = y
, etc.
- Define a
draw
method: def draw(self, screen):
. This method will contain the Pygame drawing call.
- Inside the
draw
method, use pygame.draw.circle()
to draw the ball. This function needs the screen surface, the color, the center coordinates (as a tuple (self.x, self.y)
), and the radius (self.radius
).
- In your main script, create an instance of the
Ball
class: my_ball = Ball(x=..., y=..., radius=..., color=...)
.
- In the game loop, call the
draw
method on your ball instance: my_ball.draw(screen)
.
Example (Adjacent Concept: Basic Class Structure):
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
print(f"{self.name} says Woof!")
# Creating an object
my_dog = Dog("Buddy", "Golden Retriever")
print(my_dog.name)
my_dog.bark()
Example (Adjacent Concept: Pygame Drawing):
# Assuming 'screen' is a pygame.Surface
# Draw a red circle at (100, 150) with radius 25
pygame.draw.circle(screen, (255, 0, 0), (100, 150), 25)
# Draw a blue rectangle at (50, 50) with width 80 and height 30
pygame.draw.rect(screen, (0, 0, 255), (50, 50, 80, 30))
Files: ball.py
, main.py
The First Movement
This micro-quest adds dynamic behavior to the ball object.
Core Concept: Class Methods (Behavior)
- Methods are functions defined inside a class that describe the actions or behaviors of objects of that class.
- Methods can access and modify the object's attributes using
self
.
Objective: Add speed attributes (x_speed
, y_speed
) to the Ball
class, create a move()
method to update the ball's position based on speed, and call move()
in the main game loop.
Key Points:
- Modify the
__init__
method in Ball
to accept x_speed
and y_speed
parameters and store them as self.x_speed
and self.y_speed
.
- Define a new method
def move(self):
inside the Ball
class.
- Inside the
move
method, update the ball's position: self.x += self.x_speed
and self.y += self.y_speed
.
- In your main script, when creating the
Ball
instance, provide initial values for x_speed
and y_speed
.
- In the main game loop (before drawing), call the
move
method on your ball instance: my_ball.move()
.
Example (Adjacent Concept: Method Modifying Attributes):
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
def get_count(self):
return self.count
c = Counter()
print(c.get_count()) # Output: 0
c.increment()
print(c.get_count()) # Output: 1
Files: ball.py
, main.py
Bouncing Off the Walls
This micro-quest introduces decision-making within an object's behavior.
Core Concept: Conditional Logic in Methods
if
statements allow your code to make decisions based on conditions.
- Inside a method, you can use
if
statements to check the object's state (its attributes) and perform actions accordingly.
Objective: Enhance the Ball
's move()
method to check for collisions with the screen edges and reverse the ball's speed when a collision occurs.
Key Points:
- The
move
method will need access to the screen dimensions (width and height) to check boundaries. Pass these as arguments: def move(self, screen_width, screen_height):
.
- Inside
move
, after updating the position, add if
statements to check if the ball's edges have gone past the screen boundaries. Remember the ball's position (self.x
, self.y
) is its center, so you need to account for the self.radius
.
- Left edge collision:
self.x - self.radius <= 0
- Right edge collision:
self.x + self.radius >= screen_width
- Top edge collision:
self.y - self.radius <= 0
- Bottom edge collision:
self.y + self.radius >= screen_height
- If a horizontal collision occurs (left or right), reverse
self.x_speed
by multiplying it by -1: self.x_speed *= -1
.
- If a vertical collision occurs (top or bottom), reverse
self.y_speed
by multiplying it by -1: self.y_speed *= -1
.
- In the main game loop, call
ball.move()
with the screen dimensions: ball.move(SCREEN_WIDTH, SCREEN_HEIGHT)
.
Example (Adjacent Concept: Conditional Logic):
def check_boundary(position, size, max_dimension):
if position - size <= 0 or position + size >= max_dimension:
print("Boundary hit!")
return True
else:
print("In bounds.")
return False
check_boundary(10, 5, 100) # In bounds.
check_boundary(5, 5, 100) # Boundary hit!
check_boundary(95, 5, 100) # Boundary hit!
Image/Gif Idea: A gif showing a ball bouncing off the edges of the window.
Files: ball.py
, main.py
Setting the Stage: Ball and Paddle
This micro-quest introduces the concept of having multiple different types of objects in your game.
Core Concept: Object Composition
- Object composition is when you build complex objects or systems by combining simpler objects. In games, this means having different game elements (like a ball and a paddle) represented by separate classes and instances.
Objective: Create a Paddle
class, instantiate both a Ball
and a Paddle
object, and draw both to the screen. The paddle will remain static for now.
Key Points:
- Create a new file, e.g.,
paddle.py
.
- Define a
Paddle
class in paddle.py
.
- The
Paddle
class's __init__
method should accept x
, y
, width
, height
, and color
as arguments and store them as attributes.
- Inside the
Paddle
's __init__
, create a pygame.Rect
object using the position and dimensions (self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
). pygame.Rect
is useful for drawing rectangles and handling collisions.
- Define a
draw
method in the Paddle
class that uses pygame.draw.rect(screen, self.color, self.rect)
to draw the paddle.
- In your main script (
main.py
), import the Paddle
class: from paddle import Paddle
.
- In the main script's setup (before the game loop), create instances of both
Ball
and Paddle
. Position the paddle near the bottom of the screen.
- In the main game loop (after filling the background), call the
draw
method for both the ball and the paddle instances.
Example (Adjacent Concept: Multiple Objects):
class Square:
def __init__(self, x, y, size):
self.x = x
self.y = y
self.size = size
def display(self):
print(f"Square at ({self.x}, {self.y}) with size {self.size}")
# Create multiple instances
square1 = Square(10, 10, 20)
square2 = Square(50, 50, 30)
square1.display()
square2.display()
Image/Gif Idea: A screenshot showing a ball and a paddle drawn on the screen.
Files: ball.py
, paddle.py
, main.py
Collision Course
This micro-quest adds interaction between the objects.
Core Concept: Object-to-Object Collision Detection
- Objects can interact with each other based on their positions and shapes.
- Pygame's
Rect
objects provide a convenient method (colliderect()
) to check if two rectangles overlap.
Objective: Add a check_collision(self, other_object)
method to the Ball
class that uses pygame.Rect.colliderect()
to detect collision with another object (like the paddle). In the main loop, use this method to check for collision with the paddle and reverse the ball's y_speed
if they collide.
Key Points:
- In the
Ball
class, ensure it has a self.rect
attribute (similar to the Paddle, but calculated from the ball's center and radius). You might need to update this self.rect
in the move
method after updating self.x
and self.y
.
- Define the
check_collision
method in the Ball
class: def check_collision(self, other_object):
.
- Inside
check_collision
, use return self.rect.colliderect(other_object.rect)
. This method returns True
if the rectangles overlap, False
otherwise.
- In your main script, in the game loop (after moving the ball but before drawing), call
handle_ball_paddle_collision(ball, paddle)
.
- Inside
handle_ball_paddle_collision
, use an if
statement: if ball.check_collision(paddle):
.
- Inside the
if
block, reverse the ball's vertical speed: ball.y_speed *= -1
.
Example (Adjacent Concept: Using colliderect
):
import pygame
# Assume pygame is initialized
rect1 = pygame.Rect(10, 10, 50, 50) # A square at (10,10)
rect2 = pygame.Rect(30, 30, 50, 50) # A square at (30,30) - overlaps rect1
rect3 = pygame.Rect(100, 100, 20, 20) # A square at (100,100) - does not overlap rect1
print(rect1.colliderect(rect2)) # Output: True
print(rect1.colliderect(rect3)) # Output: False
Image/Gif Idea: A gif showing the ball bouncing off the paddle.
Files: ball.py
, paddle.py
, main.py
First Test with Pytest
This micro-quest introduces the practice of writing automated tests for your code.
Core Concept: Unit Testing with Pytest
- Unit testing is testing individual, small parts (units) of your code, like a single method or class, in isolation.
- Pytest is a popular Python framework for writing and running tests.
- Tests help ensure your code works as expected and doesn't break when you make changes.
- Test functions in Pytest must start with
test_
.
- The
assert
keyword is used in tests to check if a condition is true. If an assert
fails, the test fails.
Objective: Create a simple Ball
class in ball.py
with just the __init__
method. Create a test_ball.py
file and write a Pytest function (test_ball_initialization
) that creates a Ball
instance and uses assert
to verify its attributes are set correctly.
Key Points:
- Create
ball.py
with a basic Ball
class and __init__(self, x, y, radius)
that assigns these to self.x
, self.y
, self.radius
.
- Create
test_ball.py
.
- In
test_ball.py
, import the Ball
class: from ball import Ball
.
- Define a test function starting with
test_
: def test_ball_initialization():
.
- Inside the test function:
- Define some test values for x, y, and radius.
- Create an instance of
Ball
using these test values.
- Use
assert
statements to check if the instance's attributes match the test values (e.g., assert my_ball.x == test_x
).
- Run the test from your terminal using the command
pytest
.
Example (Adjacent Concept: Basic Pytest Assertions):
# In a file named test_calculations.py
def add(a, b):
return a + b
def test_add_positive_numbers():
# Check if 2 + 3 equals 5
assert add(2, 3) == 5
def test_add_negative_numbers():
# Check if -1 + -1 equals -2
assert add(-1, -1) == -2
def test_add_zero():
# Check if 5 + 0 equals 5
assert add(5, 0) == 5
Files: ball.py
, test_ball.py