# tiny_toasters.py

Test sprites_converter.

Tiny Flying Toasters for smaller displays using a converted BMP spritesheet module using:

    `sprites_converter.py ttoasters.bmp 32 32 4 > ttoast_bitmaps.py`

The tiny_toasters.py example uses the spritesheet from CircuitPython_Flying_Toasters pendant project https://learn.adafruit.com/circuitpython-sprite-animation-pendant-mario-clouds-flying-toasters

Note

This example requires the following modules:

- st7789py

- tft_config

- tiny_toasters

      1"""
      2tiny_toasters.py
      3================
      4
      5.. figure:: ../_static/tiny_toasters.jpg
      6    :align: center
      7
      8    Test sprites_converter.
      9
     10Tiny Flying Toasters for smaller displays using a converted BMP spritesheet module using:
     11
     12.. code-block:: console
     13
     14  `sprites_converter.py ttoasters.bmp 32 32 4 > ttoast_bitmaps.py`
     15
     16.. rubric:: The tiny_toasters.py example uses the spritesheet from CircuitPython_Flying_Toasters pendant
     17  project https://learn.adafruit.com/circuitpython-sprite-animation-pendant-mario-clouds-flying-toasters
     18
     19
     20.. note:: This example requires the following modules:
     21
     22  .. hlist::
     23     :columns: 3
     24
     25     - `st7789py`
     26     - `tft_config`
     27     - `tiny_toasters`
     28
     29"""
     30
     31import gc
     32import time
     33import random
     34import st7789py as st7789
     35import tft_config
     36import tiny_toasters_bitmaps as toast_bitmaps
     37
     38TOASTER_FRAMES = [0, 1, 2, 3]
     39TOAST_FRAMES = [4]
     40
     41
     42def collide(a_col, a_row, a_width, a_height, b_col, b_row, b_width, b_height):
     43    """return true if two rectangles overlap"""
     44    return (
     45        a_col + a_width >= b_col
     46        and a_col <= b_col + b_width
     47        and a_row + a_height >= b_row
     48        and a_row <= b_row + b_height
     49    )
     50
     51
     52def random_start(tft, sprites, bitmaps, num):
     53    """
     54    Return a random location along the top or right of the screen, if that location would overlaps
     55    with another sprite return (0,0). This allows the other sprites to keep moving giving the next
     56    random_start a better chance to avoid a collision.
     57
     58    """
     59    # 50/50 chance to try along the top/right half or along the right/top half of the screen
     60    if random.getrandbits(1):
     61        row = 1
     62        col = random.randint(bitmaps.WIDTH // 2, tft.width - bitmaps.WIDTH)
     63    else:
     64        col = tft.width - bitmaps.WIDTH
     65        row = random.randint(1, tft.height // 2)
     66
     67    if any(
     68        collide(
     69            col,
     70            row,
     71            bitmaps.WIDTH,
     72            bitmaps.HEIGHT,
     73            sprite.col,
     74            sprite.row,
     75            sprite.width,
     76            sprite.height,
     77        )
     78        for sprite in sprites
     79        if num != sprite.num
     80    ):
     81        col = 0
     82        row = 0
     83
     84    return (col, row)
     85
     86
     87def main():
     88    class Toast:
     89        """
     90        Toast class to keep track of toaster and toast sprites
     91        """
     92
     93        def __init__(self, sprites, bitmaps, frames):
     94            """create new sprite in random location that does not overlap other sprites"""
     95            self.num = len(sprites)
     96            self.bitmaps = bitmaps
     97            self.frames = frames
     98            self.steps = len(frames)
     99            self.col, self.row = random_start(tft, sprites, bitmaps, self.num)
    100            self.width = bitmaps.WIDTH
    101            self.height = bitmaps.HEIGHT
    102            self.last_col = self.col
    103            self.last_row = self.row
    104            self.step = random.randint(0, self.steps)
    105            self.dir_col = -random.randint(2, 5)
    106            self.dir_row = 2
    107            self.prev_dir_col = self.dir_col
    108            self.prev_dir_row = self.dir_row
    109            self.iceberg = 0
    110
    111        def clear(self):
    112            """clear above and behind sprite"""
    113            tft.fill_rect(
    114                self.col, self.row - 1, self.width, self.dir_row + 1, st7789.BLACK
    115            )
    116
    117            tft.fill_rect(
    118                self.col + self.width + self.dir_col,
    119                self.row,
    120                -self.dir_col,
    121                self.height,
    122                st7789.BLACK,
    123            )
    124
    125        def erase(self):
    126            """erase last position of sprite"""
    127            tft.fill_rect(
    128                self.last_col, self.last_row, self.width, self.height, st7789.BLACK
    129            )
    130
    131        def move(self, sprites):
    132            """step frame and move sprite"""
    133
    134            if self.steps:
    135                self.step = (self.step + 1) % self.steps
    136
    137            self.last_col = self.col
    138            self.last_row = self.row
    139            new_col = self.col + self.dir_col
    140            new_row = self.row + self.dir_row
    141
    142            # if new location collides with another sprite, change direction for 32 frames
    143
    144            for sprite in sprites:
    145                if (
    146                    self.num != sprite.num
    147                    and collide(
    148                        new_col,
    149                        new_row,
    150                        self.width,
    151                        self.height,
    152                        sprite.col,
    153                        sprite.row,
    154                        sprite.width,
    155                        sprite.height,
    156                    )
    157                    and (self.col > sprite.col)
    158                ):
    159                    self.iceberg = 32
    160                    self.dir_col = -1
    161                    self.dir_row = 3
    162                    new_col = self.col + self.dir_col
    163                    new_row = self.row + self.dir_row
    164
    165            self.col = new_col
    166            self.row = new_row
    167
    168            # if new location touches edge of screen, erase then set new start location
    169            if self.col <= 0 or self.row > tft.height - self.height:
    170                self.erase()
    171                self.dir_col = -random.randint(2, 5)
    172                self.dir_row = 2
    173                self.col, self.row = random_start(tft, sprites, self.bitmaps, self.num)
    174
    175            # Track post collision direction change
    176            if self.iceberg:
    177                self.iceberg -= 1
    178                if self.iceberg == 1:
    179                    self.dir_col = self.prev_dir_col
    180                    self.dir_row = self.prev_dir_row
    181
    182        def draw(self):
    183            """if the location is not 0,0 draw current frame of sprite at it's location"""
    184            if self.col and self.row:
    185                tft.bitmap(self.bitmaps, self.col, self.row, self.frames[self.step])
    186
    187    # configure display driver
    188    tft = tft_config.config(tft_config.WIDE)
    189
    190    # create toast spites and set animation frames
    191    sprites = []
    192    sprites.append(Toast(sprites, toast_bitmaps, TOAST_FRAMES))
    193    sprites.append(Toast(sprites, toast_bitmaps, TOASTER_FRAMES))
    194    sprites.append(Toast(sprites, toast_bitmaps, TOASTER_FRAMES))
    195
    196    # move and draw sprites
    197
    198    while True:
    199        for sprite in sprites:
    200            sprite.clear()
    201            sprite.move(sprites)
    202            sprite.draw()
    203
    204        gc.collect()
    205        time.sleep(0.05)
    206
    207
    208main()