运用博弈树算法编写的黑白棋游戏,运行环境为Python3.9。

        树的搜索深度默认为3(下一步向前分析2步),往前随着搜索深度的增大,算法更加聪明,但是分析时间会长很多。

        可以通过修改源码中的以下部分search_depth的值来设置树的搜索深度:

        #####################设置搜索深度###############

        global search_depth
        search_depth=3 #修改search_depth的值以改变搜索深度

        #####################设置搜索深度###############

        当搜索深度为4时,程序胜率较高,但由于没有启动多线程,下子后算法分析时会卡住,一般要等待半分钟左右(时间关系,没有进行剪枝处理)。由于本程序主要关注算法,用户体验先忽略不计。勿喷。

运行如下:

源码如下:

import tkinter as tk
import numpy as np
from collections import Counter
from tkinter import StringVar, IntVar

current_player=0;#0 represents the user while 1 represents the algrithm 
current_situation=np.zeros([10,10])
current_situation[4][4]=1
current_situation[5][4]=-1
current_situation[4][5]=-1
current_situation[5][5]=1
#current_situation[5][6]=-1
#current_situation[6][6]=1
#current_situation[4][3]=-1
#current_situation[3][5]=-1
'''
current_situation[3][6]=-1
current_situation[5][7]=1
current_situation[5][8]=1
current_situation[4][7]=-1
current_situation[3][7]=-1
current_situation[6][9]=-1'''

def dropchess(event):
    global current_player,current_situation,depth,depthr,turn
    row=int(event.y/50)
    col=int(event.x/50)
    drop_points,change_points=get_avalible_drop(current_player,current_situation)
    set_change_points_on(drop_points,change_points,[row,col])
    if current_player==0 and [row,col] in drop_points:
        current_situation[row][col]=-1
    if current_player==0:
        current_player=1
    else:
        current_player=0
   
    #算法下子
    situation=current_situation.copy()
    #print(situation)
    bestdrop=getbestdrop(situation)
    #print(bestdrop)
    drop_points,change_points=get_avalible_drop(current_player,current_situation)
    set_change_points_on(drop_points,change_points,bestdrop)
    current_situation[bestdrop[0]][bestdrop[1]]=1
    current_player=0
    depth=0
    depthr=0
    #turn.set('轮到你下子o...')
    #算法下子
    drop_points,change_points=get_avalible_drop(current_player,current_situation)
    set_avalible_drop_on(drop_points)
    draw_Chess_from_Maxtrix(current_situation)
    clear_avalible_drop(drop_points)
    statistical_num(current_situation)
    turn.set(' ')

        
def draw_Chess_from_Maxtrix(current_situation):
    for i in range(len(current_situation)):
        for j in range(len(current_situation[0])):
            if current_situation[i][j]==1:
                canvas.create_oval(j*50+6,i*50+6,j*50+44,i*50+44,fill='white')#白色是算法,黑色是用户
            if current_situation[i][j]==-1:
                canvas.create_oval(j*50+6,i*50+6,j*50+44,i*50+44,fill='black')
            if current_situation[i][j]==2:
                canvas.create_oval(j*50+6,i*50+6,j*50+44,i*50+44,outline='red')
            if current_situation[i][j]==0:
                canvas.create_oval(j*50+6,i*50+6,j*50+44,i*50+44,outline='green')
                

def set_avalible_drop_on(drop_points):#通过可下子位置列表绘制可下子标识
    global current_situation
    for point in drop_points:
        current_situation[point[0]][point[1]]=2
        
def clear_avalible_drop(drop_points):#每一次绘制完棋子以及可下子标识后,清空当前局面矩阵可下子点
    global current_situation
    for point in drop_points:
        current_situation[point[0]][point[1]]=0

def set_change_points_on(avalible_drop,change_points,coordinate):
    global current_situation
    idx=avalible_drop.index(coordinate)
    points=change_points[idx]
    for point in points:
        if current_situation[point[0]][point[1]]==1:
            current_situation[point[0]][point[1]]=-1
        else:
            current_situation[point[0]][point[1]]=1
            
    
def get_avalible_drop(current_player,current_situation):#通过当前玩家序号,当前局面矩阵以及落子点计算新的局面矩阵
    ava=[]
    ava_row=[]
    if current_player==0:
        current_situation=current_situation*-1;
    for row in range(len(current_situation)):
        for col in range(len(current_situation[0])):
            ava_row=search_row(row,col,current_player,current_situation)
            ava_col=search_col(row,col,current_player,current_situation)
            ava_dig=search_diagonal(row,col,current_player,current_situation)
            ava_fdig=search_fdiagonal(row,col,current_player,current_situation)
            if ava_row!=[]:
                ava.append(ava_row)
            if ava_col!=[]:
                ava.append(ava_col)
            if ava_dig!=[]:
                ava.append(ava_dig)
            if ava_fdig!=[]:
                ava.append(ava_fdig)
    drop_points,change_points=format_avalible_drop(ava)
    current_situation=current_situation*-1
    return drop_points,change_points

            

