tiny_toasters.py
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:
|
|
|
1"""
2tiny_toasters.py
3================
4
5.. figure:: /_static/tiny_toasters.png
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 - `gc9a01`
26 - `tft_config`
27 - `tiny_toasters`
28
29"""
30
31import gc
32import time
33import random
34import gc9a01
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, gc9a01.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 gc9a01.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, gc9a01.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 tft.fill(gc9a01.BLACK)
190
191 # create toast spites and set animation frames
192 sprites = []
193 sprites.append(Toast(sprites, toast_bitmaps, TOAST_FRAMES))
194 sprites.append(Toast(sprites, toast_bitmaps, TOASTER_FRAMES))
195 sprites.append(Toast(sprites, toast_bitmaps, TOASTER_FRAMES))
196
197 # move and draw sprites
198
199 while True:
200 for sprite in sprites:
201 sprite.clear()
202 sprite.move(sprites)
203 sprite.draw()
204
205 gc.collect()
206 time.sleep(0.05)
207
208
209main()