From 0e5b5f67fac62a3bc9a47c178320ba764a0de4ec Mon Sep 17 00:00:00 2001 From: Seppe De Loore Date: Thu, 3 Apr 2025 22:00:57 +0200 Subject: [PATCH] [fix] Small errors - Ball can no longer lock to paddle - Brick edges are part of the brick --- DOCUMENTATION.md | 21 ++++++++++ README.md | 32 ++++++++++++++++ breakout.code-workspace | 14 +++++++ breakout.py | 85 ++++++++++++++++++++++------------------- 4 files changed, 112 insertions(+), 40 deletions(-) create mode 100644 DOCUMENTATION.md create mode 100644 README.md create mode 100644 breakout.code-workspace diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md new file mode 100644 index 0000000..c1b3b05 --- /dev/null +++ b/DOCUMENTATION.md @@ -0,0 +1,21 @@ +# Program Documentation + +## Overview +This program is a simple game implemented in MicroPython for the Raspberry Pi Pico with a Pico-LCD-1.14 display. The game involves controlling a character to avoid obstacles and score points. The program includes a splash screen, game over screen, and a main game loop. + +## Framebuffer +The program use framebuffer to draw the game graphics. The framebuffer is a 2D array that represents the pixels on the display. The program uses the `framebuf` module to create and manipulate the framebuffer. The framebuffer is then copied to the display using the `blit` method. + +## 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. + + +## Game Logic + +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 +- Generating new obstacles and updating their positions +- Checking for collisions between the character and obstacles +- Updating the score based on the player's performance +- Sending the updated game state to the display thread for rendering diff --git a/README.md b/README.md new file mode 100644 index 0000000..2196670 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# Breakout Game for Raspberry Pi Pico + +This is a simple Breakout game implemented in MicroPython for the Raspberry Pi Pico. The game uses the Pico-LCD-1.14 dDisplay for graphics and input. + + +## Features +- Classic Breakout gameplay +- Simple and intuitive controls +- Basic collision detection +- Score tracking +- Splash screen and game over screens + + +## Requirements +- Raspberry Pi Pico +- Pico-LCD-1.14 display +- MicroPython firmware installed on the Pico + + +## Installation and Setup + +1. Connect the Pico-LCD-1.14 display to the Raspberry Pi Pico following the wiring diagram provided in the display's documentation. + +2. Download the MicroPython firmware for the Raspberry Pi Pico from the official MicroPython website and install it on the Pico. You can use the Thonny IDE to upload the firmware. + +3. Clone or download this repository to your local machine. + +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. + +6. Once the files are copied, reset the Pico to start the game. \ No newline at end of file diff --git a/breakout.code-workspace b/breakout.code-workspace new file mode 100644 index 0000000..4d1829d --- /dev/null +++ b/breakout.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "." + }, + { + "name": "Mpy Remote Workspace", + "uri": "pico:" + } + ], + "settings": { + "python.languageServer": "Pylance" + } +} \ No newline at end of file diff --git a/breakout.py b/breakout.py index 715146e..d426ef2 100644 --- a/breakout.py +++ b/breakout.py @@ -136,10 +136,15 @@ class Paddle: self.draw() def hit(self, ball: Ball) -> bool: - """Check if the ball hits the paddle.""" - return ( - self.x < ball.x < self.x + self.width - and self.y < ball.y < self.y + self.height) + """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: @@ -163,11 +168,11 @@ class Ball: 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 screen. + """Reset ball position to the center of the paddle. Args: Paddle: The paddle object to position the ball on. """ self.x = SCREEN_HEIGHT // 2 - self.y = SCREEN_WIDTH // 2 + self.y = SCREEN_WIDTH // 2 - self.radius - 2 self.x_speed = BALL_SPEED self.y_speed = -BALL_SPEED @@ -181,7 +186,7 @@ class Ball: self.x = 0 self.x_speed = -self.x_speed elif self.x > SCREEN_HEIGHT: - self.x = SCREEN_HEIGHT + self.x = SCREEN_HEIGHT - self.radius self.x_speed = -self.x_speed # Bounce off top screen edge @@ -255,7 +260,7 @@ class BrickRow: """ 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): + 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 @@ -299,40 +304,40 @@ def main_loop(): global fbuf, buffer, buffer_width, buffer_height, joystick global render_frame - 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)) - score = 0 - lives = 3 - - paddle = Paddle() - ball = Ball(paddle, radius=5, color=WHITE) - # Create a list of small balls to represent lives - lives_balls = [] - for i in range(0, lives): - life_ball = Ball(paddle, radius=3, color=WHITE) - life_ball.x = 5 + (i - 1) * 7 - life_ball.y = 7 - life_ball.x_speed = 0 - lives_balls.append(life_ball) - - render_frame = False state = 0 # 0 = start screen, 1 = game, 2 = game over, 3 = game win try: while True: - if state == 0: # Startup screen + if state == 0: # Startup screen & init game state + 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)) + score = 0 + lives = 3 + + paddle = Paddle() + ball = Ball(paddle, radius=5, color=WHITE) + # Create a list of small balls to represent lives + lives_balls = [] + for i in range(0, lives): + life_ball = Ball(paddle, radius=3, color=WHITE) + life_ball.x = 5 + (i - 1) * 7 + life_ball.y = 7 + life_ball.x_speed = 0 + lives_balls.append(life_ball) + + render_frame = False splash_screen([0x060046, 0x056B54, 0x054A64, 0x064A46, 0x054A62, 0x054A52, 0x074B56]) if joystick.button_a() == 0: # Transition to game state when A is pressed @@ -345,10 +350,10 @@ def main_loop(): if ball.update_pos(): # If ball is out of bounds, lose a life and reset ball position lives -= 1 lives_balls.pop() - ball.reset_pos(paddle) # Reset ball position to the center of the screen + ball.reset_pos(paddle) # Reset ball position to the center of the paddle if paddle.hit(ball): - ball.y_speed = -ball.y_speed + ball.y_speed = -abs(ball.y_speed) for row in bricks: if row.hit(ball): ball.y_speed = -ball.y_speed