diff --git a/env b/.env similarity index 100% rename from env rename to .env diff --git a/.gitignore b/.gitignore index ccd92f6..82e1192 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode -.micropico \ No newline at end of file +.micropico +breakout.code-workspace diff --git a/README.md b/README.md index 2196670..a5ca30e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # 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. @@ -10,23 +9,19 @@ This is a simple Breakout game implemented in MicroPython for the Raspberry Pi P - 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. -6. Once the files are copied, reset the Pico to start the game. \ No newline at end of file +## .env file +In the .env file the user can control the behaviour of the joystick B-button. +DISABLE_B = 0 | 1 # Values: 0 = pressing B quits the program, 1 = pressing B does nothing. \ No newline at end of file diff --git a/breakout.code-workspace b/breakout.code-workspace deleted file mode 100644 index 4d1829d..0000000 --- a/breakout.code-workspace +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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 8b325ef..d4d279a 100644 --- a/breakout.py +++ b/breakout.py @@ -1,38 +1,20 @@ """ Threaded breakout game with frame buffer -Uses a single shot function for second core SPI handler. -This cleans itself when the function exits removing the -need for a garbage collection call. +Author: Seppe De Loore """ -from gc import collect -collect() -# import libraries -import math -import array -from machine import Pin, SPI -import framebuf -from random import random, seed, randint -from utime import sleep_us, ticks_cpu, ticks_us +from random import randint +from utime import sleep_us +from screen import Screen, RED, YELLOW, GREEN, WHITE import _thread -import st7789 as st7789 -from helpers import Joystick, color565 - -# ============================ -# Helper Functions -# ============================ - -RED = color565(0, 0, 255) -GREEN = color565(0, 255, 0) -YELLOW = color565(0, 255, 255) -BLACK = color565(0, 0, 0) -WHITE = color565(255, 255, 255) +from joystick import Joystick # ============================ # Constants and Configuration # ============================ + SCREEN_HEIGHT = 135 SCREEN_WIDTH = 240 SCREEN_ROTATION = 1 # Landscape mode @@ -68,7 +50,7 @@ DEBOUNCE = 300_000 # ============================ try: DISABLE_B = 0 - with open("env", "r") as file: + with open(".env", "r") as file: for line in file: key, value = line.strip().split("=") if key == "DISABLE_B": @@ -81,51 +63,6 @@ except: # CLASSES # ============================ -class Screen: - def __init__(self, width: int, height: int, rotation: int): - self.spi: SPI = SPI(1, - baudrate=31250000, - polarity=1, - phase=1, - bits=8, - firstbit=SPI.MSB, - sck=Pin(10), - mosi=Pin(11)) - self.width: int = width - self.height: int = height - self.rotation: int = rotation - self.display = st7789.ST7789( - self.spi, - self.height, - self.width, - reset=Pin(12, Pin.OUT), - cs=Pin(9, Pin.OUT), - dc=Pin(8, Pin.OUT), - backlight=Pin(13, Pin.OUT), - rotation=self.rotation) - # FrameBuffer needs 2 bytes for every RGB565 pixel - self.buffer_width = self.width - self.buffer_height = self.height + 1 - self.buffer = bytearray(self.buffer_width * self.buffer_height * 2) - self.fbuf: framebuf.FrameBuffer = framebuf.FrameBuffer(self.buffer, self.buffer_width, self.buffer_height, framebuf.RGB565) - self.render_frame = False - - def refresh(self): - self.display.blit_buffer(self.buffer, 0, 0, self.buffer_width, self.buffer_height) - - def clear(self, refresh: bool = True): - self.fbuf.fill(BLACK) - if refresh: - self.refresh() - - def render_thread(self): - """Threaded function to handle SPI rendering on a separate core.""" - self.display.blit_buffer(self.buffer, 0, 0, self.buffer_width, self.buffer_height) - self.fbuf.fill(BLACK) - self.render_frame = False - # thread will exit and self clean removing need for garbage collection - - class Paddle: def __init__(self, screen: Screen): """Initialize the paddle.""" @@ -152,9 +89,8 @@ class Paddle: """Draw paddle.""" screen.fbuf.fill_rect(self.x, self.y, self.width, self.height, PADDLE_COLOR) - def update(self, screen: Screen): + def update(self, screen: Screen, joystick: Joystick): """Update paddle position.""" - global joystick if joystick.joy_left() == 0: self.move(-1) elif joystick.joy_right() == 0: @@ -431,7 +367,7 @@ def main_loop(screen, joystick): sleep_us(DEBOUNCE) # Debounce delay elif game_state == PLAYING and lives > 0 and score < 28: # Game loop - paddle.update(screen) + paddle.update(screen, joystick) if ball_stuck: # Keep the ball stuck to the paddle ball.x = paddle.x + (paddle.width // 2) diff --git a/helpers.py b/joystick.py similarity index 67% rename from helpers.py rename to joystick.py index ac0b59e..338ced0 100644 --- a/helpers.py +++ b/joystick.py @@ -12,12 +12,3 @@ class Joystick: self.joy_left = Pin(16, Pin.IN, Pin.PULL_UP) self.joy_right = Pin(20, Pin.IN, Pin.PULL_UP) self.joy_click = Pin(3, Pin.IN, Pin.PULL_UP) - - -def color565(red: int, green: int, blue: int) -> int: - """Convert RGB888 to RGB565.""" - return ( - (((green & 0b00011100) << 3) + ((red & 0b11111000) >> 3) << 8) - + (blue & 0b11111000) - + ((green & 0b11100000) >> 5) - ) diff --git a/screen.py b/screen.py new file mode 100644 index 0000000..0862cf0 --- /dev/null +++ b/screen.py @@ -0,0 +1,67 @@ +from machine import Pin, SPI +import _thread +from st7789 import ST7789 +from framebuf import FrameBuffer, RGB565 + + + +def color565(red: int, green: int, blue: int) -> int: + """Convert RGB888 to RGB565.""" + return ( + (((green & 0b00011100) << 3) + ((red & 0b11111000) >> 3) << 8) + + (blue & 0b11111000) + + ((green & 0b11100000) >> 5) + ) + + +RED = color565(0, 0, 255) +GREEN = color565(0, 255, 0) +YELLOW = color565(0, 255, 255) +BLACK = color565(0, 0, 0) +WHITE = color565(255, 255, 255) + + +class Screen: + """Class to handle the ST7789 display and framebuffer.""" + def __init__(self, width: int, height: int, rotation: int): + self.spi: SPI = SPI(1, + baudrate=31250000, + polarity=1, + phase=1, + bits=8, + firstbit=SPI.MSB, + sck=Pin(10), + mosi=Pin(11)) + self.width: int = width + self.height: int = height + self.rotation: int = rotation + self.display = ST7789( + self.spi, + self.height, + self.width, + reset=Pin(12, Pin.OUT), + cs=Pin(9, Pin.OUT), + dc=Pin(8, Pin.OUT), + backlight=Pin(13, Pin.OUT), + rotation=self.rotation) + # FrameBuffer needs 2 bytes for every RGB565 pixel + self.buffer_width = self.width + self.buffer_height = self.height + 1 + self.buffer = bytearray(self.buffer_width * self.buffer_height * 2) + self.fbuf: FrameBuffer = FrameBuffer(self.buffer, self.buffer_width, self.buffer_height, RGB565) + self.render_frame = False + + def refresh(self): + self.display.blit_buffer(self.buffer, 0, 0, self.buffer_width, self.buffer_height) + + def clear(self, refresh: bool = True): + self.fbuf.fill(BLACK) + if refresh: + self.refresh() + + def render_thread(self): + """Threaded function to handle SPI rendering on a separate core.""" + self.display.blit_buffer(self.buffer, 0, 0, self.buffer_width, self.buffer_height) + self.fbuf.fill(BLACK) + self.render_frame = False + # thread will exit and self clean removing need for garbage collection