20212309《Python程序设计》实验报告
课程:《Python》程序设计
班级:2123
姓名:沈烨
学号:20213209
实验教师:王志强
实验日期:2022年5月30日
必选/选修:公选课
1.实验内容
Python综合运用:尝试使用python写一个程序,能够在与人进行猜拳的同时学习玩家的猜拳习惯,并利用该习惯提高计算机的胜率。
预想功能:读入玩家出招,输出计算机和玩家的出招及结果,并将历史战绩记录在一个txt文档中。
(一)构想的提出与功能的确认
这个程序的灵感来源于一个研究:人们猜拳时,总是有这样一个倾向:第一个出石头;如果赢了,下次出招保持不变;如果输了,下次倾向于出克制上次对方出招的招。但是我认为,人与人的习惯是不同的。所以我想通过在猜拳过程中学习出招习惯来更大程度提高胜率。从某种意义上说,这也有着深度学习的影子。
(二)代码的书写
程序的几个主要模块:
创建并初始化记录文件:
为了避免除零错误,这里预先填入了一些数据,也起到了在前几局对局数据较少时稳定各对局情况出现比例的作用。
# 创建记录文件 file = open(r"remember.txt", "w+") time = str(datetime.datetime.today()) file.write(time+"\n") file.write("(以下内容为初始化)\n") file.write("石头 石头,石头石头 石头,剪刀石头 石头,布石头 剪刀,石头石头 剪刀,剪刀石头 剪刀,布石头 布,石头石头 布,剪刀石头 布,布\n") file.write("剪刀 石头,石头剪刀 石头,剪刀剪刀 石头,布剪刀 剪刀,石头剪刀 剪刀,剪刀剪刀 剪刀,布剪刀 布,石头剪刀 布,剪刀剪刀 布,布\n") file.write("布 石头,石头布 石头,剪刀布 石头,布布 剪刀,石头布 剪刀,剪刀布 剪刀,布布 布,石头布 布,剪刀布 布,布\n") file.write("记录开始:\n") file.write("玩家 AI\n") file.close()
定义猜拳函数:
在程序中0代表石头,1代表剪刀,2代表布。猜拳函数finger()读入1,2,3并输出石头,剪刀,布。
# 定义猜拳函数 def finger(out): if out == 0: return "石头" if out == 1: return "剪刀" if out == 2: return "布"
处理记录文件中的历史记录:
学习玩家猜拳习惯,也就是猜测指定上局对局情况下玩家下局倾向于如何出招。也就是说,需要得到过往对局中一共27种出招情况。我在这里使用了.readlines方法读出过往对局数据,再通过.count方法提取各情况出现次数并用元组list[][][]存储。
# 学习猜拳习惯 file = open(r"remember.txt", "r") content_list = (file.readlines()) content_str = str(content_list) file.close() # 定义计数变量 summary = content_str.count(",") - 27 list = [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]]] list[0][0][0] = content_str.count("石头 石头,石头") - 1 list[0][0][1] = content_str.count("石头 石头,剪刀") - 1 list[0][0][2] = content_str.count("石头 石头,布") - 1 list[0][1][0] = content_str.count("石头 剪刀,石头") - 1 list[0][1][1] = content_str.count("石头 剪刀,剪刀") - 1 list[0][1][2] = content_str.count("石头 剪刀,布") - 1 list[0][2][0] = content_str.count("石头 布,石头") - 1 list[0][2][1] = content_str.count("石头 布,剪刀") - 1 list[0][2][2] = content_str.count("石头 布,布") - 1 list[1][0][0] = content_str.count("剪刀 石头,石头") - 1 list[1][0][1] = content_str.count("剪刀 石头,剪刀") - 1 list[1][0][2] = content_str.count("剪刀 石头,布") - 1 list[1][1][0] = content_str.count("剪刀 剪刀,石头") - 1 list[1][1][1] = content_str.count("剪刀 剪刀,剪刀") - 1 list[1][1][2] = content_str.count("剪刀 剪刀,布") - 1 list[1][2][0] = content_str.count("剪刀 布,石头") - 1 list[1][2][1] = content_str.count("剪刀 布,剪刀") - 1 list[1][2][2] = content_str.count("剪刀 布,布") - 1 list[2][0][0] = content_str.count("布 石头,石头") - 1 list[2][0][1] = content_str.count("布 石头,剪刀") - 1 list[2][0][2] = content_str.count("布 石头,布") - 1 list[2][1][0] = content_str.count("布 剪刀,石头") - 1 list[2][1][1] = content_str.count("布 剪刀,剪刀") - 1 list[2][1][2] = content_str.count("布 剪刀,布") - 1 list[2][2][0] = content_str.count("布 布,石头") - 1 list[2][2][1] = content_str.count("布 布,剪刀") - 1 list[2][2][2] = content_str.count("布 布,布") - 1
定义判断函数:
判断函数judge()的需求是按照石头、剪刀、布的顺序读入过往玩家出招的数量,把最多的一个作为预测玩家下局出招,并输出克制该出招的出招。
在两个或三个出招倾向相同的情况下,使用随机数来等概率输出。
这里的代码没有太多技巧,直接使用了7个if-elif语句。
# 定义判断函数:按照石头/剪刀/布顺序读入player出招习惯,输出克制player最可能出招的出招 def judge(x, y, z): if(x > y) and (x > z): return 2 elif(y > x) and (y > z): return 0 elif(z > x) and (z > y): return 1 elif(x == y) and (x > z): choose = random.randint(0, 1) if choose == 0: return 2 else: return 0 elif(x == z) and (x > y): choose = random.randint(0, 1) if choose == 0: return 2 else: return 1 elif (y == z) and (y > x): choose = random.randint(0, 1) if choose == 0: return 0 else: return 1 elif (x == y) and (y == z): choose = random.randint(0, 2) if choose == 0: return 2 elif choose == 1: return 0 elif choose == 2: return 1
主体函数:
定义了四个变量:player,content_player,AI,content_AI,分别记录这局以及上局双方出招。
使用while循环,将变量player作为判断变量,读入9时结束循环。
while循环内,首先使用judge()函数依次读入list[content_player][content_AI][0],list[content_player][content_AI][1],list[content_player][content_AI][2],即上局对局情况下玩家出石头、剪刀、布的过往数据,输出克制概率最大的出招的出招作为变量AI的值。
然后读入玩家的出招,打印双方出招finger(player),finger(AI)
# 初始化:AI随机出,player读入 AI = random.randint(0, 2) player = 10 # 防错输 go = 0 while go == 0: try: print("输入手势:0.石头 1.剪刀 2.布\n 输入“9”结束程序。") player = int(input()) if (player == 0) or (player == 1) or (player == 2) or (player == 9): go = 1 except: go = 0 # 输出结果 print("player:{} AI:{}".format(finger(player), finger(AI))) # 判断AI出招 AI = judge(list[content_player][content_AI][0], list[content_player][content_AI][1], list[content_player][content_AI][2])
记录数据:
这是最麻烦的部分。这个部分将会通过.write方法在memory.txt中写入上局双方出招,也就是content_player、content_AI,并且将元组中list[content_player][content_AI][player]这项数据+1,以实时更新细化玩家出招倾向。
# 记录 file = open(r"remember.txt", "a") file.write(str(finger(player))) file.write(' ') file.write(str(finger(AI))) file.write(',')
输出实时胜率:
这也是一个比较麻烦的模块。首先定义五个变量:AI_win,rate_AI,player_win,rate_player即AI和player的胜局、胜率以及总对局数summary。双方胜局数可以通过求和元组list中特定的数据来求得。这里使用for循环减少代码量。
# 输出实时胜率 AI_win = 0 player_win = 0 for i in range(2): AI_win = list[1][0][i] + list[2][1][i] + list[0][2][i] for i in range(2): player_win = list[0][1][i] + list[1][2][i] + list[2][0][i] rate_AI = AI_win/summary rate_player = player_win/summary print("AI胜率:{}\nplayer胜率:{}".format(rate_AI, rate_player))
记录对局结果:
通过.write方法在memory.txt中写入双方胜率。
# 结束 file.write("\n") file.write("AI胜率:") file.write(str(rate_AI)) file.write("\n") file.write("player胜率:") file.write(str(rate_player)) file.write("\n")
个人比较得意的代码段:
第一个是通过元组三层嵌套来存储过往对局数据,避免定义27个变量,并且方便了后续数据的调用以及更改。
# 定义计数变量 summary = content_str.count(",") - 27 list = [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]]] list[0][0][0] = content_str.count("石头 石头,石头") - 1 list[0][0][1] = content_str.count("石头 石头,剪刀") - 1 list[0][0][2] = content_str.count("石头 石头,布") - 1 list[0][1][0] = content_str.count("石头 剪刀,石头") - 1 list[0][1][1] = content_str.count("石头 剪刀,剪刀") - 1 list[0][1][2] = content_str.count("石头 剪刀,布") - 1 list[0][2][0] = content_str.count("石头 布,石头") - 1 list[0][2][1] = content_str.count("石头 布,剪刀") - 1 list[0][2][2] = content_str.count("石头 布,布") - 1 list[1][0][0] = content_str.count("剪刀 石头,石头") - 1 list[1][0][1] = content_str.count("剪刀 石头,剪刀") - 1 list[1][0][2] = content_str.count("剪刀 石头,布") - 1 list[1][1][0] = content_str.count("剪刀 剪刀,石头") - 1 list[1][1][1] = content_str.count("剪刀 剪刀,剪刀") - 1 list[1][1][2] = content_str.count("剪刀 剪刀,布") - 1 list[1][2][0] = content_str.count("剪刀 布,石头") - 1 list[1][2][1] = content_str.count("剪刀 布,剪刀") - 1 list[1][2][2] = content_str.count("剪刀 布,布") - 1 list[2][0][0] = content_str.count("布 石头,石头") - 1 list[2][0][1] = content_str.count("布 石头,剪刀") - 1 list[2][0][2] = content_str.count("布 石头,布") - 1 list[2][1][0] = content_str.count("布 剪刀,石头") - 1 list[2][1][1] = content_str.count("布 剪刀,剪刀") - 1 list[2][1][2] = content_str.count("布 剪刀,布") - 1 list[2][2][0] = content_str.count("布 布,石头") - 1 list[2][2][1] = content_str.count("布 布,剪刀") - 1 list[2][2][2] = content_str.count("布 布,布") - 1
另一个是所谓程序的“健壮性”,也就是不管使用者如何输入,程序都不会报错,也能及时调整,让使用者重新输入。这一部分我使用了while循环读入变量player,通过变量go判断循环结束与否。只有当player符合要求,go更改为1,结束循环进程继续。并且使用try/expect语句避免输入字母、回车符等字符时报错。
# 读入player出招 go = 0 while go == 0: try: player = int(input()) if (player == 0) or (player == 1) or (player == 2) or (player == 9): go = 1 else: print("输入手势:0.石头 1.剪刀 2.布\n 输入“9”结束程序。") except: go = 0 print("输入手势:0.石头 1.剪刀 2.布\n 输入“9”结束程序。") if player == 9: break
(一)第一个问题是一开始在记录过往对局时我是用回车符“\n”间隔,但是在使用.count方法时程序无法正确计数,不得已将回车改为了逗号“,”。
(二)第二个问题是写主体函数时被绕晕了,后来我再次写了一遍伪代码,逐步验证伪代码正确性,最后写出了正确的主体程序。
(三)第三个问题是过往对局情况的存储。一开始我打算直接定义变量,后来发现这几乎不可能。思索无果后我上网参考了前辈的经验,并在群里和同学确认了使用嵌套元组的可行性,解决了这个问题。
4.结课感想与体会
这个程序是我写的第一个大型程序,有着完备的功能和极强的稳定性,虽然没有什么实用性,也还有许多不完美之处,但是作为一个代码作品来说,我已经十分满意了。从构想到付诸实践,在实践中我学会了发现问题、解决问题,在事不可为的情况下与问题妥协。
Python是我目前最擅长的编程语言,极大的自由度、丰富的库调用给予了python无限可能。我使用的编译器pycharm也给我留下了很好的印象:界面简洁,功能一个不落,而且该干活就干活,不没事就弹窗,十分合我心意。
以python开启我的编程之旅,实属我幸。