def search_row(row,col,current_player,current_situation):#竖向搜索 输入坐标,玩家序号,当前局面矩阵判断是否可以落子
    avalible_row=[]
    avalible_row_u=[]
    avalible_row_d=[]
    if current_situation[row][col]==0:
        for i in range(row+1,10):
            if current_situation[i][col]==-1:
                if i==9:
                    avalible_row_u=[]
                    break
                avalible_row_u.append([i,col])
            else:
                if current_situation[i][col]==0:
                    avalible_row_u=[]
                break
        for i in range(1,row+1):
            if current_situation[row-i][col]==-1:
                if row-i==0:
                    avalible_row_d=[]
                    break
                avalible_row_d.append([row-i,col])
            else:
                if current_situation[row-i][col]==0:
                    avalible_row_d=[]
                break
    avalible_row=avalible_row_u+avalible_row_d
    if len(avalible_row)>0:
        return [[row,col],avalible_row]
    else:
        return []

def search_col(row,col,current_player,current_situation):#横向搜索
    avalible_col_u=[]
    avalible_col_d=[]
    avalible_col=[]
    if current_situation[row][col]==0:
        for i in range(col+1,10):
            if current_situation[row][i]==-1:
                if i==9:
                    avalible_col_u=[]
                    break
                avalible_col_u.append([row,i])
            else:
                if current_situation[row][i]==0:
                    avalible_col_u=[]
                break
        for i in range(1,col+1):
            if current_situation[row][col-i]==-1:
                if col-i==0:
                    avalible_col_d=[]
                    break
                avalible_col_d.append([row,col-i])
            else:
                if current_situation[row][col-i]==0:
                    avalible_col_d=[]
                break
    avalible_col=avalible_col_u+avalible_col_d
    if len(avalible_col)>0:
        return [[row,col],avalible_col]
    else:
        return []

def search_diagonal(row,col,current_player,current_situation):#正对角线搜索
    avalible_dig_u=[]
    avalible_dig_d=[]
    avalible_dig=[]
    if current_situation[row][col]==0:

        if row+col>=9:
            for i in range(1,10-col):
                if current_situation[row-i][col+i]==-1:
                    if col+i==9:
                        avalible_dig_u=[]
                        break
                    avalible_dig_u.append([row-i,col+i])
                else:
                    if current_situation[row-i][col+i]==0:
                        avalible_dig_u=[]
                    break
            for i in range(1,10-row):
                if current_situation[row+i][col-i]==-1:
                    if row+i==9:
                        avalible_dig_d=[]
                        break
                    avalible_dig_d.append([row+i,col-i])
                else:
                    if current_situation[row+i][col-i]==0:
                        avalible_dig_d=[]
                    break
        else:
            for i in range(1,row+1):
                if current_situation[row-i][col+i]==-1:
                    if row-i==0:
                        avalible_dig_u=[]
                        break
                    avalible_dig_u.append([row-i,col+i])
                else:
                    if current_situation[row-i][col+i]==0:
                        avalible_dig_u=[]
                    break
            for i in range(1,col+1):
                if current_situation[row+i][col-i]==-1:
                    if col-i==0:
                        avalible_dig_d=[]
                        break
                    avalible_dig_d.append([row+i,col-i])
                else:
                    if current_situation[row+i][col-i]==0:
                        avalible_dig_d=[]
                    break
    avalible_dig=avalible_dig_u+avalible_dig_d
    if len(avalible_dig)>0:
        return [[row,col],avalible_dig]
    else:
        return []

def search_fdiagonal(row,col,current_player,current_situation):#副对角线搜索
    avalible_fdig=[]
    avalible_fdig_u=[]
    avalible_fdig_d=[]
    if current_situation[row][col]==0:

        if row<=col:
            for i in range(1,row+1):
                if current_situation[row-i][col-i]==-1:
                    if row-i==0:
                        avalible_fdig_u=[]
                        break
                    avalible_fdig_u.append([row-i,col-i])
                else:
                    if current_situation[row-i][col-i]==0:
                        avalible_fdig_u=[]
                    break
            for i in range(1,10-col):
                if current_situation[row+i][col+i]==-1:
                    if col+i==9:
                        avalible_fdig_d=[]
                        break
                    avalible_fdig_d.append([row+i,col+i])
                else:
                    if current_situation[row+i][col+i]==0:
                        avalible_fdig_d=[]
                    break
        else:
            for i in range(1,col+1):
                if current_situation[row-i][col-i]==-1:
                    if col-i==0:
                        avalible_fdig_u=[]
                        break
                    avalible_fdig_u.append([row-i,col-i])
                else:
                    if current_situation[row-i][col-i]==0:
                        avalible_fdig_u=[]
                    break
            for i in range(1,10-row):
                if current_situation[row+i][col+i]==-1:
                    if row+i==9:
                        avalible_fdig_d=[]
                        break
                    avalible_fdig_d.append([row+i,col+i])
                else:
                    if current_situation[row+i][col+i]==0:
                        avalible_fdig_d=[]
                    break
    avalible_fdig=avalible_fdig_u+avalible_fdig_d
    if len(avalible_fdig)>0:
        return [[row,col],avalible_fdig]
    else:
        return []
    
