Tetris

让 DeepSeek v3 (0324) 用 Python tkinter 写了一个俄罗斯方块,效果还很不错。

流程图

flowchart TD
    A[游戏开始] --> B[初始化游戏]
    B --> C[生成新形状]
    C --> D[绘制下一个形状]
    D --> E[游戏循环]
    E --> F[清除画布]
    F --> G[尝试向下移动形状]
    G -->|无碰撞| H[绘制网格和形状]
    G -->|有碰撞| I[撤销移动]
    I --> J[锁定形状到网格]
    J --> K[清除完成的行]
    K --> L[更新得分]
    L --> M[生成新形状]
    M --> N{检查碰撞}
    N -->|有碰撞| O[游戏结束]
    N -->|无碰撞| E
    O --> P{是否自动重启}
    P -->|是| Q[重启游戏]
    P -->|否| R[显示游戏结束文本]
    Q --> B
    R --> S[停止游戏]

    subgraph 用户输入处理
    E --> T[监听键盘输入]
    T --> U{按键类型}
    U -->|左/右箭头| V[横向移动形状]
    U -->|下箭头| W[向下移动形状]
    U -->|上箭头| X[旋转形状]
    U -->|空格| Y[立即掉落形状]
    V --> Z{检查碰撞}
    W --> Z
    X --> Z
    Y --> Z
    Z -->|有碰撞| AA[撤销操作]
    Z -->|无碰撞| E
    AA --> E
    end

代码

import tkinter as tk
import random

