In this lesson, we will learn how to make many bricks and show them on the screen.
We will use a special loop to create five bricks and put them in a list. Then, we will draw each brick from our list onto the game screen.
In this lesson, we will learn how to make many bricks and show them on the screen.
We will use a special loop to create five bricks and put them in a list. Then, we will draw each brick from our list onto the game screen.
Before you start coding, let's check how the tests look now.
Run this command in your terminal:
pytest module-1.3/blueprint-3/quest-40
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_brick_creation_and_drawing_logic
test_brick_class_implementation
Now, let's write the code in brick.py
to make our brick remember its details and know how to draw itself.
When you create a new brick, it needs to know its position (x, y), its size (width, height), and its color. This information will be stored in the brick's __init__
part.
Also, your brick needs a draw
part that tells it how to show itself on the screen. This draw
part will use the brick's color and its size to draw a rectangle.
Here is a simple example of how you might set up a class to remember its details and draw itself:
import pygame
class Box:
def __init__(self, x, y, width, height, color):
self.rect = pygame.Rect(x, y, width, height)
self.color = color
def draw(self, surface):
pygame.draw.rect(surface, self.color, self.rect)
# Example usage:
# my_box = Box(10, 10, 50, 50, (255, 0, 0)) # Red box
# my_box.draw(my_game_screen)
Use these ideas to set up your Brick
class in brick.py
.
Now, let's work on main.py
. This is where you will make many bricks and draw them.
To make five bricks, you can use a for
loop. Inside the loop, you will create a new Brick
and add it to a list. After the loop, you will have a list of five bricks.
Then, you will use another for
loop to go through your list of bricks and tell each one to draw itself on the screen.
Here is a simple example of how you might make many objects and draw them:
class Item:
def __init__(self, number):
self.number = number
def draw(self, screen):
# Code to draw the item on the screen
pass
all_items = []
for i in range(3): # Make 3 items
new_item = Item(i)
all_items.append(new_item)
# Now draw all the items
# for item in all_items:
# item.draw(my_game_screen)
Use these ideas to create and draw your five bricks in main.py
.
Now that you've written your code, let's check if it works!
Run this command in your terminal:
pytest module-1.3/blueprint-3/quest-40
If your code is correct, all the tests should now pass.
Here are the tests that should pass:
test_brick_creation_and_drawing_logic
test_brick_class_implementation
This lesson blueprint focuses on using Python classes and lists to manage multiple objects in a Pygame application, culminating in building a wall of bricks.
The foundation of this lesson is the Brick
class. This class serves as a blueprint for creating individual brick objects, each with its own properties like position, size, and color.
Defining a class involves using the class
keyword followed by the class name. The __init__
method is a special method that runs when you create a new instance of the class. It's used to set up the initial state (attributes) of the object.
# Example: Basic class definition
class GameObject:
def __init__(self, x, y, color):
self.x = x
self.y = y
self.color = color
def describe(self):
print(f"Object at ({self.x}, {self.y}) with color {self.color}")
The Brick
class will need attributes to store its position (x
, y
), dimensions (width
, height
), and color
. It will also need a way to draw itself onto the screen.
The first step is to define the Brick
class and then create a single instance of it. Drawing this instance involves using Pygame's drawing functions, typically pygame.draw.rect
, which requires a pygame.Rect
object to define the position and size.
The pygame.Rect
object can be created using the x
, y
, width
, and height
attributes from the Brick
instance.
# Example: Creating a Rect and drawing a rectangle
import pygame
# Assume 'screen' is your Pygame surface
# Assume 'object_color' is an RGB tuple like (255, 0, 0)
# Assume 'object_x', 'object_y', 'object_width', 'object_height' are integers
object_rect = pygame.Rect(object_x, object_y, object_width, object_height)
pygame.draw.rect(screen, object_color, object_rect)
The objective is to create one Brick
instance and draw it using its attributes.
[Image/Gif: A single colored rectangle on a black background]
Instead of just one brick, a game needs many. This involves creating multiple separate instances of the Brick
class. Each instance will have its own unique set of attribute values (e.g., different x
positions).
To make drawing easier when dealing with multiple objects, it is common practice to add a draw
method directly to the class. This method takes the screen surface as an argument and uses the instance's own attributes (self.color
, self.rect
) to draw itself.
# Example: Adding a draw method to a class
import pygame
class DrawableObject:
def __init__(self, x, y, width, height, color):
self.rect = pygame.Rect(x, y, width, height)
self.color = color
def draw(self, surface):
pygame.draw.rect(surface, self.color, self.rect)
# Later, in your main drawing code:
# object1 = DrawableObject(...)
# object2 = DrawableObject(...)
# object1.draw(screen)
# object2.draw(screen)
The objective is to create two Brick
instances with different horizontal positions and call their draw
methods.
[Image/Gif: Two colored rectangles side-by-side]
When you have many objects of the same type, storing them in individual variables becomes impractical. A more efficient approach is to use a list to hold all the object instances.
Once the objects are in a list, you can use a for
loop to iterate through the list. Inside the loop, you can access each object instance and call its methods, such as the draw
method.
# Example: Creating objects and storing them in a list
object_list = []
# Create object instances...
obj_a = GameObject(10, 10, (255, 0, 0))
obj_b = GameObject(50, 10, (0, 255, 0))
# Add them to the list
object_list.append(obj_a)
object_list.append(obj_b)
# Example: Iterating through a list of objects and calling a method
# Assume 'screen' is your Pygame surface
for item in object_list:
item.draw(screen) # Assuming the object has a draw method
The objective is to create an empty list, create three Brick
instances, append them to the list, and then use a for
loop to draw all bricks in the list.
[Image/Gif: Three colored rectangles side-by-side (visually similar to two, but conceptually different)]
Manually creating and appending each object instance is tedious for large numbers. A more automated approach is to use a loop to create the instances. A for
loop with range()
is suitable for creating a fixed number of objects.
Inside the loop, you create a new instance of the class and immediately append it to the list. After the creation loop finishes, you can use a separate loop to iterate through the list and draw them, as done previously.
# Example: Creating multiple objects using a loop
object_list = []
number_of_objects = 5
for i in range(number_of_objects):
# Create a new object instance inside the loop
new_obj = GameObject(0, 0, (255, 255, 255)) # Using default or simple values for now
object_list.append(new_obj)
# Now object_list contains 5 instances
The objective is to use a for
loop with range(5)
to create five Brick
instances and add them to a list. Then, use a separate loop to draw them. Note that without changing their positions, they will all be drawn at the same location.
[Image/Gif: A single colored rectangle (as the five bricks are drawn on top of each other)]
To arrange objects in a pattern, like a row, you need to calculate their positions dynamically within the creation loop. For a horizontal row, the y
position remains constant, while the x
position changes based on the object's index in the row, its width, and any desired gap between objects.
The formula for the x
position of the i
-th object in a row, starting at start_x
with width
and gap
, is typically: start_x + i * (width + gap)
.
# Example: Calculating position in a loop
start_x = 50
start_y = 100
item_width = 80
item_gap = 10
num_items = 8
item_list = []
for i in range(num_items):
current_x = start_x + i * (item_width + item_gap)
current_y = start_y # Y is constant for a row
# Create object with calculated position
# new_item = MyObject(current_x, current_y, ...)
# item_list.append(new_item)
The objective is to use a single for
loop to create 8 Brick
instances, calculating each brick's x
position based on its index, width, and a gap. Store them in a list and then draw the complete row.
[Image/Gif: A horizontal row of colored rectangles with gaps]
In Pygame, everything drawn is ultimately placed onto a pygame.Surface
. The main game window is one large Surface
. You can also create smaller, independent Surface
objects. This is useful for creating complex sprites or drawing elements off-screen before placing them.
The pygame.Surface
object represents a rectangular area of pixels. You can draw shapes or fill colors onto this surface. To display a Surface
onto another Surface
(like the main screen), you use the blit()
method. blit()
copies pixels from one surface (source
) onto another (destination
) at a specified position.
# Example: Creating a surface, filling it, and blitting
import pygame
# Assume 'screen' is your main Pygame surface
# Assume 'custom_size' is a tuple like (100, 50)
# Assume 'fill_color' is an RGB tuple
# Assume 'blit_position' is a tuple like (200, 150)
# Create a new surface
custom_surface = pygame.Surface(custom_size)
# Fill the new surface with a color
custom_surface.fill(fill_color)
# Blit (copy) the custom surface onto the main screen
screen.blit(custom_surface, blit_position)
# Print the position
print(f"Blitted at {blit_position}")
The objective is to create a small pygame.Surface
, fill it with a color, blit
it onto the main screen at a chosen position, and print the blit coordinates.
[Image/Gif: A single colored rectangle drawn using blit]
Building a wall requires arranging bricks in both rows and columns, forming a grid. Nested loops are the standard way to iterate through a 2D structure like a grid. The outer loop typically handles the rows, and the inner loop handles the columns within each row.
Inside the inner loop, you have access to both the current row_index
and col_index
. You can use these indices, along with the object's dimensions and the grid's starting position, to calculate the unique (x, y)
coordinate for each object in the grid.
The formula for the top-left (x, y)
position of an object at (row_index, col_index)
in a grid starting at (start_x, start_y)
with object width
and height
is:
x = start_x + col_index * object_width
y = start_y + row_index * object_height
(Note: This assumes no gap between objects. Gaps would be added similarly to the row calculation).
# Example: Calculating grid positions with nested loops
start_x = 10
start_y = 20
item_width = 50
item_height = 30
num_rows = 3
num_cols = 4
for row_index in range(num_rows):
for col_index in range(num_cols):
current_x = start_x + col_index * item_width
current_y = start_y + row_index * item_height
print(f"Item at row {row_index}, col {col_index}: ({current_x}, {current_y})")
The objective is to use nested loops to calculate and print the (x, y)
coordinates for a 5x3 grid of objects, given object dimensions and a starting position.
[Image/Gif: A conceptual grid with coordinates labeled]
Combining the concepts of classes, lists, nested loops, dynamic positioning, and drawing (using blit
within the Brick
class's draw
method), you can build a complete wall of bricks.
The process involves:
x
and y
position for the current brick based on the loop indices and brick dimensions.Brick
instance using the calculated position and other brick properties.Brick
instance to a list that stores all the bricks.draw()
method on each one to render the entire wall.# Example: Structure for building a grid of objects
object_list = []
num_rows = 5
num_cols = 10
item_width = 80
item_height = 30
for row_index in range(num_rows):
for col_index in range(num_cols):
# Calculate x and y based on indices, width, height
pos_x = col_index * item_width
pos_y = row_index * item_height
# Create object instance
# new_object = MyObject(pos_x, pos_y, item_width, item_height, ...)
# Add to list
# object_list.append(new_object)
# After loops, draw all objects
# for obj in object_list:
# obj.draw(screen)
The objective is to use nested for
loops to create a grid of bricks (e.g., 5 rows of 10 bricks), calculate their x
and y
positions dynamically, store them in a list, and then draw the entire wall by iterating through the list.
[Image/Gif: A full wall of colored rectangles arranged in a grid]
Modifying a list while you are iterating over it using a standard for item in list:
loop can lead to unexpected behavior, such as skipping elements. This happens because removing an element shifts the indices of the elements that come after it.
To safely remove elements from a list during iteration, you can:
for i in range(len(list) - 1, -1, -1):
). When you remove an element at index i
, it only affects elements at indices less than i
, which you have already processed.# Example: Unsafe removal (DO NOT DO THIS)
# my_list = [1, 2, 3, 4]
# for item in my_list:
# if item % 2 == 0:
# my_list.remove(item) # This can skip elements!
# Example: Safe removal by iterating backwards
my_list = [1, 2, 3, 4, 5, 6]
print(f"Original: {my_list}")
for i in range(len(my_list) - 1, -1, -1):
if my_list[i] % 2 == 0:
my_list.pop(i) # Remove by index
print(f"Modified: {my_list}")
# Example: Safe removal by creating a new list
original_list = [1, 2, 3, 4, 5, 6]
new_list = []
for item in original_list:
if item % 2 != 0: # Keep if odd
new_list.append(item)
print(f"Original: {original_list}")
print(f"Modified: {new_list}")
The objective is to write a Python script that creates a list of numbers, iterates through it, and removes all even numbers safely (either by iterating backwards or creating a new list). Print the original and modified lists.
[Image/Gif: A list visually changing as elements are removed]