import machine
import ssd1306
from time import time, sleep
import random
# ============== HARDWARE SETUP ==============
# I2C for OLED Display
i2c = machine.I2C(0, scl=machine.Pin(17), sda=machine.Pin(16))
oled = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3c)
# Joystick Analog Inputs
joystick_x = machine.ADC(26) # HORZ
joystick_y = machine.ADC(27) # VERT
joystick_button = machine.Pin(22, machine.Pin.IN, machine.Pin.PULL_UP) # SEL
# ============== GAME CONSTANTS ==============
GRID_WIDTH = 6
GRID_HEIGHT = 10
BLOCK_SIZE = 10
# Tetris Shapes
SHAPES = [
[[[1, 1, 1, 1]]], # I
[[[1, 1], [1, 1]]], # O
[[[0, 1, 0], [1, 1, 1]], # T
[[1, 0], [1, 1], [1, 0]],
[[1, 1, 1], [0, 1, 0]],
[[0, 1], [1, 1], [0, 1]]],
[[[0, 1, 1], [1, 1, 0]], # S
[[1, 0], [1, 1], [0, 1]]],
[[[1, 1, 0], [0, 1, 1]], # Z
[[0, 1], [1, 1], [1, 0]]],
[[[1, 0, 0], [1, 1, 1]], # L
[[1, 1], [1, 0], [1, 0]],
[[1, 1, 1], [0, 0, 1]],
[[0, 1], [0, 1], [1, 1]]],
[[[0, 0, 1], [1, 1, 1]], # J
[[1, 0], [1, 0], [1, 1]],
[[1, 1, 1], [1, 0, 0]],
[[1, 1], [0, 1], [0, 1]]]
]
# ============== GAME STATE ==============
class TetrisGame:
def __init__(self):
self.board = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
self.current_shape = None
self.current_rotation = 0
self.shape_x = 0
self.shape_y = 0
self.score = 0
self.game_over = False
self.fall_time = time()
self.fall_speed = 0.8
self.spawn_new_shape()
def spawn_new_shape(self):
shape_type = random.randint(0, len(SHAPES) - 1)
self.current_shape = SHAPES[shape_type]
self.current_rotation = 0
self.shape_x = GRID_WIDTH // 2 - 1
self.shape_y = 0
if self.collides(self.shape_x, self.shape_y):
self.game_over = True
def get_shape_matrix(self):
if not self.current_shape:
return [[]]
return self.current_shape[self.current_rotation % len(self.current_shape)]
def collides(self, x, y, shape_matrix=None):
"""Check collision with walls, floor, and placed blocks"""
if shape_matrix is None:
shape_matrix = self.get_shape_matrix()
for row in range(len(shape_matrix)):
for col in range(len(shape_matrix[row])):
if shape_matrix[row][col]:
new_x = x + col
new_y = y + row
# Check left/right boundaries
if new_x < 0 or new_x >= GRID_WIDTH:
return True
# Check bottom boundary - FIX: Check BEFORE checking placed blocks
if new_y >= GRID_HEIGHT:
return True
# Check collision with placed blocks (only if in bounds)
if new_y >= 0 and new_y < GRID_HEIGHT and self.board[new_y][new_x]:
return True
return False
def place_shape(self):
"""Place current shape on the board"""
shape_matrix = self.get_shape_matrix()
for row in range(len(shape_matrix)):
for col in range(len(shape_matrix[row])):
if shape_matrix[row][col]:
x = self.shape_x + col
y = self.shape_y + row
if 0 <= y < GRID_HEIGHT and 0 <= x < GRID_WIDTH:
self.board[y][x] = 1
self.clear_lines()
self.spawn_new_shape()
def clear_lines(self):
"""Remove completed lines"""
y = GRID_HEIGHT - 1
while y >= 0:
if all(self.board[y]):
self.score += 100
del self.board[y]
self.board.insert(0, [0 for _ in range(GRID_WIDTH)])
else:
y -= 1
def move_left(self):
"""Move shape left"""
if not self.collides(self.shape_x - 1, self.shape_y):
self.shape_x -= 1
def move_right(self):
"""Move shape right"""
if not self.collides(self.shape_x + 1, self.shape_y):
self.shape_x += 1
def rotate(self):
"""Rotate shape"""
new_rotation = (self.current_rotation + 1) % len(self.current_shape)
new_matrix = self.current_shape[new_rotation]
if not self.collides(self.shape_x, self.shape_y, new_matrix):
self.current_rotation = new_rotation
def drop(self):
"""Move shape down, return True if placed"""
if not self.collides(self.shape_x, self.shape_y + 1):
self.shape_y += 1
return False
else:
# Shape hit bottom or another block - place it
self.place_shape()
return True
def update(self):
"""Update game state"""
current_time = time()
if current_time - self.fall_time > self.fall_speed:
self.drop()
self.fall_time = current_time
# ============== INPUT HANDLING ==============
last_move_time = time()
last_rotate_time = time()
move_delay = 0.15
rotate_delay = 0.2
def handle_input(game):
global last_move_time, last_rotate_time
x = joystick_x.read_u16()
y = joystick_y.read_u16()
button = not joystick_button.value()
current_time = time()
# Left/Right movement with debounce
if current_time - last_move_time > move_delay:
if x < 20000: # Left
game.move_left()
last_move_time = current_time
elif x > 45000: # Right
game.move_right()
last_move_time = current_time
# Rotate on Up with debounce
if current_time - last_rotate_time > rotate_delay:
if y < 20000: # Up
game.rotate()
last_rotate_time = current_time
# Drop on Down (immediate)
if y > 45000:
game.drop()
# Hard drop on Button
if button:
while not game.drop():
pass
# ============== DISPLAY ==============
def draw_game(game):
oled.fill(0)
# Calculate game area dimensions
game_width = GRID_WIDTH * BLOCK_SIZE
game_height = GRID_HEIGHT * BLOCK_SIZE
offset_x = 2
offset_y = 2
# Draw grid background
for y in range(GRID_HEIGHT + 1):
oled.hline(offset_x, offset_y + y * BLOCK_SIZE, game_width, 1)
for x in range(GRID_WIDTH + 1):
oled.vline(offset_x + x * BLOCK_SIZE, offset_y, game_height, 1)
# Draw placed blocks
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
if game.board[y][x]:
px = offset_x + x * BLOCK_SIZE + 1
py = offset_y + y * BLOCK_SIZE + 1
oled.fill_rect(px, py, BLOCK_SIZE - 2, BLOCK_SIZE - 2, 1)
# Draw current shape
shape_matrix = game.get_shape_matrix()
for row in range(len(shape_matrix)):
for col in range(len(shape_matrix[row])):
if shape_matrix[row][col]:
x = game.shape_x + col
y = game.shape_y + row
if 0 <= y < GRID_HEIGHT and 0 <= x < GRID_WIDTH:
px = offset_x + x * BLOCK_SIZE + 1
py = offset_y + y * BLOCK_SIZE + 1
oled.fill_rect(px, py, BLOCK_SIZE - 2, BLOCK_SIZE - 2, 1)
# Draw score on right side
oled.text("Score", 68, 10)
score_str = str(game.score)
oled.text(score_str, 70, 20)
oled.show()
# ============== MAIN GAME LOOP ==============
game = TetrisGame()
last_input_time = time()
print("Tetris Game Started!")
print("Controls:")
print(" Left/Right: Move")
print(" Up: Rotate")
print(" Down: Drop")
print(" Button: Hard Drop")
while not game.game_over:
game.update()
current_time = time()
if current_time - last_input_time > 0.05:
handle_input(game)
last_input_time = current_time
draw_game(game)
sleep(0.05)
# Game Over Screen
oled.fill(0)
oled.text("GAME OVER!", 20, 15)
oled.text("Score:", 20, 35)
oled.text(str(game.score), 60, 35)
oled.show()
print(f"Game Over! Score: {game.score}")
sleep(5)
#replace the LCD, and the joystick and the ultrasonic distance sensor, change the codetLoading
ssd1306
ssd1306