circuitpython matrix implementation
This commit is contained in:
parent
e4aac8bdbb
commit
c45d70e536
2 changed files with 226 additions and 0 deletions
226
Metro/Metro_RP2350_CircuitPython_Matrix/code.py
Normal file
226
Metro/Metro_RP2350_CircuitPython_Matrix/code.py
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
Matrix rain visual effect
|
||||||
|
|
||||||
|
Largely ported from Arduino version in Metro_HSTX_Matrix to
|
||||||
|
CircuitPython by claude with some additional tweaking to the
|
||||||
|
colors and refresh functionality.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import displayio
|
||||||
|
import supervisor
|
||||||
|
from displayio import Group, TileGrid
|
||||||
|
from tilepalettemapper import TilePaletteMapper
|
||||||
|
import adafruit_imageload
|
||||||
|
|
||||||
|
# use the built-in HSTX display
|
||||||
|
display = supervisor.runtime.display
|
||||||
|
|
||||||
|
# screen size in tiles, tiles are 16x16
|
||||||
|
SCREEN_WIDTH = display.width // 16
|
||||||
|
SCREEN_HEIGHT = display.height // 16
|
||||||
|
|
||||||
|
# disable auto_refresh, we'll call refresh() after each frame
|
||||||
|
display.auto_refresh = False
|
||||||
|
|
||||||
|
# group to hold visual elements
|
||||||
|
main_group = Group()
|
||||||
|
|
||||||
|
# show the group on the display
|
||||||
|
display.root_group = main_group
|
||||||
|
|
||||||
|
# Color gradient list from white to dark green
|
||||||
|
COLORS = [
|
||||||
|
0xFFFFFF,
|
||||||
|
0x88FF88,
|
||||||
|
0x00FF00,
|
||||||
|
0x00DD00,
|
||||||
|
0x00BB00,
|
||||||
|
0x009900,
|
||||||
|
0x007700,
|
||||||
|
0x006600,
|
||||||
|
0x005500,
|
||||||
|
0x005500,
|
||||||
|
0x003300,
|
||||||
|
0x003300,
|
||||||
|
0x002200,
|
||||||
|
0x002200,
|
||||||
|
0x001100,
|
||||||
|
0x001100,
|
||||||
|
]
|
||||||
|
|
||||||
|
# Palette to use with the mapper. Has 1 extra color
|
||||||
|
# so it can have black at index 0
|
||||||
|
shader_palette = displayio.Palette(len(COLORS) + 1)
|
||||||
|
# set black at index 0
|
||||||
|
shader_palette[0] = 0x000000
|
||||||
|
|
||||||
|
# set the colors from the gradient above in the
|
||||||
|
# remaining indexes
|
||||||
|
for i in range(0, len(COLORS)):
|
||||||
|
shader_palette[i + 1] = COLORS[i]
|
||||||
|
|
||||||
|
# mapper to change colors of tiles within the grid
|
||||||
|
grid_color_shader = TilePaletteMapper(shader_palette, 2, SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||||
|
|
||||||
|
# load the spritesheet
|
||||||
|
katakana_bmp, katakana_pixelshader = adafruit_imageload.load("matrix_characters.bmp")
|
||||||
|
|
||||||
|
# how many characters are in the sprite sheet
|
||||||
|
char_count = katakana_bmp.width // 16
|
||||||
|
|
||||||
|
# grid to display characters within
|
||||||
|
display_text_grid = TileGrid(
|
||||||
|
bitmap=katakana_bmp,
|
||||||
|
width=SCREEN_WIDTH,
|
||||||
|
height=SCREEN_HEIGHT,
|
||||||
|
tile_height=16,
|
||||||
|
tile_width=16,
|
||||||
|
pixel_shader=grid_color_shader,
|
||||||
|
)
|
||||||
|
|
||||||
|
# flip x to get backwards characters
|
||||||
|
display_text_grid.flip_x = True
|
||||||
|
|
||||||
|
# add the text grid to main_group, so it will be visible on the display
|
||||||
|
main_group.append(display_text_grid)
|
||||||
|
|
||||||
|
|
||||||
|
# Define structures for character streams
|
||||||
|
class CharStream:
|
||||||
|
def __init__(self):
|
||||||
|
self.x = 0 # X position
|
||||||
|
self.y = 0 # Y position (head of the stream)
|
||||||
|
self.length = 0 # Length of the stream
|
||||||
|
self.speed = 0 # How many frames to wait before moving
|
||||||
|
self.countdown = 0 # Counter for movement
|
||||||
|
self.active = False # Whether this stream is currently active
|
||||||
|
self.chars = [" "] * 30 # Characters in the stream
|
||||||
|
|
||||||
|
|
||||||
|
# Array of character streams
|
||||||
|
streams = [CharStream() for _ in range(250)]
|
||||||
|
|
||||||
|
# Stream creation rate (higher = more frequent new streams)
|
||||||
|
STREAM_CREATION_CHANCE = 65 # % chance per frame to create new stream
|
||||||
|
|
||||||
|
# Initial streams to create at startup
|
||||||
|
INITIAL_STREAMS = 30
|
||||||
|
|
||||||
|
|
||||||
|
def init_streams():
|
||||||
|
"""Initialize all streams as inactive"""
|
||||||
|
for _ in range(len(streams)):
|
||||||
|
streams[_].active = False
|
||||||
|
|
||||||
|
# Create initial streams for immediate visual impact
|
||||||
|
for _ in range(INITIAL_STREAMS):
|
||||||
|
create_new_stream()
|
||||||
|
|
||||||
|
|
||||||
|
def create_new_stream():
|
||||||
|
"""Create a new active stream"""
|
||||||
|
# Find an inactive stream
|
||||||
|
for _ in range(len(streams)):
|
||||||
|
if not streams[_].active:
|
||||||
|
# Initialize the stream
|
||||||
|
streams[_].x = random.randint(0, SCREEN_WIDTH - 1)
|
||||||
|
streams[_].y = random.randint(-5, -1) # Start above the screen
|
||||||
|
streams[_].length = random.randint(5, 20)
|
||||||
|
streams[_].speed = random.randint(0, 3)
|
||||||
|
streams[_].countdown = streams[_].speed
|
||||||
|
streams[_].active = True
|
||||||
|
|
||||||
|
# Fill with random characters
|
||||||
|
for j in range(streams[_].length):
|
||||||
|
# streams[i].chars[j] = get_random_char()
|
||||||
|
streams[_].chars[j] = random.randrange(0, char_count)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def update_streams():
|
||||||
|
"""Update and draw all streams"""
|
||||||
|
# Clear the display (we'll implement this by looping through display grid)
|
||||||
|
for x in range(SCREEN_WIDTH):
|
||||||
|
for y in range(SCREEN_HEIGHT):
|
||||||
|
display_text_grid[x, y] = 0 # Clear character
|
||||||
|
|
||||||
|
# Count active streams (for debugging if needed)
|
||||||
|
active_count = 0
|
||||||
|
|
||||||
|
for _ in range(len(streams)):
|
||||||
|
if streams[_].active:
|
||||||
|
active_count += 1
|
||||||
|
streams[_].countdown -= 1
|
||||||
|
|
||||||
|
# Time to move the stream down
|
||||||
|
if streams[_].countdown <= 0:
|
||||||
|
streams[_].y += 1
|
||||||
|
streams[_].countdown = streams[_].speed
|
||||||
|
|
||||||
|
# Change a random character in the stream
|
||||||
|
random_index = random.randint(0, streams[_].length - 1)
|
||||||
|
# streams[i].chars[random_index] = get_random_char()
|
||||||
|
streams[_].chars[random_index] = random.randrange(0, char_count)
|
||||||
|
|
||||||
|
# Draw the stream
|
||||||
|
draw_stream(streams[_])
|
||||||
|
|
||||||
|
# Check if the stream has moved completely off the screen
|
||||||
|
if streams[_].y - streams[_].length > SCREEN_HEIGHT:
|
||||||
|
streams[_].active = False
|
||||||
|
|
||||||
|
|
||||||
|
def draw_stream(stream):
|
||||||
|
"""Draw a single character stream"""
|
||||||
|
for _ in range(stream.length):
|
||||||
|
y = stream.y - _
|
||||||
|
|
||||||
|
# Only draw if the character is on screen
|
||||||
|
if 0 <= y < SCREEN_HEIGHT and 0 <= stream.x < SCREEN_WIDTH:
|
||||||
|
# Set the character
|
||||||
|
display_text_grid[stream.x, y] = stream.chars[_]
|
||||||
|
|
||||||
|
if _ + 1 < len(COLORS):
|
||||||
|
grid_color_shader[stream.x, y] = [0, _ + 1]
|
||||||
|
else:
|
||||||
|
grid_color_shader[stream.x, y] = [0, len(COLORS) - 1]
|
||||||
|
# Occasionally change a character in the stream
|
||||||
|
if random.randint(0, 99) < 25: # 25% chance
|
||||||
|
idx = random.randint(0, stream.length - 1)
|
||||||
|
stream.chars[idx] = random.randrange(0, 112)
|
||||||
|
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
"""Initialize the system"""
|
||||||
|
# Seed the random number generator
|
||||||
|
random.seed(int(time.monotonic() * 1000))
|
||||||
|
|
||||||
|
# Initialize all streams
|
||||||
|
init_streams()
|
||||||
|
|
||||||
|
|
||||||
|
def loop():
|
||||||
|
"""Main program loop"""
|
||||||
|
# Update and draw all streams
|
||||||
|
update_streams()
|
||||||
|
|
||||||
|
# Randomly create new streams at a higher rate
|
||||||
|
if random.randint(0, 99) < STREAM_CREATION_CHANCE:
|
||||||
|
create_new_stream()
|
||||||
|
|
||||||
|
display.refresh()
|
||||||
|
available = supervisor.runtime.serial_bytes_available
|
||||||
|
if available:
|
||||||
|
c = sys.stdin.read(available)
|
||||||
|
if c.lower() == "q":
|
||||||
|
supervisor.reload()
|
||||||
|
|
||||||
|
|
||||||
|
# Main program
|
||||||
|
setup()
|
||||||
|
while True:
|
||||||
|
loop()
|
||||||
BIN
Metro/Metro_RP2350_CircuitPython_Matrix/matrix_characters.bmp
Normal file
BIN
Metro/Metro_RP2350_CircuitPython_Matrix/matrix_characters.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
Loading…
Reference in a new issue