Skip to content

Commit fa4ca46

Browse files
committed
feat(ism330dl): Add Tetris example with accelerometer tilt controls.
1 parent c8375a6 commit fa4ca46

1 file changed

Lines changed: 324 additions & 0 deletions

File tree

lib/ism330dl/examples/tetris.py

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
# Tetris pour STeaMi — contrôle par accéléromètre ISM330DL
2+
# Ecran SSD1327 128x128, 4-bit greyscale
3+
# Incliner gauche/droite pour déplacer, secouer pour tourner
4+
# Bouton A pour hard drop
5+
6+
from machine import I2C, SPI, Pin
7+
from time import ticks_ms, sleep_ms
8+
import ssd1327
9+
from ism330dl import ISM330DL
10+
import random
11+
12+
# === Ecran ===
13+
spi = SPI(1)
14+
dc = Pin("DATA_COMMAND_DISPLAY")
15+
res = Pin("RST_DISPLAY")
16+
cs = Pin("CS_DISPLAY")
17+
display = ssd1327.WS_OLED_128X128_SPI(spi, dc, res, cs)
18+
19+
# === IMU ===
20+
i2c = I2C(1)
21+
imu = ISM330DL(i2c)
22+
23+
# === Boutons ===
24+
BTN_A = Pin("A_BUTTON", Pin.IN, Pin.PULL_UP)
25+
BTN_B = Pin("B_BUTTON", Pin.IN, Pin.PULL_UP)
26+
27+
# === Parametres du jeu ===
28+
COLS = 10
29+
ROWS = 20
30+
CELL = 5 # taille d'une cellule en pixels
31+
GRID_X = 14 # position X de la grille
32+
GRID_Y = 4 # position Y de la grille
33+
TILT_THRESH = 0.35 # seuil inclinaison (g)
34+
SHAKE_THRESH = 1.8 # seuil secousse (g)
35+
MOVE_DELAY = 180 # ms entre deux deplacements lateraux
36+
SHAKE_COOLDOWN = 400 # ms entre deux rotations
37+
38+
# === Couleurs (0-15) ===
39+
COL_BG = 0
40+
COL_GRID = 1
41+
COL_BORDER = 8
42+
COL_TEXT = 15
43+
COL_GHOST = 3
44+
45+
# Couleurs des pieces (1-15)
46+
PIECE_COLORS = [0, 12, 14, 11, 13, 10, 9, 15]
47+
48+
# === Pieces Tetris (tetrominoes) ===
49+
# Format : liste de rotations, chaque rotation = liste de (row, col)
50+
PIECES = [
51+
# I
52+
[[(0,0),(0,1),(0,2),(0,3)],
53+
[(0,0),(1,0),(2,0),(3,0)]],
54+
# O
55+
[[(0,0),(0,1),(1,0),(1,1)]],
56+
# T
57+
[[(0,1),(1,0),(1,1),(1,2)],
58+
[(0,0),(1,0),(2,0),(1,1)], # corrigé
59+
[(1,0),(1,1),(1,2),(0,1)],
60+
[(0,1),(1,1),(2,1),(1,0)]],
61+
# S
62+
[[(0,1),(0,2),(1,0),(1,1)],
63+
[(0,0),(1,0),(1,1),(2,1)]],
64+
# Z
65+
[[(0,0),(0,1),(1,1),(1,2)],
66+
[(0,1),(1,0),(1,1),(2,0)]],
67+
# J
68+
[[(0,0),(1,0),(1,1),(1,2)],
69+
[(0,0),(0,1),(1,0),(2,0)],
70+
[(1,0),(1,1),(1,2),(0,2)],
71+
[(0,1),(1,1),(2,0),(2,1)]],
72+
# L
73+
[[(0,2),(1,0),(1,1),(1,2)],
74+
[(0,0),(1,0),(2,0),(2,1)],
75+
[(1,0),(1,1),(1,2),(0,0)],
76+
[(0,0),(0,1),(1,1),(2,1)]],
77+
]
78+
79+
# === Etat du jeu ===
80+
grid = [[0] * COLS for _ in range(ROWS)]
81+
score = 0
82+
level = 1
83+
lines_cleared = 0
84+
game_over = False
85+
86+
piece_type = 0
87+
piece_rot = 0
88+
piece_row = 0
89+
piece_col = 0
90+
next_type = 0
91+
92+
last_fall = 0
93+
last_move = 0
94+
last_shake = 0
95+
fall_interval = 600
96+
97+
def new_piece():
98+
global piece_type, piece_rot, piece_row, piece_col, next_type, game_over
99+
piece_type = next_type
100+
next_type = random.randint(0, len(PIECES) - 1)
101+
piece_rot = 0
102+
piece_row = 0
103+
piece_col = COLS // 2 - 2
104+
if not valid_pos(piece_row, piece_col, piece_rot):
105+
game_over = True
106+
107+
def get_cells(row, col, rot, ptype=None):
108+
if ptype is None:
109+
ptype = piece_type
110+
return [(row + r, col + c) for r, c in PIECES[ptype][rot % len(PIECES[ptype])]]
111+
112+
def valid_pos(row, col, rot, ptype=None):
113+
for r, c in get_cells(row, col, rot, ptype):
114+
if r < 0 or r >= ROWS or c < 0 or c >= COLS:
115+
return False
116+
if grid[r][c]:
117+
return False
118+
return True
119+
120+
def lock_piece():
121+
global score, lines_cleared, level, fall_interval
122+
color = PIECE_COLORS[piece_type + 1]
123+
for r, c in get_cells(piece_row, piece_col, piece_rot):
124+
if 0 <= r < ROWS and 0 <= c < COLS:
125+
grid[r][c] = color
126+
127+
# Effacer les lignes completes
128+
full = [r for r in range(ROWS) if all(grid[r])]
129+
for r in full:
130+
del grid[r]
131+
grid.insert(0, [0] * COLS)
132+
133+
n = len(full)
134+
lines_cleared += n
135+
score += [0, 100, 300, 500, 800][n] * level
136+
level = lines_cleared // 10 + 1
137+
fall_interval = max(100, 600 - (level - 1) * 50)
138+
139+
def ghost_row():
140+
r = piece_row
141+
while valid_pos(r + 1, piece_col, piece_rot):
142+
r += 1
143+
return r
144+
145+
def draw_cell(row, col, color):
146+
x = GRID_X + col * CELL
147+
y = GRID_Y + row * CELL
148+
for dy in range(CELL - 1):
149+
for dx in range(CELL - 1):
150+
display.pixel(x + dx, y + dy, color)
151+
152+
def draw_hline(x, y, w, color):
153+
for i in range(w):
154+
display.pixel(x + i, y, color)
155+
156+
def draw_vline(x, y, h, color):
157+
for i in range(h):
158+
display.pixel(x, y + i, color)
159+
160+
def draw_rect_outline(x, y, w, h, color):
161+
draw_hline(x, y, w, color)
162+
draw_hline(x, y + h, w, color)
163+
draw_vline(x, y, h, color)
164+
draw_vline(x + w, y, h, color)
165+
166+
def draw_screen():
167+
display.fill(COL_BG)
168+
169+
# Bordure grille
170+
draw_rect_outline(GRID_X - 1, GRID_Y - 1,
171+
COLS * CELL + 1, ROWS * CELL + 1, COL_BORDER)
172+
173+
# Grille de fond
174+
for r in range(ROWS):
175+
for c in range(COLS):
176+
if grid[r][c]:
177+
draw_cell(r, c, grid[r][c])
178+
179+
# Ghost piece
180+
gr = ghost_row()
181+
if gr != piece_row:
182+
for r, c in get_cells(gr, piece_col, piece_rot):
183+
if 0 <= r < ROWS and 0 <= c < COLS:
184+
draw_cell(r, c, COL_GHOST)
185+
186+
# Piece courante
187+
color = PIECE_COLORS[piece_type + 1]
188+
for r, c in get_cells(piece_row, piece_col, piece_rot):
189+
if 0 <= r < ROWS and 0 <= c < COLS:
190+
draw_cell(r, c, color)
191+
192+
# === Panneau droite ===
193+
px = GRID_X + COLS * CELL + 4
194+
py = GRID_Y
195+
196+
# Score
197+
display.text("SCR", px, py, COL_TEXT)
198+
s = str(score)
199+
display.text(s[:5], px, py + 10, 14)
200+
201+
# Level
202+
display.text("LVL", px, py + 25, COL_TEXT)
203+
display.text(str(level), px, py + 35, 14)
204+
205+
# Lines
206+
display.text("LNS", px, py + 50, COL_TEXT)
207+
display.text(str(lines_cleared), px, py + 60, 14)
208+
209+
# Next piece
210+
display.text("NXT", px, py + 75, COL_TEXT)
211+
ncells = get_cells(0, 0, 0, next_type)
212+
for r, c in ncells:
213+
nx = px + c * (CELL - 1)
214+
ny = py + 85 + r * (CELL - 1)
215+
for dy in range(CELL - 2):
216+
for dx in range(CELL - 2):
217+
display.pixel(nx + dx, ny + dy, PIECE_COLORS[next_type + 1])
218+
219+
display.show()
220+
221+
def draw_game_over():
222+
display.fill(0)
223+
display.text("GAME", 35, 45, 15)
224+
display.text("OVER", 35, 57, 15)
225+
display.text(f"SCR:{score}", 20, 75, 10)
226+
display.text("B=restart", 15, 95, 7)
227+
display.show()
228+
229+
def draw_start():
230+
display.fill(0)
231+
display.text("TETRIS", 28, 30, 15)
232+
display.text("Tilt=move", 15, 55, 10)
233+
display.text("Shake=rot", 15, 67, 10)
234+
display.text("A=drop", 25, 79, 10)
235+
display.text("A to start", 14, 100, 7)
236+
display.show()
237+
238+
def reset_game():
239+
global grid, score, level, lines_cleared, game_over
240+
global last_fall, last_move, last_shake, fall_interval, next_type
241+
grid = [[0] * COLS for _ in range(ROWS)]
242+
score = 0
243+
level = 1
244+
lines_cleared = 0
245+
game_over = False
246+
fall_interval = 600
247+
next_type = random.randint(0, len(PIECES) - 1)
248+
new_piece()
249+
last_fall = ticks_ms()
250+
last_move = ticks_ms()
251+
last_shake = ticks_ms()
252+
253+
# === Ecran de démarrage ===
254+
draw_start()
255+
while BTN_A.value() == 1:
256+
sleep_ms(50)
257+
sleep_ms(200)
258+
259+
# === Init jeu ===
260+
reset_game()
261+
262+
# === Boucle principale ===
263+
while True:
264+
now = ticks_ms()
265+
266+
if game_over:
267+
draw_game_over()
268+
while BTN_B.value() == 1:
269+
sleep_ms(50)
270+
sleep_ms(200)
271+
reset_game()
272+
continue
273+
274+
# Lecture IMU
275+
ax, ay, az = imu.acceleration_g()
276+
277+
# Secousse → rotation
278+
magnitude = (ax*ax + ay*ay + az*az) ** 0.5
279+
if magnitude > SHAKE_THRESH and (now - last_shake) > SHAKE_COOLDOWN:
280+
new_rot = (piece_rot + 1) % len(PIECES[piece_type])
281+
if valid_pos(piece_row, piece_col, new_rot):
282+
piece_rot = new_rot
283+
last_shake = now
284+
285+
# Inclinaison → deplacement lateral
286+
if (now - last_move) > MOVE_DELAY:
287+
if ay > TILT_THRESH:
288+
if valid_pos(piece_row, piece_col + 1, piece_rot):
289+
piece_col += 1
290+
last_move = now
291+
elif ay < -TILT_THRESH:
292+
if valid_pos(piece_row, piece_col - 1, piece_rot):
293+
piece_col -= 1
294+
last_move = now
295+
296+
# Bouton A → hard drop
297+
if BTN_A.value() == 0:
298+
while valid_pos(piece_row + 1, piece_col, piece_rot):
299+
piece_row += 1
300+
lock_piece()
301+
new_piece()
302+
last_fall = now
303+
sleep_ms(150)
304+
305+
# Bouton B → rotation douce
306+
if BTN_B.value() == 0 and (now - last_shake) > SHAKE_COOLDOWN:
307+
new_rot = (piece_rot + 1) % len(PIECES[piece_type])
308+
if valid_pos(piece_row, piece_col, new_rot):
309+
piece_rot = new_rot
310+
last_shake = now
311+
sleep_ms(150)
312+
313+
# Chute automatique
314+
if (now - last_fall) > fall_interval:
315+
if valid_pos(piece_row + 1, piece_col, piece_rot):
316+
piece_row += 1
317+
else:
318+
lock_piece()
319+
new_piece()
320+
last_fall = now
321+
322+
draw_screen()
323+
sleep_ms(30)
324+

0 commit comments

Comments
 (0)