Python Tkinter手搓俄罗斯方块

Python Tkinter手搓俄罗斯方块

简介

俄罗斯方块是一个经典的电子游戏,风靡全球。下面我们一步一步地使用Python和Tkinter库制作一个简易的俄罗斯方块游戏。
Python Tkinter手搓俄罗斯方块_第1张图片

整体代码放在最前面,无须任何第三方库

整体代码

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)

你可能感兴趣的:(小玩意,python,tkinter,游戏,俄罗斯方块)