20244217 2024-2025《Python程序设计》实验四报告

课程:《Python程序设计》
班级: 2442
姓名: 胡峻豪
学号:20244217
实验教师:王志强
实验日期:2025年5月13日
必修/选修: 公选课

1、实验内容
Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
例如:编写从社交网络爬取数据,实现可视化舆情监控或者情感分析。
例如:利用公开数据集,开展图像分类、恶意软件检测等
例如:利用Python库,基于OCR技术实现自动化提取图片中数据,并填入excel中。
例如:爬取天气数据,实现自动化微信提醒
例如:利用爬虫,实现自动化下载网站视频、文件等。
例如:编写小游戏:坦克大战、贪吃蛇、扫雷等等
注:在Windows/Linux系统上使用VIM、PDB、IDLE、Pycharm等工具编程实现。

2、实验过程
·根据回顾老师上课讲授内容,以及结合自身能力和兴趣爱好,选择利用PyCharm制作扫雷游戏。
实验过程如下:
(1)用import导入对应模块。如扫雷需要导入所需的tkinter,建立一个图形用户界面;导入tkinter.messagebox,建立一个带有应用专属消息、图标和按钮组的消息窗口;导入random函数,随机生成一个数字。

(2)定义函数get_image,它同时接受filename,宽度,高度,三个参数,利用前面导入的tkinter库来加载一个文件名为“filename”的图片
利用if函数调整图片大小,如果宽度和高度不等于None,利用.subsample来缩小图像,需要注意的是这里利用的是“//(整除)”因为.subsample只接受整数参数。
如果找不到图片或发生其他异常,则简单地回到None,表示图片加载失败或出现其他错误。

(3)配置相关参数,例如定义地图宽度,高度,地雷个数,地图相对于主窗口的起点,扫除计数,游戏状态,地图方块大小。
定义一个继承自tkinter.Button的Sweep类,表明这是一个与Tkinter GUI框架集成的自定义按钮类,特别设计用于实现类似扫雷游戏的功能。
地图 = [ ]:用于存储游戏地图信息的列表,初始化为空。
宽度 = 7,高度= 8:地图的宽度和高度分别为7和8
地雷个数 = 5:设置地图中的地雷数量为5个
x0 =30,y0 = 50:地图相对于主窗口的起始坐标,分别是30和50
扫除计数 = 0:记录玩家已翻开的方块数,初始化为0
游戏状态 = 0:游戏状态,初始化为0
地图方块大小= 40:地图中方块的大小,设为40像素
定义数字0~9显示时所使用的字体颜色,每个颜色由RGB(红绿蓝)三元组表示
定义方块状态颜色与背景图片,包括未翻开的方块“area.png”,已翻开的区域“opened.png”,标记的旗帜“flag.png”,标记的怀疑方块“doubt.png”,翻错导致爆炸的方块“boom.png”,已成功清除的区域“sweeped.png”,错误标记不含地雷的方块“mistake.png”等。

(4)def creat_map创建游戏地图的配置,如高度、宽度、地雷数量等,并加载背景图片

(5) def init_map(root),该函数用于初始化或重置扫雷游戏的地图界面

(6)def reset_map(self),实现游戏的“重置”功能,当玩家想要重新开始游戏时,调用这个方法可以清除所有已有的游戏进度和标记,使游戏回归到初始布局,准备开始新的一局。

(7)def init(self, 主窗口=None, 配置={}, **参数),为扫雷游戏中的方块设置基本属性、绑定交互事件、初始化状态,将其纳入游戏地图管理。

(8)def clicked(self,event)处理左键点击,打开方块或触发游戏结束;

def right_clicked(self,event)处理右键点击,切换方块的标记状态。支持无标记、标记为雷、质疑三种状态之间的转换;

def map_mine(self)计算并设置每个非雷方块周围的地雷数量;

def update_style(self)根据方块的状态,更新显示样式,包括背景色;

def auto_sweep(self)实现自动清扫功能,当玩家打开一个周围没有地雷的方块时,相应地打开周围的空白方块 ;

def gameover(self)用以处理游戏失败的情况;

def vectory(self)用以处理游戏获胜的情况。






(9) def del_menu(args)定义一个菜单栏,允许玩家选择重新开始或不同的游戏难度

(10)if name =='main' 当脚本直接运行时,启动Tkinter的主循环,展示游戏窗口等待用户交互。

3、实验结果

