code.py
  1  # SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  """
  6  Continuously scroll randomly generated After Dark style toasters.
  7  Designed for an ItsyBitsy M4 Express and a 1.3" 240x240 TFT
  8  
  9  Adafruit invests time and resources providing this open source code.
 10  Please support Adafruit and open source hardware by purchasing
 11  products from Adafruit!
 12  
 13  Written by Dave Astels for Adafruit Industries
 14  Copyright (c) 2019 Adafruit Industries
 15  Licensed under the MIT license.
 16  
 17  All text above must be included in any redistribution.
 18  """
 19  
 20  import time
 21  from random import seed, randint
 22  import board
 23  import displayio
 24  from adafruit_st7789 import ST7789
 25  import adafruit_imageload
 26  
 27  
 28  # Sprite cell values
 29  EMPTY = 0
 30  CELL_1 = EMPTY + 1
 31  CELL_2 = CELL_1 + 1
 32  CELL_3 = CELL_2 + 1
 33  CELL_4 = CELL_3 + 1
 34  TOAST = CELL_4 + 1
 35  
 36  NUMBER_OF_SPRITES = TOAST + 1
 37  
 38  # Animation support
 39  
 40  FIRST_CELL = CELL_1
 41  LAST_CELL = CELL_4
 42  NUMBER_OF_CELLS = (LAST_CELL - FIRST_CELL) + 1
 43  
 44  # A boolean array corresponding to the sprites, True if it's part of the animation sequence.
 45  ANIMATED = [FIRST_CELL <= _sprite <= LAST_CELL for _sprite in range(NUMBER_OF_SPRITES)]
 46  
 47  # The chance (out of 10) that toast will enter
 48  CHANCE_OF_NEW_TOAST = 2
 49  
 50  # How many sprites to start with
 51  INITIAL_NUMBER_OF_SPRITES = 4
 52  
 53  seed(int(time.monotonic()))
 54  
 55  
 56  def make_display():
 57      """Set up the display support.
 58      Return the Display object.
 59      """
 60      spi = board.SPI()
 61      while not spi.try_lock():
 62          pass
 63      spi.configure(baudrate=24000000)  # Configure SPI for 24MHz
 64      spi.unlock()
 65      displayio.release_displays()
 66      display_bus = displayio.FourWire(spi, command=board.D7, chip_select=board.D10, reset=board.D9)
 67  
 68      return ST7789(display_bus, width=240, height=240, rowstart=80, auto_refresh=True)
 69  
 70  
 71  def make_tilegrid():
 72      """Construct and return the tilegrid."""
 73      group = displayio.Group()
 74  
 75      sprite_sheet, palette = adafruit_imageload.load("/spritesheet-2x.bmp",
 76                                                      bitmap=displayio.Bitmap,
 77                                                      palette=displayio.Palette)
 78      grid = displayio.TileGrid(sprite_sheet, pixel_shader=palette,
 79                                width=5, height=5,
 80                                tile_height=64, tile_width=64,
 81                                x=0, y=-64,
 82                                default_tile=EMPTY)
 83      group.append(grid)
 84      display.show(group)
 85      return grid
 86  
 87  
 88  def random_cell():
 89      return randint(FIRST_CELL, LAST_CELL)
 90  
 91  
 92  def evaluate_position(row, col):
 93      """Return whether how long of a toaster is placeable at the given location.
 94      :param row: the tile row (0-9)
 95      :param col: the tile column (0-9)
 96      """
 97      return tilegrid[col, row] == EMPTY
 98  
 99  
100  def seed_toasters(number_of_toasters):
101      """Create the initial toasters so it doesn't start empty"""
102      for _ in range(number_of_toasters):
103          while True:
104              row = randint(0, 4)
105              col = randint(0, 4)
106              if evaluate_position(row, col):
107                  break
108          tilegrid[col, row] = random_cell()
109  
110  
111  def next_sprite(sprite):
112      if ANIMATED[sprite]:
113          return (((sprite - FIRST_CELL) + 1) % NUMBER_OF_CELLS) + FIRST_CELL
114      return sprite
115  
116  
117  def advance_animation():
118      """Cycle through animation cells each time."""
119      for tile_number in range(25):
120          tilegrid[tile_number] = next_sprite(tilegrid[tile_number])
121  
122  
123  def slide_tiles():
124      """Move the tilegrid one pixel to the bottom-left."""
125      tilegrid.x -= 1
126      tilegrid.y += 1
127  
128  
129  def shift_tiles():
130      """Move tiles one spot to the left, and reset the tilegrid's position"""
131      for row in range(4, 0, -1):
132          for col in range(4):
133              tilegrid[col, row] = tilegrid[col + 1, row - 1]
134          tilegrid[4, row] = EMPTY
135      for col in range(5):
136          tilegrid[col, 0] = EMPTY
137      tilegrid.x = 0
138      tilegrid.y = -64
139  
140  
141  def get_entry_row():
142      while True:
143          row = randint(0, 4)
144          if tilegrid[4, row] == EMPTY and tilegrid[3, row] == EMPTY:
145              return row
146  
147  
148  def get_entry_column():
149      while True:
150          col = randint(0, 3)
151          if tilegrid[col, 0] == EMPTY and tilegrid[col, 1] == EMPTY:
152              return col
153  
154  
155  def add_toaster_or_toast():
156      """Maybe add a new toaster or toast on the right and/or top at a random open location"""
157      if randint(1, 10) <= CHANCE_OF_NEW_TOAST:
158          tile = TOAST
159      else:
160          tile = random_cell()
161      tilegrid[4, get_entry_row()] = tile
162  
163      if randint(1, 10) <= CHANCE_OF_NEW_TOAST:
164          tile = TOAST
165      else:
166          tile = random_cell()
167      tilegrid[get_entry_column(), 0] = tile
168  
169  
170  display = make_display()
171  tilegrid = make_tilegrid()
172  seed_toasters(INITIAL_NUMBER_OF_SPRITES)
173  display.refresh()
174  
175  while True:
176      for _ in range(64):
177          display.refresh(target_frames_per_second=80)
178          advance_animation()
179          slide_tiles()
180      shift_tiles()
181      add_toaster_or_toast()
182      display.refresh(target_frames_per_second=120)