Minimax解释了一个白痴

时间:2010-10-18 02:27:00

标签: python minimax tic-tac-toe

我浪费了一整天努力使用minimax算法制作无与伦比的tictactoe AI。我一路上都错过了一些东西(脑部油炸)。

我不是在这里寻找代码,只是更好地解释我出错的地方。

这是我当前的代码(minimax方法由于某种原因总是返回0):

from copy import deepcopy


class Square(object):
    def __init__(self, player=None):
        self.player = player

    @property
    def empty(self):
        return self.player is None


class Board(object):
    winning_combos = (
        [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8],
        [0, 4, 8], [2, 4, 6],
    )

    def __init__(self, squares={}):
        self.squares = squares
        for i in range(9):
            if self.squares.get(i) is None:
                self.squares[i] = Square()

    @property
    def available_moves(self):
        return [k for k, v in self.squares.iteritems() if v.empty]

    @property
    def complete(self):
        for combo in self.winning_combos:
            combo_available = True
            for pos in combo:
                if not pos in self.available_moves:
                    combo_available = False
            if combo_available:
                return self.winner is not None
        return True

    @property
    def player_won(self):
        return self.winner == 'X'

    @property
    def computer_won(self):
        return self.winner == 'O'

    @property
    def tied(self):
        return self.complete == True and self.winner is None

    @property
    def winner(self):
        for player in ('X', 'O'):
            positions = self.get_squares(player)
            for combo in self.winning_combos:
                win = True
                for pos in combo:
                    if pos not in positions:
                        win = False
                if win:
                    return player
        return None

    @property
    def heuristic(self):
        if self.player_won:
            return -1
        elif self.tied:
            return 0
        elif self.computer_won:
            return 1

    def get_squares(self, player):
        return [k for k,v in self.squares.iteritems() if v.player == player]

    def make_move(self, position, player):
        self.squares[position] = Square(player)

    def minimax(self, node, player):
        if node.complete:
            return node.heuristic
        a = -1e10000
        for move in node.available_moves:
            child = deepcopy(node)
            child.make_move(move, player)
            a = max([a, -self.minimax(child, get_enemy(player))])
        return a


def get_enemy(player):
    if player == 'X':
        return 'O'
    return 'X'

3 个答案:

答案 0 :(得分:18)

第1步:构建游戏树

从当前棋盘开始,产生对手可以做出的所有可能动作。 然后为每个人生成所有可能的动作。 对于Tic-Tac-Toe,只需继续直到没有人可以玩。在其他游戏中,您通常会在给定的时间或深度后停止。

这看起来像一棵树,自己画在一张纸上,当前的棋盘在顶部,所有对手在下面移动一层,所有你可能的动作响应一层以下等。

第2步:对树底部的所有电路板进行评分

对于像Tic-Tac-Toe这样的简单游戏,如果输掉,50分,100分,则得分为0.

第3步:在树上传播分数

这是min-max发挥作用的地方。以前没有考虑过的董事会的得分取决于其子女以及谁来参加比赛。假设你和你的对手总是选择在给定状态下最好的移动。对手的最佳举动是让得分最差的举动。同样,您的最佳举措是为您提供最高分。在对手轮到的情况下,您选择具有最低分数的孩子(最大化他的利益)。轮到你了,你假设你会做出最好的动作,所以你选择了最大值。

第4步:选择最佳动作

现在进行一次移动,在当前位置的所有可能的比赛中获得最佳传播得分。

在一张纸上试试,如果从空白板开始太多,你可以从一些先进的Tic-Tac-Toe位置开始。

使用递归: 通常使用递归可以简化这一过程。 “评分”功能在每个深度递归调用,并且取决于深度是否为奇数或者甚至分别为所有可能的移动选择最大值或最小值。当无法移动时,它会评估电路板的静态分数。递归解决方案(例如示例代码)可能有点难以理解。

答案 1 :(得分:7)

正如你已经知道的那样,Minimax的想法是深入寻找最佳价值,假设对手将始终以最差的价值进行移动(对我们来说最差,对他们来说最好)。

这个想法是,你会尝试为每个职位赋予一个价值。你失去的位置是负面的(我们不希望这样),你获胜的位置是积极的。你假设你总是会尝试最高价值的位置,但你也假设对手总是瞄准最低价值的位置,这对我们来说是最糟糕的结果,对他们来说是最好的(他们赢了,我们输了)。因此,你要把自己放在自己的位置,尝试尽可能好地玩,并假设他们会这样做 因此,如果你发现你可能有两个动作,一个让他们选择赢或输,一个导致平局,你认为如果你让他们这样做,他们会选择让他们获胜的动作。所以最好去抽奖。

