[refactor] Move screen class to separate file.

This commit is contained in:
2025-04-05 20:55:30 +02:00
parent d5cf59b739
commit 0b5b58bb84
7 changed files with 82 additions and 106 deletions
View File
+2 -1
View File
@@ -1,2 +1,3 @@
.vscode .vscode
.micropico .micropico
breakout.code-workspace
+4 -9
View File
@@ -1,5 +1,4 @@
# Breakout Game for Raspberry Pi Pico # 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. 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 - Score tracking
- Splash screen and game over screens - Splash screen and game over screens
## Requirements ## Requirements
- Raspberry Pi Pico - Raspberry Pi Pico
- Pico-LCD-1.14 display - Pico-LCD-1.14 display
- MicroPython firmware installed on the Pico - MicroPython firmware installed on the Pico
## Installation and Setup ## 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. 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. 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. 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. ## .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.
-14
View File
@@ -1,14 +0,0 @@
{
"folders": [
{
"path": "."
},
{
"name": "Mpy Remote Workspace",
"uri": "pico:"
}
],
"settings": {
"python.languageServer": "Pylance"
}
}
+9 -73
View File
@@ -1,38 +1,20 @@
""" """
Threaded breakout game with frame buffer Threaded breakout game with frame buffer
Uses a single shot function for second core SPI handler. Author: Seppe De Loore
This cleans itself when the function exits removing the
need for a garbage collection call.
""" """
from gc import collect
collect()
# import libraries from random import randint
import math from utime import sleep_us
import array from screen import Screen, RED, YELLOW, GREEN, WHITE
from machine import Pin, SPI
import framebuf
from random import random, seed, randint
from utime import sleep_us, ticks_cpu, ticks_us
import _thread 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 # Constants and Configuration
# ============================ # ============================
SCREEN_HEIGHT = 135 SCREEN_HEIGHT = 135
SCREEN_WIDTH = 240 SCREEN_WIDTH = 240
SCREEN_ROTATION = 1 # Landscape mode SCREEN_ROTATION = 1 # Landscape mode
@@ -68,7 +50,7 @@ DEBOUNCE = 300_000
# ============================ # ============================
try: try:
DISABLE_B = 0 DISABLE_B = 0
with open("env", "r") as file: with open(".env", "r") as file:
for line in file: for line in file:
key, value = line.strip().split("=") key, value = line.strip().split("=")
if key == "DISABLE_B": if key == "DISABLE_B":
@@ -81,51 +63,6 @@ except:
# CLASSES # 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: class Paddle:
def __init__(self, screen: Screen): def __init__(self, screen: Screen):
"""Initialize the paddle.""" """Initialize the paddle."""
@@ -152,9 +89,8 @@ class Paddle:
"""Draw paddle.""" """Draw paddle."""
screen.fbuf.fill_rect(self.x, self.y, self.width, self.height, PADDLE_COLOR) 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.""" """Update paddle position."""
global joystick
if joystick.joy_left() == 0: if joystick.joy_left() == 0:
self.move(-1) self.move(-1)
elif joystick.joy_right() == 0: elif joystick.joy_right() == 0:
@@ -431,7 +367,7 @@ def main_loop(screen, joystick):
sleep_us(DEBOUNCE) # Debounce delay sleep_us(DEBOUNCE) # Debounce delay
elif game_state == PLAYING and lives > 0 and score < 28: # Game loop elif game_state == PLAYING and lives > 0 and score < 28: # Game loop
paddle.update(screen) paddle.update(screen, joystick)
if ball_stuck: if ball_stuck:
# Keep the ball stuck to the paddle # Keep the ball stuck to the paddle
ball.x = paddle.x + (paddle.width // 2) ball.x = paddle.x + (paddle.width // 2)
-9
View File
@@ -12,12 +12,3 @@ class Joystick:
self.joy_left = Pin(16, Pin.IN, Pin.PULL_UP) self.joy_left = Pin(16, Pin.IN, Pin.PULL_UP)
self.joy_right = Pin(20, 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) 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)
)
+67
View File
@@ -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