create a tetris clone using PIL
# tetris clone
import streamlit as st
import numpy as np
from PIL import Image, ImageDraw
import time
import random
# Constants
# NOTE: if you change these constants, you need to re-run the app (CTRL+R)
GRID_WIDTH = 6
GRID_HEIGHT = 10
BLOCK_SIZE = 40 # make thre grid smaller/bigger
SPEED = 2 # Speed of falling blocks in grid cells per second
# Block types
BLOCK_TYPES = [
{'shape': [(0,0)], 'color': 'red'}, # Single block
{"shape": [(0, 0), (1, 0)], "color": "blue"}, # Horizontal duo
{'shape': [(0,0), (0,1)], 'color': 'green'}, # Vertical duo
{'shape': [(0,0), (1,0), (1,1)], 'color': 'purple'}, # L shape
{'shape': [(0,0), (1,0), (0,1), (1,1)], 'color': 'orange'} # Square
]
# Initialize game state
if "grid" not in st.session_state:
st.session_state.grid = np.zeros((GRID_HEIGHT, GRID_WIDTH), dtype=int)
if "current_block" not in st.session_state:
block_type = random.choice(BLOCK_TYPES)
st.session_state.current_block = {
"x": GRID_WIDTH // 2,
"y": 0,
"last_move": time.time(),
"shape": block_type["shape"],
"color": block_type["color"],
}
if "score" not in st.session_state:
st.session_state.score = 0
def clear_lines():
"""Clear completed lines and return number of lines cleared"""
print("clear lines: ", st.session_state.grid)
lines_cleared = 0
for y in range(GRID_HEIGHT - 1, -1, -1):
if all(st.session_state.grid[y]):
# Move all lines above down by one
for y2 in range(y, 0, -1):
st.session_state.grid[y2] = st.session_state.grid[y2 - 1].copy()
st.session_state.grid[0] = 0
lines_cleared += 1
if y == 0:
print(f"state grid ({y}):", st.session_state.grid)
return lines_cleared
def create_game_board():
# Create a new image with white background
img_width = GRID_WIDTH * BLOCK_SIZE
img_height = GRID_HEIGHT * BLOCK_SIZE
img = Image.new("RGB", (img_width, img_height), "white")
draw = ImageDraw.Draw(img)
# Draw grid lines
for i in range(GRID_WIDTH + 1):
draw.line(
[(i * BLOCK_SIZE, 0), (i * BLOCK_SIZE, img_height)], fill="gray", width=1
)
for i in range(GRID_HEIGHT + 1):
draw.line(
[(0, i * BLOCK_SIZE), (img_width, i * BLOCK_SIZE)], fill="gray", width=1
)
# Draw blocks
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
if st.session_state.grid[y, x] == 1: # Settled blocks
draw.rectangle(
[
(x * BLOCK_SIZE, y * BLOCK_SIZE),
((x + 1) * BLOCK_SIZE, (y + 1) * BLOCK_SIZE),
],
fill="gray",
)
# Draw current falling block
for block_part in st.session_state.current_block["shape"]:
x = st.session_state.current_block["x"] + block_part[0]
y = st.session_state.current_block["y"] + block_part[1]
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
draw.rectangle(
[
(x * BLOCK_SIZE, y * BLOCK_SIZE),
((x + 1) * BLOCK_SIZE, (y + 1) * BLOCK_SIZE),
],
fill=st.session_state.current_block["color"],
)
return img
def check_collision():
for block_part in st.session_state.current_block["shape"]:
x = st.session_state.current_block["x"] + block_part[0]
y = st.session_state.current_block["y"] + block_part[1]
# Check boundaries
if y + 1 >= GRID_HEIGHT or x < 0 or x >= GRID_WIDTH:
print(f"boundaries collision at ({x}, {y})")
return True
# Check other blocks
if y + 1 < GRID_HEIGHT and st.session_state.grid[y + 1, x] == 1:
print(f"other block collision at ({x}, {y})")
return True
return False
def check_side_collision(dx):
for block_part in st.session_state.current_block["shape"]:
x = st.session_state.current_block["x"] + block_part[0] + dx
y = st.session_state.current_block["y"] + block_part[1]
# Check boundaries
if x < 0 or x >= GRID_WIDTH:
return True
# Check other blocks
if y < GRID_HEIGHT and st.session_state.grid[y, x] == 1:
return True
return False
def check_rotation_collision(rotated_shape):
"""Check if a rotation would cause a collision"""
for block_part in rotated_shape:
x = st.session_state.current_block["x"] + block_part[0]
y = st.session_state.current_block["y"] + block_part[1]
# Check boundaries
if x < 0 or x >= GRID_WIDTH or y < 0 or y >= GRID_HEIGHT:
return True
# Check other blocks
if st.session_state.grid[y, x] == 1:
return True
return False
def rotate_shape(shape, clockwise=True):
"""Rotate the shape 90 degrees clockwise or counterclockwise"""
if clockwise:
return [(-y, x) for x, y in shape]
else:
return [(y, -x) for x, y in shape]
def update_game_state():
current_time = time.time()
if current_time - st.session_state.current_block["last_move"] >= 1 / SPEED:
collision = check_collision()
if not collision:
st.session_state.current_block["y"] += 1
st.session_state.current_block["last_move"] = current_time
else:
print("collision: ", st.session_state.grid)
# Settle current block
for block_part in st.session_state.current_block["shape"]:
x = st.session_state.current_block["x"] + block_part[0]
y = st.session_state.current_block["y"] + block_part[1]
if 0 <= x < GRID_WIDTH and 0 <= y < GRID_HEIGHT:
print(f"setting block at ({x}, {y})")
st.session_state.grid[y, x] = 1
print("settled block after: ", st.session_state.grid)
# Clear any completed lines and create new block
lines_cleared = clear_lines()
if lines_cleared > 0:
st.session_state.score += lines_cleared * 100
# Create new block at top
block_type = random.choice(BLOCK_TYPES)
st.session_state.current_block = {
"x": GRID_WIDTH // 2,
"y": 0,
"last_move": current_time,
"shape": block_type["shape"],
"color": block_type["color"],
}
if lines_cleared > 0:
st.rerun() # Force a rerun only after new block is created
def check_game_over():
"""Check if any block has reached the top of the grid"""
# Check if any block is in the top row
if np.any(st.session_state.grid[0] == 1):
st.write("Game Over!")
return True
return False
st.title("Basic Tetris Clone")
# Display score
st.write(f"Score: {st.session_state.score}")
# Main game loop
if st.button("Reset Game"):
st.session_state.grid = np.zeros((GRID_HEIGHT, GRID_WIDTH), dtype=int)
st.session_state.score = 0
block_type = random.choice(BLOCK_TYPES)
st.session_state.current_block = {
"x": GRID_WIDTH // 2,
"y": 0,
"last_move": time.time(),
"shape": block_type["shape"],
"color": block_type["color"],
}
# Add movement controls
col1, col2, col3, col4, col5 = st.columns(5)
with col1:
if st.button("⬅️ Move Left"):
if not check_side_collision(-1):
st.session_state.current_block["x"] -= 1
with col2:
if st.button("Move Right ➡️"):
if not check_side_collision(1):
st.session_state.current_block["x"] += 1
with col3:
if st.button("↺ Rotate Left"):
rotated_shape = rotate_shape(st.session_state.current_block["shape"], False)
if not check_rotation_collision(rotated_shape):
st.session_state.current_block["shape"] = rotated_shape
with col4:
if st.button("Rotate Right ↻"):
rotated_shape = rotate_shape(st.session_state.current_block["shape"], True)
if not check_rotation_collision(rotated_shape):
st.session_state.current_block["shape"] = rotated_shape
with col5:
if st.button("Drop ⬇️"):
if not check_collision():
st.session_state.current_block["y"] += 1
st.session_state.current_block["last_move"] = time.time()
if not check_game_over():
update_game_state()
# Create and display game board
game_board = create_game_board()
st.image(game_board)
# Auto-rerun to create animation effect
time.sleep(0.1) # Small delay to prevent too frequent updates
st.rerun()
Hi! I can help you with any questions about Streamlit and Python. What would you like to know?