源代码如下`import tkinter
import tkinter.messagebox
import random

通过路径获取图片
def 获取图片(文件名, 宽=None, 高=None):
try:
# 加载图片
图片 = tkinter.PhotoImage(file=文件名)
if 宽 is not None and 高 is not None:
图片 = 图片.subsample(图片.width() // 宽, 图片.height() // 高)
return 图片
except tkinter.TclError:
# 未找到图片加载为空
print("加载图片失败: {文件名}")
return None

class 扫雷方块(tkinter.Button):
# 定义地图宽度,高度,地雷个数,地图相对于主窗口的起点,扫除计数,游戏状态,地图方块大小
地图宽度 = 7
地图高度 = 8
地雷数量 = 5
起始X坐标 = 30
起始Y坐标 = 50
已清除计数 = 0
游戏状态 = 0
方块大小 = 40

# 数字 0 ~ 9 的字体颜色 rgb
字体颜色 = [(255, 255, 255), (9, 147, 62), (0, 187, 187), (240, 78, 0), (166, 19, 188),
            (185, 122, 87), (136, 0, 21), (163, 73, 164), (0, 0, 0), (0, 0, 0)]
# 方块状态颜色与背景图片
背景颜色 = [(128, 128, 128), (255, 255, 255), (0, 255, 0), (255, 0, 0), (255, 200, 0), (0, 255, 0), (163, 73, 164)]
图片文件 = ['area.png', 'opened.png', 'flag.png', 'doubt.png', 'boom.png', 'sweeped.png', 'mistake.png']
图片列表 = []

@classmethod
def 创建地图(cls, 宽=地图宽度, 高=地图高度, 地雷数=地雷数量, x0=起始X坐标, y0=起始Y坐标,
             方块大小=方块大小, 字体颜色=字体颜色, 背景颜色=背景颜色, 图片文件=图片文件):
    # 加载地图信息
    cls.地图宽度 = 宽
    cls.地图高度 = 高
    cls.地雷数量 = 地雷数
    cls.起始X坐标 = x0
    cls.起始Y坐标 = y0
    cls.方块大小 = 方块大小
    cls.字体颜色 = 字体颜色
    cls.背景颜色 = 背景颜色
    cls.图片文件 = 图片文件
    cls.游戏状态 = 0
    cls.已清除计数 = 0
    # 加载背景图片
    if not cls.图片列表:
        for 文件名 in cls.图片文件:
            图片 = 获取图片(文件名, cls.方块大小, cls.方块大小)
            cls.图片列表.append(图片)

@classmethod
def 初始化地图(cls, 根窗口):
    窗口大小 = f'{cls.地图宽度 * cls.方块大小 + 80}x{cls.地图高度 * cls.方块大小 + 100}+400+80'
    根窗口.geometry(窗口大小)
    cls.游戏状态 = 0
    cls.已清除计数 = 0
    # 销毁旧按钮
    for 按钮 in cls.地图:
        按钮.destroy()
    cls.地图.clear()
    # 更新界面
    根窗口.update()
    # 随机放置地雷
    随机位置 = random.sample(range(0, cls.地图宽度 * cls.地图高度), cls.地雷数量)
    for i in range(cls.地图宽度 * cls.地图高度):
        行 = i // cls.地图宽度
        列 = i % cls.地图宽度
        # 9表示地雷,其他数字表示周围地雷数
        值 = 9 if i in 随机位置 else 0
        # 创建按钮
        按钮 = 扫雷方块(根窗口)
        按钮.place(x=列 * cls.方块大小 + cls.起始X坐标, y=行 * cls.方块大小 + cls.起始Y坐标,
                   width=cls.方块大小, height=cls.方块大小)
        按钮.设置位置(行=行, 列=列, 值=值)
    # 计算每个方块周围的地雷数
    for 方块 in cls.地图:
        方块.计算周围地雷数()
    根窗口.update()

@classmethod
def 重置地图(cls):
    cls.游戏状态 = 0
    for 方块 in cls.地图:
        方块.状态 = 0
        方块.更新样式()

def __init__(self, 主窗口=None, 配置={}, **参数):
    # 继承属性
    super().__init__(主窗口, 配置, **参数)
    self.文本 = self['text']
    self.命令 = self['command']
    self.bind('<Button-1>', self.左键点击)
    self.bind('<Button-3>', self.右键点击)
    self.行 = 0
    self.列 = 0
    self.值 = 0
    # 0:未打开 1:已打开 2:已标记 3:已质疑 4:爆炸 5:已扫除 6:标记错误
    self.状态 = 0
    self.更新样式()
    self.__class__.地图.append(self)

def 左键点击(self, 事件):
    # 判断点击时机
    if self.__class__.游戏状态 != 0:
        return
    if self.状态 == 1:
        return
    if self.值 == 9:
        self.游戏结束()
        return
    if self.值 == 0:
        self.自动扫除()
        self.检查胜利()
        return
    self.__class__.已清除计数 += 1
    self.状态 = 1
    # 更新样式
    self.更新样式()
    self.检查胜利()

# 处理右键点击
def 右键点击(self, 事件):
    # 判断该方块周围雷数
    if self.状态 == 1:
        return
    if self.状态 == 0:
        self.状态 = 2
    elif self.状态 == 2:
        self.状态 = 3
    elif self.状态 == 3:
        self.状态 = 0
    # 更新样式
    self.更新样式()

# 设置位置
def 设置位置(self, 行, 列, 值):
    self.行 = 行
    self.列 = 列
    self.值 = 值

# 计算周围地雷数
def 计算周围地雷数(self):
    if self.值 != 9:
        return
    # 获取周围单元格位置
    周围位置 = [(self.行 + i, self.列 + j) for i in range(-1, 2) for j in range(-1, 2) if i != 0 or j != 0]
    for r, c in 周围位置:
        for 方块 in self.__class__.地图:
            if 方块.行 == r and 方块.列 == c and 方块.值 != 9:
                方块.值 += 1

# 根据按钮自身的不同状态去显示按钮的样式
def 更新样式(self):
    文本 = ""
    if self.值 != 0 and self.值 != 9 and self.状态 == 1:
        文本 = str(self.值)
    # 转换字体颜色为十六进制
    字体十六进制颜色 = '#{:02x}{:02x}{:02x}'.format(self.__class__.字体颜色[self.值][0],
                                                    self.__class__.字体颜色[self.值][1],
                                                    self.__class__.字体颜色[self.值][2])
    # 转换背景颜色为十六进制
    背景十六进制颜色 = '#{:02x}{:02x}{:02x}'.format(self.__class__.背景颜色[self.状态][0],
                                                    self.__class__.背景颜色[self.状态][1],
                                                    self.__class__.背景颜色[self.状态][2])
    图片 = self.__class__.图片列表[self.状态]
    # 加载图片
    self.图片 = 图片
    if self.状态 == 2:
        文本 = "!"
        字体十六进制颜色 = "red"
    elif self.状态 == 3:
        文本 = "?"
        字体十六进制颜色 = "yellow"
    elif self.状态 == 6:
        文本 = "×"
        字体十六进制颜色 = "red"
    if 图片 is not None:
        背景十六进制颜色 = None
    # 加载配置
    self.configure(bg=背景十六进制颜色, fg=字体十六进制颜色, image=图片, text=文本, compound=tkinter.CENTER)

# 处理过关后自动清除页面
def 自动扫除(self):
    if self.状态 == 1:
        return
    self.状态 = 1
    # 更新样式
    self.更新样式()
    self.__class__.已清除计数 += 1
    if self.值 != 0:
        return
    # 获取周围单元格位置
    周围位置 = [(self.行 + i, self.列 + j) for i in range(-1, 2) for j in range(-1, 2) if i != 0 or j != 0]
    for r, c in 周围位置:
        for 方块 in self.__class__.地图:
            if 方块.行 == r and 方块.列 == c and 方块.值 != 9:
                方块.左键点击(None)

# 处理游戏结束
def 游戏结束(self):
    self.状态 = 4
    self.__class__.游戏状态 = 2
    # 显示所有地雷和错误标记
    for 方块 in self.__class__.地图:
        if 方块.值 == 9 and 方块.状态 != 2:
            方块.状态 = 4
            方块.更新样式()
        elif 方块.值 != 9 and 方块.状态 == 2:
            方块.状态 = 6
            方块.更新样式()
    # 显示失败页面
    结果 = tkinter.messagebox.showinfo(parent=self, title="游戏结束!", message="失败!")
    # 关闭页面
    if 结果 == "ok":
        根窗口.quit()

# 处理过关
def 检查胜利(self):
    if self.__class__.已清除计数 == (self.__class__.地图高度 * self.__class__.地图宽度 - self.__class__.地雷数量):
        if self.__class__.游戏状态 != 1:
            tkinter.messagebox.showinfo("游戏结束!", "恭喜过关!!!")
            self.__class__.游戏状态 = 1

存储所有方块的列表

扫雷方块.地图 = []

创建主窗口

根窗口 = tkinter.Tk()

处理难度选择的函数

def 处理菜单选择(难度):
# 根据选择生成对应的地图
if 难度 == "入门":
扫雷方块.创建地图(宽=6, 高=5, 地雷数=1)
elif 难度 == "简单":
扫雷方块.创建地图(宽=10, 高=10, 地雷数=15)
elif 难度 == "一般":
扫雷方块.创建地图(宽=16, 高=16, 地雷数=40)
elif 难度 == "困难":
扫雷方块.创建地图(宽=20, 高=16, 地雷数=60)
elif 难度 == "重新开始":
扫雷方块.重置地图()
# 创建地图
扫雷方块.初始化地图(根窗口)

if name == 'main':
根窗口.title('扫雷')
# 创建地图
扫雷方块.创建地图(宽=6, 高=5, 地雷数=3)
扫雷方块.初始化地图(根窗口)

# 创建菜单栏
菜单栏 = tkinter.Menu(根窗口)
根窗口.config(menu=菜单栏)

# 创建游戏菜单
游戏菜单 = tkinter.Menu(菜单栏, tearoff=False)
菜单栏.add_cascade(label="游戏", menu=游戏菜单)

# 添加难度选项
游戏菜单.add_command(label="入门", command=lambda: 处理菜单选择("入门"))
游戏菜单.add_command(label="简单", command=lambda: 处理菜单选择("简单"))
游戏菜单.add_command(label="一般", command=lambda: 处理菜单选择("一般"))
游戏菜单.add_command(label="困难", command=lambda: 处理菜单选择("困难"))
游戏菜单.add_command(label="重新开始", command=lambda: 处理菜单选择("重新开始"))

# 启动窗口
根窗口.mainloop()`

