code.py
  1  # SPDX-FileCopyrightText: 2019 Carter Nelson for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  from random import randrange
  6  import board
  7  import busio
  8  import displayio
  9  from adafruit_gizmo import tft_gizmo
 10  import adafruit_imageload
 11  import adafruit_lis3dh
 12  
 13  #---| User Config |---------------
 14  BACKGROUND = "/blinka_dark.bmp"    # specify color or background BMP file
 15  
 16  NUM_FLAKES = 50                    # total number of snowflakes
 17  FLAKE_SHEET = "/flakes_sheet.bmp"  # flake sprite sheet
 18  FLAKE_WIDTH = 4                    # sprite width
 19  FLAKE_HEIGHT = 4                   # sprite height
 20  FLAKE_TRAN_COLOR = 0x000000        # transparency color
 21  
 22  SNOW_COLOR = 0xFFFFFF              # snow color
 23  
 24  SHAKE_THRESHOLD = 27               # shake sensitivity, lower=more sensitive
 25  #---| User Config |---------------
 26  
 27  # Accelerometer setup
 28  accelo_i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
 29  accelo = adafruit_lis3dh.LIS3DH_I2C(accelo_i2c, address=0x19)
 30  
 31  # Create the TFT Gizmo display
 32  display = tft_gizmo.TFT_Gizmo()
 33  
 34  # Load background image
 35  try:
 36      bg_bitmap, bg_palette = adafruit_imageload.load(BACKGROUND,
 37                                                      bitmap=displayio.Bitmap,
 38                                                      palette=displayio.Palette)
 39  # Or just use solid color
 40  except (OSError, TypeError, AttributeError):
 41      BACKGROUND = BACKGROUND if isinstance(BACKGROUND, int) else 0x000000
 42      bg_bitmap = displayio.Bitmap(display.width, display.height, 1)
 43      bg_palette = displayio.Palette(1)
 44      bg_palette[0] = BACKGROUND
 45  background = displayio.TileGrid(bg_bitmap, pixel_shader=bg_palette)
 46  
 47  
 48  # Snowflake setup
 49  flake_bitmap, flake_palette = adafruit_imageload.load(FLAKE_SHEET,
 50                                                        bitmap=displayio.Bitmap,
 51                                                        palette=displayio.Palette)
 52  if FLAKE_TRAN_COLOR is not None:
 53      for i, color in enumerate(flake_palette):
 54          if color == FLAKE_TRAN_COLOR:
 55              flake_palette.make_transparent(i)
 56              break
 57  NUM_SPRITES = flake_bitmap.width // FLAKE_WIDTH * flake_bitmap.height // FLAKE_HEIGHT
 58  flake_pos = [0.0] * NUM_FLAKES
 59  flakes = displayio.Group()
 60  for _ in range(NUM_FLAKES):
 61      flakes.append(displayio.TileGrid(flake_bitmap, pixel_shader=flake_palette,
 62                                       width = 1,
 63                                       height = 1,
 64                                       tile_width = FLAKE_WIDTH,
 65                                       tile_height = FLAKE_HEIGHT,
 66                                       x = randrange(0, display.width),
 67                                       default_tile=randrange(0, NUM_SPRITES)))
 68  
 69  # Snowfield setup
 70  snow_depth = [display.height] * display.width
 71  snow_palette = displayio.Palette(2)
 72  snow_palette[0] = 0xADAF00   # transparent color
 73  snow_palette[1] = SNOW_COLOR # snow color
 74  snow_palette.make_transparent(0)
 75  snow_bitmap = displayio.Bitmap(display.width, display.height, len(snow_palette))
 76  snow = displayio.TileGrid(snow_bitmap, pixel_shader=snow_palette)
 77  
 78  # Add everything to display
 79  splash = displayio.Group()
 80  splash.append(background)
 81  splash.append(flakes)
 82  splash.append(snow)
 83  display.show(splash)
 84  
 85  def clear_the_snow():
 86      #pylint: disable=global-statement, redefined-outer-name
 87      global flakes, flake_pos, snow_depth
 88      display.auto_refresh = False
 89      for flake in flakes:
 90          # set to a random sprite
 91          flake[0] = randrange(0, NUM_SPRITES)
 92          # set to a random x location
 93          flake.x = randrange(0, display.width)
 94      # set random y locations, off screen to start
 95      flake_pos = [-1.0*randrange(0, display.height) for _ in range(NUM_FLAKES)]
 96      # reset snow level
 97      snow_depth = [display.height] * display.width
 98      # and snow bitmap
 99      for i in range(display.width*display.height):
100          snow_bitmap[i] = 0
101      display.auto_refresh = True
102  
103  def add_snow(index, amount, steepness=2):
104      location = []
105      # local steepness check
106      for x in range(index - amount, index + amount):
107          add = False
108          if x == 0:
109              # check depth to right
110              if snow_depth[x+1] - snow_depth[x] < steepness:
111                  add = True
112          elif x == display.width - 1:
113              # check depth to left
114              if snow_depth[x-1] - snow_depth[x] < steepness:
115                  add = True
116          elif 0 < x < display.width - 1:
117              # check depth to left AND right
118              if snow_depth[x-1] - snow_depth[x] < steepness and \
119                 snow_depth[x+1] - snow_depth[x] < steepness:
120                  add = True
121          if add:
122              location.append(x)
123      # add where snow is not too steep
124      for x in location:
125          new_level = snow_depth[x] - 1
126          if new_level >= 0:
127              snow_depth[x] = new_level
128              snow_bitmap[x, new_level] = 1
129  
130  while True:
131      clear_the_snow()
132      # loop until globe is full of snow
133      while snow_depth.count(0) < display.width:
134          # check for shake
135          if accelo.shake(SHAKE_THRESHOLD, 5, 0):
136              break
137          # update snowflakes
138          for i, flake in enumerate(flakes):
139              # speed based on sprite index
140              flake_pos[i] += 1 - flake[0] / NUM_SPRITES
141              # check if snowflake has hit the ground
142              if flake_pos[i] >= snow_depth[flake.x]:
143                  # add snow where it fell
144                  add_snow(flake.x, FLAKE_WIDTH)
145                  # reset flake to top
146                  flake_pos[i] = 0
147                  # at a new x location
148                  flake.x = randrange(0, display.width)
149              flake.y = int(flake_pos[i])
150          display.refresh()