def format_avalible_drop(drop_list):
    drop_points=[]
    change_points=[]
    for i in range(len(drop_list)):
        if drop_list[i][0] in drop_points:
            change_points[len(change_points)-1].append(drop_list[i][1][0])
        else:
            drop_points.append(drop_list[i][0])
            change_points.append(drop_list[i][1])
    return drop_points,change_points


##############################################建立博弈树##########################################

def MultipleTree(r):#建立一棵新树,根节点为r
    return [r,[]]

def insertTree(root, newBranch): #在树的root节点(不一定是根节点)插入新枝newBranch
    root[root.index([])]=[newBranch,[],[],[],[]]
    return root

def LoadTree(root):#递归建立一棵博弈树,当深度为depth时停止递归
    global depth,search_depth
    #print((depth)%2)
    depth+=1
    #print(depth)
    bs=getbranch((depth)%2,root[0][0])
    if len(bs)==0:
        return root
    elif depth>=search_depth:
        return root
    for b in bs:
        root.insert(root.index([]),LoadTree(b))
        depth-=1
    return root

def getbranch(player,situation):#获得root节点(不一定是根节点)的所有叶子节点的值
    #这里可以代入每一个局面的所有可下子点
    rr=[]
    #print('situation is')
    #print('player is'+str(player))
    #print(situation)
    drop_points,change_points=get_avalible_drop_rc(player,situation.copy())
    #print('drop_points is'+str(drop_points))
    for drop in drop_points:
        new=get_new_situation(player,situation.copy(),drop)
        #print('new_situation is')
        #print('player is'+str(player))
        #print(new)
        rr.append([new,[]])
    #print(rr)
    return rr


def getChildren(root):#获取一个节点的所有子节点
    c=[]
    for i in range(1,100):
        if root[i]!=[]:
            c.append(root[i])
        else:
            break
    return c

def hasChild(root):#判断一个节点是否有子节点
    if root[1]==[]:
        return False
    else:
        return True
    
def getMaxChild(root):#或者一个节点所有子节点的最大值
    ma=-9999999999
    c=getChildren(root)
    if len(c)>0:
        for cc in c:
            if cc[0][2]>ma:
                ma=cc[0][2]
        return ma
    else:
        return root[0][2]

def getMinChild(root):#或者一个节点所有子节点的最小值
    mi=9999999999
    c=getChildren(root)
    if len(c)>0:
        for cc in c:
            if cc[0][2]<mi:
                mi=cc[0][2]
        return mi
    else:
        return root[0][2]
    
def getMaxChildDrop(root):#获得Max and Min的下子点
    ma=-9999999999
    drop=[-1,-1]
    c=getChildren(root)
    if len(c)>0:
        for cc in c:
            #print(cc[0][2],cc[0][1])
            if cc[0][2]>ma:
                ma=cc[0][2]
                drop=cc[0][1]
        return drop
    else:
        return root[0][1]
    
    
def Max_and_Min(root):
    global depthr
    depthr+=1
    if hasChild(root):
        for i in range(1,len(root)-1):
            root[i][0][0]=''
            root[i][0][2]=Max_and_Min(root[i])[0]
            depthr-=1
    if depthr%2==1:
        mc=getMaxChild(root)
    else:
        mc=getMinChild(root)
    root[0][0]=''
    root[0][2]=mc
    return [mc,root]

###########################################用于递归函数的子函数#########################################

def set_change_points_on_rc(avalible_drop,change_points,situation,coordinate):
    #print('set_change_points_on_rc..............')
    #print('avalible_drop is:'+str(avalible_drop))
    idx=avalible_drop.index(coordinate)
    points=change_points[idx]
    for point in points:
        if situation[point[0]][point[1]]==1:
            situation[point[0]][point[1]]=-1
        else:
            situation[point[0]][point[1]]=1
    return situation
            
    
