Created
August 25, 2024 21:48
-
-
Save timotree3/4f7613beadadd4c4d998da2129f31a63 to your computer and use it in GitHub Desktop.
Tetris using Python turtle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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