From fa4ca462fa8b53e857df5715d24417a09c19ea78 Mon Sep 17 00:00:00 2001 From: MatteoCnda1 Date: Mon, 27 Apr 2026 11:16:08 +0200 Subject: [PATCH 1/4] feat(ism330dl): Add Tetris example with accelerometer tilt controls. --- lib/ism330dl/examples/tetris.py | 324 ++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 lib/ism330dl/examples/tetris.py diff --git a/lib/ism330dl/examples/tetris.py b/lib/ism330dl/examples/tetris.py new file mode 100644 index 0000000..05c8bec --- /dev/null +++ b/lib/ism330dl/examples/tetris.py @@ -0,0 +1,324 @@ +# Tetris pour STeaMi — contrôle par accéléromètre ISM330DL +# Ecran SSD1327 128x128, 4-bit greyscale +# Incliner gauche/droite pour déplacer, secouer pour tourner +# Bouton A pour hard drop + +from machine import I2C, SPI, Pin +from time import ticks_ms, sleep_ms +import ssd1327 +from ism330dl import ISM330DL +import random + +# === Ecran === +spi = SPI(1) +dc = Pin("DATA_COMMAND_DISPLAY") +res = Pin("RST_DISPLAY") +cs = Pin("CS_DISPLAY") +display = ssd1327.WS_OLED_128X128_SPI(spi, dc, res, cs) + +# === IMU === +i2c = I2C(1) +imu = ISM330DL(i2c) + +# === Boutons === +BTN_A = Pin("A_BUTTON", Pin.IN, Pin.PULL_UP) +BTN_B = Pin("B_BUTTON", Pin.IN, Pin.PULL_UP) + +# === Parametres du jeu === +COLS = 10 +ROWS = 20 +CELL = 5 # taille d'une cellule en pixels +GRID_X = 14 # position X de la grille +GRID_Y = 4 # position Y de la grille +TILT_THRESH = 0.35 # seuil inclinaison (g) +SHAKE_THRESH = 1.8 # seuil secousse (g) +MOVE_DELAY = 180 # ms entre deux deplacements lateraux +SHAKE_COOLDOWN = 400 # ms entre deux rotations + +# === Couleurs (0-15) === +COL_BG = 0 +COL_GRID = 1 +COL_BORDER = 8 +COL_TEXT = 15 +COL_GHOST = 3 + +# Couleurs des pieces (1-15) +PIECE_COLORS = [0, 12, 14, 11, 13, 10, 9, 15] + +# === Pieces Tetris (tetrominoes) === +# Format : liste de rotations, chaque rotation = liste de (row, col) +PIECES = [ + # I + [[(0,0),(0,1),(0,2),(0,3)], + [(0,0),(1,0),(2,0),(3,0)]], + # O + [[(0,0),(0,1),(1,0),(1,1)]], + # T + [[(0,1),(1,0),(1,1),(1,2)], + [(0,0),(1,0),(2,0),(1,1)], # corrigé + [(1,0),(1,1),(1,2),(0,1)], + [(0,1),(1,1),(2,1),(1,0)]], + # S + [[(0,1),(0,2),(1,0),(1,1)], + [(0,0),(1,0),(1,1),(2,1)]], + # Z + [[(0,0),(0,1),(1,1),(1,2)], + [(0,1),(1,0),(1,1),(2,0)]], + # J + [[(0,0),(1,0),(1,1),(1,2)], + [(0,0),(0,1),(1,0),(2,0)], + [(1,0),(1,1),(1,2),(0,2)], + [(0,1),(1,1),(2,0),(2,1)]], + # L + [[(0,2),(1,0),(1,1),(1,2)], + [(0,0),(1,0),(2,0),(2,1)], + [(1,0),(1,1),(1,2),(0,0)], + [(0,0),(0,1),(1,1),(2,1)]], +] + +# === Etat du jeu === +grid = [[0] * COLS for _ in range(ROWS)] +score = 0 +level = 1 +lines_cleared = 0 +game_over = False + +piece_type = 0 +piece_rot = 0 +piece_row = 0 +piece_col = 0 +next_type = 0 + +last_fall = 0 +last_move = 0 +last_shake = 0 +fall_interval = 600 + +def new_piece(): + global piece_type, piece_rot, piece_row, piece_col, next_type, game_over + piece_type = next_type + next_type = random.randint(0, len(PIECES) - 1) + piece_rot = 0 + piece_row = 0 + piece_col = COLS // 2 - 2 + if not valid_pos(piece_row, piece_col, piece_rot): + game_over = True + +def get_cells(row, col, rot, ptype=None): + if ptype is None: + ptype = piece_type + return [(row + r, col + c) for r, c in PIECES[ptype][rot % len(PIECES[ptype])]] + +def valid_pos(row, col, rot, ptype=None): + for r, c in get_cells(row, col, rot, ptype): + if r < 0 or r >= ROWS or c < 0 or c >= COLS: + return False + if grid[r][c]: + return False + return True + +def lock_piece(): + global score, lines_cleared, level, fall_interval + color = PIECE_COLORS[piece_type + 1] + for r, c in get_cells(piece_row, piece_col, piece_rot): + if 0 <= r < ROWS and 0 <= c < COLS: + grid[r][c] = color + + # Effacer les lignes completes + full = [r for r in range(ROWS) if all(grid[r])] + for r in full: + del grid[r] + grid.insert(0, [0] * COLS) + + n = len(full) + lines_cleared += n + score += [0, 100, 300, 500, 800][n] * level + level = lines_cleared // 10 + 1 + fall_interval = max(100, 600 - (level - 1) * 50) + +def ghost_row(): + r = piece_row + while valid_pos(r + 1, piece_col, piece_rot): + r += 1 + return r + +def draw_cell(row, col, color): + x = GRID_X + col * CELL + y = GRID_Y + row * CELL + for dy in range(CELL - 1): + for dx in range(CELL - 1): + display.pixel(x + dx, y + dy, color) + +def draw_hline(x, y, w, color): + for i in range(w): + display.pixel(x + i, y, color) + +def draw_vline(x, y, h, color): + for i in range(h): + display.pixel(x, y + i, color) + +def draw_rect_outline(x, y, w, h, color): + draw_hline(x, y, w, color) + draw_hline(x, y + h, w, color) + draw_vline(x, y, h, color) + draw_vline(x + w, y, h, color) + +def draw_screen(): + display.fill(COL_BG) + + # Bordure grille + draw_rect_outline(GRID_X - 1, GRID_Y - 1, + COLS * CELL + 1, ROWS * CELL + 1, COL_BORDER) + + # Grille de fond + for r in range(ROWS): + for c in range(COLS): + if grid[r][c]: + draw_cell(r, c, grid[r][c]) + + # Ghost piece + gr = ghost_row() + if gr != piece_row: + for r, c in get_cells(gr, piece_col, piece_rot): + if 0 <= r < ROWS and 0 <= c < COLS: + draw_cell(r, c, COL_GHOST) + + # Piece courante + color = PIECE_COLORS[piece_type + 1] + for r, c in get_cells(piece_row, piece_col, piece_rot): + if 0 <= r < ROWS and 0 <= c < COLS: + draw_cell(r, c, color) + + # === Panneau droite === + px = GRID_X + COLS * CELL + 4 + py = GRID_Y + + # Score + display.text("SCR", px, py, COL_TEXT) + s = str(score) + display.text(s[:5], px, py + 10, 14) + + # Level + display.text("LVL", px, py + 25, COL_TEXT) + display.text(str(level), px, py + 35, 14) + + # Lines + display.text("LNS", px, py + 50, COL_TEXT) + display.text(str(lines_cleared), px, py + 60, 14) + + # Next piece + display.text("NXT", px, py + 75, COL_TEXT) + ncells = get_cells(0, 0, 0, next_type) + for r, c in ncells: + nx = px + c * (CELL - 1) + ny = py + 85 + r * (CELL - 1) + for dy in range(CELL - 2): + for dx in range(CELL - 2): + display.pixel(nx + dx, ny + dy, PIECE_COLORS[next_type + 1]) + + display.show() + +def draw_game_over(): + display.fill(0) + display.text("GAME", 35, 45, 15) + display.text("OVER", 35, 57, 15) + display.text(f"SCR:{score}", 20, 75, 10) + display.text("B=restart", 15, 95, 7) + display.show() + +def draw_start(): + display.fill(0) + display.text("TETRIS", 28, 30, 15) + display.text("Tilt=move", 15, 55, 10) + display.text("Shake=rot", 15, 67, 10) + display.text("A=drop", 25, 79, 10) + display.text("A to start", 14, 100, 7) + display.show() + +def reset_game(): + global grid, score, level, lines_cleared, game_over + global last_fall, last_move, last_shake, fall_interval, next_type + grid = [[0] * COLS for _ in range(ROWS)] + score = 0 + level = 1 + lines_cleared = 0 + game_over = False + fall_interval = 600 + next_type = random.randint(0, len(PIECES) - 1) + new_piece() + last_fall = ticks_ms() + last_move = ticks_ms() + last_shake = ticks_ms() + +# === Ecran de démarrage === +draw_start() +while BTN_A.value() == 1: + sleep_ms(50) +sleep_ms(200) + +# === Init jeu === +reset_game() + +# === Boucle principale === +while True: + now = ticks_ms() + + if game_over: + draw_game_over() + while BTN_B.value() == 1: + sleep_ms(50) + sleep_ms(200) + reset_game() + continue + + # Lecture IMU + ax, ay, az = imu.acceleration_g() + + # Secousse → rotation + magnitude = (ax*ax + ay*ay + az*az) ** 0.5 + if magnitude > SHAKE_THRESH and (now - last_shake) > SHAKE_COOLDOWN: + new_rot = (piece_rot + 1) % len(PIECES[piece_type]) + if valid_pos(piece_row, piece_col, new_rot): + piece_rot = new_rot + last_shake = now + + # Inclinaison → deplacement lateral + if (now - last_move) > MOVE_DELAY: + if ay > TILT_THRESH: + if valid_pos(piece_row, piece_col + 1, piece_rot): + piece_col += 1 + last_move = now + elif ay < -TILT_THRESH: + if valid_pos(piece_row, piece_col - 1, piece_rot): + piece_col -= 1 + last_move = now + + # Bouton A → hard drop + if BTN_A.value() == 0: + while valid_pos(piece_row + 1, piece_col, piece_rot): + piece_row += 1 + lock_piece() + new_piece() + last_fall = now + sleep_ms(150) + + # Bouton B → rotation douce + if BTN_B.value() == 0 and (now - last_shake) > SHAKE_COOLDOWN: + new_rot = (piece_rot + 1) % len(PIECES[piece_type]) + if valid_pos(piece_row, piece_col, new_rot): + piece_rot = new_rot + last_shake = now + sleep_ms(150) + + # Chute automatique + if (now - last_fall) > fall_interval: + if valid_pos(piece_row + 1, piece_col, piece_rot): + piece_row += 1 + else: + lock_piece() + new_piece() + last_fall = now + + draw_screen() + sleep_ms(30) + From a547ddc44f3d80970b9c443fe1f88703ddfdc54c Mon Sep 17 00:00:00 2001 From: MatteoCnda1 Date: Mon, 27 Apr 2026 11:22:44 +0200 Subject: [PATCH 2/4] fix(ism330dl): Fix ruff lint issues in Tetris example. --- lib/ism330dl/examples/tetris.py | 156 +++++++++++++++++--------------- 1 file changed, 85 insertions(+), 71 deletions(-) diff --git a/lib/ism330dl/examples/tetris.py b/lib/ism330dl/examples/tetris.py index 05c8bec..d2b0099 100644 --- a/lib/ism330dl/examples/tetris.py +++ b/lib/ism330dl/examples/tetris.py @@ -2,28 +2,29 @@ # Ecran SSD1327 128x128, 4-bit greyscale # Incliner gauche/droite pour déplacer, secouer pour tourner # Bouton A pour hard drop - -from machine import I2C, SPI, Pin -from time import ticks_ms, sleep_ms + +import random +from time import sleep_ms, ticks_ms + import ssd1327 from ism330dl import ISM330DL -import random - +from machine import I2C, SPI, Pin + # === Ecran === spi = SPI(1) dc = Pin("DATA_COMMAND_DISPLAY") res = Pin("RST_DISPLAY") cs = Pin("CS_DISPLAY") display = ssd1327.WS_OLED_128X128_SPI(spi, dc, res, cs) - + # === IMU === i2c = I2C(1) imu = ISM330DL(i2c) - + # === Boutons === BTN_A = Pin("A_BUTTON", Pin.IN, Pin.PULL_UP) BTN_B = Pin("B_BUTTON", Pin.IN, Pin.PULL_UP) - + # === Parametres du jeu === COLS = 10 ROWS = 20 @@ -33,67 +34,68 @@ TILT_THRESH = 0.35 # seuil inclinaison (g) SHAKE_THRESH = 1.8 # seuil secousse (g) MOVE_DELAY = 180 # ms entre deux deplacements lateraux -SHAKE_COOLDOWN = 400 # ms entre deux rotations - +SHAKE_COOLDOWN = 400 # ms entre deux rotations + # === Couleurs (0-15) === COL_BG = 0 COL_GRID = 1 COL_BORDER = 8 COL_TEXT = 15 COL_GHOST = 3 - + # Couleurs des pieces (1-15) PIECE_COLORS = [0, 12, 14, 11, 13, 10, 9, 15] - + # === Pieces Tetris (tetrominoes) === # Format : liste de rotations, chaque rotation = liste de (row, col) PIECES = [ # I - [[(0,0),(0,1),(0,2),(0,3)], - [(0,0),(1,0),(2,0),(3,0)]], + [[(0, 0), (0, 1), (0, 2), (0, 3)], + [(0, 0), (1, 0), (2, 0), (3, 0)]], # O - [[(0,0),(0,1),(1,0),(1,1)]], + [[(0, 0), (0, 1), (1, 0), (1, 1)]], # T - [[(0,1),(1,0),(1,1),(1,2)], - [(0,0),(1,0),(2,0),(1,1)], # corrigé - [(1,0),(1,1),(1,2),(0,1)], - [(0,1),(1,1),(2,1),(1,0)]], + [[(0, 1), (1, 0), (1, 1), (1, 2)], + [(0, 0), (1, 0), (2, 0), (1, 1)], # corrigé + [(1, 0), (1, 1), (1, 2), (0, 1)], + [(0, 1), (1, 1), (2, 1), (1, 0)]], # S - [[(0,1),(0,2),(1,0),(1,1)], - [(0,0),(1,0),(1,1),(2,1)]], + [[(0, 1), (0, 2), (1, 0), (1, 1)], + [(0, 0), (1, 0), (1, 1), (2, 1)]], # Z - [[(0,0),(0,1),(1,1),(1,2)], - [(0,1),(1,0),(1,1),(2,0)]], + [[(0, 0), (0, 1), (1, 1), (1, 2)], + [(0, 1), (1, 0), (1, 1), (2, 0)]], # J - [[(0,0),(1,0),(1,1),(1,2)], - [(0,0),(0,1),(1,0),(2,0)], - [(1,0),(1,1),(1,2),(0,2)], - [(0,1),(1,1),(2,0),(2,1)]], + [[(0, 0), (1, 0), (1, 1), (1, 2)], + [(0, 0), (0, 1), (1, 0), (2, 0)], + [(1, 0), (1, 1), (1, 2), (0, 2)], + [(0, 1), (1, 1), (2, 0), (2, 1)]], # L - [[(0,2),(1,0),(1,1),(1,2)], - [(0,0),(1,0),(2,0),(2,1)], - [(1,0),(1,1),(1,2),(0,0)], - [(0,0),(0,1),(1,1),(2,1)]], + [[(0, 2), (1, 0), (1, 1), (1, 2)], + [(0, 0), (1, 0), (2, 0), (2, 1)], + [(1, 0), (1, 1), (1, 2), (0, 0)], + [(0, 0), (0, 1), (1, 1), (2, 1)]], ] - + # === Etat du jeu === grid = [[0] * COLS for _ in range(ROWS)] score = 0 level = 1 lines_cleared = 0 game_over = False - + piece_type = 0 piece_rot = 0 piece_row = 0 piece_col = 0 next_type = 0 - + last_fall = 0 last_move = 0 last_shake = 0 fall_interval = 600 - + + def new_piece(): global piece_type, piece_rot, piece_row, piece_col, next_type, game_over piece_type = next_type @@ -103,12 +105,14 @@ def new_piece(): piece_col = COLS // 2 - 2 if not valid_pos(piece_row, piece_col, piece_rot): game_over = True - + + def get_cells(row, col, rot, ptype=None): if ptype is None: ptype = piece_type return [(row + r, col + c) for r, c in PIECES[ptype][rot % len(PIECES[ptype])]] - + + def valid_pos(row, col, rot, ptype=None): for r, c in get_cells(row, col, rot, ptype): if r < 0 or r >= ROWS or c < 0 or c >= COLS: @@ -116,96 +120,103 @@ def valid_pos(row, col, rot, ptype=None): if grid[r][c]: return False return True - + + def lock_piece(): global score, lines_cleared, level, fall_interval color = PIECE_COLORS[piece_type + 1] for r, c in get_cells(piece_row, piece_col, piece_rot): if 0 <= r < ROWS and 0 <= c < COLS: grid[r][c] = color - + # Effacer les lignes completes full = [r for r in range(ROWS) if all(grid[r])] for r in full: del grid[r] grid.insert(0, [0] * COLS) - + n = len(full) lines_cleared += n score += [0, 100, 300, 500, 800][n] * level level = lines_cleared // 10 + 1 fall_interval = max(100, 600 - (level - 1) * 50) - + + def ghost_row(): r = piece_row while valid_pos(r + 1, piece_col, piece_rot): r += 1 return r - + + def draw_cell(row, col, color): x = GRID_X + col * CELL y = GRID_Y + row * CELL for dy in range(CELL - 1): for dx in range(CELL - 1): display.pixel(x + dx, y + dy, color) - + + def draw_hline(x, y, w, color): for i in range(w): display.pixel(x + i, y, color) - + + def draw_vline(x, y, h, color): for i in range(h): display.pixel(x, y + i, color) - + + def draw_rect_outline(x, y, w, h, color): draw_hline(x, y, w, color) draw_hline(x, y + h, w, color) draw_vline(x, y, h, color) draw_vline(x + w, y, h, color) - + + def draw_screen(): display.fill(COL_BG) - + # Bordure grille draw_rect_outline(GRID_X - 1, GRID_Y - 1, COLS * CELL + 1, ROWS * CELL + 1, COL_BORDER) - + # Grille de fond for r in range(ROWS): for c in range(COLS): if grid[r][c]: draw_cell(r, c, grid[r][c]) - + # Ghost piece gr = ghost_row() if gr != piece_row: for r, c in get_cells(gr, piece_col, piece_rot): if 0 <= r < ROWS and 0 <= c < COLS: draw_cell(r, c, COL_GHOST) - + # Piece courante color = PIECE_COLORS[piece_type + 1] for r, c in get_cells(piece_row, piece_col, piece_rot): if 0 <= r < ROWS and 0 <= c < COLS: draw_cell(r, c, color) - + # === Panneau droite === px = GRID_X + COLS * CELL + 4 py = GRID_Y - + # Score display.text("SCR", px, py, COL_TEXT) s = str(score) display.text(s[:5], px, py + 10, 14) - + # Level display.text("LVL", px, py + 25, COL_TEXT) display.text(str(level), px, py + 35, 14) - + # Lines display.text("LNS", px, py + 50, COL_TEXT) display.text(str(lines_cleared), px, py + 60, 14) - + # Next piece display.text("NXT", px, py + 75, COL_TEXT) ncells = get_cells(0, 0, 0, next_type) @@ -215,9 +226,10 @@ def draw_screen(): for dy in range(CELL - 2): for dx in range(CELL - 2): display.pixel(nx + dx, ny + dy, PIECE_COLORS[next_type + 1]) - + display.show() - + + def draw_game_over(): display.fill(0) display.text("GAME", 35, 45, 15) @@ -225,7 +237,8 @@ def draw_game_over(): display.text(f"SCR:{score}", 20, 75, 10) display.text("B=restart", 15, 95, 7) display.show() - + + def draw_start(): display.fill(0) display.text("TETRIS", 28, 30, 15) @@ -234,7 +247,8 @@ def draw_start(): display.text("A=drop", 25, 79, 10) display.text("A to start", 14, 100, 7) display.show() - + + def reset_game(): global grid, score, level, lines_cleared, game_over global last_fall, last_move, last_shake, fall_interval, next_type @@ -249,20 +263,21 @@ def reset_game(): last_fall = ticks_ms() last_move = ticks_ms() last_shake = ticks_ms() - + + # === Ecran de démarrage === draw_start() while BTN_A.value() == 1: sleep_ms(50) sleep_ms(200) - + # === Init jeu === reset_game() - + # === Boucle principale === while True: now = ticks_ms() - + if game_over: draw_game_over() while BTN_B.value() == 1: @@ -270,10 +285,10 @@ def reset_game(): sleep_ms(200) reset_game() continue - + # Lecture IMU ax, ay, az = imu.acceleration_g() - + # Secousse → rotation magnitude = (ax*ax + ay*ay + az*az) ** 0.5 if magnitude > SHAKE_THRESH and (now - last_shake) > SHAKE_COOLDOWN: @@ -281,7 +296,7 @@ def reset_game(): if valid_pos(piece_row, piece_col, new_rot): piece_rot = new_rot last_shake = now - + # Inclinaison → deplacement lateral if (now - last_move) > MOVE_DELAY: if ay > TILT_THRESH: @@ -292,7 +307,7 @@ def reset_game(): if valid_pos(piece_row, piece_col - 1, piece_rot): piece_col -= 1 last_move = now - + # Bouton A → hard drop if BTN_A.value() == 0: while valid_pos(piece_row + 1, piece_col, piece_rot): @@ -301,7 +316,7 @@ def reset_game(): new_piece() last_fall = now sleep_ms(150) - + # Bouton B → rotation douce if BTN_B.value() == 0 and (now - last_shake) > SHAKE_COOLDOWN: new_rot = (piece_rot + 1) % len(PIECES[piece_type]) @@ -309,7 +324,7 @@ def reset_game(): piece_rot = new_rot last_shake = now sleep_ms(150) - + # Chute automatique if (now - last_fall) > fall_interval: if valid_pos(piece_row + 1, piece_col, piece_rot): @@ -318,7 +333,6 @@ def reset_game(): lock_piece() new_piece() last_fall = now - + draw_screen() sleep_ms(30) - From 381f2c862d0abc3f7cddc53f9e41435e9810792d Mon Sep 17 00:00:00 2001 From: MatteoCnda1 Date: Tue, 28 Apr 2026 17:19:19 +0200 Subject: [PATCH 3/4] fix(ism330dl): Fix issues in Tetris example. --- lib/ism330dl/examples/tetris.py | 142 +++++++++++++++++--------------- 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/lib/ism330dl/examples/tetris.py b/lib/ism330dl/examples/tetris.py index d2b0099..9f57505 100644 --- a/lib/ism330dl/examples/tetris.py +++ b/lib/ism330dl/examples/tetris.py @@ -1,7 +1,7 @@ -# Tetris pour STeaMi — contrôle par accéléromètre ISM330DL -# Ecran SSD1327 128x128, 4-bit greyscale -# Incliner gauche/droite pour déplacer, secouer pour tourner -# Bouton A pour hard drop +# Tetris for STeaMi — controlled via ISM330DL accelerometer +# SSD1327 128x128 OLED, 4-bit greyscale +# Tilt left/right to move, shake to rotate, button A for hard drop, +# button B for soft rotation. import random from time import sleep_ms, ticks_ms @@ -10,7 +10,7 @@ from ism330dl import ISM330DL from machine import I2C, SPI, Pin -# === Ecran === +# === Display === spi = SPI(1) dc = Pin("DATA_COMMAND_DISPLAY") res = Pin("RST_DISPLAY") @@ -21,63 +21,65 @@ i2c = I2C(1) imu = ISM330DL(i2c) -# === Boutons === +# === Buttons === BTN_A = Pin("A_BUTTON", Pin.IN, Pin.PULL_UP) BTN_B = Pin("B_BUTTON", Pin.IN, Pin.PULL_UP) -# === Parametres du jeu === +# === Game parameters === COLS = 10 ROWS = 20 -CELL = 5 # taille d'une cellule en pixels -GRID_X = 14 # position X de la grille -GRID_Y = 4 # position Y de la grille -TILT_THRESH = 0.35 # seuil inclinaison (g) -SHAKE_THRESH = 1.8 # seuil secousse (g) -MOVE_DELAY = 180 # ms entre deux deplacements lateraux -SHAKE_COOLDOWN = 400 # ms entre deux rotations - -# === Couleurs (0-15) === +CELL = 5 # cell size in pixels +GRID_X = 25 # grid X position (centered for round OLED) +GRID_Y = 14 # grid Y position (centered for round OLED) +TILT_THRESH = 0.35 # tilt threshold (g) +SHAKE_THRESH = 1.8 # shake threshold (g) +MOVE_DELAY = 180 # ms between two lateral moves +SHAKE_COOLDOWN = 400 # ms between two rotations + +# === Colors (0-15) === COL_BG = 0 -COL_GRID = 1 COL_BORDER = 8 COL_TEXT = 15 COL_GHOST = 3 -# Couleurs des pieces (1-15) +# Piece colors (1-15) PIECE_COLORS = [0, 12, 14, 11, 13, 10, 9, 15] -# === Pieces Tetris (tetrominoes) === -# Format : liste de rotations, chaque rotation = liste de (row, col) +# === Tetris pieces (tetrominoes) === +# Format: list of rotations, each rotation = list of (row, col) PIECES = [ # I - [[(0, 0), (0, 1), (0, 2), (0, 3)], - [(0, 0), (1, 0), (2, 0), (3, 0)]], + [[(0, 0), (0, 1), (0, 2), (0, 3)], [(0, 0), (1, 0), (2, 0), (3, 0)]], # O [[(0, 0), (0, 1), (1, 0), (1, 1)]], # T - [[(0, 1), (1, 0), (1, 1), (1, 2)], - [(0, 0), (1, 0), (2, 0), (1, 1)], # corrigé - [(1, 0), (1, 1), (1, 2), (0, 1)], - [(0, 1), (1, 1), (2, 1), (1, 0)]], + [ + [(0, 1), (1, 0), (1, 1), (1, 2)], + [(0, 0), (1, 0), (2, 0), (1, 1)], + [(1, 0), (1, 1), (1, 2), (0, 1)], + [(0, 1), (1, 1), (2, 1), (1, 0)], + ], # S - [[(0, 1), (0, 2), (1, 0), (1, 1)], - [(0, 0), (1, 0), (1, 1), (2, 1)]], + [[(0, 1), (0, 2), (1, 0), (1, 1)], [(0, 0), (1, 0), (1, 1), (2, 1)]], # Z - [[(0, 0), (0, 1), (1, 1), (1, 2)], - [(0, 1), (1, 0), (1, 1), (2, 0)]], + [[(0, 0), (0, 1), (1, 1), (1, 2)], [(0, 1), (1, 0), (1, 1), (2, 0)]], # J - [[(0, 0), (1, 0), (1, 1), (1, 2)], - [(0, 0), (0, 1), (1, 0), (2, 0)], - [(1, 0), (1, 1), (1, 2), (0, 2)], - [(0, 1), (1, 1), (2, 0), (2, 1)]], + [ + [(0, 0), (1, 0), (1, 1), (1, 2)], + [(0, 0), (0, 1), (1, 0), (2, 0)], + [(1, 0), (1, 1), (1, 2), (0, 2)], + [(0, 1), (1, 1), (2, 0), (2, 1)], + ], # L - [[(0, 2), (1, 0), (1, 1), (1, 2)], - [(0, 0), (1, 0), (2, 0), (2, 1)], - [(1, 0), (1, 1), (1, 2), (0, 0)], - [(0, 0), (0, 1), (1, 1), (2, 1)]], + [ + [(0, 2), (1, 0), (1, 1), (1, 2)], + [(0, 0), (1, 0), (2, 0), (2, 1)], + [(1, 0), (1, 1), (1, 2), (0, 0)], + [(0, 0), (0, 1), (1, 1), (2, 1)], + ], ] -# === Etat du jeu === +# === Game state === grid = [[0] * COLS for _ in range(ROWS)] score = 0 level = 1 @@ -129,7 +131,7 @@ def lock_piece(): if 0 <= r < ROWS and 0 <= c < COLS: grid[r][c] = color - # Effacer les lignes completes + # Clear full lines full = [r for r in range(ROWS) if all(grid[r])] for r in full: del grid[r] @@ -174,14 +176,23 @@ def draw_rect_outline(x, y, w, h, color): draw_vline(x + w, y, h, color) +def draw_text_centered(text, y, color): + # MicroPython framebuf font is 8 px wide per char + x = 64 - (len(text) * 8) // 2 + if x < 0: + x = 0 + display.text(text, x, y, color) + + def draw_screen(): display.fill(COL_BG) - # Bordure grille - draw_rect_outline(GRID_X - 1, GRID_Y - 1, - COLS * CELL + 1, ROWS * CELL + 1, COL_BORDER) + # Grid border + draw_rect_outline( + GRID_X - 1, GRID_Y - 1, COLS * CELL + 1, ROWS * CELL + 1, COL_BORDER + ) - # Grille de fond + # Background grid (locked cells) for r in range(ROWS): for c in range(COLS): if grid[r][c]: @@ -194,13 +205,13 @@ def draw_screen(): if 0 <= r < ROWS and 0 <= c < COLS: draw_cell(r, c, COL_GHOST) - # Piece courante + # Current piece color = PIECE_COLORS[piece_type + 1] for r, c in get_cells(piece_row, piece_col, piece_rot): if 0 <= r < ROWS and 0 <= c < COLS: draw_cell(r, c, color) - # === Panneau droite === + # === Right info panel === px = GRID_X + COLS * CELL + 4 py = GRID_Y @@ -232,20 +243,21 @@ def draw_screen(): def draw_game_over(): display.fill(0) - display.text("GAME", 35, 45, 15) - display.text("OVER", 35, 57, 15) - display.text(f"SCR:{score}", 20, 75, 10) - display.text("B=restart", 15, 95, 7) + draw_text_centered("GAME", 45, 15) + draw_text_centered("OVER", 57, 15) + draw_text_centered("SCR:" + str(score), 75, 10) + draw_text_centered("B=restart", 95, 7) display.show() def draw_start(): display.fill(0) - display.text("TETRIS", 28, 30, 15) - display.text("Tilt=move", 15, 55, 10) - display.text("Shake=rot", 15, 67, 10) - display.text("A=drop", 25, 79, 10) - display.text("A to start", 14, 100, 7) + draw_text_centered("TETRIS", 22, 15) + draw_text_centered("Tilt=move", 45, 10) + draw_text_centered("Shake=rot", 57, 10) + draw_text_centered("B=rot", 69, 10) + draw_text_centered("A=drop", 81, 10) + draw_text_centered("A to start", 102, 7) display.show() @@ -265,16 +277,16 @@ def reset_game(): last_shake = ticks_ms() -# === Ecran de démarrage === +# === Start screen === draw_start() while BTN_A.value() == 1: sleep_ms(50) sleep_ms(200) -# === Init jeu === +# === Init game === reset_game() -# === Boucle principale === +# === Main loop === while True: now = ticks_ms() @@ -286,18 +298,18 @@ def reset_game(): reset_game() continue - # Lecture IMU + # IMU read ax, ay, az = imu.acceleration_g() - # Secousse → rotation - magnitude = (ax*ax + ay*ay + az*az) ** 0.5 + # Shake -> rotation + magnitude = (ax * ax + ay * ay + az * az) ** 0.5 if magnitude > SHAKE_THRESH and (now - last_shake) > SHAKE_COOLDOWN: new_rot = (piece_rot + 1) % len(PIECES[piece_type]) if valid_pos(piece_row, piece_col, new_rot): piece_rot = new_rot last_shake = now - # Inclinaison → deplacement lateral + # Tilt -> lateral movement if (now - last_move) > MOVE_DELAY: if ay > TILT_THRESH: if valid_pos(piece_row, piece_col + 1, piece_rot): @@ -308,7 +320,7 @@ def reset_game(): piece_col -= 1 last_move = now - # Bouton A → hard drop + # Button A -> hard drop if BTN_A.value() == 0: while valid_pos(piece_row + 1, piece_col, piece_rot): piece_row += 1 @@ -317,7 +329,7 @@ def reset_game(): last_fall = now sleep_ms(150) - # Bouton B → rotation douce + # Button B -> soft rotation if BTN_B.value() == 0 and (now - last_shake) > SHAKE_COOLDOWN: new_rot = (piece_rot + 1) % len(PIECES[piece_type]) if valid_pos(piece_row, piece_col, new_rot): @@ -325,7 +337,7 @@ def reset_game(): last_shake = now sleep_ms(150) - # Chute automatique + # Automatic fall if (now - last_fall) > fall_interval: if valid_pos(piece_row + 1, piece_col, piece_rot): piece_row += 1 From 194004d98e9c92bb8136134f6fd3d9cd7119342b Mon Sep 17 00:00:00 2001 From: MatteoCnda1 Date: Tue, 28 Apr 2026 17:22:05 +0200 Subject: [PATCH 4/4] fix(ism330dl): Fix lint issues in Tetris example. --- lib/ism330dl/examples/tetris.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ism330dl/examples/tetris.py b/lib/ism330dl/examples/tetris.py index 9f57505..d0e83a0 100644 --- a/lib/ism330dl/examples/tetris.py +++ b/lib/ism330dl/examples/tetris.py @@ -179,8 +179,7 @@ def draw_rect_outline(x, y, w, h, color): def draw_text_centered(text, y, color): # MicroPython framebuf font is 8 px wide per char x = 64 - (len(text) * 8) // 2 - if x < 0: - x = 0 + x = max(x, 0) display.text(text, x, y, color)