现在提供更多“算法”视图。

想象一下,除了两个可能的位置之外,你的网格几乎已满 考虑一下你演奏第一首时会发生什么:
对手将扮演另一个。这是他们唯一可能的举动,所以我们不必考虑其他动作。查看结果,关联结果值(如果获胜则为+∞,如果是绘制则为0,如果丢失则为-∞:对于tic tac toe,您可以将其表示为+1 0和-1)。
现在考虑当你演奏第二个时会发生什么:
(同样在这里,对手只有一个动作,看看结果位置,重视位置)。

您需要在两个动作之间进行选择。这是我们的举动,所以我们想要最好的结果(这是minimax中的“max”)。选择结果较高的那个作为我们的“最佳”举措。这就是“从最后的2个移动”的例子。

现在想象一下你还没有2个但是还有3个动作 原理是一样的,你想为你的3个可能的动作分配一个值,这样你就可以选择最好的动作 所以你首先要考虑三个动作中的一个 你现在处于上面的情况,只有2个可能的动作,但这是对手的回合。然后你开始考虑对手的一个可能的动作,就像我们上面做的那样。同样,您查看每个可能的移动,并找到它们的结果值。这是对手的举动,所以我们假设他们将为他们发挥“最佳”的举动,对我们来说是投票率最高的那个,所以它是价值较小的那个(这是minimax中的“min”)。忽略另一个;假设他们会发挥你发现的最适合他们的东西。这就是你的举动会产生的,所以它是你分配给你的三个动作中的第一个的值。

现在你考虑其他每一个可能的2个动作。你以同样的方式给他们一个价值。从你的三个动作中,你选择一个具有最大值的动作。

现在考虑4次移动会发生什么。对于你的4个动作中的每一个,你看看你的对手的3次动作会发生什么,并且对于每个动作你都假设他们会选择能够为你提供剩下的2个动作中最好的结果的那个动作。< / p>

你知道这是怎么回事。要评估从最后开始的步骤,您可以查看n个可能的移动中可能发生的情况,尝试给它们一个值,以便您可以选择最佳状态。在此过程中,您将不得不尝试为在n-1下玩的玩家找到最佳移动:对手,然后选择值较小的步骤。在评估n-1移动的过程中,你必须在可能的n-2移动之间进行选择,这将是我们的移动,并假设我们将在此步骤中尽可能地发挥作用。等

这就是为什么这个算法本质上是递归的。无论如何,在步骤n,您将评估n-1处的所有可能步骤。冲洗并重复。

对于tic-tac-toe来说,今天的机器足够强大,足以在游戏开始时计算所有可能的结果,因为它们只有几百个。当你想要为更复杂的游戏实现它时,你将不得不在某个时候停止计算,因为它需要太长时间。因此,对于复杂的游戏,您还必须编写代码来决定是继续寻找所有可能的下一步动作,还是尝试现在为该位置赋值并提前返回。这意味着你还必须计算一个非最终位置的值 - 例如对于国际象棋,你会考虑每个对手在棋盘上有多少材料,没有配合检查的直接可能性,你控制多少个瓷砖和所有这些都使得它变得微不足道。

答案 2 :(得分:3)

您的完整功能未按预期工作,导致游戏在任何事情发生之前被声明为绑定。例如,请考虑以下设置:

>> oWinning = {
 1: Square('X'),
 3: Square('O'), 4: Square('X'),
 6: Square('O'), 8: Square('X'),
}
>> nb = Board(oWinning)
>> nb.complete
True
>> nb.tied
True

这应该是下一步计算机的胜利。相反,它说游戏是并列的。

问题在于,您现在的完整逻辑会检查组合中的所有方块是否都是空闲的。如果他们中的任何一个不是,那么它假定那个组合不能赢。它需要做的是检查该组合中的任何位置是否被占用,并且只要所有这些组合都是无或相同的玩家,该组合应被视为仍然可用。

e.g。

def available_combos(self, player):
    return self.available_moves + self.get_squares(player)

@property
def complete(self):
    for player in ('X', 'O'):
        for combo in self.winning_combos:
            combo_available = True
            for pos in combo:
                if not pos in self.available_combos(player):
                   combo_available = False
            if combo_available:
                return self.winner is not None
    return True

现在我已使用更新的代码对此进行了正确测试,我在此测试用例中获得了预期的结果:

>>> nb.minimax(nb, 'O')
-1
>>> nb.minimax(nb, 'X')
1