不需要额外安装库、可以直接在Python 3.11上运行的3D俄罗斯方块游戏

3D俄罗斯方块游戏说明

我创建了一个不需要任何外部库的3D俄罗斯方块游戏,使用Python标准库中的tkinter来实现。这个游戏可以在Python 3.11上直接运行,不需要安装任何额外的包。

游戏特点

  • 多视图显示:游戏提供三个不同的视图(前视图、俯视图和侧视图)让您可以看到3D空间中的方块位置
  • 伪3D渲染:额外提供一个旋转的3D视图,通过透视投影和颜色深度来创建3D效果
  • 3D游戏机制:方块可以在三个维度上移动和旋转
  • 特殊形状:除了传统的俄罗斯方块形状,还包含了几种真正的3D形状

控制方式

  • ←→ 键:左右移动方块
  • ↑↓ 键:前后移动方块(Z轴方向)
  • Z/X 键:上下移动方块
  • A/S/D 键:分别绕X轴、Y轴、Z轴旋转方块
  • 空格键:快速下落
  • P 键:暂停/继续游戏

如何运行

  1. 将代码复制到一个.py文件中
  2. 在命令行中运行: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()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值