[refactor] Move objets to separate files.

- ball
- bricks
- paddle
Update documentation and readme.
This commit is contained in:
2025-04-06 21:37:01 +02:00
parent b8de025881
commit 5597b91bd5
7 changed files with 240 additions and 231 deletions
+28 -222
View File
@@ -1,12 +1,15 @@
"""
Threaded breakout game with frame buffer
Threaded breakout game using frame buffer
Author: Seppe De Loore
Author: Seppe De Loore - 2025
"""
from random import randint
from utime import sleep_us
from screen import Screen, RED, YELLOW, GREEN, WHITE
from paddle import Paddle, PADDLE_WIDTH, PADDLE_HEIGHT
from ball import Ball
from bricks import BrickRow, create_bricks, BRICK_PADDING
import _thread
from joystick import Joystick
@@ -19,19 +22,6 @@ SCREEN_HEIGHT = 135
SCREEN_WIDTH = 240
SCREEN_ROTATION = 1 # Landscape mode
PADDLE_WIDTH = 70
PADDLE_HEIGHT = 10
PADDLE_COLOR = WHITE
PADDLE_SPEED = 10
BRICK_WIDTH = 30
BRICK_HEIGHT = 8
BRICK_PADDING = 4
BRICKS_PER_ROW = 7
ROWS = 4
BALL_SPEED = 3
SPLASH_WIDTH = 8
SPLASH_HEIGHT = 5
SPLASH_PADDING = 2
@@ -45,9 +35,7 @@ GAME_NEXT_LEVEL = 4
DEBOUNCE = 300_000
# ============================
# load environment variables
# ============================
try:
DISABLE_B = 0
with open(".env", "r") as file:
@@ -59,177 +47,6 @@ except:
DISABLE_B = 0
# ============================
# CLASSES
# ============================
class Paddle:
def __init__(self, screen: Screen):
"""Initialize the paddle."""
self.screen_width = screen.width
self.screen_height = screen.height
self.x = (self.screen_width - PADDLE_WIDTH) // 2
self.y = self.screen_height - PADDLE_HEIGHT - 5
self.width = PADDLE_WIDTH
self.height = PADDLE_HEIGHT
self.speed = PADDLE_SPEED
def move(self, direction: int):
"""
Move paddle left or right.
Args: direction: -1 for left, 1 for right
"""
self.x += self.speed * direction
if self.x < 0:
self.x = 0
elif self.x > self.screen_width - self.width:
self.x = self.screen_width - self.width
def draw(self, screen: Screen):
"""Draw paddle."""
screen.fbuf.fill_rect(self.x, self.y, self.width, self.height, PADDLE_COLOR)
def update(self, screen: Screen, joystick: Joystick):
"""Update paddle position."""
if joystick.joy_left() == 0:
self.move(-1)
elif joystick.joy_right() == 0:
self.move(1)
self.draw(screen)
def hit(self, ball: Ball) -> bool:
"""Check if the ball hits the paddle and adjust its position."""
if (
self.x <= ball.x <= self.x + self.width
and self.y <= ball.y + ball.radius <= self.y + self.height
):
# Adjust the ball's position to be just above the paddle
ball.y = self.y - ball.radius - 2
return True
return False
class Ball:
def __init__(self, screen: Screen, paddle: Paddle, radius: int, color: int):
"""
Initialize the ball.
Args:
paddle (Paddle): The paddle object to position the ball on.
radius (int): Radius of the ball.
color (int): RGB565 color value of the ball.
"""
self.screen_width = screen.width
self.screen_height = screen.height
self.radius = radius
self.color = color
self.reset_pos(paddle)
self.x_speed = BALL_SPEED if randint(0, 1) == 0 else -BALL_SPEED
self.y_speed = -BALL_SPEED
# Position the ball in the middle of the paddle
self.x = paddle.x + (paddle.width // 2)
self.y = paddle.y - radius - 2 # Place the ball just above the paddle
def reset_pos(self, paddle: Paddle):
"""Reset ball position to the center of the paddle.
Args: Paddle: The paddle object to position the ball on.
"""
self.x = self.screen_width // 2
self.y = self.screen_height // 2 - self.radius - 2
self.x_speed = BALL_SPEED
self.y_speed = -BALL_SPEED
def update_pos(self):
"""Update ball position."""
self.x += self.x_speed
self.y += self.y_speed
# Bounce off left or right screen edge
if self.x < 0:
self.x = 0
self.x_speed = -self.x_speed
elif self.x > self.screen_width - self.radius:
self.x = self.screen_width - self.radius
self.x_speed = -self.x_speed
# Bounce off top screen edge
if self.y < BRICK_PADDING + self.radius:
self.y = BRICK_PADDING + self.radius
self.y_speed = -self.y_speed
# Drop through bottom screen edge & return True to indicate we lose a life
if self.y > self.screen_height:
self.y = self.screen_height
self.y_speed = -self.y_speed
return True
def draw(self, screen: Screen):
"""Draw ball."""
screen.fbuf.ellipse(self.x, self.y, self.radius, self.radius, self.color, True)
class Brick:
def __init__(self, x: int, y: int, width: int, height: int, color: int):
"""
Initialize a brick.
Args: x (int): x-coordinate of the brick.
y (int): y-coordinate of the brick.
width (int): width of the brick.
height (int): height of the brick.
color (int): color of the brick.
"""
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def draw(self, screen: Screen):
"""Draw brick."""
screen.fbuf.fill_rect(self.x, self.y, self.width, self.height, self.color)
class BrickRow:
def __init__(self, brick_width: int, brick_height: int, padding:int, offset_top: int, color: int):
"""
Initialize a row of bricks.
Args: brick_width (int): width of each brick.
brick_height (int): height of each brick.
padding (int): padding between bricks.
offset_top (int): y-coordinate of the top of the row.
color (int): color of the bricks.
"""
self.brick_width = brick_width
self.brick_height = brick_height
self.color = color
self.padding = padding
self.offset_top = offset_top
self.bricks = [Brick(padding + i * (brick_width + padding), offset_top, brick_width, brick_height, color) for i in range(BRICKS_PER_ROW)]
self.brick_x = [padding + i * (brick_width + padding) for i in range(BRICKS_PER_ROW)]
self.brick_y = [offset_top] * BRICKS_PER_ROW
def draw(self, screen: Screen):
"""Draw all bricks in the row."""
for brick in self.bricks:
if brick is not None:
brick.draw(screen)
def hit(self, ball: Ball) -> bool:
"""
Check if the ball hits any brick in the row and remove it if hit.
Args: ball (Ball): The ball object to check for collision.
Returns: bool: True if the ball hits a brick, False otherwise.
"""
for i, brick in enumerate(self.bricks):
if brick is not None:
if (brick.x <= ball.x <= brick.x + brick.width) and (brick.y <= ball.y <= brick.y + brick.height):
# Remove the brick by setting it to None
self.bricks[i] = None
return True
return False
class High_score:
def __init__(self):
self.high_score = 0
@@ -298,32 +115,19 @@ def splash_screen(screen: Screen, data_rows: list[int], text: list[str], high_sc
screen.display.blit_buffer(screen.buffer, 0, 0, screen.buffer_width, screen.buffer_height)
def create_bricks() -> list[BrickRow]:
bricks = []
for row in range(ROWS):
if row == 0:
color = RED
elif row == 1:
color = YELLOW
else:
color = GREEN
bricks.append(
BrickRow(BRICK_WIDTH,
BRICK_HEIGHT,
BRICK_PADDING,
10 + row * (BRICK_HEIGHT + BRICK_PADDING),
color))
return bricks
def create_lives(screen: Screen, paddle: Paddle, lives: int) -> list[Ball]:
"""
Create a list of small balls to represent lives
Args: lives (int): Number of lives left
Create a list of small balls to represent lives.
Args:
screen (Screen): The screen object.
paddle (Paddle): The paddle object.
lives (int): Number of lives left.
Returns:
list[Ball]: List of Ball objects representing lives.
"""
lives_balls = []
for i in range(0, lives):
life_ball = Ball(screen, paddle, radius=3, color=WHITE)
life_ball = Ball(screen, paddle, radius=3, color=WHITE, brick_padding=BRICK_PADDING)
life_ball.x = 5 + (i - 1) * 7
life_ball.y = 7
life_ball.x_speed = 0
@@ -350,7 +154,7 @@ def main_loop(screen, joystick):
level = 1
ball_stuck = True # Reset ball to be stuck to the paddle
# Initialize paddle and ball
ball = Ball(screen, paddle, radius=5, color=WHITE)
ball = Ball(screen, paddle, radius=5, color=WHITE, brick_padding=BRICK_PADDING)
paddle.width = PADDLE_WIDTH
paddle.height = PADDLE_HEIGHT
@@ -376,8 +180,8 @@ def main_loop(screen, joystick):
# Launch the ball when "A" is pressed
if joystick.button_a() == 0:
ball_stuck = False
ball.y_speed = -BALL_SPEED
ball.x_speed = BALL_SPEED if randint(0, 1) == 0 else -BALL_SPEED
ball.y_speed = -ball.speed
ball.x_speed = ball.speed if randint(0, 1) == 0 else -ball.speed
else:
if ball.update_pos(): # If ball is out, lose a life and reset ball
lives -= 1
@@ -444,8 +248,8 @@ def main_loop(screen, joystick):
lives += 1
lives_balls = create_lives(screen, paddle, lives)
score = 0
ball_stuck = True # Ball is stuck again
sleep_us(DEBOUNCE) # Debounce delay
ball_stuck = True
sleep_us(DEBOUNCE)
if DISABLE_B == 0 and joystick.button_b() == 0:
break
@@ -455,11 +259,13 @@ def main_loop(screen, joystick):
if __name__ == "__main__":
joystick = Joystick()
screen = Screen(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_ROTATION)
main_loop(screen, joystick)
# Clean up
screen.clear()
buffer = None
fbuf = None
try:
joystick = Joystick()
screen = Screen(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_ROTATION)
main_loop(screen, joystick)
except KeyboardInterrupt:
pass
finally:
# Clean up resources
screen.clear()
screen.cleanup()