当前位置: 首页 > 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.rkmt.cn/news/1383000.html

相关文章:

  • Python 语法糖详解:让代码简洁优雅的编程小技巧
  • 搜维尔科技:“2026第五届中国力触觉技术及应用会议”将于2026年5月22-24日在京举办,我司携设备参展!
  • BetterJoy:让Switch手柄在Windows上重获新生的终极解决方案
  • 操作符从浅入深的讲解
  • NBTExplorer:让Minecraft数据编辑从专业工具变成人人可用的可视化平台
  • 告别多头对接!DMXAPI 为企业打造国产大模型 “统一入口”
  • 输电线路在线监测系统|架空线路安全运行的“第一道防线“!
  • WPF控件颜色集合
  • 牛客周赛Round145
  • 如何在Windows 11上免费安装安卓子系统:完整简易指南
  • 无穿戴自主定位,规避矿场人员管控各类风险
  • 5分钟掌握OBS多平台直播:obs-multi-rtmp插件一键配置终极指南
  • 我用DMXAPI同时调用DeepSeek和Kimi,做了一个能处理长文档的问答工具
  • 小龙虾OpenClaw 全方位实战指南:下载、安装、配置豆包 API Key 与高阶使用技巧
  • 【Claude实战】使用 GitHub CLI (gh) 汇总 GitHub 仓库
  • 引力波透镜检测:非高斯后验下的统计推断挑战与应对
  • ESXi 8.0 运维实战:从硬件RAID卡驱动更新到NTP时间同步,一篇搞定日常管理
  • Bannerlord联机技术指南:主机托管架构下的硬核调优五步法
  • 终极惠普OMEN游戏本性能优化指南:免费开源工具OmenSuperHub完整使用教程
  • 告别卡顿!用Nginx+图新地球+CesiumLab搭建本地离线地图服务(附完整配置代码)
  • Nginx CORS配置陷阱:Origin反射与Credentials滥用风险解析
  • 摄影后期神器!DxO PhotoLab
  • Taotoken助力初创团队以可控成本快速集成AI能力到产品中
  • 【C++】零基础入门 · 第 3 节:条件判断(if、switch)
  • 借助Taotoken多模型能力为产品设计动态的AI功能模块
  • Hermes Agent工具连接Taotoken多模型服务的配置指引
  • 基于Atmega32U4的可穿戴LED控制器设计:从电源管理到PCB布局
  • UE:如何让 AI 直接修改 DataAsset
  • 保姆级教程:在Ubuntu 22.04上搞定NVIDIA驱动、Anaconda和CUDA 12.4(含常见报错解决)
  • 3步快速上手:TigerVNC实现跨平台远程桌面控制的完整指南