The code begins by importing the CircuitPython libraries.

Download: file
import time
import board
import audioio
import audiomp3
import framebufferio
import rgbmatrix
import displayio
import adafruit_imageload
import digitalio
from adafruit_display_shapes.rect import Rect
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label

The RGB matrix is setup using the rgbmatrix library.

Download: file
#  matrix setup
displayio.release_displays()
matrix = rgbmatrix.RGBMatrix(
    width=64,
    height=32,
    bit_depth=4,
    rgb_pins=[board.D6, board.D5, board.D9, board.D11, board.D10, board.D12],
    addr_pins=[board.A5, board.A4, board.A3, board.A2],
    clock_pin=board.D13,
    latch_pin=board.D0,
    output_enable_pin=board.D1,
)
display = framebufferio.FramebufferDisplay(matrix)

Then, the two display groups are created. There will be a group to show the start-up graphic and a second one to display the score when you're playing the game.

Download: file
#  display groups
start_group = displayio.Group(max_size=30, scale=1)
score_group = displayio.Group(max_size=30, scale=1)

The text (score_text) and colorful background (score_bg) are setup next for the scoreboard. The text is using a bitmap font.

Download: file
#  text & bg color setup for scoreboard
score_text = "      "
font = bitmap_font.load_font("/Fixedsys-32.bdf")
yellow = (255, 127, 0)
pink = 0xFF00FF

score_text = label.Label(font, text=score_text, color=0x0)
score_text.x = 23
score_text.y = 15

score_bg = Rect(0, 0, 64, 32, fill=yellow, outline=pink, stroke=3)

Following the score text setup, the start-up graphic bitmap is setup using the adafruit_imageload library.

Download: file
#  start splash screen graphic
start, start_pal = adafruit_imageload.load("/pixelHoops.bmp",
                                           bitmap=displayio.Bitmap,
                                           palette=displayio.Palette)

start_grid = displayio.TileGrid(start, pixel_shader=start_pal,
                                width=64, height=32)

The start-up bitmap and score text graphics are added to their respective display groups. The start-up bitmap is shown first using display.show(start_group).

Download: file
#  adding graphics to display groups
start_group.append(start_grid)
score_group.append(score_bg)
score_group.append(score_text)

#  start by showing start splash
display.show(start_group)

After the display is setup, the I/O pins are setup for the break beam LED, push button and speaker. The LED and button are setup as inputs and the speaker is setup as an AudioOut using the audioio library.

Download: file
#  setup for break beam LED pin
break_beam = digitalio.DigitalInOut(board.A1)
break_beam.direction = digitalio.Direction.INPUT
break_beam.pull = digitalio.Pull.UP

#  setup for button pin
button = digitalio.DigitalInOut(board.D4)
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP

#  setup for speaker pin
speaker = audioio.AudioOut(board.A0)

The MP3Decoder is setup before the loop, similar to how display objects are setup early in the code. This helps to save memory when playing multiple .mp3 files. Notice how you aren't playing an .mp3 in this section, it's purely for setup.

Download: file
#  mp3 decoder setup
file = "/hoopBloop0.mp3"
mp3stream = audiomp3.MP3Decoder(open(file, "rb"))

The final portion of the setup creates that state machines that will be used in the loop. Their functions are commented below.

Download: file
#  state machines used in the loop
score = 0 #  holds your score for the game
hoops = False #  tracks if a game is active or not
button_state = False #  button debouncing
beam_state = False #  break beam LED debouncing
sample = 0 #  file count for the mp3 files

The loop begins with debouncing setup for the button and break beam LED. This eliminates any glitches in receiving data from their inputs.

Download: file
while True:
    #  button debouncing
    if not button.value and not button_state:
        button_state = True
    #  debouncing for break beam LED
    if not break_beam.value and not beam_state:
        beam_state = True

The button has a different function depending on whether or not a game is in progress. If a game has not started, then pressing the button begins a new game and also changes the display to show the scoreboard graphic.

Download: file
#  if a game hasn't started and you press the button:
    if not button.value and not hoops:
        #  game starts
        hoops = True
        button_state = False
        #  display shows scoreboard
        display.show(score_group)
        print("start game!")
        time.sleep(0.5)

A game runs if the hoops state is True. Every time the break beam LED detects a break, your score increases by two points and an .mp3 file plays.

There are three different .mp3 files and they play in order as the game goes on. The code is able to iterate through the files easily because they are all named hoopBloop*. By naming them like this, you can use "/hoopBloop{}.mp3".format(sample), to iterate through them with sample holding the number.

The code is referencing the earlier mp3stream that was setup before the loop to decode and play the .mp3 files.

Additionally, there is some text formatting that takes place depending on how many digits your score is. This allows for the score to remain centered during game play. 

Download: file
if hoops:
        #  if the break beam LED detects a hoop:
        if not break_beam.value and beam_state:
            #  score increase by 2 points
            score += 2
            #  an mp3 plays
            file = "/hoopBloop{}.mp3".format(sample)
            mp3stream.file = open(file, "rb")
            speaker.play(mp3stream)
            print("score!")
            #  resets break beam
            beam_state = False
            #  increases mp3 file count
            #  plays the 3 files in order
            sample = (sample + 1) % 3
        #  score text x pos if 4 digit score
        if score >= 1000:
            score_text.x = -1
        #  score text x pos if 3 digit score
        elif score >= 100:
            score_text.x = 7
        #  score text x pos if 2 digit score
        elif score >= 10:
            score_text.x = 16
        #  score text x pos if 1 digit score
        elif score >= 0:
            score_text.x = 23
        #  updates score text to show current score
        score_text.text = score
        time.sleep(0.1)

If a game is in progress and you press the button, it changes the hoops state to False and ends the game. The score is also reset to 0 and the display shows the start-up bitmap.

Download: file
#  if a game is in progress and you press the button:
    if not button.value and hoops:
        #  game stops
        hoops = False
        button_state = False
        #  score is reset to 0
        score = 0
        #  display shows the start splash graphic
        display.show(start_group)
        print("end game!")
        time.sleep(0.5)

This guide was first published on Jul 16, 2020. It was last updated on Jul 16, 2020.

This page (LED Matrix Scoreboard Code Walkthrough) was last updated on Mar 26, 2021.

Text editor powered by tinymce.