Skip to content

Commit 697c2fa

Browse files
committed
fix(bq27441): Fix tamagotchi example naming, logic and style.
Address review comments from #399: 1. Rename tamagotchie.py -> tamagotchi.py (fix English spelling). 2. Harmonize NEED casing: "i'm hungry" -> "I'm hungry" and update the matching logic in action_check accordingly. 3. Simplify action_check: remove the unused `win` parameter (it was always overwritten before being read) and return a boolean directly. 4. Remove unused "evolution" sound (defined but never played). 5. Rename SPRITE_HANGRY -> SPRITE_ANGRY (standard English). 6. Fix all style issues: spaces before colons (E203), inconsistent SOUND dict indentation, missing blank lines before functions (E302), missing operator spacing in draw_character (E225), double blank lines inside blocks (E303). 7. Add docstrings to all functions and a module-level docstring. 8. Name magic timing constants: IDLE_DISPLAY_MS, RESPONSE_TIMEOUT_MS, RESULT_DISPLAY_MS. 9. Add phase comments in main() for readability (idle / need / response / result).
1 parent 31c820c commit 697c2fa

1 file changed

Lines changed: 93 additions & 91 deletions

File tree

Lines changed: 93 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
"""Battery Tamagotchi example using BQ27441, MCP23009E D-PAD, SSD1327 OLED and buzzer.
2+
3+
The creature ages based on the real battery percentage and periodically asks
4+
to be fed or played with via the D-PAD. A correct answer triggers a happy
5+
sprite + success sound; a wrong or missed answer triggers an angry sprite +
6+
fail sound. When the battery drops below 10 %, the game is over.
7+
8+
Controls:
9+
UP / DOWN -> navigate the action menu
10+
LEFT -> confirm selection
11+
"""
12+
113
import random
214
from time import sleep_ms, ticks_diff, ticks_ms
315

@@ -17,27 +29,26 @@
1729
)
1830
from pyb import Timer
1931

20-
# setup screen
32+
# --- Hardware setup ---
33+
2134
spi = SPI(1)
2235
dc = Pin("DATA_COMMAND_DISPLAY")
2336
res = Pin("RST_DISPLAY")
2437
cs = Pin("CS_DISPLAY")
2538
display = ssd1327.WS_OLED_128X128_SPI(spi, dc, res, cs)
2639

27-
# setup mcp23009e
2840
i2c = I2C(1)
2941
reset_expander = Pin("RST_EXPANDER", Pin.OUT)
3042
mcp = MCP23009E(i2c, address=MCP23009_I2C_ADDR, reset_pin=reset_expander)
3143

32-
# setup battery
3344
fg = BQ27441(i2c)
3445

35-
# sound
3646
buzzer_tim = Timer(1, freq=1000)
3747
buzzer_ch = buzzer_tim.channel(4, Timer.PWM, pin=Pin("SPEAKER"))
3848
buzzer_ch.pulse_width_percent(0)
3949

40-
# D-PAD button mapping
50+
# --- Constants ---
51+
4152
BUTTONS = {
4253
MCP23009_BTN_UP: "UP",
4354
MCP23009_BTN_DOWN: "DOWN",
@@ -46,14 +57,17 @@
4657
}
4758

4859
ACTION = ["food", "play"]
49-
NEED = ["I'm bored", "i'm hungry"]
60+
NEED = ["I'm bored", "I'm hungry"]
61+
62+
IDLE_DISPLAY_MS = 1000
63+
RESPONSE_TIMEOUT_MS = 5000
64+
RESULT_DISPLAY_MS = 1000
5065

51-
# position of button
5266
X0 = 35
5367
ITEM_Y = 100
5468
ITEM_SPACING = 14
5569

56-
# sprite
70+
# --- Sprites ---
5771

5872
SPRITE_BASE = [
5973
[0, 0, 15, 15, 0, 0, 0, 0, 0, 15, 15, 0, 0],
@@ -127,7 +141,7 @@
127141
[0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0],
128142
]
129143

