20212309实验四《python程序设计》实验报告

 

 

20212309《Python程序设计》实验报告

 

课程:《Python》程序设计

班级:2123

姓名:沈烨

学号:20213209

实验教师:王志强

实验日期:2022年5月30日

必选/选修:公选课

 

1.实验内容

Python综合运用:尝试使用python写一个程序,能够在与人进行猜拳的同时学习玩家的猜拳习惯,并利用该习惯提高计算机的胜率。

预想功能:读入玩家出招,输出计算机和玩家的出招及结果,并将历史战绩记录在一个txt文档中。

 

  1. 实验过程及结果

(一)构想的提出与功能的确认

这个程序的灵感来源于一个研究:人们猜拳时,总是有这样一个倾向:第一个出石头;如果赢了,下次出招保持不变;如果输了,下次倾向于出克制上次对方出招的招。但是我认为,人与人的习惯是不同的。所以我想通过在猜拳过程中学习出招习惯来更大程度提高胜率。从某种意义上说,这也有着深度学习的影子。

 

 

(二)代码的书写

程序的几个主要模块:

 

创建并初始化记录文件:

为了避免除零错误,这里预先填入了一些数据,也起到了在前几局对局数据较少时稳定各对局情况出现比例的作用。

 

# 创建记录文件
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

 

  1. 实验过程中遇到的问题和解决过程

 

(一)第一个问题是一开始在记录过往对局时我是用回车符“\n”间隔,但是在使用.count方法时程序无法正确计数,不得已将回车改为了逗号“,”。

 

(二)第二个问题是写主体函数时被绕晕了,后来我再次写了一遍伪代码,逐步验证伪代码正确性,最后写出了正确的主体程序。

 

(三)第三个问题是过往对局情况的存储。一开始我打算直接定义变量,后来发现这几乎不可能。思索无果后我上网参考了前辈的经验,并在群里和同学确认了使用嵌套元组的可行性,解决了这个问题。


4.结课感想与体会

这个程序是我写的第一个大型程序,有着完备的功能和极强的稳定性,虽然没有什么实用性,也还有许多不完美之处,但是作为一个代码作品来说,我已经十分满意了。从构想到付诸实践,在实践中我学会了发现问题、解决问题,在事不可为的情况下与问题妥协。

Python是我目前最擅长的编程语言,极大的自由度、丰富的库调用给予了python无限可能。我使用的编译器pycharm也给我留下了很好的印象:界面简洁,功能一个不落,而且该干活就干活,不没事就弹窗,十分合我心意。

python开启我的编程之旅,实属我幸。

posted on 2022-05-31 23:21  20212309沈烨  阅读(103)  评论(0编辑  收藏  举报