[refactor] Move objets to separate files.
- ball - bricks - paddle Update documentation and readme. [fix] Game over: update player score.
This commit is contained in:
+7
-7
@@ -6,16 +6,16 @@ This program is a simple game implemented in MicroPython for the Raspberry Pi Pi
|
|||||||
## Framebuffer
|
## Framebuffer
|
||||||
The program uses a framebuffer to draw the game graphics. The framebuffer is a 2D array that represents the pixels on the display. The program uses the micropyhton `framebuf` module to create and manipulate the framebuffer. The framebuffer is then copied to the display using the `blit` method.
|
The program uses a framebuffer to draw the game graphics. The framebuffer is a 2D array that represents the pixels on the display. The program uses the micropyhton `framebuf` module to create and manipulate the framebuffer. The framebuffer is then copied to the display using the `blit` method.
|
||||||
|
|
||||||
## Multithreading
|
If the program crashes, the framebuffer is not always reset, causing the program to crash due to lack of memeory.
|
||||||
|
A hard reset is required to fix this issue.
|
||||||
|
|
||||||
|
## Multithreading
|
||||||
The program uses multithreading to handle the game logic and the display updates separately. The `threading` module is used to create and manage the threads. The game logic is run in a separate thread from the display updates to ensure smooth gameplay and responsive controls.
|
The program uses multithreading to handle the game logic and the display updates separately. The `threading` module is used to create and manage the threads. The game logic is run in a separate thread from the display updates to ensure smooth gameplay and responsive controls.
|
||||||
|
|
||||||
|
|
||||||
## Game Logic
|
## Game Logic
|
||||||
|
|
||||||
The game logic is implemented in the `game_loop` function. This function runs in a separate thread and handles the following tasks:
|
The game logic is implemented in the `game_loop` function. This function runs in a separate thread and handles the following tasks:
|
||||||
- Updating the game state based on user input and game rules
|
- Updating the game state based on user input andgame flow.
|
||||||
- Generating new obstacles and updating their positions
|
- Updating ball position, paddle position, and bricks on the screen.
|
||||||
- Checking for collisions between the character and obstacles
|
- Checking for collisions between the ball, borders, paddle and bricks.
|
||||||
- Updating the score based on the player's performance
|
- Updating the score based on the player's performance
|
||||||
- Sending the updated game state to the display thread for rendering
|
- Sending the updated game state to the display thread for rendering.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ This is a simple Breakout game implemented in MicroPython for the Raspberry Pi P
|
|||||||
3. Clone or download this repository to your local machine.
|
3. Clone or download this repository to your local machine.
|
||||||
4. Open the Thonny IDE and connect to your Raspberry Pi Pico.
|
4. Open the Thonny IDE and connect to your Raspberry Pi Pico.
|
||||||
5. Copy the contents of the repository to the Pico's file system. You can do this by dragging and dropping the files from your local machine to the Thonny file explorer.
|
5. Copy the contents of the repository to the Pico's file system. You can do this by dragging and dropping the files from your local machine to the Thonny file explorer.
|
||||||
6. Once the files are copied, reset the Pico to start the game.
|
6. Once the files are copied, reset the Pico to start the game (main.py will run automatically and start breakout.py).
|
||||||
|
|
||||||
## .env file
|
## .env file
|
||||||
In the .env file the user can control the behaviour of the joystick B-button.
|
In the .env file the user can control the behaviour of the joystick B-button.
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
from random import randint
|
||||||
|
|
||||||
|
from screen import Screen, WHITE
|
||||||
|
from paddle import Paddle
|
||||||
|
|
||||||
|
BALL_SPEED = 3
|
||||||
|
|
||||||
|
|
||||||
|
class Ball:
|
||||||
|
def __init__(self, screen: Screen, paddle: Paddle, radius: int, color: int = WHITE, brick_padding: int = 0, speed: int = BALL_SPEED):
|
||||||
|
self.screen_width = screen.width
|
||||||
|
self.screen_height = screen.height
|
||||||
|
self.radius = radius
|
||||||
|
self.color = color
|
||||||
|
self.speed = speed
|
||||||
|
self.x_speed = self.speed if randint(0, 1) == 0 else -self.speed
|
||||||
|
self.y_speed = -self.speed
|
||||||
|
self.brick_padding = brick_padding
|
||||||
|
self.reset_pos(paddle)
|
||||||
|
|
||||||
|
def reset_pos(self, paddle: Paddle):
|
||||||
|
"""Reset ball position to the center of the paddle."""
|
||||||
|
self.x = self.screen_width // 2
|
||||||
|
self.y = self.screen_height // 2 - self.radius - 2
|
||||||
|
self.x_speed = self.speed if randint(0, 1) == 0 else -self.speed
|
||||||
|
self.y_speed = -self.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 < self.brick_padding + self.radius:
|
||||||
|
self.y = self.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)
|
||||||
+29
-222
@@ -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 random import randint
|
||||||
from utime import sleep_us
|
from utime import sleep_us
|
||||||
from screen import Screen, RED, YELLOW, GREEN, WHITE
|
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
|
import _thread
|
||||||
|
|
||||||
from joystick import Joystick
|
from joystick import Joystick
|
||||||
@@ -19,19 +22,6 @@ SCREEN_HEIGHT = 135
|
|||||||
SCREEN_WIDTH = 240
|
SCREEN_WIDTH = 240
|
||||||
SCREEN_ROTATION = 1 # Landscape mode
|
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_WIDTH = 8
|
||||||
SPLASH_HEIGHT = 5
|
SPLASH_HEIGHT = 5
|
||||||
SPLASH_PADDING = 2
|
SPLASH_PADDING = 2
|
||||||
@@ -45,9 +35,7 @@ GAME_NEXT_LEVEL = 4
|
|||||||
|
|
||||||
DEBOUNCE = 300_000
|
DEBOUNCE = 300_000
|
||||||
|
|
||||||
# ============================
|
|
||||||
# load environment variables
|
# load environment variables
|
||||||
# ============================
|
|
||||||
try:
|
try:
|
||||||
DISABLE_B = 0
|
DISABLE_B = 0
|
||||||
with open(".env", "r") as file:
|
with open(".env", "r") as file:
|
||||||
@@ -59,177 +47,6 @@ except:
|
|||||||
DISABLE_B = 0
|
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:
|
class High_score:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.high_score = 0
|
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)
|
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]:
|
def create_lives(screen: Screen, paddle: Paddle, lives: int) -> list[Ball]:
|
||||||
"""
|
"""
|
||||||
Create a list of small balls to represent lives
|
Create a list of small balls to represent lives.
|
||||||
Args: lives (int): Number of lives left
|
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 = []
|
lives_balls = []
|
||||||
for i in range(0, lives):
|
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.x = 5 + (i - 1) * 7
|
||||||
life_ball.y = 7
|
life_ball.y = 7
|
||||||
life_ball.x_speed = 0
|
life_ball.x_speed = 0
|
||||||
@@ -350,7 +154,7 @@ def main_loop(screen, joystick):
|
|||||||
level = 1
|
level = 1
|
||||||
ball_stuck = True # Reset ball to be stuck to the paddle
|
ball_stuck = True # Reset ball to be stuck to the paddle
|
||||||
# Initialize paddle and ball
|
# 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.width = PADDLE_WIDTH
|
||||||
paddle.height = PADDLE_HEIGHT
|
paddle.height = PADDLE_HEIGHT
|
||||||
|
|
||||||
@@ -376,8 +180,8 @@ def main_loop(screen, joystick):
|
|||||||
# Launch the ball when "A" is pressed
|
# Launch the ball when "A" is pressed
|
||||||
if joystick.button_a() == 0:
|
if joystick.button_a() == 0:
|
||||||
ball_stuck = False
|
ball_stuck = False
|
||||||
ball.y_speed = -BALL_SPEED
|
ball.y_speed = -ball.speed
|
||||||
ball.x_speed = BALL_SPEED if randint(0, 1) == 0 else -BALL_SPEED
|
ball.x_speed = ball.speed if randint(0, 1) == 0 else -ball.speed
|
||||||
else:
|
else:
|
||||||
if ball.update_pos(): # If ball is out, lose a life and reset ball
|
if ball.update_pos(): # If ball is out, lose a life and reset ball
|
||||||
lives -= 1
|
lives -= 1
|
||||||
@@ -413,6 +217,7 @@ def main_loop(screen, joystick):
|
|||||||
game_state = GAME_NEXT_LEVEL # Transition to next level
|
game_state = GAME_NEXT_LEVEL # Transition to next level
|
||||||
|
|
||||||
if game_state == GAME_OVER: # Game over screen
|
if game_state == GAME_OVER: # Game over screen
|
||||||
|
current_score += score
|
||||||
high_score.update_high_score(current_score)
|
high_score.update_high_score(current_score)
|
||||||
splash_screen(
|
splash_screen(
|
||||||
screen,
|
screen,
|
||||||
@@ -444,8 +249,8 @@ def main_loop(screen, joystick):
|
|||||||
lives += 1
|
lives += 1
|
||||||
lives_balls = create_lives(screen, paddle, lives)
|
lives_balls = create_lives(screen, paddle, lives)
|
||||||
score = 0
|
score = 0
|
||||||
ball_stuck = True # Ball is stuck again
|
ball_stuck = True
|
||||||
sleep_us(DEBOUNCE) # Debounce delay
|
sleep_us(DEBOUNCE)
|
||||||
|
|
||||||
if DISABLE_B == 0 and joystick.button_b() == 0:
|
if DISABLE_B == 0 and joystick.button_b() == 0:
|
||||||
break
|
break
|
||||||
@@ -455,11 +260,13 @@ def main_loop(screen, joystick):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
joystick = Joystick()
|
try:
|
||||||
screen = Screen(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_ROTATION)
|
joystick = Joystick()
|
||||||
main_loop(screen, joystick)
|
screen = Screen(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_ROTATION)
|
||||||
|
main_loop(screen, joystick)
|
||||||
# Clean up
|
except KeyboardInterrupt:
|
||||||
screen.clear()
|
pass
|
||||||
buffer = None
|
finally:
|
||||||
fbuf = None
|
# Clean up resources
|
||||||
|
screen.clear()
|
||||||
|
screen.cleanup()
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
from screen import Screen, RED, YELLOW, GREEN
|
||||||
|
from ball import Ball
|
||||||
|
|
||||||
|
BRICK_WIDTH = 30
|
||||||
|
BRICK_HEIGHT = 8
|
||||||
|
BRICK_PADDING = 4
|
||||||
|
BRICKS_PER_ROW = 7
|
||||||
|
ROWS = 4
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
from screen import Screen, WHITE
|
||||||
|
|
||||||
|
PADDLE_WIDTH = 70
|
||||||
|
PADDLE_HEIGHT = 10
|
||||||
|
PADDLE_COLOR = WHITE
|
||||||
|
PADDLE_SPEED = 10
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
@@ -2,7 +2,7 @@ from machine import Pin, SPI
|
|||||||
import _thread
|
import _thread
|
||||||
from st7789 import ST7789
|
from st7789 import ST7789
|
||||||
from framebuf import FrameBuffer, RGB565
|
from framebuf import FrameBuffer, RGB565
|
||||||
|
import gc
|
||||||
|
|
||||||
|
|
||||||
def color565(red: int, green: int, blue: int) -> int:
|
def color565(red: int, green: int, blue: int) -> int:
|
||||||
@@ -55,6 +55,8 @@ class Screen:
|
|||||||
self.display.blit_buffer(self.buffer, 0, 0, self.buffer_width, self.buffer_height)
|
self.display.blit_buffer(self.buffer, 0, 0, self.buffer_width, self.buffer_height)
|
||||||
|
|
||||||
def clear(self, refresh: bool = True):
|
def clear(self, refresh: bool = True):
|
||||||
|
if self.fbuf is None:
|
||||||
|
return
|
||||||
self.fbuf.fill(BLACK)
|
self.fbuf.fill(BLACK)
|
||||||
if refresh:
|
if refresh:
|
||||||
self.refresh()
|
self.refresh()
|
||||||
@@ -65,3 +67,10 @@ class Screen:
|
|||||||
self.fbuf.fill(BLACK)
|
self.fbuf.fill(BLACK)
|
||||||
self.render_frame = False
|
self.render_frame = False
|
||||||
# thread will exit and self clean removing need for garbage collection
|
# thread will exit and self clean removing need for garbage collection
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""Free resources used by the framebuffer and SPI."""
|
||||||
|
self.buffer = None
|
||||||
|
self.fbuf = None
|
||||||
|
self.spi.deinit()
|
||||||
|
gc.collect()
|
||||||
|
|||||||
Reference in New Issue
Block a user