code.py
1 # SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 import random 6 import time 7 8 import board 9 import displayio 10 import framebufferio 11 import rgbmatrix 12 13 displayio.release_displays() 14 15 matrix = rgbmatrix.RGBMatrix( 16 width=64, height=32, bit_depth=3, 17 rgb_pins=[board.D6, board.D5, board.D9, board.D11, board.D10, board.D12], 18 addr_pins=[board.A5, board.A4, board.A3, board.A2], 19 clock_pin=board.D13, latch_pin=board.D0, output_enable_pin=board.D1) 20 display = framebufferio.FramebufferDisplay(matrix, auto_refresh=False) 21 22 # This bitmap contains the emoji we're going to use. It is assumed 23 # to contain 20 icons, each 20x24 pixels. This fits nicely on the 64x32 24 # RGB matrix display. 25 26 filename = "emoji.bmp" 27 28 # CircuitPython 6 & 7 compatible 29 bitmap_file = open(filename, 'rb') 30 bitmap = displayio.OnDiskBitmap(bitmap_file) 31 pixel_shader = getattr(bitmap, 'pixel_shader', displayio.ColorConverter()) 32 33 # # CircuitPython 7+ compatible 34 # bitmap = displayio.OnDiskBitmap(filename) 35 # pixel_shader = bitmap.pixel_shader 36 37 # Each wheel can be in one of three states: 38 STOPPED, RUNNING, BRAKING = range(3) 39 40 # Return a duplicate of the input list in a random (shuffled) order. 41 def shuffled(seq): 42 return sorted(seq, key=lambda _: random.random()) 43 44 # The Wheel class manages the state of one wheel. "pos" is a position in 45 # scaled integer coordinates, with one revolution being 7680 positions 46 # and 1 pixel being 16 positions. The wheel also has a velocity (in positions 47 # per tick) and a state (one of the above constants) 48 class Wheel(displayio.TileGrid): 49 def __init__(self): 50 # Portions of up to 3 tiles are visible. 51 super().__init__(bitmap=bitmap, pixel_shader=pixel_shader, 52 width=1, height=3, tile_width=20, tile_height=24) 53 self.order = shuffled(range(20)) 54 self.state = STOPPED 55 self.pos = 0 56 self.vel = 0 57 self.y = 0 58 self.x = 0 59 self.stop_time = time.monotonic_ns() 60 61 def step(self): 62 # Update each wheel for one time step 63 if self.state == RUNNING: 64 # Slowly lose speed when running, but go at least speed 64 65 self.vel = max(self.vel * 9 // 10, 64) 66 if time.monotonic_ns() > self.stop_time: 67 self.state = BRAKING 68 elif self.state == BRAKING: 69 # More quickly lose speed when braking, down to speed 7 70 self.vel = max(self.vel * 85 // 100, 7) 71 72 # Advance the wheel according to the velocity, and wrap it around 73 # after 7680 positions 74 self.pos = (self.pos + self.vel) % 7680 75 76 # Compute the rounded Y coordinate 77 yy = round(self.pos / 16) 78 # Compute the offset of the tile (tiles are 24 pixels tall) 79 yyy = yy % 24 80 # Find out which tile is the top tile 81 off = yy // 24 82 83 # If we're braking and a tile is close to midscreen, 84 # then stop and make sure that tile is exactly centered 85 if self.state == BRAKING and self.vel == 7 and yyy < 4: 86 self.pos = off * 24 * 16 87 self.vel = 0 88 self.state = STOPPED 89 90 # Move the displayed tiles to the correct height and make sure the 91 # correct tiles are displayed. 92 self.y = yyy - 20 93 for i in range(3): 94 self[i] = self.order[(19 - i + off) % 20] 95 96 # Set the wheel running again, using a slight bit of randomness. 97 # The 'i' value makes sure the first wheel brakes first, the second 98 # brakes second, and the third brakes third. 99 def kick(self, i): 100 self.state = RUNNING 101 self.vel = random.randint(256, 320) 102 self.stop_time = time.monotonic_ns() + 3_000_000_000 + i * 350_000_000 103 104 # Our fruit machine has 3 wheels, let's create them with a correct horizontal 105 # (x) offset and arbitrary vertical (y) offset. 106 g = displayio.Group() 107 wheels = [] 108 for idx in range(3): 109 wheel = Wheel() 110 wheel.x = idx * 22 111 wheel.y = -20 112 g.append(wheel) 113 wheels.append(wheel) 114 display.show(g) 115 116 # Make a unique order of the emoji on each wheel 117 orders = [shuffled(range(20)), shuffled(range(20)), shuffled(range(20))] 118 119 # And put up some images to start with 120 for si, oi in zip(wheels, orders): 121 for idx in range(3): 122 si[idx] = oi[idx] 123 124 # We want a way to check if all the wheels are stopped 125 def all_stopped(): 126 return all(si.state == STOPPED for si in wheels) 127 128 # To start with, though, they're all in motion 129 for idx, si in enumerate(wheels): 130 si.kick(idx) 131 132 # Here's the main loop 133 while True: 134 # Refresh the display (doing this manually ensures the wheels move 135 # together, not at different times) 136 display.refresh(minimum_frames_per_second=0) 137 if all_stopped(): 138 # Once everything comes to a stop, wait a little bit and then 139 # start everything over again. Maybe you want to check if the 140 # combination is a "winner" and add a light show or something. 141 for idx in range(100): 142 display.refresh(minimum_frames_per_second=0) 143 for idx, si in enumerate(wheels): 144 si.kick(idx) 145 146 # Otherwise, let the wheels keep spinning... 147 for idx, si in enumerate(wheels): 148 si.step()