Skip to content

Instantly share code, notes, and snippets.

@timotree3
Created August 25, 2024 21:48
Show Gist options
  • Save timotree3/4f7613beadadd4c4d998da2129f31a63 to your computer and use it in GitHub Desktop.
Save timotree3/4f7613beadadd4c4d998da2129f31a63 to your computer and use it in GitHub Desktop.
Tetris using Python turtle
import turtle
import random
GRID_HEIGHT = 40
GRID_WIDTH = 10
CELL_SIZE = 15
EMPTY_COLOR = "gray"
FULL = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
PREV_COLOR = [[None for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
COLOR = [[EMPTY_COLOR for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
score = 0
snap_to_end = False
speed = 20
def spawn_piece():
global piece_x, piece_y, next_piece_x, piece, piece_color, next_piece
piece_x = 5
piece_y = GRID_HEIGHT + 1
next_piece_x = 5
rand = random.randrange(0, 7)
print("spawned", rand)
if rand == 0:
piece = [(-1, 0), (0, 0), (1, 0), (2, 0)]
piece_color = "teal"
elif rand == 1:
piece = [(0, 0), (1, 0), (-1, 0), (0, -1)]
piece_color = "purple"
elif rand == 2:
piece = [(1, 0), (0, 0), (0, -1), (-1, -1)]
piece_color = "green"
elif rand == 3:
piece = [(-1, 0), (0, 0), (0, -1), (1, -1)]
piece_color = "red"
elif rand == 4:
piece = [(-1, 0), (0, 0), (1, 0), (1, -1)]
piece_color = "blue"
elif rand == 5:
piece = [(1, 0), (0, 0), (-1, 0), (-1, -1)]
piece_color = "orange"
elif rand == 6:
piece = [(0, 0), (-1, 0), (-1, -1), (0, -1)]
piece_color = "yellow"
next_piece = piece
spawn_piece()
turtle.setup(350, 650)
turtle.tracer(30, 0)
t = turtle.Turtle()
t.penup()
t.hideturtle()
t.speed(0)
def set_cell(x, y, color):
if x not in range(GRID_WIDTH) or y not in range(GRID_HEIGHT):
return
COLOR[y][x] = color
def draw_board():
def draw_cell(x, y, color):
t.goto(x * CELL_SIZE - (GRID_WIDTH * CELL_SIZE / 2),
y * CELL_SIZE - (GRID_HEIGHT * CELL_SIZE / 2) + 20)
t.setheading(0)
t.color(color)
t.begin_fill()
for _ in range(4):
t.forward(CELL_SIZE)
t.right(90)
t.end_fill()
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
if COLOR[y][x] != PREV_COLOR[y][x]:
print("changed", x, y, COLOR[y][x])
draw_cell(x, y, COLOR[y][x])
PREV_COLOR[y][x] = COLOR[y][x]
def piece_fits(x, y, candidate_piece):
for (x_offset, y_offset) in candidate_piece:
if y + y_offset >= GRID_HEIGHT:
# falling pieces are allowed to go above the screen
continue
if x + x_offset not in range(GRID_WIDTH) \
or y + y_offset not in range(GRID_HEIGHT) \
or FULL[y + y_offset][x + x_offset]:
return False
return True
def try_clear(row):
if all(FULL[row]):
FULL.pop(row)
FULL.append([False for _ in range(GRID_WIDTH)])
COLOR.pop(row)
COLOR.append([EMPTY_COLOR for _ in range(GRID_WIDTH)])
return True
return False
def score_from_lines(num):
if num == 0:
return 0
elif num == 1:
return 100
elif num == 2:
return 300
elif num == 3:
return 500
elif num == 4:
return 800
else:
raise "uh oh"
def place_piece():
global piece_y, score
sorted_segments = sorted(
piece, key=lambda offsets: offsets[1])
print("place piece", sorted_segments)
cleared_total = 0
for (x_offset, y_offset) in sorted_segments:
if piece_y + y_offset >= GRID_HEIGHT:
return False
FULL[piece_y + y_offset][piece_x + x_offset] = True
cleared = try_clear(piece_y + y_offset)
if cleared:
cleared_total += 1
# move piece down a row now that the bottom of the piece has caused a clear
piece_y -= 1
score += score_from_lines(cleared_total)
return True
def on_left():
global next_piece_x
if piece_fits(next_piece_x - 1, piece_y, next_piece):
next_piece_x -= 1
def on_right():
global next_piece_x
if piece_fits(next_piece_x + 1, piece_y, next_piece):
next_piece_x += 1
def on_space():
global snap_to_end
snap_to_end = True
def on_down():
global speed
speed *= 3
def on_down_released():
global speed
speed //= 3
def on_z():
global next_piece
candidate_piece = [rotate_left(offsets) for offsets in next_piece]
if piece_fits(next_piece_x, piece_y, candidate_piece):
next_piece = candidate_piece
def on_x():
global next_piece
candidate_piece = [rotate_right(offsets) for offsets in next_piece]
if piece_fits(next_piece_x, piece_y, candidate_piece):
next_piece = candidate_piece
def rotate_right(offsets):
(x_offset, y_offset) = offsets
return (y_offset, -x_offset)
def rotate_left(offsets):
(x_offset, y_offset) = offsets
return (-y_offset, x_offset)
def move_piece():
global piece_x, piece_y, piece
if not piece_fits(next_piece_x, piece_y - 1, next_piece):
return False
for (x_offset, y_offset) in piece:
set_cell(piece_x + x_offset, piece_y + y_offset, EMPTY_COLOR)
piece_x = next_piece_x
piece_y -= 1
piece = next_piece
for (x_offset, y_offset) in piece:
set_cell(piece_x + x_offset, piece_y + y_offset, piece_color)
return True
turtle.onkeypress(on_left, "Left")
turtle.onkeypress(on_right, "Right")
turtle.onkeypress(on_space, " ")
turtle.onkeypress(on_down, "Down")
turtle.onkeyrelease(on_down_released, "Down")
turtle.onkeypress(on_z, "z")
turtle.onkeypress(on_x, "x")
turtle.listen()
draw_board()
step = 0
is_game_over = False
def game_over():
global is_game_over
is_game_over = True
t.goto(0, 0)
t.color("black")
t.write("Game over! Score: " + str(score),
align="center", font=("arial", 20, "normal"))
def update():
global step, snap_to_end
if is_game_over:
return
step += 1
print("update", step)
turtle.ontimer(update, 1000//speed)
draw_board()
fits = move_piece()
if snap_to_end:
while fits:
fits = move_piece()
snap_to_end = False
if not fits:
good = place_piece()
print("placed good=", good)
if not good:
game_over()
return
spawn_piece()
update()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment