-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuddy.py
More file actions
288 lines (258 loc) · 8.3 KB
/
Copy pathbuddy.py
File metadata and controls
288 lines (258 loc) · 8.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
import time
# Each frame below is the whole crab, 16 wide x 10 tall, drawn as an ASCII
# grid that you can read at a glance and edit by hand. The character inside
# each non-transparent cell picks the pen via _PEN:
#
# B = body C = claw (body pen) L = leg (body pen)
# E = eye h = heart / accent
# . or space = transparent
#
# Adding a new pose = copy a frame and change cells. That's it.
_PEN = {
"B": "body",
"C": "body",
"L": "body",
"E": "eye",
"e": "eye", # half-height eye — thin slit, used for closed / blinking eyes
"h": "accent",
}
_FRAME_BASE = [
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBEBBBBBBEBB..", # eyes centred
"..BBEBBBBBBEBB..",
"CCBBBBBBBBBBBBCC", # claws extend sideways
"CCBBBBBBBBBBBBCC",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...", # 4 legs planted
"...L.L....L.L...",
]
_FRAME_BLINK = [
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..", # eyelid (top half of the eye area)
"..BeeBBBBBBeeB..", # two-cell-wide slit, 1 logical px tall
"CCBBBBBBBBBBBBCC",
"CCBBBBBBBBBBBBCC",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
"...L.L....L.L...",
]
_FRAME_LOOK_LEFT = [
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BEBBBBBBEBBB..", # both eyes shifted 1 col left
"..BEBBBBBBEBBB..",
"CCBBBBBBBBBBBBCC",
"CCBBBBBBBBBBBBCC",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
"...L.L....L.L...",
]
_FRAME_LOOK_RIGHT = [
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBBEBBBBBBEB..", # both eyes shifted 1 col right
"..BBBEBBBBBBEB..",
"CCBBBBBBBBBBBBCC",
"CCBBBBBBBBBBBBCC",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
"...L.L....L.L...",
]
_FRAME_WIDE = [
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBEEBBBBEEBB..", # 2-wide pupils, moved inward
"..BBEEBBBBEEBB..",
"CCBBBBBBBBBBBBCC",
"CCBBBBBBBBBBBBCC",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
"...L.L....L.L...",
]
_FRAME_HEART = [
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBhBBBBBBhBB..", # heart-accent eyes
"..BBhBBBBBBhBB..",
"CCBBBBBBBBBBBBCC",
"CCBBBBBBBBBBBBCC",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
"...L.L....L.L...",
]
# Right claw raised above the head on the top-right corner, left claw at its
# usual post. Two frames so the tip bobs in and out for a waving-hello motion.
# Taller (12 rows) than the standard 10-row frame — Buddy.draw anchors the
# last 10 rows at the normal body position and lets the overhang rows extend
# above, which is what reads as "arm above the head" rather than beside it.
_FRAME_HAND_A = [
"............CC..",
"............CC..",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBEBBBBBBEBB..",
"..BBEBBBBBBEBB..",
"CCBBBBBBBBBBBB..",
"CCBBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
"...L.L....L.L...",
]
_FRAME_HAND_B = [
"................",
"............CC..",
"............CC..",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBEBBBBBBEBB..",
"..BBEBBBBBBEBB..",
"CCBBBBBBBBBBBB..",
"CCBBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
]
# Dance poses: each frame has opposite arm/leg raised for a jig feel.
# Frame A = right arm up + left legs lifted; frame B = mirror.
_FRAME_CELEBRATE_A = [
"................", # right claw raised above the head
"................",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBEBBBBBBEBB..",
"..BBEBBBBBBEBBCC",
"..BBBBBBBBBBBBCC", # both side claws at normal middle position
"CCBBBBBBBBBBBB..",
"CCBBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
".....L......L...", # left legs lifted (right pair planted)
]
_FRAME_CELEBRATE_B = [
"................", # left claw raised above the head
"................",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBEBBBBBBEBB..",
"CCBBEBBBBBBEBB..",
"CCBBBBBBBBBBBB..",
"..BBBBBBBBBBBBCC",
"..BBBBBBBBBBBBCC",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
"...L......L.....", # right legs lifted (left pair planted)
]
# Busy scuttle: alternate which pair of legs is planted vs lifted.
_FRAME_WALK_A = [
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBEBBBBBBEBB..",
"..BBEBBBBBBEBB..",
"CCBBBBBBBBBBBBCC",
"CCBBBBBBBBBBBBCC",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
"...L.L..........", # right pair lifted
]
_FRAME_WALK_B = [
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"..BBEBBBBBBEBB..",
"..BBEBBBBBBEBB..",
"CCBBBBBBBBBBBBCC",
"CCBBBBBBBBBBBBCC",
"..BBBBBBBBBBBB..",
"..BBBBBBBBBBBB..",
"...L.L....L.L...",
"..........L.L...", # left pair lifted
]
_PALETTES = {
"default": {"body": (195, 100, 70), "eye": ( 30, 30, 30), "accent": (245, 210, 60)},
"attention": {"body": (230, 90, 70), "eye": ( 30, 30, 30), "accent": (255, 80, 60)},
"heart": {"body": (230, 140, 170), "eye": (180, 50, 90), "accent": (255, 150, 180)},
}
class Buddy:
def __init__(self, display, top_y=16, px=8):
self.display = display
self.top_y = top_y
self.px = px
self.sprite_w = 16 * px
self.sprite_h = 10 * px
w, _ = display.get_bounds()
self.x = (w - self.sprite_w) // 2
self._pens = {
name: {k: display.create_pen(*v) for k, v in rgb.items()}
for name, rgb in _PALETTES.items()
}
# Whole-sprite animation offset (pixels). State handlers set these
# for hops, shakes and nudges that don't warrant a new frame.
self.sprite_dx = 0
self.sprite_dy = 0
def _pick(self, state):
"""Pick (frame, palette) and set sprite_dx/dy for this tick."""
self.sprite_dx = 0
self.sprite_dy = 0
t = time.ticks_ms()
if state == "sleep":
return _FRAME_BLINK, "default"
if state == "idle":
# 6 s cycle: blink, hold, glance left, hold, glance right, hold.
phase = t % 6000
if phase < 140:
return _FRAME_BLINK, "default"
if 1500 <= phase < 1800:
return _FRAME_LOOK_LEFT, "default"
if 3000 <= phase < 3300:
return _FRAME_LOOK_RIGHT, "default"
return _FRAME_BASE, "default"
if state == "busy":
return (_FRAME_WALK_A if (t // 110) & 1 else _FRAME_WALK_B), "default"
if state == "attention":
self.sprite_dx = 1 if (t // 110) & 1 else -1
return _FRAME_WIDE, "attention"
if state == "celebrate":
return (_FRAME_CELEBRATE_A if (t // 280) & 1 else _FRAME_CELEBRATE_B), "default"
if state == "heart":
return _FRAME_HEART, "heart"
if state == "hand":
return (_FRAME_HAND_A if (t // 320) & 1 else _FRAME_HAND_B), "default"
return _FRAME_BASE, "default"
def draw(self, state):
frame, palette_key = self._pick(state)
pens = self._pens[palette_key]
d = self.display
px = self.px
# Frames taller than the 10-row baseline extend upward: the bottom
# 10 rows always sit at top_y, and any prepended rows stick up above.
overhang = max(0, len(frame) - 10)
ox = self.x + self.sprite_dx
oy = self.top_y + self.sprite_dy - overhang * px
slit_h = 4
slit_dy = (px - slit_h) // 2
body_pen = pens["body"]
for r, line in enumerate(frame):
y = oy + r * px
for c, ch in enumerate(line):
pen_key = _PEN.get(ch)
if pen_key is None:
continue
if ch == "e":
# Fill the cell with body first so the non-slit area isn't
# the screen background showing through.
d.set_pen(body_pen)
d.rectangle(ox + c * px, y, px, px)
d.set_pen(pens[pen_key])
d.rectangle(ox + c * px, y + slit_dy, px, slit_h)
else:
d.set_pen(pens[pen_key])
d.rectangle(ox + c * px, y, px, px)