视频作业:

【20250601_214615】 https://www.bilibili.com/video/BV1T37kzoE7R/?share_source=copy_web&vd_source=eceab63c8cf307347641822f750a5143

4、实验中遇到的困难
(1)游戏代码设计难度过大,仅凭自己的知识储备难以操作
解决方案:首先复习老师课上所讲的一些知识点,然后询问了deepseek和观看了小红书等平台上的教学视频,逐渐试图去理解每一步代码编写的逻辑和整体程序的运行架构。
(2)在具体的一些设置上存在困难,如背景颜色的设定上,起初存在着无法识别和运用,导致扫雷操作界面为白底,游戏无法操作。
解决方案:在网上查找了相关资料,得知可以通过默认设置和未找到图片加载在为空来进行解决。

5、其他(感悟、思考等)
本次实验感悟:在本次实验的开始时内心是紧张和忐忑的,因为感觉自己所学的知识在运用上存在着不小的问题,觉得自己没有能力独立完成这项实验。在后期开始逐步进行实验时,果然发现了自己在过去对于老师讲解的知识的不理解,然后为了做好本次实验又回头再学习通上查阅老师的资料并且观看小红书和b站上的一些视频来辅助自己理解,逐渐就明白了知识上的漏洞。我将本次扫雷游戏的程序设计给模块化拆解,一个版块一个版块的攻克,从嵌入函数到配置参数,慢慢地就完成了整个实验。或许本次实验还存在着许多不完美的地方,但确实是我完成程序设计的一个开端。比如在编写这个程序的过程中我进一步加深并细化了之前所学到的一些小点。比如“!=”表示不等于,“//”表示整除,“str”表示字符串等等,进一步巩固了我最基础的python知识。这是python课程的最后一次实验,通过这次实验我进一步复习了前面所学的知识,加深了印象。一路走来尽管由于自己知识的浅薄而倍感艰辛,但是也是受益匪浅,让我这个文科学生也体会到了计算机编程的奇妙之处。