130-
SPRITE_HANGRY = [
144+
SPRITE_ANGRY = [
131145
[0, 0, 15, 15, 0, 0, 0, 0, 0, 15, 15, 0, 0],
132146
[0, 15, 15, 15, 15, 0, 0, 0, 15, 15, 15, 15, 0],
133147
[0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0],
@@ -163,48 +177,39 @@
163177
[0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0],
164178
]
165179

180+
# --- Sounds ---
181+
166182
SOUND = {
167-
"start": [
168-
(523, 120),
169-
(659, 120),
170-
(784, 120),
183+
"start": [
184+
(523, 120),
185+
(659, 120),
186+
(784, 120),
171187
(1047, 400),
172-
],
173-
174-
"hungry": [
175-
(400, 150),
176-
(350, 150),
177-
(300, 300),
178-
],
179-
180-
"bored": [
181-
(500, 120),
182-
(650, 120),
183-
(800, 200),
184-
],
185-
186-
"success": [
187-
(600, 100),
188-
(800, 100),
189-
(1000, 200),
190-
],
191-
192-
"evolution": [
193-
(600, 120),
194-
(750, 120),
195-
(900, 120),
196-
(1100, 150),
197-
(1300, 300),
198-
],
199-
200-
"fail": [
201-
(500, 150),
202-
(400, 150),
203-
(300, 400),
204-
]
205-
}
206-
207-
# ------------------------------------SCREEN----------------------------------------------
188+
],
189+
"hungry": [
190+
(400, 150),
191+
(350, 150),
192+
(300, 300),
193+
],
194+
"bored": [
195+
(500, 120),
196+
(650, 120),
197+
(800, 200),
198+
],
199+
"success": [
200+
(600, 100),
201+
(800, 100),
202+
(1000, 200),
203+
],
204+
"fail": [
205+
(500, 150),
206+
(400, 150),
207+
(300, 400),
208+
],
209+
}
210+
211+
# --- Helpers ---
212+
208213

209214
def setup_buttons():
210215
"""Configure all D-PAD buttons as inputs with pull-ups."""
@@ -213,84 +218,77 @@ def setup_buttons():
213218

214219

215220
def wait_for_button():
216-
"""Wait for a button press and return its name."""
221+
"""Poll D-PAD once and return the pressed button name, or None."""
217222
for pin_number, name in BUTTONS.items():
218223
if mcp.get_level(pin_number) == MCP23009_LOGIC_LOW:
219224
while mcp.get_level(pin_number) == MCP23009_LOGIC_LOW:
220225
sleep_ms(20)
221226
return name
222227
return None
223228

229+
224230
def draw_character(cx, cy, scale, sprite):
225-
"""Draw character"""
231+
"""Draw a scaled pixel-art sprite on the display framebuf."""
226232
fb = display.framebuf
227233
for y, row in enumerate(sprite):
228234
for x, color in enumerate(row):
229235
for dy in range(scale):
230236
for dx in range(scale):
231-
fb.pixel(cx + x*scale + dx, cy + y*scale + dy, color)
237+
fb.pixel(cx + x * scale + dx, cy + y * scale + dy, color)
232238

233239

234240
def create_screen(selected_index, need, sprite, charge):
235-
"""displays the screen"""
241+
"""Render one game frame: sprite, need text, charge and action menu."""
236242
display.fill(0)
237243
display.text(need, 25, 20, 15)
238244
display.text(str(charge), 50, 10, 15)
239245

240246
if charge >= 70:
241-
scale = 1
242-
x = 55
243-
y = 60
247+
scale, x, y = 1, 55, 60
244248
elif charge >= 40:
245-
scale = 2
246-
x = 45
247-
y = 50
249+
scale, x, y = 2, 45, 50
248250
else:
249-
scale = 3
250-
x = 40
251-
y = 40
251+
scale, x, y = 3, 40, 40
252252

253253
draw_character(x, y, scale, sprite)
254254

255255
for index, label in enumerate(ACTION):
256-
y = ITEM_Y + index * ITEM_SPACING
256+
row_y = ITEM_Y + index * ITEM_SPACING
257257
prefix = ">" if index == selected_index else " "
258-
display.text(prefix + label, X0, y, 15)
258+
display.text(prefix + label, X0, row_y, 15)
259259

