3D俄罗斯方块游戏说明
我创建了一个不需要任何外部库的3D俄罗斯方块游戏,使用Python标准库中的tkinter来实现。这个游戏可以在Python 3.11上直接运行,不需要安装任何额外的包。
游戏特点
- 多视图显示:游戏提供三个不同的视图(前视图、俯视图和侧视图)让您可以看到3D空间中的方块位置
- 伪3D渲染:额外提供一个旋转的3D视图,通过透视投影和颜色深度来创建3D效果
- 3D游戏机制:方块可以在三个维度上移动和旋转
- 特殊形状:除了传统的俄罗斯方块形状,还包含了几种真正的3D形状
控制方式
- ←→ 键:左右移动方块
- ↑↓ 键:前后移动方块(Z轴方向)
- Z/X 键:上下移动方块
- A/S/D 键:分别绕X轴、Y轴、Z轴旋转方块
- 空格键:快速下落
- P 键:暂停/继续游戏
如何运行
- 将代码复制到一个.py文件中
- 在命令行中运行:
python 文件名.py
游戏会立即启动,不需要安装任何额外的包,因为它只使用了Python标准库中的tkinter。
这个游戏使用多视图和旋转效果创建了3D体验,同时保持了代码的简洁性和可移植性。您可以随时使用右侧面板上的按钮暂停游戏或开始新游戏。
import tkinter as tk
import random
import time
import math
from collections import deque
class Tetris3D:
def __init__(self, root):
self.root = root
self.root.title("3D 俄罗斯方块")
self.root.geometry("800x600")
self.root.resizable(False, False)
# 游戏参数
self.width = 10
self.height = 20
self.depth = 10
self.cell_size = 20
self.game_speed = 500 # 毫秒
self.score = 0
self.level = 1
self.paused = False
self.game_over = False
# 颜色
self.colors = [
"#FF0000", # 红色
"#00FF00", # 绿色
"#0000FF", # 蓝色
"#FFFF00", # 黄色
"#FF00FF", # 紫色
"#00FFFF", # 青色
"#FFA500" # 橙色
]
# 形状定义: 3D 坐标列表
self.shapes = [
# I 形状 (2D平面上带有Z轴)
[(-1, 0, 0), (0, 0, 0), (1, 0, 0), (2, 0, 0)],
# O 形状 (2D平面上带有Z轴)
[(0, 0, 0), (1, 0, 0), (0, 1, 0), (1, 1, 0)],
# T 形状 (2D平面上带有Z轴)
[(-1, 0, 0), (0, 0, 0), (1, 0, 0), (0, 1, 0)],
# L 形状 (2D平面上带有Z轴)
[(-1, 0, 0), (0, 0, 0), (1, 0, 0), (1, 1, 0)],
# J 形状 (2D平面上带有Z轴)
[(-1, 0, 0), (0, 0, 0), (1, 0, 0), (-1, 1, 0)],
# S 形状 (2D平面上带有Z轴)
[(-1, 0, 0), (0, 0, 0), (0, 1, 0), (1, 1, 0)],
# Z 形状 (2D平面上带有Z轴)
[(1, 0, 0), (0, 0, 0), (0, 1, 0), (-1, 1, 0)],
# 3D 立方体
[(0, 0, 0), (1, 0, 0), (0, 1, 0), (1, 1, 0),
(0, 0, 1), (1, 0, 1), (0, 1, 1), (1, 1, 1)],
# 3D T
[(0, 0, 0), (0, 0, 1), (0, 0, -1), (1, 0, 0), (-1, 0, 0)]
]
# 界面设置
self.setup_ui()
# 游戏状态
self.grid = [[[0 for _ in range(self.depth)] for _ in range(self.height)] for _ in range(self.width)]
self.current_piece = None
self.current_pos = [0, 0, 0]
self.current_color = ""
self.rotation_angle = 0
# 视图参数 (用于伪3D效果)
self.views = {
"front": {"canvas": self.front_canvas, "show_x": True, "show_y": True, "show_z": False, "projection": "xy"},
"top": {"canvas": self.top_canvas, "show_x": True, "show_y": False, "show_z": True, "projection": "xz"},
"side": {"canvas": self.side_canvas, "show_x": False, "show_y": True, "show_z": True, "projection": "zy"}
}
# 启动游戏
self.new_game()
def setup_ui(self):
# 主框架
main_frame = tk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True)
# 左侧游戏区域
game_frame = tk.Frame(main_frame, bg="black")
game_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建三个画布代表不同的视角
canvas_frame = tk.Frame(game_frame)
canvas_frame.pack(fill=tk.BOTH, expand=True)
# 前视图 (XY平面)
front_frame = tk.Frame(canvas_frame)
front_frame.grid(row=0, column=0, padx=5, pady=5)
front_label = tk.Label(front_frame, text="前视图 (XY)")
front_label.pack()
self.front_canvas = tk.Canvas(
front_frame,
width=self.width * self.cell_size + 2,
height=self.height * self.cell_size + 2,
bg="black"
)
self.front_canvas.pack()
# 俯视图 (XZ平面)
top_frame = tk.Frame(canvas_frame)
top_frame.grid(row=0, column=1, padx=5, pady=5)
top_label = tk.Label(top_frame, text="俯视图 (XZ)")
top_label.pack()
self.top_canvas = tk.Canvas(
top_frame,
width=self.width * self.cell_size + 2,
height=self.depth * self.cell_size + 2,
bg="black"
)
self.top_canvas.pack()
# 侧视图 (ZY平面)
side_frame = tk.Frame(canvas_frame)
side_frame.grid(row=1, column=0, padx=5, pady=5)
side_label = tk.Label(side_frame, text="侧视图 (ZY)")
side_label.pack()
self.side_canvas = tk.Canvas(
side_frame,
width=self.depth * self.cell_size + 2,
height=self.height * self.cell_size + 2,
bg="black"
)
self.side_canvas.pack()
# 3D 渲染区域(伪3D效果)
render_frame = tk.Frame(canvas_frame)
render_frame.grid(row=1, column=1, padx=5, pady=5)
render_label = tk.Label(render_frame, text="3D 视图")
render_label.pack()
self.render_canvas = tk.Canvas(
render_frame,
width=300,
height=300,
bg="black"
)
self.render_canvas.pack()
# 右侧信息和控制区域
info_frame = tk.Frame(main_frame)
info_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10)
# 分数显示
score_frame = tk.Frame(info_frame)
score_frame.pack(fill=tk.X, pady=5)
tk.Label(score_frame, text="分数:").pack(side=tk.LEFT)
self.score_var = tk.StringVar(value="0")
tk.Label(score_frame, textvariable=self.score_var).pack(side=tk.RIGHT)
# 等级显示
level_frame = tk.Frame(info_frame)
level_frame.pack(fill=tk.X, pady=5)
tk.Label(level_frame, text="等级:").pack(side=tk.LEFT)
self.level_var = tk.StringVar(value="1")
tk.Label(level_frame, textvariable=self.level_var).pack(side=tk.RIGHT)
# 下一个方块预览区域
next_frame = tk.Frame(info_frame)
next_frame.pack(fill=tk.X, pady=10)
tk.Label(next_frame, text="下一个:").pack()
self.next_canvas = tk.Canvas(
next_frame,
width=100,
height=100,
bg="black"
)
self.next_canvas.pack(pady=5)
# 控制按钮
buttons_frame = tk.Frame(info_frame)
buttons_frame.pack(fill=tk.X, pady=20)
self.pause_button = tk.Button(buttons_frame, text="暂停", command=self.toggle_pause)
self.pause_button.pack(fill=tk.X, pady=2)
self.new_game_button = tk.Button(buttons_frame, text="新游戏", command=self.new_game)
self.new_game_button.pack(fill=tk.X, pady=2)
# 控制说明
controls_frame = tk.Frame(info_frame)
controls_frame.pack(fill=tk.X, pady=10)
tk.Label(controls_frame, text="控制方式:").pack(anchor=tk.W)
tk.Label(controls_frame, text="← → : 左右移动").pack(anchor=tk.W)
tk.Label(controls_frame, text="↑ ↓ : 前后移动").pack(anchor=tk.W)
tk.Label(controls_frame, text="Z/X : 上下移动").pack(anchor=tk.W)
tk.Label(controls_frame, text="A/S/D : 绕X/Y/Z轴旋转").pack(anchor=tk.W)
tk.Label(controls_frame, text="空格键 : 快速下落").pack(anchor=tk.W)
tk.Label(controls_frame, text="P : 暂停/继续").pack(anchor=tk.W)
# 键盘事件绑定
self.root.bind("<Left>", lambda e: self.move(-1, 0, 0))
self.root.bind("<Right>", lambda e: self.move(1, 0, 0))
self.root.bind("<Up>", lambda e: self.move(0, 0, -1))
self.root.bind("<Down>", lambda e: self.move(0, 0, 1))
self.root.bind("z", lambda e: self.move(0, 1, 0))
self.root.bind("x", lambda e: self.move(0, -1, 0))
self.root.bind("a", lambda e: self.rotate('x'))
self.root.bind("s", lambda e: self.rotate('y'))
self.root.bind("d", lambda e: self.rotate('z'))
self.root.bind("<space>", lambda e: self.drop())
self.root.bind("p", lambda e: self.toggle_pause())
def new_game(self):
# 重置游戏状态
self.grid = [[[0 for _ in range(self.depth)] for _ in range(self.height)] for _ in range(self.width)]
self.score = 0
self.level = 1
self.game_speed = 500
self.paused = False
self.game_over = False
# 更新UI
self.score_var.set(str(self.score))
self.level_var.set(str(self.level))
self.pause_button.config(text="暂停")
# 创建新方块
self.new_piece()
self.next_piece = self.random_piece()
self.draw_next_piece()
# 开始游戏循环
self.update()
def new_piece(self):
if hasattr(self, 'next_piece'):
self.current_piece = self.next_piece
else:
self.current_piece = self.random_piece()
self.next_piece = self.random_piece()
self.draw_next_piece()
# 初始位置在顶部中央
self.current_pos = [self.width // 2 - 1, self.height - 2, self.depth // 2 - 1]
# 检查游戏是否结束
if not self.is_valid_position():
self.game_over = True
self.draw_game_over()
def random_piece(self):
shape_idx = random.randint(0, len(self.shapes) - 1)
color_idx = random.randint(0, len(self.colors) - 1)
return {
"shape": self.shapes[shape_idx],
"color": self.colors[color_idx]
}
def draw_next_piece(self):
self.next_canvas.delete("all")
shape = self.next_piece["shape"]
color = self.next_piece["color"]
# 计算缩放和偏移以适应画布
min_x = min(x for x, _, _ in shape)
max_x = max(x for x, _, _ in shape)
min_y = min(y for _, y, _ in shape)
max_y = max(y for _, y, _ in shape)
scale = 15
offset_x = 50 - (max_x + min_x) * scale / 2
offset_y = 50 - (max_y + min_y) * scale / 2
# 画出每个方块
for x, y, z in shape:
x1 = offset_x + x * scale
y1 = offset_y + y * scale
x2 = x1 + scale
y2 = y1 + scale
# 基于Z坐标添加阴影效果
shade = 1.0 - (z * 0.1) # Z值越大,颜色越暗
r, g, b = self.hex_to_rgb(color)
r = int(r * shade)
g = int(g * shade)
b = int(b * shade)
block_color = f"#{r:02x}{g:02x}{b:02x}"
self.next_canvas.create_rectangle(x1, y1, x2, y2, fill=block_color, outline="white")
def hex_to_rgb(self, hex_color):
# 将十六进制颜色代码转换为RGB
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
def is_valid_position(self, piece=None, pos=None):
# 检查当前位置是否有效
if piece is None:
piece = self.current_piece["shape"]
if pos is None:
pos = self.current_pos
for x, y, z in piece:
nx, ny, nz = pos[0] + x, pos[1] + y, pos[2] + z
# 检查是否超出边界
if (nx < 0 or nx >= self.width or
ny < 0 or ny >= self.height or
nz < 0 or nz >= self.depth):
return False
# 检查是否与其他方块重叠
if ny >= 0 and self.grid[nx][ny][nz] != 0:
return False
return True
def rotate(self, axis):
if self.paused or self.game_over:
return
# 保存当前方块的形状以便需要时恢复
old_shape = self.current_piece["shape"].copy()
# 根据指定轴旋转
new_shape = []
for point in self.current_piece["shape"]:
x, y, z = point
if axis == 'x':
# 绕X轴旋转90度
new_point = (x, -z, y)
elif axis == 'y':
# 绕Y轴旋转90度
new_point = (z, y, -x)
elif axis == 'z':
# 绕Z轴旋转90度
new_point = (-y, x, z)
new_shape.append(new_point)
self.current_piece["shape"] = new_shape
# 如果旋转后位置无效,则恢复原来的形状
if not self.is_valid_position():
self.current_piece["shape"] = old_shape
self.draw_game()
def move(self, dx=0, dy=0, dz=0):
if self.paused or self.game_over:
return
# 尝试移动
new_pos = [
self.current_pos[0] + dx,
self.current_pos[1] + dy,
self.current_pos[2] + dz
]
# 检查新位置是否有效
if self.is_valid_position(self.current_piece["shape"], new_pos):
self.current_pos = new_pos
elif dy < 0: # 向下移动但不能移动,则固定方块
self.lock_piece()
self.check_lines()
self.new_piece()
self.draw_game()
def drop(self):
if self.paused or self.game_over:
return
# 快速下落
while True:
new_pos = [
self.current_pos[0],
self.current_pos[1] - 1,
self.current_pos[2]
]
if not self.is_valid_position(self.current_piece["shape"], new_pos):
break
self.current_pos = new_pos
# 固定方块
self.lock_piece()
self.check_lines()
self.new_piece()
self.draw_game()
def lock_piece(self):
# 将当前方块固定到网格中
for x, y, z in self.current_piece["shape"]:
nx, ny, nz = self.current_pos[0] + x, self.current_pos[1] + y, self.current_pos[2] + z
if 0 <= nx < self.width and 0 <= ny < self.height and 0 <= nz < self.depth:
# 存储颜色索引
color_idx = self.colors.index(self.current_piece["color"]) + 1
self.grid[nx][ny][nz] = color_idx
def check_lines(self):
lines_cleared = 0
# 检查每一层
for y in range(self.height):
# 检查XZ平面是否已满
full_plane = True
for x in range(self.width):
for z in range(self.depth):
if self.grid[x][y][z] == 0:
full_plane = False
break
if not full_plane:
break
# 如果一层已满,则清除该层
if full_plane:
lines_cleared += 1
# 将上面的层向下移
for y2 in range(y, self.height - 1):
for x in range(self.width):
for z in range(self.depth):
self.grid[x][y2][z] = self.grid[x][y2 + 1][z]
# 清空顶层
for x in range(self.width):
for z in range(self.depth):
self.grid[x][self.height - 1][z] = 0
# 计算分数
if lines_cleared > 0:
points = {1: 100, 2: 300, 3: 500, 4: 800}
self.score += points.get(lines_cleared, 1000) * self.level
self.score_var.set(str(self.score))
# 升级逻辑
new_level = min(10, 1 + self.score // 1000)
if new_level > self.level:
self.level = new_level
self.level_var.set(str(self.level))
self.game_speed = max(100, 500 - (self.level - 1) * 50)
def toggle_pause(self):
self.paused = not self.paused
if self.paused:
self.pause_button.config(text="继续")
else:
self.pause_button.config(text="暂停")
# 恢复游戏循环
self.update()
def update(self):
if not self.paused and not self.game_over:
# 自动下落
self.move(0, -1, 0)
# 设置下一次更新
self.root.after(self.game_speed, self.update)
def draw_game(self):
# 清除所有画布
for view_info in self.views.values():
view_info["canvas"].delete("all")
self.render_canvas.delete("all")
# 绘制网格边界
for view_name, view_info in self.views.items():
canvas = view_info["canvas"]
projection = view_info["projection"]
# 绘制网格线
if projection == "xy":
width, height = self.width, self.height
elif projection == "xz":
width, height = self.width, self.depth
elif projection == "zy":
width, height = self.depth, self.height
# 绘制背景格子
for i in range(width + 1):
canvas.create_line(
i * self.cell_size, 0,
i * self.cell_size, height * self.cell_size,
fill="#333333"
)
for i in range(height + 1):
canvas.create_line(
0, i * self.cell_size,
width * self.cell_size, i * self.cell_size,
fill="#333333"
)
# 绘制已放置的方块
for x in range(self.width):
for y in range(self.height):
for z in range(self.depth):
if self.grid[x][y][z] != 0:
color_idx = self.grid[x][y][z] - 1
color = self.colors[color_idx]
self.draw_block(x, y, z, color)
# 绘制当前移动的方块
if self.current_piece:
for block in self.current_piece["shape"]:
bx, by, bz = block
x = self.current_pos[0] + bx
y = self.current_pos[1] + by
z = self.current_pos[2] + bz
self.draw_block(x, y, z, self.current_piece["color"])
# 绘制伪3D视图
self.draw_3d_view()
def draw_block(self, x, y, z, color):
# 在各个视图中绘制方块
# 前视图 (XY平面)
self.front_canvas.create_rectangle(
x * self.cell_size, (self.height - y - 1) * self.cell_size,
(x + 1) * self.cell_size, (self.height - y) * self.cell_size,
fill=color, outline="white"
)
# 俯视图 (XZ平面)
self.top_canvas.create_rectangle(
x * self.cell_size, z * self.cell_size,
(x + 1) * self.cell_size, (z + 1) * self.cell_size,
fill=color, outline="white"
)
# 侧视图 (ZY平面)
self.side_canvas.create_rectangle(
z * self.cell_size, (self.height - y - 1) * self.cell_size,
(z + 1) * self.cell_size, (self.height - y) * self.cell_size,
fill=color, outline="white"
)
def draw_3d_view(self):
# 绘制伪3D视图
canvas = self.render_canvas
canvas_width = 300
canvas_height = 300
center_x = canvas_width // 2
center_y = canvas_height // 2
# 绘制3D坐标轴
axis_length = 50
canvas.create_line(center_x, center_y, center_x + axis_length, center_y, fill="red", width=2, arrow=tk.LAST)
canvas.create_line(center_x, center_y, center_x, center_y - axis_length, fill="green", width=2, arrow=tk.LAST)
canvas.create_line(center_x, center_y, center_x - axis_length * 0.7, center_y + axis_length * 0.7, fill="blue", width=2, arrow=tk.LAST)
canvas.create_text(center_x + axis_length + 10, center_y, text="X", fill="red")
canvas.create_text(center_x, center_y - axis_length - 10, text="Y", fill="green")
canvas.create_text(center_x - axis_length * 0.7 - 10, center_y + axis_length * 0.7 + 10, text="Z", fill="blue")
# 增加旋转角度
self.rotation_angle = (self.rotation_angle + 1) % 360
theta = math.radians(self.rotation_angle)
# 计算投影参数
scale = 10
distance = 200 # 观察距离
# 收集所有要绘制的方块
blocks = []
# 添加已放置的方块
for x in range(self.width):
for y in range(self.height):
for z in range(self.depth):
if self.grid[x][y][z] != 0:
color_idx = self.grid[x][y][z] - 1
color = self.colors[color_idx]
blocks.append((x - self.width/2, y - self.height/2, z - self.depth/2, color))
# 添加当前移动的方块
if self.current_piece:
for block in self.current_piece["shape"]:
bx, by, bz = block
x = self.current_pos[0] + bx - self.width/2
y = self.current_pos[1] + by - self.height/2
z = self.current_pos[2] + bz - self.depth/2
blocks.append((x, y, z, self.current_piece["color"]))
# 对方块进行深度排序
def get_depth(block):
x, y, z, _ = block
# 旋转后的z坐标决定深度
rot_z = x * math.sin(theta) + z * math.cos(theta)
return rot_z
blocks.sort(key=get_depth, reverse=True)
# 绘制方块
for x, y, z, color in blocks:
# 应用旋转
rot_x = x * math.cos(theta) - z * math.sin(theta)
rot_z = x * math.sin(theta) + z * math.cos(theta)
# 应用透视投影
scale_factor = distance / (distance - rot_z)
proj_x = rot_x * scale * scale_factor
proj_y = y * scale * scale_factor
# 绘制立方体
cube_size = scale * scale_factor
x1 = center_x + proj_x - cube_size/2
y1 = center_y - proj_y - cube_size/2
x2 = center_x + proj_x + cube_size/2
y2 = center_y - proj_y + cube_size/2
# 根据深度调整颜色
r, g, b = self.hex_to_rgb(color)
shade = max(0.3, min(1.0, 1.0 - rot_z * 0.05))
r = int(r * shade)
g = int(g * shade)
b = int(b * shade)
adjusted_color = f"#{r:02x}{g:02x}{b:02x}"
canvas.create_rectangle(x1, y1, x2, y2, fill=adjusted_color, outline="white")
def draw_game_over(self):
# 在前视图上显示游戏结束消息
self.front_canvas.create_text(
self.width * self.cell_size / 2,
self.height * self.cell_size / 2,
text="游戏结束",
fill="red",
font=("Helvetica", 20, "bold")
)
# 显示最终分数
self.front_canvas.create_text(
self.width * self.cell_size / 2,
self.height * self.cell_size / 2 + 30,
text=f"最终分数: {self.score}",
fill="white",
font=("Helvetica", 14)
)
# 提示重新开始
self.front_canvas.create_text(
self.width * self.cell_size / 2,
self.height * self.cell_size / 2 + 60,
text="按'新游戏'按钮重新开始",
fill="yellow",
font=("Helvetica", 12)
)
# 主函数
def main():
root = tk.Tk()
game = Tetris3D(root)
root.mainloop()
if __name__ == "__main__":
main()