python3 井字棋 GUI - 人機對戰、機器對戰 (threading、tkinter庫)

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]python

估價函數(以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)點擊某個格子,觸發綁定事件app

  1. 判斷該格子是否有子,無子繼續
  2. 設置該格子爲 1
  3. 判斷X是否贏了,沒有繼續,有X贏
  4. 判斷棋盤是否無子可下,有繼續,沒有平局
  5. 輪到機器下棋
  6. 判斷O是否差一子即可贏,有則對應格子,無則繼續
  7. 判斷O是否處於危險狀態,即對方只差一子贏棋,有則選擇該格子,無則繼續
  8. 機器選擇最佳格子
  9. 判斷輸贏並判斷是有無子可下,循環

機器對戰流程

  1. 隨機產生先下棋者
  2. 第一顆棋子隨機下
  3. 各自判斷最佳走法
  4. 判斷輸贏即棋盤是否無子可下
  5. 循環

總結

1. 學習了python threading庫的用法

線程的使用:dom

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作爲主線程儘可能避免被阻塞,以避免界面卡死ide

建立窗口:函數

top = tk.Tk()#建立窗口
top.title('井字棋 -> Fighting')#標題
top.geometry("300x300")#大小
top.resizable()#可改變大小

建立Frame:oop

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局已經結束!\nX 雙贏 {}次\nO 雙贏 {}次\n平局 {} 次".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],先中間,再四角,最後四邊

附錄

相關文章
相關標籤/搜索