In this lesson, we will make a ball move and bounce inside a game window.
Here is what the ball bouncing in the game window will look like:
(This is an animated image showing a ball bouncing off the walls of a Pygame window.)
In this lesson, we will make a ball move and bounce inside a game window.
Here is what the ball bouncing in the game window will look like:
(This is an animated image showing a ball bouncing off the walls of a Pygame window.)
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-30
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_ball_initialization
test_move_no_collision
test_bounce_off_right_wall
test_bounce_off_left_wall
test_bounce_off_bottom_wall
test_bounce_off_top_wall
test_bounce_off_top_right_corner
test_bounce_off_bottom_left_corner
To make our ball bounce, we need to understand a little bit about how movement works in games.
Imagine our game window is a grid, like a graph. Every position on the screen has an X (horizontal) and a Y (vertical) coordinate.
When our ball moves, its X and Y coordinates change based on its speed in each direction. If the ball hits a wall, we need to "reflect" its speed.
Think about a ball hitting a wall: it bounces off in the opposite direction. In terms of speed, this means if it was moving right (positive X speed), it will start moving left (negative X speed). If it was moving down (positive Y speed), it will start moving up (negative Y speed).
So, when the ball hits a horizontal wall (top or bottom), we reverse its Y speed. When it hits a vertical wall (left or right), we reverse its X speed. This is done by multiplying the speed by -1.
Now, let's write the code for ball.py
.
Your ball needs to move and bounce off the walls. You will need to update its position and change its direction when it hits the edges of the window.
Here are some general examples of how you might work with movement and bouncing:
x_position = 100
y_position = 100
x_speed = 5
y_speed = 5
# Update position
x_position += x_speed
y_position += y_speed
# Check for bouncing off walls (example screen size 800x600)
if x_position > 800 or x_position < 0:
x_speed = -x_speed # Reverse horizontal speed
if y_position > 600 or y_position < 0:
y_speed = -y_speed # Reverse vertical speed
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-30
If your code is correct, all the tests should now pass.
Here are the tests that should pass:
test_ball_initialization
test_move_no_collision
test_bounce_off_right_wall
test_bounce_off_left_wall
test_bounce_off_bottom_wall
test_bounce_off_top_wall
test_bounce_off_top_right_corner
test_bounce_off_bottom_left_corner
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