def get_avalible_drop_rc(player,situation):#通过当前玩家序号,当前局面矩阵以及落子点计算新的局面矩阵
    #print('get_avalible_drop_rc..............')
    #print('player ='+str(player))
    #print('situation='+str(situation))
    ava=[]
    ava_row=[]
    #print(player)
    if player==0:
        situation=situation*-1;
    for row in range(len(situation)):
        for col in range(len(situation[0])):
            ava_row=search_row(row,col,player,situation)
            ava_col=search_col(row,col,player,situation)
            ava_dig=search_diagonal(row,col,player,situation)
            ava_fdig=search_fdiagonal(row,col,player,situation)
            if ava_row!=[]:
                ava.append(ava_row)
            if ava_col!=[]:
                ava.append(ava_col)
            if ava_dig!=[]:
                ava.append(ava_dig)
            if ava_fdig!=[]:
                ava.append(ava_fdig)
    drop_points,change_points=format_avalible_drop(ava)
    situation=situation*-1
    #print('++++++++++++++++'+str(drop_points))
    return drop_points,change_points


def get_new_situation(player,situation,drop):#根据可下子列表设置当前局面矩阵
    drop_points,change_points=get_avalible_drop_rc(player,situation)
    set_change_points_on_rc(drop_points,change_points,situation,drop)
    if player==1 and drop in drop_points:
        situation[drop[0]][drop[1]]=1
    if player==0 and drop in drop_points:
        situation[drop[0]][drop[1]]=-1
    player_algrithm,player_user=statistical(situation)
    advantage=player_algrithm-player_user
    return [situation,drop,advantage]

def getbestdrop(situation):
    roott=MultipleTree([situation,[-1,-1],0])
    r=LoadTree(roott)
    mc,rr=Max_and_Min(r)
    bestdrop=getMaxChildDrop(rr)
    return bestdrop

def statistical(situation):#根据一个局面矩阵计算黑白子的数量
    global white_num
    global black_num
    player_algrithm=0
    player_user=0
    for row in situation:
        for val in row:
            if val==1:
                player_algrithm+=1
            if val==-1:
                player_user+=1
    return player_algrithm,player_user

def statistical_num(situation):#根据一个局面矩阵计算黑白子的数量
    global white_num
    global black_num
    player_algrithm=0
    player_user=0
    for row in situation:
        for val in row:
            if val==1:
                player_algrithm+=1
            if val==-1:
                player_user+=1
    white_num.set(str(player_algrithm))
    black_num.set(str(player_user))
    return player_algrithm,player_user



###########################################用于递归函数的子函数#########################################

#####################设置搜索深度###############

global search_depth
search_depth=3

#####################设置搜索深度###############


global depthr
global depth
global white_num
global black_num
global turn
depth=0
depthr=0
white_num=2
black_num=2

canvas=''
white_label=''
black_label=''
playerturn_label=''

rt=tk.Tk(className="bw")
rt.resizable(0,0)

white_num= StringVar()
white_num.set('2')
black_num= StringVar()
black_num.set('2')
turn= StringVar()
turn.set('轮到你下子...')

canvas=tk.Canvas(rt,width=700,height=500,bg='green')
canvas.bind()
canvas.pack(padx=10,pady=10)

white_label=tk.Label(rt,width=4,height=1,textvariable=white_num)
white_label.place(x=600,y=220)
black_label=tk.Label(rt,width=4,height=1,textvariable=black_num)
black_label.place(x=600,y=290)
drop_label=tk.Label(rt,width=7,height=1,text='可下子点')
drop_label.place(x=600,y=360)
playerturn_label=tk.Label(rt,width=10,height=2,bg='green',textvariable=turn)
playerturn_label.place(x=550,y=150)


canvas.create_line(0,0,0,500,fill='black',width=10)
canvas.create_line(0,0,500,0,fill='black',width=10)
canvas.create_line(500,500,0,500,fill='black',width=3)
canvas.create_line(500,500,500,0,fill='black',width=3)
for i in range(10):
    canvas.create_line(50*i,0,50*i,500,fill='black',width=2)
    canvas.create_line(0,50*i,500,50*i,fill='black',width=2)
canvas.bind(sequence='<Button-1>',func=dropchess)
canvas.create_oval(540,200,580,240,fill='white')#白色是算法,黑色是用户
canvas.create_oval(540,270,580,310,fill='black')#白色是算法,黑色是用户
canvas.create_oval(540,340,580,380,outline='red')#白色是算法,黑色是用户
drop_points,change_points=get_avalible_drop(current_player,current_situation)
set_avalible_drop_on(drop_points)
draw_Chess_from_Maxtrix(current_situation)
clear_avalible_drop(drop_points)
rt.mainloop()

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