• python3 井字棋 GUI


    python3 井字棋 GUI - 人机对战、机器对战

    功能

    1. GUI界面
    2. 人机对战(可选择机器先走)
    3. 机器对战(50局)
    流程图

    内核

    棋盘

    [0][1][2]
    [3][4][5]
    [6][7][8]
    
    最佳下棋顺序:

    best_way = [4,0,2,6,8,1,3,5,7]

    估价函数(以X为对象)

    1. 可以赢的行数 +1
    2. 可以赢的行数上有自己的棋子 +2
    3. 可导致自己赢 +2
    4. 可导致对手赢 -2

    判断赢局

    win_chess = [[0,4,8],[2,4,6],[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8]]

    人机对战流程

    人(X)点击某个格子,触发绑定事件

    1. 判断该格子是否有子,无子继续
    2. 设置该格子为 1
    3. 判断X是否赢了,没有继续,有X赢
    4. 判断棋盘是否无子可下,有继续,没有平局
    5. 轮到机器下棋
    6. 判断O是否差一子便可赢,有则对应格子,无则继续
    7. 判断O是否处于危险状态,即对方只差一子赢棋,有则选择该格子,无则继续
    8. 机器选择最佳格子
    9. 判断输赢并判断是有无子可下,循环

    机器对战流程

    1. 随机产生先下棋者
    2. 第一颗棋子随机下
    3. 各自判断最佳走法
    4. 判断输赢即棋盘是否无子可下
    5. 循环

    总结

    1. 学习了python threading库的用法

    线程的使用:

        id = 1
        th = []
        for i in range(50):
            id = id * -1
            try:
                th.append(threading.Thread(target=run,args=(i,id)))
                th[i].start()
            except Exception as e:
                print(e)
                i = i - 1
    

    2. 学习了python tkinter库的用法

    tkinter的mianloop做为主线程尽量避免被阻塞,以免界面卡死

    创建窗口:

    top = tk.Tk()#创建窗口
    top.title('井字棋 -> Fighting')#标题
    top.geometry("300x300")#大小
    top.resizable()#可改变大小
    

    创建Frame:

    frame_top = tk.Frame(top)#top是上层
    

    创建按钮:

    tk.Button(frame_top,text='人机对决',command=but1).pack(side=tk.LEFT)
    

    创建labe:

    label1 = tk.Label(frame_cont,justify=tk.CENTER,textvariable=show_str,font=("幼圆",30))
    

    显示可刷新变量:

    tips = tk.StringVar(top)    #提示信息
    tips.set("")#设置显示内容
    label_bottom = tk.Label(frame_bot,justify=tk.CENTER,textvariable=tips,font=("幼圆",20),padx=0)#设置显示的值为tips
    

    绑定事件及解绑:

        l0.bind("<Button-1>", touch_l0)#绑定
        l0.unbind("<Button-1>")#解绑
    

    布局:

    l0.pack(side=tk.LEFT)
    frame_top.pack()
    

    开启消息循环:

    top.mainloop()
    

    代码

    运行截图

    待修复问题

    1. 若产生平局会导致该线程卡死 , 即count_z无法计算,并造成卡顿
    2. 程序优化不够,代码较为杂乱
    3. 上一个问题导致通过GUI关闭程序会有进程仍在跑,需要用任务管理器关闭
    4. 先走角落易赢,机器走法单一,即下面情况
    X _ O 
    O O _
    X X X
    

    补充:之前之所以会卡,是因为在计算下一步时,如果只剩下3或2个格子时,无法返回下一步的值,导致棋没有下,外层循环又是while True,便导致死循环。针对这个问题我添加了具体的改进,已修复。

    运行 gui.py 即可

    chess.py 内核部分

    #coding=utf-8
    """
    [0,1,2]
    [3,4,5]
    [6,7,8]
    """
    
    #胜利的走法
    win_chess = [[0,4,8],[2,4,6],[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8]]
    #最佳下棋顺序
    best_way = [4,0,2,6,8,1,3,5,7]
    #棋盘
    chess = [0,0,0,0,0,0,0,0,0]
    
    def is_win(now_chess,who):
        """
        判断游戏方(who)是否赢局
        """
        temp = now_chess[:]
        for w_c in win_chess:
            if temp[w_c[0]] == who and temp[w_c[1]] == who and temp[w_c[2]] == who :
                return who
        return 0
    
    def count_zero(now_chess):
        """
        统计剩余格子
        返回个数
        """
        temp = now_chess[:]
        count = 0
        for te in temp:
            if te == 0:
                count = count + 1
        return count
    
    def evaluation(now_chess):
        """
        估价函数(以X为对象)
        可以赢的行数 +1
        可以赢的行数上有自己的棋子 +2
        可导致自己赢 +2
        可导致对手赢 -2
        """
        temp = now_chess[:]
        count = 0
        for w_c in win_chess:
            if temp[w_c[0]] >= 0 and temp[w_c[1]] >= 0 and temp[w_c[2]] >= 0 :
                if temp[w_c[0]] == 1 or temp[w_c[1]] == 1 or temp[w_c[2]] == 1 :
                    count += 1
                count += 1
        if is_win(temp,1) == 1:
            count = count + 2
        if is_win(temp,-1) == -1:
            count = count - 2
        return count
    
    def all_go(now_chess,who):
        """
        遍历所有走法
        """
        temp = now_chess[:]
        tempp = []
        for i in best_way:
            if temp[i] == 0:
                temppp = temp[:]
                temppp[i]=who
                tempp.append([temppp,i])
        return tempp
    
    def get_next_x(now_chess,who):
        """
        x获取下一个位置
        """
        temp = now_chess[:]
        best_list = None
        best_one = -1
        if count_zero(temp) <= 3 :
            for te in all_go(temp,who):
                if best_one == -1:
                    best_list = te[0]
                    best_one = te[1]
                else :
                    if evaluation(te[0]) > evaluation(best_list):
                        best_list = te[0]
                        best_one = te[1]
            return best_one
        for te in all_go(temp,who):
            for tee in all_go(te[0],who*-1):
                for teee in all_go(tee[0],who):
                    if best_list is None:
                        best_list = teee[0]
                        best_one = te[1]
                    else:
                        if evaluation(teee[0]) > evaluation(best_list) :
                            best_list = teee[0]
                            best_one = te[1]
        return best_one
    
    def get_next_o(now_chess,who):
        """
        o获取下一个位置
        """
        temp = now_chess[:]
        best_list = None
        best_one = -1
        if count_zero(temp) <= 2 :
            for te in all_go(temp,who):
                if best_one == -1:
                    best_list = te[0]
                    best_one = te[1]
                else :
                    if evaluation(te[0]) < evaluation(best_list):
                        best_list = te[0]
                        best_one = te[1]
            return best_one
        for te in all_go(temp,who):
            for tee in all_go(te[0],who*-1):
                if best_list is None:
                    best_list = tee[0]
                    best_one = te[1]
                else:
                    if evaluation(tee[0]) < evaluation(best_list) :
                        best_list = tee[0]
                        best_one = te[1]
        return best_one
    
    def is_danger(now_chess,who=0):
        """
        判断自己是否处于危险状态(即 对手可能已经差一子赢局)
        """
        temp = now_chess[:]
        for te in all_go(temp,who*-1):
            if is_win(te[0],who*-1) == who*-1:
                return te[1]
        return -1
    
    if __name__ == "__main__":
        """
        测试用
        """
        chess = [0,0,0,
                0,1,0,
                0,0,0]
        #print(get_next_old(chess,-1,1))
        #print(all_go(chess,1))
        print(get_next_o(chess,-1))
    

    gui.py 图形及控制 部分

    #coding=utf-8
    """
    """
    import tkinter as tk
    import time
    import threading
    import random
    import chess
    
    init_chess = [0,0,0,0,0,0,0,0,0]    #原始棋盘
    the_chess = [0,0,0,0,0,0,0,0,0]    #记录棋盘
    show_chess = ''
    flag = True
    who = 1
    count_x = 0
    count_y = 0
    count_z = 0
    
    top = tk.Tk()
    top.title('井字棋 -> Fighting')
    top.geometry("300x300")
    top.resizable()
    show_str = tk.StringVar(top)
    tips = tk.StringVar(top)    #提示信息
    
    #初始化棋盘信息
    ch = []
    for i in range(9):
        ch.append(tk.StringVar(top))
    
    #初始化提示信息
    tips.set("")
    
    frame_top = tk.Frame(top)
    frame_cont = tk.Frame(top)
    frame_bot = tk.Frame(top)
    frame_cont1 = tk.Frame(frame_cont)
    frame_cont2 = tk.Frame(frame_cont)
    frame_cont3 = tk.Frame(frame_cont)
    
    label1 = tk.Label(frame_cont,justify=tk.CENTER,textvariable=show_str,font=("幼圆",30))
    
    # 棋盘显示label 0~9
    l0 = tk.Label(frame_cont1,textvariable=ch[0],font=("幼圆",30),padx=0)
    l1 = tk.Label(frame_cont1,textvariable=ch[1],font=("幼圆",30),padx=0)
    l2 = tk.Label(frame_cont1,textvariable=ch[2],font=("幼圆",30),padx=0)
    
    l3 = tk.Label(frame_cont2,textvariable=ch[3],font=("幼圆",30),padx=0)
    l4 = tk.Label(frame_cont2,textvariable=ch[4],font=("幼圆",30),padx=0)
    l5 = tk.Label(frame_cont2,textvariable=ch[5],font=("幼圆",30),padx=0)
    
    l6 = tk.Label(frame_cont3,textvariable=ch[6],font=("幼圆",30),padx=0)
    l7 = tk.Label(frame_cont3,textvariable=ch[7],font=("幼圆",30),padx=0)
    l8 = tk.Label(frame_cont3,textvariable=ch[8],font=("幼圆",30),padx=0)
    
    label_bottom = tk.Label(frame_bot,justify=tk.CENTER,textvariable=tips,font=("幼圆",20),padx=0)
    
    def update_chess():
        """
        更新棋盘
        """
        for i in range(9):
            if the_chess[i] == 1 :
                ch[i].set('|X|')
            elif the_chess[i] == -1 :
                ch[i].set('|O|')
            else :
                ch[i].set('| |')
            #print(i)
    
    def init_ch():
        """
        初始化棋盘
        """
        for i in range(9):
            the_chess[i] = init_chess[i]
        update_chess()
        return the_chess
    
    def ai_go_first():
        if chess.count_zero(the_chess) == 9:
            the_chess[random.randint(0,8)] = -1
        update_chess()
        forget()
    
    ai_go_fir_b = tk.Button(frame_cont,text='机器先下',command=ai_go_first)
    
    def forget():
        ai_go_fir_b.pack_forget()
    
    def but1():
        """
        人机对战
        """
        flag = True
        init_ch()
        tips.set("人机对战模式")
        l0.bind("<Button-1>", touch_l0)
        l1.bind("<Button-1>", touch_l1)
        l2.bind("<Button-1>", touch_l2)
        l3.bind("<Button-1>", touch_l3)
        l4.bind("<Button-1>", touch_l4)
        l5.bind("<Button-1>", touch_l5)
        l6.bind("<Button-1>", touch_l6)
        l7.bind("<Button-1>", touch_l7)
        l8.bind("<Button-1>", touch_l8)
        ai_go_fir_b.pack(side=tk.TOP)
    
    def run(i,id):
        """
        创建一个机器对打局
        """
        new_chess = init_chess[:]
        global count_x
        global count_y
        global count_z
        if id == 1 :
            new_chess[random.randint(0,8)] = -1
        else :
            new_chess[random.randint(0,8)] = 1
        x = 0
        for x in range(10):
            if chess.count_zero(new_chess) > 0 :
                #print(chess.count_zero(new_chess))
                if id == 1:
                    #print('*****')
                    #print(chess.get_next_x(new_chess,id))
                    pos = chess.get_next_x(new_chess,id)
                    if pos != -1 :
                        new_chess[int(chess.get_next_x(new_chess,id))] = id
                    else :
                        for xx in range(9):
                            if new_chess[xx] == 0 :
                                new_chess[xx] = id
                else :
                    pos = chess.get_next_o(new_chess,id)
                    if pos != -1 :
                        new_chess[int(chess.get_next_o(new_chess,id))] = id
                    else :
                        for xx in range(9):
                            if new_chess[xx] == 0 :
                                new_chess[xx] = id
                id = id * -1
                if chess.is_win(new_chess,id) == id :
                    name = ''
                    if id == 1 :
                        name = 'X'
                        update_chess()
                        print("第 {} 局 : {} 赢了!".format(i+1,name) + ' ' + str(new_chess))
                        tips.set("第 {} 局 : {} 赢了!".format(i+1,name))
                        threading.Lock()
                        count_x = count_x + 1
                        threading.RLock()
                        time.sleep(3)
                        break
                    else :
                        name = 'O'
                        update_chess()
                        print("第 {} 局 : {} 赢了!".format(i+1,name) + ' ' + str(new_chess))
                        tips.set("第 {} 局 : {} 赢了!".format(i+1,name))
                        threading.Lock()
                        count_y = count_y + 1
                        threading.RLock()
                        time.sleep(3)
                        break
                elif chess.is_win(new_chess,id*-1) == id*-1 :
                    id = id * -1
                    name = ''
                    if id == 1 :
                        name = 'X'
                        print("第 {} 局 : {} 赢了!".format(i+1,name) + ' ' + str(new_chess))
                        tips.set("第 {} 局 : {} 赢了!".format(i+1,name))
                        threading.Lock()
                        count_x = count_x + 1
                        threading.RLock()
                        break
                    else :
                        name = 'O'
                        print("第 {} 局 : {} 赢了!".format(i+1,name) + ' ' + str(new_chess))
                        tips.set("第 {} 局 : {} 赢了!".format(i+1,name))
                        threading.Lock()
                        count_y = count_y + 1
                        threading.RLock()
                        break
                elif chess.count_zero(new_chess) == 0:
                    print("第 {} 局 : 平局".format(i+1) + ' ' + str(new_chess))
                    tips.set("第 {} 局 : 平局".format(i+1))
                    threading.Lock()
                    count_z = count_z + 1
                    threading.RLock()
                    break
                else :
                    pass
            else :
                print("第 {} 局 : 平局".format(i+1) + ' ' + str(new_chess))
                tips.set("第 {} 局 : 平局".format(i+1))
                threading.Lock()
                count_z = count_z + 1
                threading.RLock()
                break
        #print(str(i + 1) + ' ' + str(new_chess))
        '''if i == 9:
            print("第 {} 局 : 平局".format(i+1) + ' ' + str(new_chess))
            tips.set("第 {} 局 : 平局".format(i+1))
            threading.Lock()
            count_z = count_z + 1
            threading.RLock()'''
        time.sleep(3)
        for i in range(9):
            the_chess[i] = new_chess[i]
        update_chess()
        threading.Lock()
        tips.set("50局已经结束!
    X 共赢 {}次
    O 共赢 {}次
    平局 {} 次".format(count_x,count_y,count_z))
        threading.RLock()
    
    def but2():
        """
        机器对战
        """
        print(" ")
        ai_go_fir_b.pack_forget()
        flag = False
        global count_x
        global count_y
        global count_z
        count_x = 0
        count_y = 0
        count_z = 0
        init_ch()
        tips.set("机器对战模式")
        l0.unbind("<Button-1>")
        l1.unbind("<Button-1>")
        l2.unbind("<Button-1>")
        l3.unbind("<Button-1>")
        l4.unbind("<Button-1>")
        l5.unbind("<Button-1>")
        l6.unbind("<Button-1>")
        l7.unbind("<Button-1>")
        l8.unbind("<Button-1>")
        id = 1
        th = []
        for i in range(50):
            id = id * -1
            try:
                th.append(threading.Thread(target=run,args=(i,id)))
                th[i].start()
            except Exception as e:
                print(e)
                i = i - 1
        #tips.set("50 局已经结束! X 共赢 {}次, O 共赢 {}次, 平局 {} 次".format(count_x,count_y,count_z))
            
    
    def ai_go(w):
        """
        机器走棋 O
        """
        if chess.count_zero(the_chess) < 9:   
            po = chess.is_danger(the_chess,1)
            if po != -1 :
                the_chess[po] = w
                update_chess()
            elif constraint(w) == False:
                pass
            else :
                the_chess[chess.get_next_o(the_chess,-1)] = w
                update_chess()
            if chess.is_win(the_chess,-1) == -1:
                tips.set("你输了!")
        if chess.count_zero(the_chess) == 0:
            tips.set("平局!")
    
    def constraint(w):
        """
        判断是否处于危险状态
        """
        po = chess.is_danger(the_chess,-1)
        if po != -1:
            the_chess[po] = w
            update_chess()
            return False
        return True
    
    def peo_go(po):
        """
        获取人们按键,并下棋
        """
        if the_chess[po] == 0 :
            the_chess[po] = who
            update_chess()
            if chess.is_win(the_chess,who) == who:
                tips.set('你赢了!')
            elif chess.count_zero(the_chess) == 0:
                tips.set("平局!")
            else :
                ai_go(who*-1)
    
    def touch_l0(e):
        peo_go(0)
    
    def touch_l1(e):
        peo_go(1)
    
    def touch_l2(e):
        peo_go(2)
    
    def touch_l3(e):
        peo_go(3)
    
    def touch_l4(e):
        peo_go(4)
    
    def touch_l5(e):
        peo_go(5)
    
    def touch_l6(e):
        peo_go(6)
    
    def touch_l7(e):
        peo_go(7)
    
    def touch_l8(e):
        peo_go(8)
            
    
    tk.Button(frame_top,text='人机对决',command=but1).pack(side=tk.LEFT)
    tk.Button(frame_top,text='机器对决',command=but2).pack(side=tk.RIGHT)
    
    update_chess()
    
    l0.pack(side=tk.LEFT)
    l1.pack(side=tk.LEFT)
    l2.pack(side=tk.LEFT)
    l3.pack(side=tk.LEFT)
    l4.pack(side=tk.LEFT)
    l5.pack(side=tk.LEFT)
    l6.pack(side=tk.LEFT)
    l7.pack(side=tk.LEFT)
    l8.pack(side=tk.LEFT)
    
    label_bottom.pack()
    
    frame_cont1.pack()
    frame_cont2.pack()
    frame_cont3.pack()
    
    frame_top.pack()
    frame_cont.pack()
    frame_bot.pack()
    
    top.mainloop()
    

    结束回顾:

    • ① 一开始计算最佳走法我用迭代函数,但是一直写不出最佳的效果,后面直接改成循环2/3次遍历所有可能,计算所有可能的评估值。
    • ② 一开始的评估函数只有计算可能赢得边数,我发现在实际过程中有不准的情况,于是我添加了几个两个条件做为附加条件,可以使得一条直线已经有己方棋子的权重加大,可以直接导致己方赢得走法加大权重,导致对方赢的降低权重。X方选择大者,O方选择值小者。
    • ③ 在机器对战中我用多线程并行计算,发现平局的情况会导致线程卡死,无法准确计算出平局的次数。后面发现在最佳走法中,有时因为棋盘剩余格子不多,导致遍历失败,返回空值,使得机器无子可下的情况。为了解决这个问题,我在最佳走法中添加了一个判断,若棋盘剩余格子不多,则舍弃部分遍历,只遍历一轮,若只剩一个位置,则返回该位置。
    • ④ 人机对战中目前只发现一种赢局方式:先走角落易赢,机器走法单一,即类似下面情况:
      X _ O
      O O _
      X X X
      在机器优先中还未找到赢局方法
    • ⑤ 起初机器对战的结果只有固定几种,导致输赢单一,于是我先让XO先下棋方随机,再让第一子的位置随机,计算出的结果便就有意义了,出现了许多种结果。
      这次实验我使用了tkinter库,百度了许多相关使用的方法。例如,mainloop窗口消息循环尽量以独立线程运行,避免有使其阻塞的语句,起初我不知道,把机器对战五十次直接写在循环里,便导致GUI卡死。Tkinter中可以随着值变化而改变GUI显示文本的参数是StringVar,隐藏控件用pack_forget
    • ⑥ 为了避免评估值相同无法保证最佳位置,我设置了一个遍历顺序:[4,0,2,6,8,1,3,5,7],先中间,再四角,最后四边

    附录

  • 相关阅读:
    NeurIPS 2018 中的贝叶斯研究
    史上最全采样方法详细解读与代码实现
    采样方法(二)MCMC相关算法介绍及代码实现
    第七十四篇:机器学习优化方法及超参数设置综述
    论文阅读:《Bag of Tricks for Efficient Text Classification》
    训练技巧详解【含有部分代码】Bag of Tricks for Image Classification with Convolutional Neural Networks
    数值分析-Legendre正交多项式 实现函数逼近
    指针生成网络(Pointer-Generator-Network)原理与实战
    强化学习入门 第五讲 值函数逼近
    UniGUI的 TUniPageControl控件动态拖动tabsheet的实现方法
  • 原文地址:https://www.cnblogs.com/boxker/p/10580969.html
Copyright © 2020-2023  润新知