feat(ism330dl): Add maze game example with OLED and accelerometer.#390
feat(ism330dl): Add maze game example with OLED and accelerometer.#390
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new MicroPython example game to the ISM330DL library, demonstrating interactive use of the accelerometer for tilt input and the SSD1327-based round OLED via steami_screen.
Changes:
- Added
maze_game.pyexample implementing maze generation (DFS) and scoring via shortest-path comparison (Dijkstra) - Integrated SSD1327 round OLED rendering using
steami_screenwidgets and drawing primitives - Implemented tilt-based movement using
ISM330DL.acceleration_g()
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| maze = generate_maze(MAZE_W, MAZE_H) | ||
| optimal = dijkstra(maze, START_ROW, START_COL, GOAL_ROW, GOAL_COL) | ||
|
|
||
| player_row = START_ROW | ||
| player_col = START_COL | ||
| steps = 0 | ||
|
|
||
| show_start_screen() | ||
|
|
||
| while True: | ||
| render(maze, player_row, player_col, steps, optimal) | ||
|
|
||
| direction = read_direction() | ||
| if direction != NONE: | ||
| dr, dc = direction | ||
| nr = player_row + dr | ||
| nc = player_col + dc | ||
| if 0 <= nr < MAZE_H and 0 <= nc < MAZE_W and maze[nr][nc] == PATH: | ||
| player_row = nr | ||
| player_col = nc | ||
| steps += 1 | ||
|
|
||
| # Check win condition | ||
| if player_row == GOAL_ROW and player_col == GOAL_COL: | ||
| show_win_screen(steps, optimal) | ||
| run_game() | ||
| return | ||
|
|
||
| sleep_ms(MOVE_DELAY_MS) |
There was a problem hiding this comment.
run_game() restarts the game by calling itself recursively when the player wins. After multiple wins this will grow the call stack and can eventually raise a recursion/stack overflow or exhaust memory on-device. Refactor to use an outer loop (e.g., while True: ... generate maze ... play until win ... show_win_screen ... continue) instead of recursive self-calls.
| maze = generate_maze(MAZE_W, MAZE_H) | |
| optimal = dijkstra(maze, START_ROW, START_COL, GOAL_ROW, GOAL_COL) | |
| player_row = START_ROW | |
| player_col = START_COL | |
| steps = 0 | |
| show_start_screen() | |
| while True: | |
| render(maze, player_row, player_col, steps, optimal) | |
| direction = read_direction() | |
| if direction != NONE: | |
| dr, dc = direction | |
| nr = player_row + dr | |
| nc = player_col + dc | |
| if 0 <= nr < MAZE_H and 0 <= nc < MAZE_W and maze[nr][nc] == PATH: | |
| player_row = nr | |
| player_col = nc | |
| steps += 1 | |
| # Check win condition | |
| if player_row == GOAL_ROW and player_col == GOAL_COL: | |
| show_win_screen(steps, optimal) | |
| run_game() | |
| return | |
| sleep_ms(MOVE_DELAY_MS) | |
| while True: | |
| maze = generate_maze(MAZE_W, MAZE_H) | |
| optimal = dijkstra(maze, START_ROW, START_COL, GOAL_ROW, GOAL_COL) | |
| player_row = START_ROW | |
| player_col = START_COL | |
| steps = 0 | |
| show_start_screen() | |
| while True: | |
| render(maze, player_row, player_col, steps, optimal) | |
| direction = read_direction() | |
| if direction != NONE: | |
| dr, dc = direction | |
| nr = player_row + dr | |
| nc = player_col + dc | |
| if 0 <= nr < MAZE_H and 0 <= nc < MAZE_W and maze[nr][nc] == PATH: | |
| player_row = nr | |
| player_col = nc | |
| steps += 1 | |
| # Check win condition | |
| if player_row == GOAL_ROW and player_col == GOAL_COL: | |
| show_win_screen(steps, optimal) | |
| break | |
| sleep_ms(MOVE_DELAY_MS) |
Address review comments on #390: 1. Replace recursive run_game() self-call with an outer `while True` loop. On MicroPython's limited stack (8-16 KB), each completed game added a stack frame that was never freed — a few wins would crash with a RecursionError or silent stack overflow. 2. Add try/except/finally around the game loop: Ctrl+C now cleanly clears the display and powers off the IMU (same pattern as spirit_level.py) to avoid battery drain. 3. Add maze_game.py to the README examples table.
Address review comments on #390: 1. Replace recursive run_game() self-call with an outer `while True` loop. On MicroPython's limited stack (8-16 KB), each completed game added a stack frame that was never freed — a few wins would crash with a RecursionError or silent stack overflow. 2. Add try/except/finally around the game loop: Ctrl+C now cleanly clears the display and powers off the IMU (same pattern as spirit_level.py) to avoid battery drain. 3. Add maze_game.py to the README examples table.
5093119 to
83b480a
Compare
nedseb
left a comment
There was a problem hiding this comment.
Bel exemple Matteo — le labyrinthe généré par DFS + le scoring Dijkstra sur un OLED 128x128 piloté par accéléromètre, c'est ambitieux et ça fonctionne bien.
J'ai poussé un commit de review (83b480a) qui corrige trois points :
1. Récursion → boucle (critique sur MicroPython) — run_game() s'appelait elle-même après chaque victoire. Sur le stack limité de MicroPython (8-16 KB), quelques parties gagnées auraient provoqué un crash (RecursionError ou overflow silencieux). Remplacé par une boucle while True dans le point d'entrée, et run_game() retourne simplement après chaque partie.
2. Cleanup try/finally — un Ctrl+C laissait l'écran allumé et l'IMU tournant à 104 Hz. Ajouté screen.clear() + screen.show() + imu.power_off() dans le finally, comme dans spirit_level.py.
3. README — ajouté maze_game.py dans la table des exemples (manquant dans la PR originale). Conflit de rebase résolu avec la ligne spirit_level.py arrivée via main.
Branche rebasée sur main. Prêt à merger.
# [0.21.0](v0.20.3...v0.21.0) (2026-04-16) ### Features * **ism330dl:** Add maze game example with OLED and accelerometer. ([#390](#390)) ([5552b5c](5552b5c))
|
🎉 This PR is included in version 0.21.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
Summary
Add a maze game example using the ISM330DL accelerometer and SSD1327 OLED display. Closes #335
Changes
lib/ism330dl/examples/maze_game.py: a single-file maze game where the player navigates a randomly generated maze by tilting the STeaMi boardsteami_screenwidget library on the round 128x128 OLEDChecklist
ruff checkpassespython -m pytest tests/ -k mock -vpasses (no mock test broken)lib/ism330dl/examples/maze_game.py)<scope>: <Description.>format