课程总结:Python课程让我系统学习了Python编程的核心知识与应用,涵盖基础语法、数据结构、函数与模块、面向对象编程、文件操作、异常处理及实战项目等内容。课程上老师通过理论讲解与案例实操结合,深入浅出地帮助我掌握Python编程思维与开发技能。从最先的基础语法与数据类型,让我学习了变量与运算符:掌握变量命名规则、数据类型(int/float/str/bool等)及算术、逻辑、比较运算符的使用;输入输出:使用 input() 获取用户输入,print() 格式化输出结果。还有数据结构与容器,列表、字典、集合、元组;函数与模块化编程;面向对象编程等等知识,让我对于Python基础的系统知识有了一个直观的浅薄的了解。其次除了理论学习之外,我还掌握了一些小程序的编写,如剪刀石头布,信息检索等,可以为我日后的学习工作提供必要的助力,也是学科交叉融合的一大优势所在。通过本次课程,我掌握了Python编程的核心语法与开发流程,从基础的数据处理到面向对象的项目设计,逐步建立了编程思维。Python的简洁性和灵活性让我感受到编程的乐趣,而实战项目则帮助我将理论转化为实践能力。未来需在算法优化、框架应用等方面持续深耕,不断提升解决复杂问题的能力。

课程建议:王老师总体讲解的节奏都把控的很好,知识的难度也有梯度,有简单到复杂。但是在后期的讲解中因为知识对于文科生的我们显得有些难度,在理解上还存在着比较大的问题,所以老师可以在今后的教学中加快前面知识的讲解,着重在后面例如socket等部分,更有助于学生的系统学习。王老师还可以在课前和课后发布一下本节课的学习提纲,便于同学们及时的预习和复习,更有利于上课教学。
最后再次感谢老师这一学期的辛苦付出!期待我在未来能够继续保持对于Python学习的热情,不断丰富自己的学识!

posted @ 2025-06-01 22:34  莲笑生  阅读(78)  评论(0)    收藏  举报