当前位置: 首页 > news >正文

20244321 2025-2026-2 《Python程序设计》实验四报告

20244321 2025-2026-2 《Python程序设计》实验四报告
课程名称: Python程序设计
实验项目: 综合实践——扫雷游戏的设计与实现
姓 名: 李梓睿
学 号: 20244321
班 级: 2443
实验日期: 2026年5月12日
一、 实验分析
扫雷游戏的核心玩法是在一个由方格组成的矩形区域(雷区)中,通过逻辑推理找出所有不包含地雷的方格。其核心机制可分解为以下几点:

  1. 棋盘与地雷: 游戏在一个 M x N 的网格上进行,其中随机分布着 K 个地雷。
  2. 方格状态: 每个方格有三种状态:未揭开、已揭开、标记(插旗)。
  3. 数字提示: 当一个非地雷方格被揭开时,会显示其周围8个相邻方格中地雷的总数。这个数字是玩家进行逻辑判断的唯一依据。
  4. 空白格扩散: 如果揭开的方格周围没有地雷(即数字为0),则游戏会自动递归地揭开其周围所有相邻的方格,直到遇到有数字提示的边界为止。这一机制是提升游戏体验的关键。
  5. 胜利与失败:
    失败: 玩家左键点击了包含地雷的方格。
    胜利: 玩家成功揭开了所有不包含地雷的方格。

二、 实验设计

  1. 技术选型
    本次项目选用 Python 语言进行开发。在图形界面库的选择上,考虑到项目的简洁性和课程要求,决定使用 Python 自带的标准库 tkinter。tkinter 库无需额外安装,跨平台兼容性好,且其事件驱动模型非常适合开发此类交互式小游戏。
  2. 程序结构规划
    整个程序采用面向对象(OOP)的设计思想,将游戏的所有逻辑和界面元素封装在一个 Minesweeper 类中。这样做可以使代码结构清晰,便于维护和扩展。程序主要分为以下几个模块:
    ①初始化模块: 负责设置游戏窗口、定义游戏参数(行数、列数、地雷数)、初始化数据结构(如地雷位置、按钮网格等)。
    ②界面构建模块: 负责创建游戏主界面,包括顶部的信息栏(显示剩余雷数、计时器)和主体部分的按钮网格。
    ③游戏逻辑模块:
    地雷生成: 在游戏开始时,随机在网格中布置指定数量的地雷。
    点击事件处理: 分别处理鼠标左键(揭开)和右键(标记)的点击事件。
    核心算法: 实现计算周围地雷数量的算法和空白格自动扩散的递归算法(Flood Fill)。
    胜负判定: 实时检查游戏状态,判断玩家是否胜利或失败。
    ④游戏控制模块: 提供重新开始游戏、选择难度等功能。
  3. 核心数据结构设计
    self.mine_positions (集合 set): 用于存储所有地雷的坐标 (r, c)。使用集合是因为其查找效率为 O(1),可以快速判断某个坐标是否有雷。
    self.buttons (字典 dict): 键为方格的坐标 (r, c),值为对应的 tk.Button 对象。通过坐标可以快速访问和操作界面上的任意一个按钮。

三、 实现过程

  1. 游戏初始化与界面搭建
    首先,导入 tkinter 和 random 库。在 Minesweeper 类的 init 方法中,初始化主窗口和各项游戏参数。接着,创建顶部的菜单栏和信息栏,用于显示游戏状态和控制游戏流程。最后,通过一个嵌套循环,动态生成 M x N 个 tk.Button 按钮,并将它们放置在网格布局中,同时为每个按钮绑定左键和右键的点击事件。
  2. 核心游戏逻辑实现
    地雷生成 (start_game 方法): 游戏开始时,程序会生成一个包含所有方格坐标的列表,然后使用 random.sample() 函数从中随机抽取 K 个坐标作为地雷位置,并存入 self.mine_positions 集合中。这种方式能有效避免地雷位置的重复。
    左键点击处理 (on_left_click 方法): 当玩家左键点击一个方格时,首先判断游戏是否结束以及该方格是否已被揭开。然后,检查该方格坐标是否在 self.mine_positions 中。如果是,则调用 game_over(False) 结束游戏;如果不是,则调用 reveal_cell(r, c) 揭开该方格。
    右键点击处理 (on_right_click 方法): 当玩家右键点击时,会在该方格上放置或移除一个旗帜标记(通过修改按钮的 text 属性实现),并同步更新顶部信息栏显示的剩余雷数。
    方格揭开与扩散 (reveal_cell 方法): 这是游戏最核心的算法。当一个非雷方格被揭开时,程序会调用 count_adjacent_mines(r, c) 计算其周围的地雷数量。
    如果数量大于0,则直接在按钮上显示该数字。
    如果数量为0,则触发“扩散”效果。程序会递归地调用自身,去揭开当前方格周围所有8个相邻的方格。这个递归过程会一直持续,直到遇到周围有地雷的方格为止,形成一个“空白区域”的自动展开。
    胜负判定 (check_win 方法): 每次成功揭开一个方格后,程序都会检查当前已揭开的方格总数是否等于 总方格数 - 地雷总数。如果相等,说明所有安全区域都已被找出,玩家获胜,调用 game_over(True)。
  3. 用户体验优化
    计时功能: 游戏从玩家第一次点击后开始计时,通过 root.after(1000, ...) 方法实现每秒更新一次界面上的时间显示。
    难度选择: 通过顶部菜单,玩家可以轻松选择“简单”、“中等”、“困难”三种预设难度,程序会根据选择动态调整棋盘大小和地雷数量。
    视觉反馈: 使用不同的颜色区分不同的数字,用凹陷效果表示已揭开的方格,用旗帜和炸弹的emoji图标增强视觉效果,使游戏界面更加直观友好。

