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()