Minmax tic-tac-toe算法永不丢失

时间:2016-12-20 17:24:04

标签: python algorithm tree minmax

我试图为永不丢失的Tic-Tac-Toe构建一个min-max算法...

我尝试通过阅读几个来源来构建它:

  1. http://neverstopbuilding.com/minimax
  2. http://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-3-tic-tac-toe-ai-finding-optimal-move/(我建造的东西与此非常相似)。
  3. 这是代码: class tree:

    def find_best_move(self,board,depth,myTurn,sign):
        """
    
        :param board:
        :return:
        """
        if (board.empty==[]): return None
    
        best_move=-(2**(board.s**2))
        m=board.empty[0]
        for move in board.empty:
            b=copy.deepcopy(board)
            b.ins(move,sign)
            if (self.is_win(b,sign) or self.is_win(b,xo.opp_sign(sign))):
                return move
            curr_move=self.minimax(b,depth,myTurn,sign)
            if (curr_move > best_move):
                best_move = curr_move
                m=move
            print(curr_move,best_move,m)
    
        return m #This should be the right move to do....
    
    
    # *****************************************************************************************************#
    
    def minimax(self,board,depth,myTurn,sign):
        """
    
        :param depth:
        :param myTurn:
        :return:
        """
        #print(depth,end='\n')
        if (self.is_win(board,sign)):
            #print("I win!")
            return (board.s**2+1) - depth
        elif (self.is_win(board,xo.opp_sign(sign))):
            #print("You win!")
            return -(board.s**2+1) + depth
        elif (board.is_full()):
            return 0
    
        if (myTurn):
            bestVal=-(2**700)
            for move in board.empty: #empty - the empty squares at the board 
                b = copy.deepcopy(board)
                b.ins(move, sign)
                value=self.minimax(b,depth+1,not myTurn, xo.opp_sign(sign))
                #xo.opp_sign(sign) - if function for the opposite sign: x=>o and o=>x
                bestVal = max([bestVal,value])
            return bestVal
    
        else:
            bestVal = (2**700)
            for move in board.empty:
                b = copy.deepcopy(board)
                b.ins(move, xo.opp_sign(sign))
                value = self.minimax(b, depth + 1, not myTurn, xo.opp_sign(sign))
                #print("opp val: ",value)
                bestVal = min([bestVal, value])
            return bestVal
    
    
    # *****************************************************************************************************#
    def is_win(self,board, sign):
        """
        The function gets a board and a sign.
        :param board: The board.
        :param sign: The sign (There are only two options: x/o).
        :return: True if sign "wins" the board, i.e. some row or col or diag are all with then sing. Else return False.
        """
    
        temp=board.s
        wins = []  # The options to win at the game.
        for i in range(1, temp + 1):
            wins.append(board.get_col(i))
            wins.append(board.get_row(i))
        wins.append(board.get_diag1())
        wins.append(board.get_diag2())
    
        for i in wins:
            if (self.is_same(i, sign)):
                return True
        return False
    
    
    
    # *****************************************************************************************************#
    def is_same(self, l, sign):
        """
        The function get a list l and returns if ALL the list have the same sign.
        :param l: The list.
        :param sign: The sign.
        :return: True or false
        """
    
        for i in l:
            if (i != sign):
                return False
        return True
    

    如果我的代码有问题请告诉我!

    但是,我总能打败这个 - 我只需要做一个" fork"
    .e.g。:(我' m x,算法是o)

    xx-   
    xo-   
    -o- 
    

    我赢了...
    有用于制作可以阻挡货叉的树的算法吗?

1 个答案:

答案 0 :(得分:1)

您有三个错误。

1。在 minimax 方法中,符号交换次数太多

您交换了else区块中的标志 - 对于 myTurn False的情况 - 但您不应该这样做。您已经在每个递归调用中交换了符号。由于这个错误,你总是在你的极小极大搜索期间在板上放置相同的标志,而不是相反的标志。显然,你因此错过了对手的所有威胁。

所以改变:

    else:
        bestVal = (2**700)
        for move in board.empty:
            b = copy.deepcopy(board)
            b.ins(move, error xo.opp_sign(sign)) # <-- bug

为:

    else:
        bestVal = (2**700)
        for move in board.empty:
            b = copy.deepcopy(board)
            b.ins(move, sign) # <-- corrected 

2。在 find_best_move 中,您应该在调用 minimax

时交换此移动

find_best_move 中也出现了类似的错误。当你完成每个动作时,你必须在新棋盘中调用 minimax 时交换标志,否则你让同一个玩家玩两次。

所以改变这个:

    for move in board.empty:
        b=copy.deepcopy(board)
        b.ins(move,sign)
        if (self.is_win(b,sign) or self.is_win(b,xo.opp_sign(sign))):
            return move
        curr_move=self.minimax(b,depth,myTurn,sign) # <-- bug

为:

    for move in board.empty:
        b=copy.deepcopy(board)
        b.ins(move,sign)
        if (self.is_win(b,sign) or self.is_win(b,xo.opp_sign(sign))):
            return move
        curr_move=self.minimax(b,depth,not myTurn,xo.opp_sign(sign)) # <-- corrected

请注意,第二个条件不一定是必要的:如果你是刚搬家的人,那么另一个人应该获胜是不合逻辑的。

3。在 minimax 中,当有胜利时,你不会考虑 myTurn 的价值

虽然您考虑 myTurn 的值来确定是否最小化或最大化,但在检查获胜时您不会执行此操作。

你目前有这个:

if (self.is_win(board,sign)):
    #print("I win!")
    return (board.s**2+1) - depth
elif (self.is_win(board,xo.opp_sign(sign))):
    #print("You win!")
    return -(board.s**2+1) + depth
elif (board.is_full()):
    return 0

首先,第一个if条件不应该是真的,因为最近的移动是针对相反的符号,因此永远不会导致 sign 的胜利。

但问题是:第二个if不会查看 myTurn 来确定返回值是负数还是正数。它应该这样做是一致的。所以将上面的代码更改为:

if self.is_win(board,xo.opp_sign(sign)):
    if myTurn: 
        return -(board.s**2+1) + depth
    else:
        return (board.s**2+1) - depth
elif board.is_full():
    return 0

如何致电 find_best_move

最后,如果您始终使用 myTurn 参数调用 find_best_move 作为True,则上述方法有效,因为 find_best_move 会使结果最大化从if (curr_move > best_move)可以看出。因此,为了避免您使用False调用它,您最好删除此参数并将False传递给 minimax 。所以:

def find_best_move(self,board,depth,sign): # <-- myTurn removed as argument
    # ... etc ...

        curr_move=self.minimax(b,depth,False,xo.opp_sign(sign)) # pass False

这样,参数 myTurn 表示转牌是否与调用了 find_best_move 的玩家相同。

工作解决方案

添加最少的代码以使其有效(添加了BoardXO个类),可以看到程序在repl.it上运行。

请注意,此算法不是最佳的。这只是蛮力。您可以查看存储先前评估的位置的结果,执行alpha-beta修剪等...