93
社区成员




课程:《Python程序设计》
班级: 2342
姓名: 李天心
学号:20234225
实验教师:王志强
实验日期:2024年5月14日
必修/选修: 专选课
一、实验内容
Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
课代表和各小组负责人收集作业(源代码、视频、综合实践报告)
例如:编写从社交网络爬取数据,实现可视化舆情监控或者情感分析。
例如:利用公开数据集,开展图像分类、恶意软件检测等
例如:利用Python库,基于OCR技术实现自动化提取图片中数据,并填入excel中。
例如:爬取天气数据,实现自动化微信提醒
例如:利用爬虫,实现自动化下载网站视频、文件等。
例如:编写小游戏:坦克大战、贪吃蛇、扫雷等等
注:在Windows/Linux系统上使用VIM、PDB、IDLE、Pycharm等工具编程实现。
二、实验过程及结果
导入所需的tkinter,建立一个图形用户界面
导入tkinter.messagebox,建立一个带有应用专属消息、图标和按钮组的消息窗口。
导入random函数,随机生成一个数字
定义函数get_image,它同时接受filename,w(宽度),h(高度),三个参数,利用前面导入的tkinter库来加载一个文件名为“filename”的图片
利用if函数调整图片大小,如果w和h不等于None,利用.subsample来缩小图像,需要注意的是这里利用的是"//(整除)"因为.subsample只接受整数参数。
如果找不到图片或发生其他异常,则简单地回到None,表示图片加载失败或出现其他错误
tkinter.Button用于实现各种各样的按钮
定义一个继承自tkinter.Button的Sweep类,表明这是一个与Tkinter GUI框架集成的自定义按钮类,特别设计用于实现类似扫雷游戏的功能。
继续进行详细定义
map = [ ]:用于存储游戏地图信息的列表,初始化为空。
w = 7,h = 8:地图的宽度和高度分别为7和8
mine = 5:设置地图中的地雷数量为5个
x0 =30,y0 = 50:地图相对于主窗口的起始坐标,分别是30和50
count = 0:记录玩家已翻开的方块数,初始化为0
state = 0:游戏状态,初始化为0
mine_size = 40:地图中方块的大小,设为40像素
定义数字0~9显示时所使用的字体颜色,每个颜色由RGB(红绿蓝)三元组表示
定义方块状态颜色与背景图片,包括未翻开的方块“area.png”,已翻开的区域“opened.png”,标记的旗帜“flag.png",标记的怀疑方块”doubt.png",翻错导致爆炸的方块“boom.png",已成功清除的区域”sweeped.png",错误标记不含地雷的方块“mistake.png"等。
图片对象列表pic = [ ],初始化为空,预期用于存储从上述图片文件名加载得到的实际图片对象,便于后续在GUI中显示。
def creat_map初始化游戏地图的配置,如高度、宽度、地雷数量等,并加载背景图片
if not Sweep.pic 调用一个条件函数,用以检查Sweep的静态变量pic是否为空然后按需加载游戏所需的所有图片资源。
调用一个假设存在的函数get_image(filename,width,height)此处的width和height都是通过Sweep.mine_size获得,表示方块的尺寸。函数get_image应当负责根据给定的文件名和尺寸加载图片,并返回图片对象。加载的图片对象随后被追加到Sweep.pic列表中。
def init_map(root),该函数用于初始化或重置扫雷游戏的地图界面
def reset_map(self)实现游戏的“重置”功能,当玩家想要重新开始游戏时,调用这个方法可以清除所有已有的游戏进度和标记,使游戏回归到初始布局,准备开始新的一局。
def__init__(self,master = None,cnf = { },**kw)为扫雷游戏中的方块设置基本属性、绑定交互事件、初始化状态,并将其纳入游戏地图管理,为实现扫雷游戏的逻辑和界面交互打下基础。
def clicked(self,event)处理左键点击,打开方块或触发游戏结束。
if self.__class__,state ! = 0:检查游戏的整体状态,如果游戏不是处于初始状态,即不等于0,则直接“return”,不做任何处理
if self.state == 1,如果当前方块已被翻开,也直接“return”避免重复处理
if self.n == 9,说明点击的是雷,此时调用“self.gameover( )”的处理方法,游戏结束
if self.n == 0,说明点击的方块附近没有雷,此时调用“self.auto_sweep( )”尝试自动翻开周围不是雷的方块。并接着调用“self.vectory( )”的方法判断是否赢了游戏。
如果以上条件都不满足,就说明玩家点开了一个普通方块,此时要增加扫雷计时器数量,将方块状态设置为已翻开更新方块的显示样式,检查是否达到胜利条件。
def right_clicked(self,event)处理右键点击,切换方块的标记状态。支持无标记、标记为雷、质疑三种状态之间的转换,并及时更新方块的视觉效果
def map_mine(self),计算并设置每个非雷方块周围的地雷数量
def update_style(self)根据方块的状态,更新显示样式,包括背景色“self._class._bgs”,前景色“self._class._fgs”和图标等
def auto_sweep(self)实现自动清扫功能,当玩家打开一个周围没有地雷的方块时,相应地打开周围的空白方块
def gameover(self)用以处理游戏失败的情况
对于地图上的每一个方块“for mine in self._class_.map“如果该方块确实是雷”if mine.n == 9“且没有被正确标记为雷”mine state != 2“,则将其状态设置为4”mine.state = 4“,并更新其样式,显示出雷的形象“mine.update_style( )”
如果方块不是雷“mine.n != 9“但被错误地标记成了雷”mine state == 2“,则将其状态设置为6”mine.state = 6“,并更新其样式,显示出错误标记“mine.update_style( )”
通过tkinte.messagebox.showinfo 弹出一个消息框,告知玩家游戏结束且失败,标题为"游戏结束!",信息内容为"失败!"
如果用户点击了消息框的"OK"按钮(返回值为"ok"),则调用
root.quit()`关闭游戏窗口,结束程序
def vectory(self)用以处理游戏获胜的情况
if self._class_.count == (self._class_.h*self._class_.w - self._class_.mine 判断玩家是否满足胜利条件,即翻开的方块数是否等于总方块数减去地雷数
if self._class_.state != 1 在确认达到胜利条件后,进一步检查全局状态
tkinter.messagebox.showinfo(title"游戏结束“,message”恭喜过关!!!“在确认上述条件都满足后,弹出该消息框
self._class_.state = 1 在显示胜利消息更新游戏状态,以便下一次游戏的更好开展
def del_menu(args)定义一个菜单栏,允许玩家选择重新开始或不同的游戏难度
if args == “入门” Sweep.creat_map(w = 6,h = 5,mine = 1),创建一个5*6,含有1个地雷的地图
if args == “简单” Sweep.creat_map(w =10,h = 10,mine = 15),创建一个10*10,含有15个地雷的地图
if args == “一般” Sweep.creat_map(w = 16,h = 16,mine = 40),创建一个16*16,含有40个地雷的地图
if args == “困难” Sweep.creat_map(w = 20,h = 16,mine = 60),创建一个20*16,含有60个地雷的地图
elif args ==“重新开始”,直接使用pass语句跳过
if _name_ =='_main_' 当脚本直接运行时,启动Tkinter的主循环,展示游戏窗口等待用户交互
root.title = (‘扫雷’) 设置Tkinter的窗口标题为“扫雷”
Sweep.creat_map(w = 6,h = 5,mine = 3) 创建一个尺寸为6*5,含有三个地雷的初始地图
Sweep.init_map(root) 将地图显示在GUI窗口上
menu_bar = tkinter.Menu(root)初始化一个菜单栏
root.config(menu = menu_bar) 将其设置为窗口的菜单
game_menu = tkinter.Menu(menu_bar,tearoff=False) 创建一个名为“游戏”的下拉菜单,并禁止该菜单从其父菜单中分离
menu_bar.add_cascade(label = "游戏“,menu = game_menu) 为游戏菜单添加多个难度级别或操作
root.mainloop( ) 启动事件循环,使窗口保持打开状态,期待与用户进行交互
2.实验的源代码如下
import tkinter import tkinter.messagebox import random # 通过路径获取图片 def get_image(filename, w=None, h=None): try: # 加载图片 im = tkinter.PhotoImage(file=filename) if w != None and h != None: im = im.subsample(im.width() // w, im.height() // h) return im except: # 未找到图片加载未空 return None class Sweep(tkinter.Button): # 定义地图宽度,高度,地雷个数,地图相对于主窗口的起点,扫除计数,游戏状态,地图方块大小 map, w, h, mine, x0, y0, count, state, mine_size = [], 7, 8, 5, 30, 50, 0, 0, 40 # 数字 0 ~ 9 的字体颜色 rgb fgs = [(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)] # 方块状态颜色与背景图片 bgs = [(128, 128, 128), (255, 255, 255), (0, 255, 0), (255, 0, 0), (255, 200, 0), (0, 255, 0), (163, 73, 164)] images = ['area.png', 'opened.png', 'flag.png', 'doubt.png', 'boom.png', 'sweeped.png', 'mistake.png'] pic = [] # 创建地图 def create_map(w=w, h=h, mine=mine, x0=x0, y0=x0, mine_size=mine_size, fgs=fgs, bgs=bgs, images=images): # 加载地图信息 Sweep.w = w Sweep.h = h Sweep.mine = mine Sweep.x0 = x0 Sweep.y0 = y0 Sweep.mine_size = mine_size Sweep.fgs = fgs Sweep.bgs = bgs Sweep.images = images Sweep.state = 0 Sweep.count = 0 # 加载背景图片 if not Sweep.pic: for filename in Sweep.images: image = get_image(filename, Sweep.mine_size, Sweep.mine_size) Sweep.pic.append(image) # 处理地图中数据 def init_map(root): size_str = '{}x{}+400+80'.format(Sweep.w * Sweep.mine_size + 80, Sweep.h * Sweep.mine_size + 100) root.geometry(size_str) Sweep.state = 0 Sweep.count = 0 # 判断按钮点击位置 for button in Sweep.map: button.destroy() Sweep.map.clear() # 更新界面 root.update() random_numbers = random.sample(range(0, Sweep.w * Sweep.h), Sweep.mine) for i in range(Sweep.w * Sweep.h): r = i // Sweep.w c = i % Sweep.w n = 9 if i in random_numbers else 0 # 操作按钮 button = Sweep(root) button.place(x=c * Sweep.mine_size + Sweep.x0, y=r * Sweep.mine_size + Sweep.y0 , width=Sweep.mine_size, height=Sweep.mine_size) button.setPos(r=r, c=c, n=n) # 遍历地图每一个单元格 for mine in Sweep.map: mine.map_mine() root.update() def reset_map(self): Sweep.state = 0 for mine in Sweep.map: mine.state = 0 mine.update_style() def __init__(self, master=None, cnf={}, **kw): # 继承属性 super().__init__(master, cnf, **kw) self.text = self['text'] self.command = self['command'] self.bind('<Button-1>', self.clicked) self.bind('<Button-3>', self.right_clicked) self.r = 0 self.c = 0 self.n = 0 # 0没有被打开 1已经被打开 2被标志 3被质疑 4打开是雷被爆炸 5被扫除 6标志错误 self.state = 0 self.update_style() self.__class__.map.append(self) def clicked(self, event): # 判断点击实际 if self.__class__.state != 0: return if self.state == 1: return if self.n == 9: self.gameover() return if self.n == 0: self.auto_sweep() self.victory() return self.__class__.count += 1 self.state = 1 # 更新样式 self.update_style() self.victory() # 处理右键点击 def right_clicked(self, event): # 判断该方块周围雷数 if self.state == 1: return if self.state == 0: self.state = 2 elif self.state == 2: self.state = 3 elif self.state == 3: self.state = 0 # 更新样式 self.update_style() # 设置位置 def setPos(self, r, c, n): self.r = r self.c = c self.n = n # 处理每一行 def map_mine(self): if self.n != 9: return # 将(r,c)的邻居的位置都找出来 neighbors = [(self.r + i, self.c + j) for i in range(-1, 2) for j in range(-1, 2) if i != 0 or j != 0] for r, c in neighbors: for mine in self.__class__.map: if mine.r == r and mine.c == c and mine.n != 9: mine.n += 1 # #根据按钮自身的不同状态去显示按钮的样式 def update_style(self): text = str("") if self.n != 0 and self.n != 9 and self.state == 1: text = str(self.n) hex_fg = '#{:02x}{:02x}{:02x}'.format(self.__class__.fgs[self.n][0] , self.__class__.fgs[self.n][1], self.__class__.fgs[self.n][2]) hex_bg = '#{:02x}{:02x}{:02x}'.format(self.__class__.bgs[self.state][0] , self.__class__.bgs[self.state][1], self.__class__.bgs[self.state][2]) image = self.__class__.pic[self.state] # 加载图片 self.image = image if self.state == 2: text = str("!") hex_fg = "red" elif self.state == 3: text = str("?") hex_fg = str("yellow") elif self.state == 6: text = str("×") hex_fg = str("red") if image != None: hex_bg = None # 加载怕配置 self.configure(bg=hex_bg, fg=hex_fg, image=image, text=text, compound=tkinter.CENTER) # 处理过关后自动清除页面 def auto_sweep(self): if self.state == 1: return self.state = 1 # 更新样式 self.update_style() self.__class__.count += 1 if self.n != 0: return # # 将(r,c)的邻居的位置都找出来 neighbors = [(self.r + i, self.c + j) for i in range(-1, 2) for j in range(-1, 2) if i != 0 or j != 0] for r, c in neighbors: for mine in self.__class__.map: if mine.r == r and mine.c == c and mine.n != 9: mine.clicked(None) # 处理游戏结束 def gameover(self): self.state = 4 self.__class__.state = 2 # 根据选中状态和地图判断每一个单元格 for mine in self.__class__.map: if mine.n == 9 and mine.state != 2: mine.state = 4 mine.update_style() elif mine.n != 9 and mine.state == 2: mine.state = 6 mine.update_style() # 显示失败页面 result = tkinter.messagebox.showinfo(parent=self, title="游戏结束!", message="失败!") # 关闭页面 if result == "ok": root.quit() # 处理过关 def victory(self): if self.__class__.count == (self.__class__.h * self.__class__.w - self.__class__.mine): if self.__class__.state != 1: tkinter.messagebox.showinfo("游戏结束!", "恭喜过关!!!") self.__class__.state = 1 root = tkinter.Tk() # 处理难度选择的函数 def del_menu(args): # 根据选择生成对应的地图 if args == "入门": Sweep.create_map(w=6, h=5, mine=1) elif args == "简单": Sweep.create_map(w=10, h=10, mine=15) elif args == "一般": Sweep.create_map(w=16, h=16, mine=40) elif args == "困难": Sweep.create_map(w=20, h=16, mine=60) elif args == "重新开始": pass # 创建地图 Sweep.init_map(root) if __name__ == '__main__': root.title('扫雷') # 创建地图 Sweep.create_map(w=6, h=5, mine=3) Sweep.init_map(root) menu_bar = tkinter.Menu(root) # 加载配置 root.config(menu=menu_bar) game_menu = tkinter.Menu(menu_bar, tearoff=False) # 加载标题和难度 menu_bar.add_cascade(label="游戏", menu=game_menu) game_menu.add_command(label="入门", command=lambda: del_menu("入门")) game_menu.add_command(label="简单", command=lambda: del_menu("简单")) game_menu.add_command(label="一般", command=lambda: del_menu("一般")) game_menu.add_command(label="困难", command=lambda: del_menu("困难")) game_menu.add_command(label="重新开始", command=lambda: del_menu("重新开始")) # 启动窗口 root.mainloop()
3.实验操作视频
file:///C:/Users/llj/Desktop/%E5%B1%8F%E5%B9%95%E5%BD%95%E5%88%B6%202024-05-27%20223633.mp4
三、实验四遇到的问题及感想体会
1、遇到的问题
问题一:在实验刚开始的过程中,一直显示前几行“import”部分存在问题,我感到很不解
问题一解决方法:这是说明我的python没有安装相应的库,经过老师指导,点击红色波浪线部分会出现一个红色的小灯泡,点击红色小灯泡根据弹出的内容进行安装,如果显示安装失败,可以在cmd中再次尝试安装。
问题二:因为我个人能力不足,所以我上网进行了大量参考,例如,我刚开始并不理解别人写的程序中的tkinter模块的作用是什么
问题二解决方法:搜索了tkinter,对其有了一个初步的了解之后,又进一步对其定义中的“GUI”进行了搜索,并在接下来的实践中逐步深化对它的认识
问题三:在游戏刚开始运行时,总是运行一部分然后突然终止失败
问题三解决方法:上网查找了扫雷游戏的规则与基本原理之后,重新检查自己的代码,发现有许多定义的缺失,比如不同的数字应该用什么颜色表示,分别代表什么状态,如何判断还剩多少雷,成功以及失败等等。这些问题都通过我不断地定义,完善程序得到了解决。
2、感想体会
在本次实验刚开始的过程中,因为我对python的掌握还不是很好,所以感受到了非常大的恐惧。但当实验逐步展开,这个恐惧一点点地被我克服了。当我把这个很大的实验拆分成一小块一小块进行完成时,那些很困难的问题便一点点地被解决了。
比如,导入版块,利用import语句就能很好地解决。接下来就是多次运用def函数来进行定义,利用elif函数处理多种情况,利用for函数处理循环等等这些上课老师都有教学到的知识,通过查看学习通上的资料并借鉴一下网络上的知识,便可以得到很好的解决。
在编写这个程序的过程中我进一步加深并细化了之前所学到的一些小点。比如“!=”表示不等于,“//"表示整除,”str“表示字符串等等,进一步巩固了我最基础的python知识。
这是python课程的最后一次实验,通过这次实验 我进一步复习了前面所学的知识,加深了印象。我认识到python不是洪水猛兽,将其一步步拆解开来,利用我现在所掌握的知识也能够很好地解决它。在进行本次实验的过程中,我很荣幸地和研究生学长进行了交流,在我看起来桀骜难驯的猛兽,在他手中却如同一只听话的小猫,轻轻敲击几行代码,问题便能迎刃而解。对于我的畏难情绪,他并没有嘲笑,他只是以一个过来人的视角向我强调了这门学科的重要性,这是一个我以前不曾接触到的视角,作为电科院的学生,我可能不会走上考研这条道路,python对于现在的我来说,可能也只是一个不得不完成的任务,但是对于将来步上职业生涯的我,她可能会感激现在的我愿意再坚持一下,弄懂这看似晦涩,实则妙趣横生的python吧。
四、课程总结
python这节课一开始对我来讲,我就感到十分的担忧,我总担心自己没有办法很好地领悟老师所讲的内容并将其付诸实践。但好在在老师的帮助下,我一步步从最基础的知识点开始学起,语法、序列、函数、字符串,一点点到面向对象、socket、爬虫,虽然现在的我对这些内容可能仍然是一知半解,但我学会了勇敢地去进行实践,不管我编出来的程序有多少错误,我都会尝试根据所学内容进行编程,它可能会进行一遍又一遍的报错,看着屏幕上总是暗不下去的红色光芒,我也经常会感到迷茫与挣扎。但好在,经过这门课程的学习,我已经有了足够的勇气从头开始,按照它的报错提醒一个一个地改过去,总会改到没有bug的时候,虽然这个过程可能并不好过,虽然光凭我一个人的努力可能没有那么顺利,但在我在这条路上艰难前行的过程中 ,老师永远是我最大的助力,不管有多么繁忙,您都愿意抽出时间来为我解答一个有一个的弱智问题,是您保住了我学习python的星星之火,只希望在我接下来继续深入学习Python的过程中,这点星星之火可成燎原之势。在前进的道路上,还有我的同学与伙伴的陪同,我们可能共同对着一个问题发愁,对着同一个bug破口大骂,为了一点小小的进步欢欣雀跃,我们都是这条路上背着沉重行囊的旅人,但有彼此作伴,好像也没那么劳累。是python这节课让我结识了更多的伙伴,我们可能是为了进行socket对话而结识,我们可能是为了解决一个bug而对话,我们可能是为了借鉴一个程序而交流,但不管怎样在python的课堂上,我认识了更多的伙伴,在接下来的前进道路上,我们仍会一起。
提及对课程的意见或建议,其实我本没有什么发言的立场,我觉得现在的课程也进行的很好,但我想最重要的意见和建议就是希望老师能够好好休息,多注重自己的身体健康,这样才能为我们带来更好的课程,不是吗?