tiny_toasters.py

../_images/tiny_toasters.jpg

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()