俄罗斯方块是一个经典的电子游戏,风靡全球。下面我们一步一步地使用Python和Tkinter库制作一个简易的俄罗斯方块游戏。
整体代码放在最前面,无须任何第三方库
import tkinter as tk
import random
# 方块形状定义及其对应的颜色
SHAPES = [
([['O', 'O', 'O', 'O']], 'cyan'), # I
([['O', 'O'], ['O', 'O']], 'yellow'), # O
([['O', 'O', 'O'], [' ', 'O', ' ']], 'purple'), # T
([['O', 'O', ' '], [' ', 'O', 'O']], 'green'), # S
([[' ', 'O', 'O'], ['O', 'O', ' ']], 'red'), # Z
([['O', 'O', 'O'], ['O', ' ', ' ']], 'orange'), # L
([['O', 'O', 'O'], [' ', ' ', 'O']], 'blue') # J
]
# 游戏配置
CELL_SIZE = 30
COLUMNS = 10
ROWS = 20
DELAY = 500 # 毫秒
class Tetris(tk.Tk):
def __init__(self):
super().__init__()
self.title("Tetris")
# 创建主要框架
self.main_frame = tk.Frame(self)
self.main_frame.pack()
# 创建左侧游戏区域
self.canvas = tk.Canvas(self.main_frame, width=COLUMNS * CELL_SIZE, height=ROWS * CELL_SIZE, bg="white")
self.canvas.grid(row=0, column=0)
# 创建右侧功能区
self.side_frame = tk.Frame(self.main_frame)
self.side_frame.grid(row=0, column=1, padx=10)
self.score_label = tk.Label(self.side_frame, text="Score: 0", font=("Helvetica", 16))
self.score_label.pack(pady=10)
self.next_label = tk.Label(self.side_frame, text="Next:", font=("Helvetica", 16))
self.next_label.pack(pady=10)
self.next_canvas = tk.Canvas(self.side_frame, width=4 * CELL_SIZE, height=4 * CELL_SIZE, bg="white")
self.next_canvas.pack(pady=10)
self.pause_button = tk.Button(self.side_frame, text="Pause", command=self.toggle_pause)
self.pause_button.pack(pady=10)
self.start_button = tk.Button(self, text="Start Game", command=self.start_game, font=("Helvetica", 16))
self.start_button.pack(pady=10)
self.bind("" , self.key_press)
self.paused = False
self.score = 0
def start_game(self):
self.start_button.pack_forget()
self.init_game()
def init_game(self):
self.board = [[' ']*COLUMNS for _ in range(ROWS)]
self.game_over = False
self.current_shape, self.current_color = self.new_shape()
self.current_x = COLUMNS // 2 - len(self.current_shape[0]) // 2
self.current_y = 0
self.next_shape, self.next_color = self.new_shape()
if self.collides(self.current_shape, self.current_x, self.current_y):
self.show_game_over()
else:
self.draw_board()
self.draw_next_shape()
self.tick()
def new_shape(self):
return random.choice(SHAPES)
def draw_board(self):
self.canvas.delete(tk.ALL)
for y in range(ROWS):
for x in range(COLUMNS):
if self.board[y][x] != ' ':
self.draw_cell(self.canvas, x, y, self.board[y][x])
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell == 'O':
self.draw_cell(self.canvas, self.current_x + x, self.current_y + y, self.current_color)
self.score_label.config(text=f"Score: {self.score}")
def draw_cell(self, canvas, x, y, color):
canvas.create_rectangle(x*CELL_SIZE, y*CELL_SIZE, (x+1)*CELL_SIZE, (y+1)*CELL_SIZE, fill=color, outline="black")
def key_press(self, event):
if event.keysym == 'Left':
self.move(-1, 0)
elif event.keysym == 'Right':
self.move(1, 0)
elif event.keysym == 'Down':
self.move(0, 1)
elif event.keysym == 'Up':
self.rotate()
elif event.keysym == 'p': # Pause/resume
self.toggle_pause()
def move(self, dx, dy):
if not self.collides(self.current_shape, self.current_x + dx, self.current_y + dy):
self.current_x += dx
self.current_y += dy
self.draw_board()
return True
return False
def rotate(self):
rotated_shape = list(zip(*self.current_shape[::-1]))
if not self.collides(rotated_shape, self.current_x, self.current_y):
self.current_shape = rotated_shape
self.draw_board()
def collides(self, shape, x, y):
for i, row in enumerate(shape):
for j, cell in enumerate(row):
if cell == 'O':
if x + j < 0 or x + j >= COLUMNS or y + i >= ROWS or (y + i >= 0 and self.board[y + i][x + j] != ' '):
return True
return False
def tick(self):
if self.game_over or self.paused:
return
if not self.move(0, 1):
self.freeze()
self.clear_lines()
self.current_shape, self.current_color = self.next_shape, self.next_color
self.current_x = COLUMNS // 2 - len(self.current_shape[0]) // 2
self.current_y = 0
self.next_shape, self.next_color = self.new_shape()
if self.collides(self.current_shape, self.current_x, self.current_y):
self.game_over = True
self.show_game_over()
self.draw_board()
self.draw_next_shape()
self.after(DELAY, self.tick)
def freeze(self):
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell == 'O':
self.board[self.current_y + y][self.current_x + x] = self.current_color
def clear_lines(self):
new_board = [row for row in self.board if any(cell == ' ' for cell in row)]
lines_cleared = ROWS - len(new_board)
self.board = [[' ']*COLUMNS for _ in range(lines_cleared)] + new_board
self.score += lines_cleared * 100
def show_game_over(self):
self.canvas.create_text(COLUMNS*CELL_SIZE // 2, ROWS*CELL_SIZE // 2, text="GAME OVER", fill="red", font=("Helvetica", 24))
self.restart_button = tk.Button(self.side_frame, text="Restart Game", command=self.restart_game)
self.restart_button.pack(pady=10)
def restart_game(self):
self.restart_button.pack_forget()
self.init_game()
def draw_next_shape(self):
self.next_canvas.delete(tk.ALL)
for y, row in enumerate(self.next_shape):
for x, cell in enumerate(row):
if cell == 'O':
self.draw_cell(self.next_canvas, x, y, self.next_color)
def toggle_pause(self):
self.paused = not self.paused
if not self.paused:
self.tick()
if __name__ == "__main__":
game = Tetris()
game.mainloop()
首先,确保您已经安装了Python 3.x。然后,安装Tkinter库(Python内置),无需额外安装。
我们先创建一个基本的Tkinter窗口,并设置游戏的主要配置参数,例如每个方块的大小、游戏区域的列数和行数、以及下落速度。
import tkinter as tk
import random
# 方块形状定义及其对应的颜色
SHAPES = [
([['O', 'O', 'O', 'O']], 'cyan'), # I
([['O', 'O'], ['O', 'O']], 'yellow'), # O
([['O', 'O', 'O'], [' ', 'O', ' ']], 'purple'), # T
([['O', 'O', ' '], [' ', 'O', 'O']], 'green'), # S
([[' ', 'O', 'O'], ['O', 'O', ' ']], 'red'), # Z
([['O', 'O', 'O'], ['O', ' ', ' ']], 'orange'), # L
([['O', 'O', 'O'], [' ', ' ', 'O']], 'blue') # J
]
# 游戏配置
CELL_SIZE = 30
COLUMNS = 10
ROWS = 20
DELAY = 500 # 毫秒
class Tetris(tk.Tk):
def __init__(self):
super().__init__()
self.title("Tetris")
# 创建主要框架
self.main_frame = tk.Frame(self)
self.main_frame.pack()
# 创建左侧游戏区域
self.canvas = tk.Canvas(self.main_frame, width=COLUMNS * CELL_SIZE, height=ROWS * CELL_SIZE, bg="white")
self.canvas.grid(row=0, column=0)
# 创建右侧功能区
self.side_frame = tk.Frame(self.main_frame)
self.side_frame.grid(row=0, column=1, padx=10)
self.score_label = tk.Label(self.side_frame, text="Score: 0", font=("Helvetica", 16))
self.score_label.pack(pady=10)
self.next_label = tk.Label(self.side_frame, text="Next:", font=("Helvetica", 16))
self.next_label.pack(pady=10)
self.next_canvas = tk.Canvas(self.side_frame, width=4 * CELL_SIZE, height=4 * CELL_SIZE, bg="white")
self.next_canvas.pack(pady=10)
self.pause_button = tk.Button(self.side_frame, text="Pause", command=self.toggle_pause)
self.pause_button.pack(pady=10)
self.start_button = tk.Button(self, text="Start Game", command=self.start_game, font=("Helvetica", 16))
self.start_button.pack(pady=10)
self.bind("" , self.key_press)
self.paused = False
self.score = 0
def start_game(self):
self.start_button.pack_forget()
self.init_game()
def init_game(self):
self.board = [[' ']*COLUMNS for _ in range(ROWS)]
self.game_over = False
self.current_shape, self.current_color = self.new_shape()
self.current_x = COLUMNS // 2 - len(self.current_shape[0]) // 2
self.current_y = 0
self.next_shape, self.next_color = self.new_shape()
if self.collides(self.current_shape, self.current_x, self.current_y):
self.show_game_over()
else:
self.draw_board()
self.draw_next_shape()
self.tick()
def new_shape(self):
return random.choice(SHAPES)
def draw_board(self):
self.canvas.delete(tk.ALL)
for y in range(ROWS):
for x in range(COLUMNS):
if self.board[y][x] != ' ':
self.draw_cell(self.canvas, x, y, self.board[y][x])
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell == 'O':
self.draw_cell(self.canvas, self.current_x + x, self.current_y + y, self.current_color)
self.score_label.config(text=f"Score: {self.score}")
def draw_cell(self, canvas, x, y, color):
canvas.create_rectangle(x*CELL_SIZE, y*CELL_SIZE, (x+1)*CELL_SIZE, (y+1)*CELL_SIZE, fill=color, outline="black")
def key_press(self, event):
if event.keysym == 'Left':
self.move(-1, 0)
elif event.keysym == 'Right':
self.move(1, 0)
elif event.keysym == 'Down':
self.move(0, 1)
elif event.keysym == 'Up':
self.rotate()
elif event.keysym == 'p': # Pause/resume
self.toggle_pause()
def move(self, dx, dy):
if not self.collides(self.current_shape, self.current_x + dx, self.current_y + dy):
self.current_x += dx
self.current_y += dy
self.draw_board()
return True
return False
def rotate(self):
rotated_shape = list(zip(*self.current_shape[::-1]))
if not self.collides(rotated_shape, self.current_x, self.current_y):
self.current_shape = rotated_shape
self.draw_board()
def collides(self, shape, x, y):
for i, row in enumerate(shape):
for j, cell in enumerate(row):
if cell == 'O':
if x + j < 0 or x + j >= COLUMNS or y + i >= ROWS or (y + i >= 0 and self.board[y + i][x + j] != ' '):
return True
return False
def tick(self):
if self.game_over or self.paused:
return
if not self.move(0, 1):
self.freeze()
self.clear_lines()
self.current_shape, self.current_color = self.next_shape, self.next_color
self.current_x = COLUMNS // 2 - len(self.current_shape[0]) // 2
self.current_y = 0
self.next_shape, self.next_color = self.new_shape()
if self.collides(self.current_shape, self.current_x, self.current_y):
self.game_over = True
self.show_game_over()
self.draw_board()
self.draw_next_shape()
self.after(DELAY, self.tick)
def freeze(self):
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell == 'O':
self.board[self.current_y + y][self.current_x + x] = self.current_color
def clear_lines(self):
new_board = [row for row in self.board if any(cell == ' ' for cell in row)]
lines_cleared = ROWS - len(new_board)
self.board = [[' ']*COLUMNS for _ in range(lines_cleared)] + new_board
self.score += lines_cleared * 100
def show_game_over(self):
self.canvas.create_text(COLUMNS*CELL_SIZE // 2, ROWS*CELL_SIZE // 2, text="GAME OVER", fill="red", font=("Helvetica", 24))
self.restart_button = tk.Button(self.side_frame, text="Restart Game", command=self.restart_game)
self.restart_button.pack(pady=10)
def restart_game(self):
self.restart_button.pack_forget()
self.init_game()
def draw_next_shape(self):
self.next_canvas.delete(tk.ALL)
for y, row in enumerate(self.next_shape):
for x, cell in enumerate(row):
if cell == 'O':
self.draw_cell(self.next_canvas, x, y, self.next_color)
def toggle_pause(self):
self.paused = not self.paused
if not self.paused:
self.tick()
if __name__ == "__main__":
game = Tetris()
game.mainloop()
我们使用tk.Tk
类创建主窗口,并使用tk.Canvas
在窗口中绘制游戏区域。tk.Frame
用于布局不同的UI元素,包括显示当前分数、下一个方块、暂停按钮和开始按钮。
self.main_frame = tk.Frame(self)
self.main_frame.pack()
self.canvas = tk.Canvas(self.main_frame, width=COLUMNS * CELL_SIZE, height=ROWS * CELL_SIZE, bg="white")
self.canvas.grid(row=0, column=0)
self.side_frame = tk.Frame(self.main_frame)
self.side_frame.grid(row=0, column=1, padx=10)
init_game
方法初始化游戏状态,包括生成新的方块、设置初始位置和检测是否立即发生碰撞。如果发生碰撞,游戏结束;否则,启动游戏循环。
def init_game(self):
self.board = [[' ']*COLUMNS for _ in range(ROWS)]
self.game_over = False
self.current_shape, self.current_color = self.new_shape()
self.current_x = COLUMNS // 2 - len(self.current_shape[0]) // 2
self.current_y = 0
self.next_shape, self.next_color = self.new_shape()
if self.collides(self.current_shape, self.current_x, self.current_y):
self.show_game_over()
else:
self.draw_board()
self.draw_next_shape()
self.tick()
new_shape
方法随机选择一个形状和颜色的组合,并返回该组合。
def new_shape(self):
return random.choice(SHAPES)
draw_board
方法清空画布,并重新绘制当前游戏板上的所有方块。
def draw_board(self):
self.canvas.delete(tk.ALL)
for y in range(ROWS):
for x in range(COLUMNS):
if self.board[y][x] != ' ':
self.draw_cell(self.canvas, x, y, self.board[y][x])
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell == 'O':
self.draw_cell(self.canvas, self.current_x + x, self.current_y + y, self.current_color)
self.score_label.config(text=f"Score: {self.score}")
draw_cell
方法根据给定的颜色绘制一个方块单元格。
def draw_cell(self, canvas, x, y, color):
canvas.create_rectangle(x*CELL_SIZE, y*CELL_SIZE, (x+1)*CELL_SIZE, (y+1)*CELL_SIZE, fill=color, outline="black")
我们通过按键事件处理器key_press
来响应用户输入,以移动或旋转当前方块。
def key_press(self, event):
if event.keysym == 'Left':
self.move(-1, 0)
elif event.keysym == 'Right':
self.move(1, 0)
elif event.keysym == 'Down':
self.move(0, 1)
elif event.keysym == 'Up':
self.rotate()
elif event.keysym == 'p': # Pause/resume
self.toggle_pause()
move
方法检测方块是否可以移动到新的位置,并更新方块位置。
def move(self, dx, dy):
if not self.collides(self.current_shape, self.current_x + dx, self.current_y + dy):
self.current_x += dx
self.current_y += dy
self.draw_board()
return True
return False
rotate
方法将当前方块旋转90度,并检测是否发生碰撞。
def rotate(self):
rotated_shape = list(zip(*self.current_shape[::-1]))
if not self.collides(rotated_shape, self.current_x, self.current_y):
self.current_shape = rotated_shape
self.draw_board()
collides
方法检测方块是否与其他方块或边界发生碰撞。
def collides(self, shape, x, y):
for i, row in enumerate(shape):
for j, cell in enumerate(row):
if cell == 'O':
if x + j < 0 or x + j >= COLUMNS or y + i >= ROWS or (y + i >= 0 and self.board[y + i][x + j] != ' '):
return True
return False
tick
方法是游戏的主循环,每隔一定时间调用一次以更新游戏状态。
def tick(self):
if self.game_over or self.paused:
return
if not self.move(0, 1):
self.freeze()
self.clear_lines()
self.current_shape, self.current_color = self.next_shape, self.next_color
self.current_x = COLUMNS // 2 - len(self.current_shape[0]) // 2
self.current_y = 0
self.next_shape, self.next_color = self.new_shape()
if self.collides(self.current_shape, self.current_x, self.current_y):
self.game_over = True
self.show_game_over()
self.draw_board()
self.draw_next_shape()
self.after(DELAY, self.tick)
freeze
方法将当前方块固定到游戏板上。
def freeze(self):
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell == 'O':
self.board[self.current_y + y][self.current_x + x] = self.current_color
clear_lines
方法检测并清除已填满的行。
def clear_lines(self):
new_board = [row for row in self.board if any(cell == ' ' for cell in row)]
lines_cleared = ROWS - len(new_board)
self.board = [[' ']*COLUMNS for _ in range(lines_cleared)] + new_board
self.score += lines_cleared * 100
show_game_over
方法显示游戏结束信息,并提供重启按钮。
def show_game_over(self):
self.canvas.create_text(COLUMNS*CELL_SIZE // 2, ROWS*CELL_SIZE // 2, text="GAME OVER", fill="red", font=("Helvetica", 24))
self.restart_button = tk.Button(self.side_frame, text="Restart Game", command=self.restart_game)
self.restart_button.pack(pady=10)
restart_game
方法重新开始游戏。
def restart_game(self):
self.restart_button.pack_forget()
self.init_game()
draw_next_shape
方法在侧边画布中绘制下一个将要出现的方块。
def draw_next_shape(self):
self.next_canvas.delete(tk.ALL)
for y, row in enumerate(self.next_shape):
for x, cell in enumerate(row):
if cell == 'O':
self.draw_cell(self.next_canvas, x, y, self.next_color)