游戏界面:
一个方块区域(游戏板),显示当前分数和最高分的标签,以及控制按钮(重启、暂停、显示排行榜)。玩家可以通过点击“Restart”按钮重启游戏,点击“Pause”按钮暂停游戏,点击“Show Leaderboard”按钮查看排行榜(最高的十个分数)。
游戏规则
玩家需要通过移动和旋转从屏幕顶部掉落的方块,使它们在屏幕底部堆积成完整的行。 游戏中有7种不同形状的方块:I形、O形、T形、S形、Z形、L形和J形,每种形状都有不同的颜色。
-
使用键盘方向键来控制方块的移动和旋转:
- 左箭头键:向左移动方块。
- 右箭头键:向右移动方块。
- 下箭头键:向下移动方块。
- 上箭头键:旋转方块。
-
当方块堆积到屏幕顶部,无法再放置新的方块时,游戏结束。
-
每清除一行,玩家会获得100分。游戏结束时,当前分数会保存到文件中,如果当前分数超过最高分,则更新最高分。
游戏代码
import tkinter as tk
import random
import os
# 游戏配置
CELL_SIZE = 35 # 每个方块的大小
COLUMNS = 10 # 游戏板的列数
ROWS = 20 # 游戏板的行数
INITIAL_DELAY = 500 # 初始延迟时间(毫秒)
DELAY = 500 # 每次更新的延迟时间(毫秒)
SCORE_FILE = "scores.txt" # 存储分数的文件
# 方块形状
SHAPES = [
[[1, 1, 1, 1]], # I 形
[[1, 1], [1, 1]], # O 形
[[0, 1, 0], [1, 1, 1]], # T 形
[[1, 1, 0], [0, 1, 1]], # S 形
[[0, 1, 1], [1, 1, 0]], # Z 形
[[1, 0, 0], [1, 1, 1]], # L 形
[[0, 0, 1], [1, 1, 1]] # J 形
]
# 方块颜色
COLORS = ["cyan", "yellow", "purple", "green", "red", "orange", "blue"]
class Tetris:
def __init__(self, root):
self.root = root
self.score = 0 # 当前分数
self.delay = INITIAL_DELAY
self.high_score = self.get_high_score() # 最高分
# 创建一个单独的框架来放置按钮
self.button_frame = tk.Frame(root)
self.button_frame.pack(side=tk.TOP, pady=10)
# 创建并放置排行榜按钮
self.leaderboard_button = tk.Button(self.button_frame, text="Show Leaderboard", command=self.show_leaderboard)
self.leaderboard_button.pack(side=tk.LEFT, padx=0) # 将排行榜按钮放在页面顶端
self.leaderboard_button.pack_forget() # 初始化时隐藏排行榜按钮
# 创建并放置重启按钮
self.restart_button = tk.Button(self.button_frame, text="Restart", command=self.restart_game)
self.restart_button.pack(side=tk.LEFT, padx=0) # 将重启按钮放在页面顶端
# 创建并放置暂停按钮
self.pause_button = tk.Button(self.button_frame, text="Pause", command=self.toggle_pause)
self.pause_button.pack(side=tk.LEFT, padx=0) # 将暂停按钮放在页面顶端
self.score_label = tk.Label(root, text=f"Score: {self.score} High Score: {self.high_score}")
self.score_label.pack()
self.canvas = tk.Canvas(root, width=COLUMNS*CELL_SIZE, height=ROWS*CELL_SIZE)
self.canvas.pack()
self.game_over = False # 游戏结束标志
self.paused = False # 游戏暂停标志
self.board = [[0] * COLUMNS for _ in range(ROWS)] # 初始化游戏板
self.current_shape = self.new_shape() # 当前方块形状
self.current_position = [0, COLUMNS // 2 - len(self.current_shape[0]) // 2] # 当前方块位置
self.root.bind("<Key>", self.key_press) # 绑定键盘事件
self.update() # 开始游戏更新循环
def new_shape(self):
"""生成一个新的方块形状"""
shape = random.choice(SHAPES) # 随机选择一个方块形状
color = COLORS[SHAPES.index(shape)] # 获取对应的颜色
return [[(cell and color) for cell in row] for row in shape] # 返回带颜色的方块形状
def draw_board(self):
"""绘制游戏板和当前方块"""
self.canvas.delete("all") # 清空画布
for r in range(ROWS):
for c in range(COLUMNS):
if self.board[r][c]:
self.canvas.create_rectangle(c*CELL_SIZE, r*CELL_SIZE, (c+1)*CELL_SIZE, (r+1)*CELL_SIZE, fill=self.board[r][c])
self.canvas.create_rectangle(c*CELL_SIZE, r*CELL_SIZE, (c+1)*CELL_SIZE, (r+1)*CELL_SIZE, outline="gray")
for r, row in enumerate(self.current_shape):
for c, cell in enumerate(row):
if cell:
self.canvas.create_rectangle((self.current_position[1]+c)*CELL_SIZE, (self.current_position[0]+r)*CELL_SIZE, (self.current_position[1]+c+1)*CELL_SIZE, (self.current_position[0]+r+1)*CELL_SIZE, fill=cell)
def move(self, delta_row, delta_col):
"""移动当前方块"""
new_position = [self.current_position[0] + delta_row, self.current_position[1] + delta_col]
if self.valid_position(new_position):
self.current_position = new_position
return True
return False
def rotate(self):
"""旋转当前方块"""
new_shape = list(zip(*self.current_shape[::-1])) # 顺时针旋转方块
if self.valid_position(self.current_position, new_shape):
self.current_shape = new_shape
def valid_position(self, position, shape=None):
"""检查方块在给定位置是否有效"""
if shape is None:
shape = self.current_shape
for r, row in enumerate(shape):
for c, cell in enumerate(row):
if cell:
if r + position[0] >= ROWS or c + position[1] < 0 or c + position[1] >= COLUMNS or self.board[r + position[0]][c + position[1]]:
return False
return True
def freeze(self):
"""冻结当前方块并生成新方块"""
for r, row in enumerate(self.current_shape):
for c, cell in enumerate(row):
if cell:
self.board[self.current_position[0] + r][self.current_position[1] + c] = cell
self.clear_lines()
self.current_shape = self.new_shape()
self.current_position = [0, COLUMNS // 2 - len(self.current_shape[0]) // 2]
if not self.valid_position(self.current_position):
self.game_over = True
def clear_lines(self):
"""清除已填满的行并更新分数"""
new_board = [row for row in self.board if any(cell == 0 for cell in row)]
lines_cleared = ROWS - len(new_board)
self.board = [[0] * COLUMNS for _ in range(lines_cleared)] + new_board
self.score += lines_cleared * 100 # 每消除一行增加100分
self.score_label.config(text=f"Score: {self.score} High Score: {self.high_score}")
def update(self):
"""更新游戏状态"""
if not self.game_over and not self.paused:
if not self.move(1, 0):
self.freeze()
self.draw_board()
self.root.after(self.delay, self.update)
elif self.game_over:
self.save_score()
self.canvas.create_text(COLUMNS*CELL_SIZE//2, ROWS*CELL_SIZE//2, text="GAME OVER", fill="red", font=("Helvetica", 24))
self.pause_button.pack_forget() # 隐藏暂停按钮
self.leaderboard_button.pack(side=tk.LEFT) # 显示排行榜按钮
def key_press(self, event):
"""处理键盘按键事件"""
if event.keysym == "Left":
self.move(0, -1)
elif event.keysym == "Right":
self.move(0, 1)
elif event.keysym == "Down":
self.move(1, 0)
elif event.keysym == "Up":
self.rotate()
self.draw_board()
def toggle_pause(self):
"""切换游戏暂停状态"""
self.paused = not self.paused
if not self.paused:
self.update()
def save_score(self):
"""保存当前分数到文件"""
with open(SCORE_FILE, "a") as file:
file.write(f"{self.score}\n")
if self.score > self.high_score:
self.high_score = self.score
def get_high_score(self):
"""获取最高分"""
if os.path.exists(SCORE_FILE):
with open(SCORE_FILE, "r") as file:
scores = [int(line.strip()) for line in file.readlines()]
if scores:
return max(scores)
return 0
def get_leaderboard_text(self):
"""获取排行榜文本"""
if os.path.exists(SCORE_FILE):
with open(SCORE_FILE, "r") as file:
scores = [int(line.strip()) for line in file.readlines()]
scores.sort(reverse=True)
top_scores = scores[:10]
leaderboard = "\n".join([f"{i+1}. {score}" for i, score in enumerate(top_scores)])
else:
leaderboard = "No scores available."
return leaderboard
def show_leaderboard(self):
"""显示排行榜窗口"""
leaderboard_window = tk.Toplevel(self.root)
leaderboard_window.title("Leaderboard")
leaderboard_text = self.get_leaderboard_text()
tk.Label(leaderboard_window, text=leaderboard_text).pack()
def restart_game(self):
"""重启游戏"""
self.score = 0
self.game_over = False
self.paused = False
self.board = [[0] * COLUMNS for _ in range(ROWS)]
self.current_shape = self.new_shape()
self.current_position = [0, COLUMNS // 2 - len(self.current_shape[0]) // 2]
self.delay = INITIAL_DELAY # 重置延迟时间
# 隐藏排行榜按钮
self.leaderboard_button.pack_forget()
# 重新布局按钮,确保它们在同一行
self.button_frame.pack_forget()
self.button_frame.pack(side=tk.TOP, pady=10)
self.restart_button.pack(side=tk.LEFT, padx=0)
self.pause_button.pack(side=tk.LEFT, padx=0)
# 确保按钮框架在网格之前被布局
self.canvas.pack_forget()
# 更新分数标签并放置在按钮和网格之间
self.score_label.pack_forget()
self.score_label.config(text=f"Score: {self.score} High Score: {self.high_score}")
self.score_label.pack()
self.canvas.pack()
self.update()
self.score_label.config(text=f"Score: {self.score} High Score: {self.high_score}")
self.update()
root = tk.Tk()
root.title("Tetris")
root.geometry(f"{COLUMNS*CELL_SIZE+20}x{ROWS*CELL_SIZE+100}+{(root.winfo_screenwidth()//2)-(COLUMNS*CELL_SIZE//2)}+{(root.winfo_screenheight()//2)-(ROWS*CELL_SIZE//2)}")
game = Tetris(root)
root.mainloop()
页面效果