class Tetris:
    """Main class for Tetris game"""
    def __init__(self, master: tk.Tk, auto_restart: bool = False) -> None:
        """Initialize the game

        Args:
            master: tkinter root window
            auto_restart: whether to auto-restart after game over
        """
        self.master = master
        master.title("Tetris")
        self.auto_restart = auto_restart # Store the flag

        self.width = 300
        self.height = 600
        self.square_size = 30
        self.grid_width = self.width // self.square_size
        self.grid_height = self.height // self.square_size

        self.canvas = tk.Canvas(master, width=self.width, height=self.height, bg="black")
        self.canvas.pack()

        self.next_canvas = tk.Canvas(master, width=self.width, height=self.square_size * 4, bg="black")
        self.canvas.grid(row=1, column=0, sticky="nsew")
        self.next_canvas.grid(row=0, column=0, sticky="ew")
        self.master.grid_rowconfigure(1, weight=1)
        self.master.grid_columnconfigure(0, weight=1)

        self.grid = [[0 for _ in range(self.grid_width)] for _ in range(self.grid_height)]  # Avoid potential reference issues with list multiplication
        self.shapes = [  # Shapes are now tuples for immutability
            ((1, 1, 1, 1),),
            ((1, 0, 0), (1, 1, 1)),
            ((0, 0, 1), (1, 1, 1)),
            ((1, 1), (1, 1)),
            ((0, 1, 1), (1, 1, 0)),
            ((1, 1, 1), (0, 1, 0)),
            ((1, 1, 0), (0, 1, 1))
        ]
        self.colors = ["cyan", "blue", "orange", "yellow", "green", "purple", "red"]
        self.current_shape = None
        self.current_shape_color = None
        self.current_x = 0
        self.current_y = 0

        self.next_shape = None
        self.next_shape_color = None

        self.score = 0
        self.score_label = tk.Label(self.master, text="Score: 0", fg="white", bg="black", font=("Helvetica", 16))
        self.score_label.grid(row=2, column=0, sticky="ew")

        self.game_running = True
        self.new_shape()
        self.bind_keys()
        self.game_loop()

    def bind_keys(self) -> None:
        """Bind keyboard key events"""
        key_bindings = {
            "<Left>": lambda e: self.move_sideways(-1),
            "<Right>": lambda e: self.move_sideways(1),
            "<Down>": lambda e: self.move_down(),
            "<Up>": lambda e: self.rotate_shape(),
            "<space>": lambda e: self.drop_shape()
        }
        for key, func in key_bindings.items():
            self.master.bind(key, func)

    def new_shape(self) -> None:
        """Generate new current shape and next shape"""
        def get_random_shape() -> tuple[int, tuple[tuple[int, ...], ...], str]:
            """Get random shape info"""
            index = random.randint(0, len(self.shapes) - 1)
            return index, self.shapes[index], self.colors[index]

        # Generate current shape
        current_idx, current_shape, current_color = get_random_shape()
        self.current_shape = current_shape
        self.current_shape_color = current_color
        self.current_x = self.grid_width // 2 - len(current_shape[0]) // 2
        self.current_y = 0

        # Generate next shape
        next_idx, next_shape, next_color = get_random_shape()
        self.next_shape = next_shape
        self.next_shape_color = next_color

    def draw_shape(self) -> None:
        """Draw the currently moving shape"""
        for y, row in enumerate(self.current_shape):
            for x, cell in enumerate(row):
                if cell:
                    self._draw_square(
                        x=self.current_x + x,
                        y=self.current_y + y,
                        color=self.current_shape_color,
                        tags="grid_and_shape"
                    )

    def draw_next_shape(self):
        if self.next_shape is not None:
            next_shape = self.next_shape
            next_shape_color = self.next_shape_color
            next_canvas = self.next_canvas
            next_canvas.delete("all")

            # Calculate center offset
            max_width = 0
            for row in next_shape:
              max_width = max(max_width, len(row))

            offset_x = (self.width - max_width * self.square_size) // 2
            offset_y = (self.square_size * 4 - len(next_shape) * self.square_size) // 2  # Ensure integer division for correct centering

            for y, row in enumerate(next_shape):
                for x, cell in enumerate(row):
                    if cell:
                        x_pos = offset_x + x * self.square_size
                        y_pos = offset_y + y * self.square_size
                        next_canvas.create_rectangle(
                            x_pos, y_pos, x_pos + self.square_size, y_pos + self.square_size,
                            fill=next_shape_color, outline="black"
                        )

    def move_sideways(self, direction: int) -> None:
        """Move shape horizontally

        Args:
            direction: move direction (-1 left, 1 right)
        """
        self.current_x += direction
        if self.check_collision():
            self.current_x -= direction  # Revert if collision

    def move_down(self) -> None:
        """Move shape down, lock it and generate new shape if bottom reached"""
        self.current_y += 1
        if self.check_collision():
            self.current_y -= 1  # Revert
            self.place_shape()   # Lock current shape
            self.new_shape()     # Generate new shape
            if self.check_collision():  # Check game over
                self.game_over()

    def drop_shape(self) -> None:
        """Drop current shape to bottom instantly"""
        while not self.check_collision():
            self.current_y += 1
        self.current_y -= 1      # Revert to pre-collision position
        self.place_shape()       # Lock the shape
        self.new_shape()         # Generate new shape
        if self.check_collision():  # Check game over
            self.game_over()

    def rotate_shape(self) -> None:
        """Rotate current shape (except O-shape)"""
        # O-shape doesn't need rotation
        if self.current_shape == self.shapes[3]:
            return

        # Rotate shape (transpose then reverse each column)
        rotated_shape = [list(reversed(col)) for col in zip(*self.current_shape)]
        old_shape = self.current_shape
        self.current_shape = rotated_shape

        # Revert if collision after rotation
        if self.check_collision():
            self.current_shape = old_shape

    def check_collision(self) -> bool:
        """Check if current shape collides with boundaries or locked shapes

        Returns:
            bool: whether collision occurred
        """
        for y, row in enumerate(self.current_shape):
            for x, cell in enumerate(row):
                if cell:
                    grid_x = self.current_x + x
                    grid_y = self.current_y + y

                    # Check boundary collision
                    if (grid_x < 0 or
                        grid_x >= self.grid_width or
                        grid_y >= self.grid_height):
                        return True

                    # Check collision with locked shapes
                    if grid_y >= 0 and self.grid[grid_y][grid_x]:
                        return True
        return False

    def place_shape(self) -> None:
        """Lock current shape to grid and clear completed lines"""
        for y, row in enumerate(self.current_shape):
            for x, cell in enumerate(row):
                if cell:
                    self.grid[self.current_y + y][self.current_x + x] = self.current_shape_color
        self.clear_lines()  # Check and clear completed lines

    def clear_lines(self) -> None:
        """Clear filled lines and update score"""
        new_grid = []
        lines_cleared = 0

        # Filter unfilled lines
        for y in range(self.grid_height):
            if not all(self.grid[y]):
                new_grid.append(self.grid[y])
            else:
                lines_cleared += 1

        # Add empty lines at top
        while len(new_grid) < self.grid_height:
            new_grid.insert(0, [0] * self.grid_width)

        self.grid = new_grid

        # Update score
        if lines_cleared > 0:
            self.update_score(lines_cleared)

    def update_score(self, lines_cleared: int) -> None:
        """Update score

        Args:
            lines_cleared: number of lines cleared this time
        """
        score_table = {1: 100, 2: 300, 3: 500, 4: 800}
        self.score += score_table.get(lines_cleared, 0)
        self.score_label.config(text=f"Score: {self.score}")

    def game_over(self) -> None:
        """Handle game over logic"""
        if self.auto_restart:
            self.restart_game()
        else:
            self._show_game_over_text()
            self.game_running = False

    def _show_game_over_text(self) -> None:
        """Show game over text"""
        self.canvas.create_text(
            self.width // 2, self.height // 2,
            text="Game Over",
            fill="white",
            font=("Helvetica", 30),
            tags="game_over_text"
        )

    def restart_game(self):
        """Reset game state to start a new game"""
        # Clear possible "Game Over" text
        self.canvas.delete("game_over_text")
        # Reset the grid
        self.grid = [[0] * self.grid_width for _ in range(self.grid_height)]
        # Reset the score
        self.score = 0
        self.score_label.config(text=f"Score: {self.score}")
        # Reset the next shape (to avoid retaining old ones)
        self.next_shape = None
        self.next_shape_color = None
        # Generate a new shape
        self.new_shape()
        # Ensure game state is running
        self.game_running = True

    def draw_grid(self) -> None:
        """Draw locked shapes grid"""
        for y in range(self.grid_height):
            for x in range(self.grid_width):
                if self.grid[y][x]:
                    self._draw_square(
                        x=x,
                        y=y,
                        color=self.grid[y][x],
                        tags="grid_and_shape"
                    )

    def _draw_square(self, x: int, y: int, color: str, tags: str = "") -> None:
        """Draw single square

        Args:
            x: grid x coordinate
            y: grid y coordinate
            color: square color
            tags: canvas tags
        """
        x_pos = x * self.square_size
        y_pos = y * self.square_size
        self.canvas.create_rectangle(
            x_pos, y_pos,
            x_pos + self.square_size, y_pos + self.square_size,
            fill=color, outline="black", tags=tags
        )

    def game_loop(self) -> None:
        """Main game loop"""
        if not self.game_running:
            return

        self.canvas.delete("grid_and_shape")
        self.draw_next_shape()

        try:
            self.move_down()
        except Exception as e:
            print(f"Error in move_down: {e}")
            return

        self.draw_grid()
        self.draw_shape()

        # Adjust speed dynamically based on score
        base_speed = 500
        min_speed = 100
        speed_reduction = (self.score // 1000) * 50
        speed = max(min_speed, base_speed - speed_reduction)

        self.master.after(speed, self.game_loop)

if __name__ == "__main__":
    root = tk.Tk()
    tetris = Tetris(root)
    root.mainloop()

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注