[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
+1
View File
@@ -1,2 +1,3 @@
.vscode
.micropico
breakout.code-workspace
+4 -9
View File
@@ -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.
## .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
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)
-9
View File
@@ -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)
)
+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