四、 实验结果
程序成功运行,实现了扫雷游戏的所有预期功能。
功能完整性: 游戏可以正常开始、重置,支持三种难度级别。左键揭开、右键插旗功能正常,空白区域能够正确自动扩散。
逻辑正确性: 地雷随机生成,数字提示准确无误。胜利和失败的判定逻辑正确,游戏结束后能正确显示所有地雷或弹出胜利提示。
界面友好性: 界面布局整洁,操作响应流畅。计时器和剩余雷数显示准确,为玩家提供了良好的游戏体验。

程序运行截图:
简单难度:

图片1

图片2

中等难度:

图片3

困难难度:

图片4

五、 全课总结与感想体会
回顾整个《Python程序设计》课程的学习历程,从最初对语法的懵懂,到如今能够独立完成一个综合性项目,我深感收获颇丰。课程的安排循序渐进,理论与实践紧密结合,尤其是四次实验,一步步让我对Python编程的认识引向深入。
猜数字游戏: 这是我的第一次实验,它让我初步理解了变量、循环和条件判断这些编程的基本要素,感受到了与计算机进行逻辑交互的乐趣。
计算器设计: 这次实验让我接触到了函数的入门知识。将一个个算法与背后的计算逻辑联系起来,让我体会到了事件驱动编程的魅力。
Socket编程技术: 这次实验为我打开了网络编程的大门。虽然过程颇具挑战,但当我看到两个程序能够通过网络成功通信时,我对计算机世界的连接方式有了更深刻的认识。
扫雷游戏(综合实践): 这是对所有知识的集大成者。它不仅考验了我对 tkinter 库的熟练程度,更挑战了我的算法设计能力和逻辑思维。特别是“空白格扩散”的递归算法,从理解到实现,再到调试成功,整个过程让我对递归这一强大的编程思想有了切身的体会。
课程感想与体会:
这门课程带给我的,远不止是学会了一门编程语言。更重要的是,它培养了我的“计算思维”。我学会了如何将一个复杂的问题分解成一个个可执行的小步骤,如何用严谨的逻辑去构建解决方案,以及如何通过不断的调试和优化来完善我的作品。编程不再是枯燥的代码堆砌,而是一个充满创造和解决问题乐趣的过程。当看到自己编写的程序从一行行代码变成一个可以交互、可以“玩”的游戏时,那种成就感是无与伦比的。
意见与建议:
课程内容充实,结构合理。如果提一点建议的话,希望未来可以增加一些关于代码规范和性能优化的讲解,例如如何编写更易读、更易维护的代码,以及如何处理更大规模的数据。
总而言之,这是一段非常宝贵的学习经历。感谢老师的悉心指导,让我不仅掌握了Python这一强大的工具,更点燃了我对编程世界的热情。我将带着这份收获,在未来的学习和探索中继续前行。

