In this lesson, we will make a ball and a paddle hit each other!
When the ball touches the paddle, it will bounce off. This is called a 'collision'.
Here is what the ball bouncing off the paddle will look like:
In this lesson, we will make a ball and a paddle hit each other!
When the ball touches the paddle, it will bounce off. This is called a 'collision'.
Here is what the ball bouncing off the paddle will look like:
Before you start coding, let's check how the tests look now.
Run this command in your terminal:
pytest module-1.3/blueprint-2/quest-50
You will see that some tests are not passing yet. This is normal because you haven't written the code for them.
Here are the tests that are not passing:
test_main_logic_reverses_speed_on_collision
test_main_logic_keeps_speed_on_no_collision
test_ball_check_collision_method_returns_true
test_ball_check_collision_method_returns_false
Now, let's write the code for ball.py
.
Your ball needs a way to check if it is touching another object. This is called a collision.
Here are some general examples of how you might check for collisions between two objects in Pygame:
import pygame
class GameObject:
def __init__(self, x, y, width, height):
self.rect = pygame.Rect(x, y, width, height)
# Example: Check if two rectangles overlap
def check_overlap(rect1, rect2):
return rect1.colliderect(rect2)
# Create example objects
object1 = GameObject(10, 10, 30, 30)
object2 = GameObject(20, 20, 30, 30)
object3 = GameObject(100, 100, 20, 20)
# Check for collision
if check_overlap(object1.rect, object2.rect):
print("Objects 1 and 2 are touching!")
if check_overlap(object1.rect, object3.rect):
print("Objects 1 and 3 are touching!")
else:
print("Objects 1 and 3 are not touching.")
class Ball:
def __init__(self, x, y, radius):
self.rect = pygame.Rect(x - radius, y - radius, radius * 2, radius * 2)
def check_collision(self, other_object):
# This method should return True if self.rect touches other_object.rect
return self.rect.colliderect(other_object.rect)
Now, let's write the code for main.py
.
In main.py
, you will use the check_collision
method to see if the ball hits the paddle. If it does, you need to make the ball bounce.
Here are some general examples of how you might use a collision check and change a ball's speed:
class Ball:
def __init__(self, speed_y):
self.speed_y = speed_y
def check_collision(self, other_object):
# Imagine this checks if they are touching
return True # or False
def reverse_y_speed(self):
self.speed_y = -self.speed_y
class Paddle:
pass # Paddle class definition
# Inside your game loop or update function:
my_ball = Ball(speed_y=5)
my_paddle = Paddle()
# Check for collision and react
if my_ball.check_collision(my_paddle):
my_ball.reverse_y_speed()
print("Ball hit paddle!")
else:
print("No collision.")
Now that you've written your code, let's check if it works!
Run this command in your terminal:
pytest module-1.3/blueprint-2/quest-50
If your code is correct, all the tests should now pass.
Here are the tests that should pass:
test_main_logic_reverses_speed_on_collision
test_main_logic_keeps_speed_on_no_collision
test_ball_check_collision_method_returns_true
test_ball_check_collision_method_returns_false
This micro-quest focuses on defining a class and creating an object from it.
Core Concept: Class Definition and __init__
__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:
Ball
class using class Ball:
.__init__
method inside the class: def __init__(self, x, y, radius, color):
.__init__
, assign the input parameters to instance attributes: self.x = x
, self.y = y
, etc.draw
method: def draw(self, screen):
. This method will contain the Pygame drawing call.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
).Ball
class: my_ball = Ball(x=..., y=..., radius=..., color=...)
.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
This micro-quest adds dynamic behavior to the ball object.
Core Concept: Class Methods (Behavior)
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:
__init__
method in Ball
to accept x_speed
and y_speed
parameters and store them as self.x_speed
and self.y_speed
.def move(self):
inside the Ball
class.move
method, update the ball's position: self.x += self.x_speed
and self.y += self.y_speed
.Ball
instance, provide initial values for x_speed
and y_speed
.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
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.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:
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):
.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
.
self.x - self.radius <= 0
self.x + self.radius >= screen_width
self.y - self.radius <= 0
self.y + self.radius >= screen_height
self.x_speed
by multiplying it by -1: self.x_speed *= -1
.self.y_speed
by multiplying it by -1: self.y_speed *= -1
.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
This micro-quest introduces the concept of having multiple different types of objects in your game.
Core Concept: Object Composition
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:
paddle.py
.Paddle
class in paddle.py
.Paddle
class's __init__
method should accept x
, y
, width
, height
, and color
as arguments and store them as attributes.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.draw
method in the Paddle
class that uses pygame.draw.rect(screen, self.color, self.rect)
to draw the paddle.main.py
), import the Paddle
class: from paddle import Paddle
.Ball
and Paddle
. Position the paddle near the bottom of the screen.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
This micro-quest adds interaction between the objects.
Core Concept: Object-to-Object Collision Detection
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:
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
.check_collision
method in the Ball
class: def check_collision(self, other_object):
.check_collision
, use return self.rect.colliderect(other_object.rect)
. This method returns True
if the rectangles overlap, False
otherwise.handle_ball_paddle_collision(ball, paddle)
.handle_ball_paddle_collision
, use an if
statement: if ball.check_collision(paddle):
.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
This micro-quest introduces the practice of writing automated tests for your code.
Core Concept: Unit Testing with Pytest
test_
.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:
ball.py
with a basic Ball
class and __init__(self, x, y, radius)
that assigns these to self.x
, self.y
, self.radius
.test_ball.py
.test_ball.py
, import the Ball
class: from ball import Ball
.test_
: def test_ball_initialization():
.Ball
using these test values.assert
statements to check if the instance's attributes match the test values (e.g., assert my_ball.x == test_x
).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