Setup

CircuitPython Libraries

The CircuitPython code begins by importing the libraries that are used.

Download: file
import random
import time

import adafruit_imageload.bmp
import audioio
import audiocore
import audiomp3
import board
import displayio
import digitalio
import framebufferio
import rgbmatrix

Display setup

First, any display left over from a previous code.py is released. Then, the LED matrix is initialized according to its pinout. Because we want to ensure that all 3 wheels on screen are updated at the same time, we specify auto_refresh=False. To learn more about how to write code using the rgbmatrix module, see the dedicated guide.

Download: file
displayio.release_displays()

matrix = rgbmatrix.RGBMatrix(
    width=64, height=32, bit_depth=3,
    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, auto_refresh=False)

Additional definitions

The three states let us track whether each wheel is stopped, running, or braking.

shuffled lets us take an input sequence and return a new sequence with the order of the elements randomized. This will be used to put the symbols in a different order on each wheel.

Wheel is a class that controls the behavior of each of the 3 fruit wheels. Check out the full source code if you want to see how it works!

Download: file
# Each wheel can be in one of three states:
STOPPED, RUNNING, BRAKING = range(3)

# Return a duplicate of the input list in a random (shuffled) order.
def shuffled(seq):
    return sorted(seq, key=lambda _: random.random())

# The Wheel class manages the state of one wheel. "pos" is a position in
# floating point coordinates, with one 1 pixel being 1 position.
# The wheel also has a velocity (in positions
# per tick) and a state (one of the above constants)
class Wheel(displayio.TileGrid):
    def __init__(self, bitmap, pixel_shader): ...
    def step(self):
        """Update each wheel for one time step"""
        ...
    def kick(self, i):
        """Set the wheel running again, using a slight bit of randomness.
        The 'i' value makes sure the first wheel brakes first, the second
        brakes second, and the third brakes third."""
        ...

Setting up the wheels

First, load the emoji bitmap into RAM memory. It is small (just a few hundred bytes), and doing this lets the display update much more fluidly than when the bitmap is in FLASH memory.

Then, create 3 wheels and put them all in a group, setting their X and Y positions so that they span the display from left to right.

Download: file
# This bitmap contains the emoji we're going to use. It is assumed
# to contain 20 icons, each 20x24 pixels. This fits nicely on the 64x32
# RGB matrix display.
bitmap, palette = adafruit_imageload.load(
    "/emoji.bmp",
    bitmap=displayio.Bitmap,
    palette=displayio.Palette)

# Our fruit machine has 3 wheels, let's create them with a correct horizontal
# (x) offset and arbitrary vertical (y) offset.
g = displayio.Group(max_size=3)
wheels = []
for idx in range(3):
    wheel = Wheel(bitmap, palette)
    wheel.x = idx * 22
    wheel.y = -20
    g.append(wheel)
    wheels.append(wheel)
display.show(g)

Setting up the activation switch

The switch is connected to A1, and closing it connects it to GND. By enabling a pull up, the button.value will be False when it is pressed and True otherwise.

Download: file
# We want a digital input to trigger the fruit machine
button = digitalio.DigitalInOut(board.A1)
button.switch_to_input(pull=digitalio.Pull.UP)

Setting up the background sounds

The speaker is connected to A0. Play casino-style noises in MP3 format. If you want to change the MP3, make sure you use a file with the following properties:

  • 16000 or 22050 sample rate
  • 1 channel (mono)
  • 64kbit/s or lower MP3 CBR encoding

You might also like to have multiple different sounds, for instance to play when the lever is pulled and when the wheels stop. You can change to a different sound file by open()ing a new file and assigning it to sample.file. Make sure your files all match in their sample rate, channels, and encoding.

Download: file
mp3file = open("/slots.mp3", "rb")
sample = audiomp3.MP3Decoder(mp3file)

speaker = audioio.AudioOut(board.A0)
speaker.play(sample, loop=True)

Main loop

Here's how the main loop runs:

  • Refresh the screen each time through the loop
  • If all the wheels are stopped, then wait for the lever to be pulled (check button.value) and when it is, set all the wheels in motion by kick()ing them
  • Otherwise, let each wheel rotate a little bit by calling step() 
  • Go back to the top of the loop
Download: file
while True:
    # Refresh the dislpay (doing this manually ensures the wheels move
    # together, not at different times)
    display.refresh(minimum_frames_per_second=0, target_frames_per_second=60)

    all_stopped = all(si.state == STOPPED for si in wheels)
    if all_stopped:
        # Once everything comes to a stop, wait until the lever is pulled and
        # start everything over again. Maybe you want to check if the
        # combination is a "winner" and add a light show or something.
        while button.value:
            pass
        for idx, si in enumerate(wheels):
            si.kick(idx)

    # Otherwise, let the wheels keep spinning...
    for idx, si in enumerate(wheels):
        si.step()
This guide was first published on Jun 17, 2020. It was last updated on Jun 17, 2020.
This page (CircuitPython Code Walkthrough) was last updated on Sep 02, 2020.