程序运行视频:
[](录制:Welcome – game.py
日期:2026-05-25 19:04:58
录制文件:https://meeting.tencent.com/crm/2BYaAvpbce)

[](录制:Welcome – game.py
日期:2026-05-25 19:29:29
录制文件:https://meeting.tencent.com/crm/2BYa8Mwk4e)

代码如下:
import tkinter as tk
from tkinter import messagebox
import random

--- 常量定义 ---

难度设置 (行数, 列数, 雷数)

LEVELS = {
"简单": (9, 9, 10),
"中等": (16, 16, 40),
"困难": (16, 30, 99)
}

COLORS = {
1: 'blue', 2: 'green', 3: 'red', 4: 'purple',
5: 'maroon', 6: 'turquoise', 7: 'black', 8: 'gray'
}

class Minesweeper:
def init(self, root):
self.root = root
self.root.title("Python 扫雷")

游戏状态变量

self.rows = 0
self.cols = 0
self.mines = 0
self.buttons = {} # 存储按钮对象 {(r, c): button}
self.mine_positions = set() # 地雷坐标
self.is_game_over = False
self.flags = 0
self.start_time = 0
self.timer_job = None

顶部菜单栏

self.create_menu()

顶部信息栏 (雷数, 时间)

self.info_frame = tk.Frame(self.root)
self.info_frame.pack(pady=5)

self.lbl_mines = tk.Label(self.info_frame, text="雷数: 0", font=("Arial", 12, "bold"))
self.lbl_mines.pack(side=tk.LEFT, padx=10)

self.lbl_time = tk.Label(self.info_frame, text="时间: 0", font=("Arial", 12, "bold"))
self.lbl_time.pack(side=tk.LEFT, padx=10)

游戏区域框架

self.game_frame = tk.Frame(self.root)
self.game_frame.pack(padx=10, pady=10)

开始默认游戏

self.start_game("简单")

def create_menu(self):
menubar = tk.Menu(self.root)
level_menu = tk.Menu(menubar, tearoff=0)
for level in LEVELS:
level_menu.add_command(label=level, command=lambda l=level: self.start_game(l))
menubar.add_cascade(label="难度", menu=level_menu)
menubar.add_command(label="重置游戏", command=lambda: self.start_game(self.get_current_level()))
self.root.config(menu=menubar)

def get_current_level(self):
# 简单获取当前菜单选中的难度,这里为了简化,默认返回"简单"或根据实际逻辑修改
# 实际应用中可以用 StringVar 追踪菜单状态
return "简单"

def start_game(self, level_name):
# 重置状态
self.is_game_over = False
self.flags = 0
self.start_time = 0
if self.timer_job:
self.root.after_cancel(self.timer_job)

self.rows, self.cols, self.mines = LEVELS[level_name]
self.lbl_mines.config(text=f"雷数: {self.mines}")
self.lbl_time.config(text="时间: 0")

清理旧界面

for widget in self.game_frame.winfo_children():
widget.destroy()
self.buttons.clear()
self.mine_positions.clear()

生成地雷

all_positions = [(r, c) for r in range(self.rows) for c in range(self.cols)]
self.mine_positions = set(random.sample(all_positions, self.mines))

创建按钮网格

for r in range(self.rows):
for c in range(self.cols):
btn = tk.Button(
self.game_frame,
width=2,
height=1,
font=("Arial", 12, "bold"),
bg="#dddddd",
relief=tk.RAISED
)
# 绑定左键点击 (揭开)
btn.bind('', lambda event, row=r, col=c: self.on_left_click(row, col))
# 绑定右键点击 (插旗)
btn.bind('', lambda event, row=r, col=c: self.on_right_click(row, col))

btn.grid(row=r, column=c)
self.buttons[(r, c)] = btn

def update_timer(self):
if not self.is_game_over:
self.start_time += 1
self.lbl_time.config(text=f"时间: {self.start_time}")
self.timer_job = self.root.after(1000, self.update_timer)

def count_adjacent_mines(self, r, c):
count = 0
for i in range(-1, 2):
for j in range(-1, 2):
nr, nc = r + i, c + j
if (0 <= nr < self.rows and 0 <= nc < self.cols and
(nr, nc) in self.mine_positions):
count += 1
return count

def on_left_click(self, r, c):
if self.is_game_over or self.buttons[(r, c)]['state'] == tk.DISABLED:
return

第一次点击才开始计时

if self.start_time == 0:
self.update_timer()

if (r, c) in self.mine_positions:
self.game_over(win=False)
else:
self.reveal_cell(r, c)
self.check_win()

def on_right_click(self, r, c):
if self.is_game_over or self.buttons[(r, c)]['state'] == tk.DISABLED:
return

btn = self.buttons[(r, c)]
if btn['text'] == "":
btn.config(text="🚩", fg="red")
self.flags += 1
elif btn['text'] == "🚩":
btn.config(text="")
self.flags -= 1

self.lbl_mines.config(text=f"雷数: {self.mines - self.flags}")

def reveal_cell(self, r, c):
if not (0 <= r < self.rows and 0 <= c < self.cols):
return

btn = self.buttons[(r, c)]
if btn['state'] == tk.DISABLED:
return

标记为已揭开 (禁用按钮)

btn.config(state=tk.DISABLED, relief=tk.SUNKEN, bg="#eeeeee")

计算周围雷数

count = self.count_adjacent_mines(r, c)

if count > 0:
btn.config(text=str(count), fg=COLORS.get(count, 'black'))
else:
# 如果是0,递归揭开周围格子 (Flood Fill 算法)
for i in range(-1, 2):
for j in range(-1, 2):
if i == 0 and j == 0: continue
self.reveal_cell(r + i, c + j)

def check_win(self):
# 检查是否所有非雷格子都被揭开了
revealed_count = 0
for r in range(self.rows):
for c in range(self.cols):
if self.buttons[(r, c)]['state'] == tk.DISABLED:
revealed_count += 1

total_cells = self.rows * self.cols
if revealed_count == total_cells - self.mines:
self.game_over(win=True)

def game_over(self, win):
self.is_game_over = True
if self.timer_job:
self.root.after_cancel(self.timer_job)

if win:
messagebox.showinfo("胜利", f"恭喜你赢了!用时: {self.start_time}秒")
else:
# 输了显示所有地雷
for r, c in self.mine_positions:
self.buttons[(r, c)].config(text="💣", bg="red")
messagebox.showerror("失败", "游戏结束,你踩到地雷了!")

if name == "main":
root = tk.Tk()
game = Minesweeper(root)
root.mainloop()

http://www.jsqmd.com/news/885501/

相关文章:

  • 英文初稿查AI率、降ai率,这几个宝藏网站直接搞定! - 殷念写论文
  • TII投稿避坑实录:从LaTeX编译报错到作者照片命名,我踩过的那些雷
  • Nginx CORS配置陷阱:Origin反射与Credentials滥用风险解析
  • 3步快速上手:TigerVNC实现跨平台远程桌面控制的完整指南
  • FinceptTerminal 深度拆解:23k Star 的开源金融终端,到底做对了什么?
  • 我仓库内cad python 有哪些应用到聚类的方法
  • Bedrock Prompt Optimization 进阶版:5 个模型同时对比,一条 Prompt 自动调到能打
  • 超声波液位计厂家排行榜:2026年国产十大品牌深度评测与选型指南 - 仪表品牌榜
  • Simulink模型测试踩坑实录:Test Manager里那些容易忽略的配置项(比如Comparison勾选)
  • 用python进行简单计算
  • 系统单一时区场景下的时间类型传输设计方案(固定时区:东八区)
  • 决战破晓手游官网下载:决战破晓最新官方下载渠道
  • 基于Arduino的MPPT太阳能充电控制器:从Buck电路到算法实现全解析
  • Product Hunt 每日热榜 | 2026-05-24
  • Recuva真的能恢复被‘文件粉碎’的数据吗?实测腾讯管家、火绒删除后的恢复效果
  • WPF控件颜色集合
  • 我用DMXAPI同时调用DeepSeek和Kimi,做了一个能处理长文档的问答工具
  • 牛客周赛Round145
  • taotoken token plan套餐在实际开发中的成本节省感受
  • 主流源代码管理工具介绍
  • 如何在Windows 11上免费安装安卓子系统:完整简易指南
  • 为学术研究项目构建可复现且成本可控的大模型实验平台
  • NS-USBLoader终极指南:一站式Switch文件传输与RCM注入解决方案
  • 从XP盗版泛滥到Win11强制联网:聊聊微软这二十年是怎么用KMS等机制‘围剿’盗版的
  • 一份来自 Karpathy 的 AI 编程 skill
  • 文档地狱求生指南:从“缺失、过时、晦涩”到“清晰、准确、可用”的技术文档治理实战
  • 小龙虾OpenClaw 全方位实战指南:下载、安装、配置豆包 API Key 与高阶使用技巧
  • 从零开始:Icarus Verilog 开源硬件仿真器完全指南 [特殊字符]
  • 基于FakeAVCeleb数据集的多模态深度伪造检测系统开发:从数据预处理到模型部署的完整指南FakeAVCeleb音频视频多模态数据集的训练和测试
  • 短视频矩阵系统的技术演进:当AI Agent重新定义全域内容运营