260260
display.show()
261261

262+
262263
def create_game_over_screen():
264+
"""Display the game-over screen."""
263265
display.fill(0)
264-
display.text("Game-Over",25, 20, 15)
266+
display.text("Game Over", 25, 20, 15)
265267
draw_character(35, 45, 3, SPRITE_DEAD)
266268
display.show()
267-
# ------------------------------------gameplay----------------------------------------------
268269

269-
def action_check(selected_index, need,win):
270+
271+
def action_check(selected_index, need):
272+
"""Check if the selected action matches the current need."""
270273
name = ACTION[selected_index]
271-
if need == "I'm bored" and name == "play":
272-
win = True
273-
return win
274-
if need == "i'm hungry" and name == "food":
275-
win = True
276-
return win
277-
else :
278-
win = False
279-
return win
274+
return (need == "I'm bored" and name == "play") or (need == "I'm hungry" and name == "food")
275+
280276

281277
def sound_effect(name):
282-
melody = SOUND[name]
283-
for freq, duration_ms in melody:
278+
"""Play a short melody from the SOUND dictionary."""
279+
for freq, duration_ms in SOUND[name]:
284280
buzzer_tim.freq(freq)
285281
buzzer_ch.pulse_width_percent(10)
286282
sleep_ms(duration_ms)
287283
buzzer_ch.pulse_width_percent(0)
288284
sleep_ms(30)
289285

290286

291-
# ------------------------------------main----------------------------------------------
287+
# --- Main game loop ---
288+
292289

293290
def main():
291+
"""Run the Tamagotchi game."""
294292
setup_buttons()
295293
sound_effect("start")
296294
is_alive = True
@@ -300,52 +298,56 @@ def main():
300298
selected_index = 0
301299
charge = fg.state_of_charge()
302300

303-
need = " "
304-
create_screen(selected_index, need, SPRITE_BASE, charge)
305-
sleep_ms(1000)
301+
# Idle phase
302+
create_screen(selected_index, " ", SPRITE_BASE, charge)
303+
sleep_ms(IDLE_DISPLAY_MS)
306304

305+
# Need phase
307306
need = random.choice(NEED)
308-
if need == "I'm bored" :
307+
if need == "I'm bored":
309308
sprite = SPRITE_SAD
310309
sound_effect("bored")
311-
else :
310+
else:
312311
sprite = SPRITE_HUNGRY
313312
sound_effect("hungry")
314313

315314
create_screen(selected_index, need, sprite, charge)
316315

316+
# Response phase
317317
start = ticks_ms()
318318
win = None
319319

320320
while True:
321-
timer = ticks_diff(ticks_ms(), start)
322-
if timer >= 5000:
321+
elapsed = ticks_diff(ticks_ms(), start)
322+
if elapsed >= RESPONSE_TIMEOUT_MS:
323323
break
324324

325-
326325
button = wait_for_button()
327326
if button == "UP":
328-
selected_index = (selected_index - 1)% len(ACTION)
327+
selected_index = (selected_index - 1) % len(ACTION)
329328
create_screen(selected_index, need, sprite, charge)
330329
elif button == "DOWN":
331330
selected_index = (selected_index + 1) % len(ACTION)
332331
create_screen(selected_index, need, sprite, charge)
333332
elif button == "LEFT":
334-
win = action_check(selected_index, need, win)
333+
win = action_check(selected_index, need)
335334
break
336335
sleep_ms(20)
337336

337+
# Result phase
338338
if win:
339339
create_screen(selected_index, need, SPRITE_HAPPY, charge)
340340
sound_effect("success")
341341
else:
342-
create_screen(selected_index, need, SPRITE_HANGRY, charge)
342+
create_screen(selected_index, need, SPRITE_ANGRY, charge)
343343
sound_effect("fail")
344-
sleep_ms(1000)
344+
sleep_ms(RESULT_DISPLAY_MS)
345345

346346
if charge < 10:
347347
is_alive = False
348348
create_game_over_screen()
349349
finally:
350350
buzzer_ch.pulse_width_percent(0)
351+
352+
351353
main()

0 commit comments

Comments
 (0)