一、作品简介
这款基于Python的 tkinter 库开发的扫雷游戏,旨在重现经典扫雷游戏的玩法与乐趣。玩家需要在一个由方块组成的棋盘上,通过合理推测与点击操作,避开隐藏的地雷并揭示所有安全区域,以此来完成游戏挑战。它为喜欢逻辑推理和休闲益智游戏的玩家提供了一个方便在电脑上随时体验的选择,并且借助 tkinter 的图形界面功能,呈现出简洁直观的游戏界面。
二、技术架构
整体基于Python语言,利用 tkinter 库构建图形用户界面(GUI)来实现游戏的可视化呈现与交互操作。通过定义类和各种方法来组织游戏逻辑,包括地雷的放置、周围数字的计算、方块状态的管理以及输赢判断等功能,各个模块紧密协作,共同支撑起整个扫雷游戏的运行。
三、实现过程
- 初始化相关属性(
__init__方法):- 在游戏类
Minesweeper的构造函数中,接收游戏窗口对象root,以及棋盘的宽度width、高度height和地雷数量mines等参数(默认值分别为10、10、10),初始化游戏的各项核心属性,如棋盘状态board(二维列表,初始值为0代表安全区域,-1代表地雷)、方块是否已揭示revealed、是否插旗flags、游戏是否结束game_over等状态变量,同时创建用于存放按钮对象的二维列表buttons,并调用一系列方法来初始化游戏界面、放置地雷、计算数字以及创建游戏交互的按钮组件。
- 在游戏类
- 创建顶部面板(
create_top_panel方法):- 构建一个位于游戏窗口顶部的面板
top_panel,设置其背景色为lightgray,用于显示游戏相关信息和放置操作按钮。 - 在面板上添加一个标签
mine_label,用于显示剩余地雷数量,初始文本根据传入的地雷数量设置,字体为Arial、字号12,文本颜色跟随背景色为lightgray,使其在面板上自然融入。 - 创建一个“Reset”按钮
reset_button,按钮文本为“Reset”,字体同样为Arial、字号12,绑定reset_game方法,点击该按钮可重置游戏,按钮放置在面板右侧,与左侧的标签形成合理布局。
- 构建一个位于游戏窗口顶部的面板
- 放置地雷(
place_mines方法):- 通过循环,在棋盘范围内随机选择坐标位置
(x, y),只要该位置对应的board列表元素值不为 -1(即不是已经放置过地雷的位置),就在此位置放置地雷(将对应元素值设为 -1),直到放置的地雷数量达到预设的mines值,确保地雷分布随机且符合数量要求。
- 通过循环,在棋盘范围内随机选择坐标位置
- 计算数字(
calculate_numbers方法):- 遍历整个棋盘的每一个格子,对于非地雷的格子(值不为 -1),通过嵌套循环遍历其周围九宫格(通过
dy和dx在 [-1, 0, 1] 范围内变化来确定九宫格相对位置),统计周围地雷(值为 -1)的个数,将该个数赋值给当前格子对应的board元素,以此来标记每个安全格子周围的地雷数量,为后续玩家推理提供依据。
- 遍历整个棋盘的每一个格子,对于非地雷的格子(值不为 -1),通过嵌套循环遍历其周围九宫格(通过
- 创建游戏交互按钮(
create_widgets方法):- 再次嵌套循环遍历棋盘的行和列,针对每个坐标位置创建一个
tkinter的按钮对象button,设置按钮文本为空、宽度为2、高度为1、字体为加粗的Arial、字号12,并设置按钮样式为凸起(relief=tk.RAISED),使其呈现出方块的视觉效果。 - 为按钮的左键点击事件绑定
reveal方法,意味着玩家点击按钮时会执行该方法来揭示对应方块的内容;同时为右键点击事件绑定toggle_flag方法,用于实现插旗和取消插旗的操作,最后将按钮按照棋盘坐标布局放置在游戏窗口中,并将按钮对象存储到buttons二维列表中,方便后续操作和状态管理。
- 再次嵌套循环遍历棋盘的行和列,针对每个坐标位置创建一个
- 揭示方块内容(
reveal方法):- 首先判断游戏是否结束或者该方块是否已经插旗,如果满足这两个条件之一则直接返回,不做进一步操作。
- 若方块对应位置为地雷(
board[y][x] == -1),则将游戏状态设为结束,把对应按钮的文本设为“💣”(代表地雷的图标),背景色改为红色,样式设为凹陷(relief=tk.SUNKEN),同时调用show_all_mines方法显示所有地雷位置,并弹出游戏结束提示框告知玩家踩到地雷了。 - 若方块为安全区域,则将该方块的
revealed状态设为True,表示已揭示,更新按钮文本为对应位置的数字(通过board[y][x]获取),样式设为凹陷,背景色改为lightgray,然后根据数字大小设置不同的文本颜色(通过set_number_color方法),增强视觉辨识度。如果该方块周围没有地雷(数字为0),则递归地揭示其周围的未揭示方块,帮助玩家自动展开一大片安全区域。最后,每次揭示方块后调用check_win方法检查是否赢得游戏,如果是则弹出获胜提示框。
- 插旗操作(
toggle_flag方法):- 同样先判断游戏是否结束或者方块是否已揭示,如果满足则返回,不进行操作。
- 对该方块的插旗状态取反(
self.flags[y][x] = not self.flags[y][x]),如果插旗则将按钮文本设为“🚩”(代表旗帜的图标),文本颜色设为红色,并将剩余地雷数量减1;若取消插旗,则清空按钮文本,恢复文本颜色为黑色,同时剩余地雷数量加1,最后更新顶部面板上显示剩余地雷数量的标签文本,确保信息实时准确。
- 设置数字颜色(
set_number_color方法):- 根据方块对应的数字
number(从board[y][x]获取),按照不同的数字范围设置按钮文本的颜色,例如数字1对应的文本颜色设为蓝色、数字2设为绿色等,以此帮助玩家更直观地分辨不同危险程度的区域,辅助推理判断。
- 根据方块对应的数字
- 显示所有地雷(
show_all_mines方法):- 遍历整个棋盘,找到所有值为 -1(即地雷所在位置)的格子,将对应的按钮文本设为“💣”,背景色设为黄色,样式设为凹陷,用于在游戏结束时展示所有地雷的分布情况,方便玩家复盘。
- 检查游戏胜利(
check_win方法):- 遍历整个棋盘,只要存在不是地雷(
board[y][x]!= -1)且还未被揭示(not self.revealed[y][x])的方块,就返回False,表示游戏未胜利;只有当所有非地雷方块都被揭示时,才返回True,意味着玩家成功完成游戏挑战。
- 遍历整个棋盘,只要存在不是地雷(
- 重置游戏(
reset_game方法):- 遍历棋盘上的所有按钮,将按钮文本清空、背景色恢复为默认的
SystemButtonFace、样式设为凸起、文本颜色设为黑色,重置按钮的外观状态。 - 重新初始化棋盘
board、方块揭示状态revealed、插旗状态flags等核心数据结构,将游戏结束标志game_over设为False,剩余地雷数量恢复为初始传入的mines值,并更新顶部面板上的剩余地雷数量标签文本,然后再次调用place_mines和calculate_numbers方法,重新放置地雷并计算周围数字,完成游戏的重置操作,让玩家可以重新开始游戏。
- 遍历棋盘上的所有按钮,将按钮文本清空、背景色恢复为默认的
四、开发环境、开发流程
- 系统:代码基于Python语言编写,理论上可在支持Python运行的各种操作系统上运行,如Windows、Linux、macOS等,由于使用了
tkinter库创建图形界面,在不同系统上可能会呈现出略微不同的原生界面风格,但不影响游戏功能实现。 - 工具:开发过程中主要使用普通的文本编辑器(如Visual Studio Code、Sublime Text等)或者专业的Python集成开发环境(如PyCharm等)来编写代码,通过Python解释器来运行程序进行测试与调试。
- 插件(可选):在开发过程中可以根据需求安装一些辅助插件,例如在使用VS Code时,安装Python相关的代码格式化插件(如
black插件)确保代码风格统一美观;安装代码检查插件(如pylint)帮助发现潜在的代码规范问题等,提高代码质量和开发效率。
五、关键技术解析
- 二维列表的运用:通过使用二维列表(如
board、revealed、flags等)来表示棋盘的各种状态,方便对棋盘上每个格子的信息进行存储、查询和更新,例如board[y][x]可以直观地获取或设置棋盘上坐标为(x, y)的格子的属性(是地雷、周围地雷数量等),这种数据结构与扫雷游戏的二维棋盘布局天然契合,易于理解和操作。 - 事件绑定与交互逻辑:利用
tkinter按钮的事件绑定机制,为按钮的左键点击和右键点击分别绑定不同的方法(reveal和toggle_flag),实现了玩家操作与游戏逻辑的紧密连接,使得玩家在界面上的点击操作能够及时触发对应的游戏功能,如揭示方块内容、插旗操作等,构建起流畅的交互体验。 - 递归算法的应用(揭示空白区域):在
reveal方法中,当揭示的方块周围没有地雷(数字为0)时,通过递归调用reveal方法来自动揭示其周围的未揭示方块,形成连锁反应,帮助玩家快速展开一大片安全区域,这一递归算法的运用简化了用户操作,同时也符合扫雷游戏的常见玩法逻辑,提升了游戏的便利性和趣味性。 - 游戏状态管理与判断:通过多个状态变量(如
game_over、revealed、flags等)以及相应的判断逻辑(如check_win方法),细致地管理游戏的不同阶段和状态,例如判断游戏是否结束、玩家是否胜利等,确保游戏流程的准确性和完整性,根据不同状态来决定是否响应用户操作、展示相应提示信息等,让游戏能够按照预期规则正常运行。
六、使用说明
- 启动游戏后,会看到一个带有“Reset”按钮和显示剩余地雷数量标签的游戏窗口,窗口主体部分是由多个方块按钮组成的棋盘,初始时所有方块均为未揭示状态。
- 玩家可以使用鼠标左键点击方块来揭示其内容,若点击到地雷则游戏结束,会显示所有地雷位置并弹出提示框告知失败;若点击到安全区域,会显示该区域周围的地雷数量(数字),数字为0时会自动展开周围的安全区域。
- 玩家可使用鼠标右键在方块上插旗,插旗的方块会显示“🚩”图标,代表玩家标记此处为疑似地雷区域,同时顶部面板上的剩余地雷数量会相应减少,再次右键点击可取消插旗,图标消失,剩余地雷数量增加,通过合理插旗和揭示方块,尝试揭示所有非地雷区域来赢得游戏,若成功完成则会弹出获胜提示框,点击“Reset”按钮可重新开始游戏。
七、项目源码
import tkinter as tk
from tkinter import messagebox
import random
class Minesweeper:
def __init__(self, root, width=10, height=10, mines=10):
self.root = root
self.width = width
self.height = height
self.mines = mines
self.board = [[0 for _ in range(width)] for _ in range(height)]
self.revealed = [[False for _ in range(width)] for _ in range(height)]
self.flags = [[False for _ in range(width)] for _ in range(height)]
self.game_over = False
self.buttons = [[None for _ in range(width)] for _ in range(height)]
self.remaining_mines = mines
self.create_top_panel()
self.place_mines()
self.calculate_numbers()
self.create_widgets()
def create_top_panel(self):
self.top_panel = tk.Frame(self.root, bg="lightgray")
self.top_panel.grid(row=0, column=0, columnspan=self.width, sticky="ew")
self.mine_label = tk.Label(self.top_panel, text=f"Mines: {self.remaining_mines}", font=("Arial", 12), bg="lightgray")
self.mine_label.pack(side="left", padx=10)
self.reset_button = tk.Button(self.top_panel, text="Reset", font=("Arial", 12), command=self.reset_game)
self.reset_button.pack(side="right", padx=10)
def place_mines(self):
mines_placed = 0
while mines_placed < self.mines:
x = random.randint(0, self.width - 1)
y = random.randint(0, self.height - 1)
if self.board[y][x] != -1:
self.board[y][x] = -1
mines_placed += 1
def calculate_numbers(self):
for y in range(self.height):
for x in range(self.width):
if self.board[y][x] == -1:
continue
count = 0
for dy in [-1, 0, 1]:
for dx in [-1, 0, 1]:
if dy == 0 and dx == 0:
continue
ny, nx = y + dy, x + dx
if 0 <= ny < self.height and 0 <= nx < self.width and self.board[ny][nx] == -1:
count += 1
self.board[y][x] = count
def create_widgets(self):
for y in range(self.height):
for x in range(self.width):
button = tk.Button(
self.root,
text="",
width=2,
height=1,
font=("Arial", 12, "bold"),
relief=tk.RAISED,
command=lambda x=x, y=y: self.reveal(x, y),
)
button.bind("<Button-3>", lambda event, x=x, y=y: self.toggle_flag(x, y))
button.grid(row=y + 1, column=x, padx=1, pady=1)
self.buttons[y][x] = button
def reveal(self, x, y):
if self.game_over or self.flags[y][x]:
return
if self.board[y][x] == -1:
self.game_over = True
self.buttons[y][x].config(text="💣", bg="red", relief=tk.SUNKEN)
self.show_all_mines()
messagebox.showinfo("Game Over", "You hit a mine! Game over.")
return
self.revealed[y][x] = True
self.buttons[y][x].config(text=str(self.board[y][x]), relief=tk.SUNKEN, bg="lightgray")
self.set_number_color(x, y)
if self.board[y][x] == 0:
for dy in [-1, 0, 1]:
for dx in [-1, 0, 1]:
if 0 <= y + dy < self.height and 0 <= x + dx < self.width:
if not self.revealed[y + dy][x + dx]:
self.reveal(x + dx, y + dy)
if self.check_win():
messagebox.showinfo("Congratulations!", "You won!")
def toggle_flag(self, x, y):
if self.game_over or self.revealed[y][x]:
return
self.flags[y][x] = not self.flags[y][x]
if self.flags[y][x]:
self.buttons[y][x].config(text="🚩", fg="red")
self.remaining_mines -= 1
else:
self.buttons[y][x].config(text="", fg="black")
self.remaining_mines += 1
self.mine_label.config(text=f"Mines: {self.remaining_mines}")
def set_number_color(self, x, y):
number = self.board[y][x]
if number == 1:
self.buttons[y][x].config(fg="blue")
elif number == 2:
self.buttons[y][x].config(fg="green")
elif number == 3:
self.buttons[y][x].config(fg="red")
elif number == 4:
self.buttons[y][x].config(fg="purple")
elif number >= 5:
self.buttons[y][x].config(fg="orange")
def show_all_mines(self):
for y in range(self.height):
for x in range(self.width):
if self.board[y][x] == -1:
self.buttons[y][x].config(text="💣", bg="yellow", relief=tk.SUNKEN)
def check_win(self):
for y in range(self.height):
for x in range(self.width):
if self.board[y][x] != -1 and not self.revealed[y][x]:
return False
return True
def reset_game(self):
for y in range(self.height):
for x in range(self.width):
self.buttons[y][x].config(text="", bg="SystemButtonFace", relief=tk.RAISED, fg="black")
self.board = [[0 for _ in range(self.width)] for _ in range(self.height)]
self.revealed = [[False for _ in range(self.width)] for _ in range(self.height)]
self.flags = [[False for _ in range(self.width)] for _ in range(self.height)]
self.game_over = False
self.remaining_mines = self.mines
self.mine_label.config(text=f"Mines: {self.remaining_mines}")
self.place_mines()
self.calculate_numbers()
def main():
root = tk.Tk()
root.title("Minesweeper")
game = Minesweeper(root)
root.mainloop()
if __name__ == "__main__":
main()
八、效果展示
- 写一个的扫雷游戏,使用Python的Tkinter库来创建图形用户界面
- 对页面进行优化
游戏运行后,呈现出一个具有经典扫雷风格的界面,棋盘上的方块按钮布局整齐,随着玩家操作,按钮的外观(文本、颜色、样式等)会根据游戏状态实时变化,如揭示方块时显示数字、插旗时出现旗帜图标、踩到地雷时变红等,直观地展示游戏进展,给玩家带来熟悉且直观的游戏体验。

九、可体现价值
- 休闲娱乐价值:为玩家提供了一个简单易上手、趣味性强的休闲游戏选择,无论是在工作间隙还是闲暇时光,都可以通过玩几局扫雷来放松身心、消磨时间,锻炼逻辑思维能力。
- 逻辑思维锻炼:游戏过程中,玩家需要根据已揭示方块的数字信息,合理推测周围未揭示方块是否为地雷,通过不断分析、推理和决策来推进游戏,有助于提高玩家的逻辑推理、空间判断和观察能力。
- 学习与实践意义:对于Python初学者以及想要学习
tkinter图形界面编程的开发者来说,该代码是一个很好的学习案例,通过阅读和理解代码结构、逻辑以及功能实现,可以学习到类的定义与使用、图形界面组件的创建与交互、游戏逻辑的设计等多方面知识,并能在此基础上进行扩展和改进,实践自己的编程想法。

252

被折叠的 条评论
为什么被折叠?



