所以,我正在尝试为一个简单的游戏实现minimax
算法,这个游戏有两个玩家,每个玩家有两个皇后。总共有4个皇后在一个7×7的棋盘上。因此,每个回合,玩家需要将他的两个皇后移动到新的位置。
我尝试通过递归minimax
函数来找到min
和max
,如下所示。基本情况应该返回一个整数,这是评估函数返回的分数。但是,当我遍历到叶子节点后,我如何找到min
和max
呢?
这个函数应该能够返回queen1和queen2的最佳移动。但我不明白如何从叶子节点的值中找到min和max。我不知道如何传播这些值。我实在无法理解/想象这一点。
回答:
从你的问题中,我感觉到你大部分的困惑在于,函数应该返回什么?是返回一个分数,还是一个移动?一般来说,你实际上应该将这件事分成两个独立的函数;
-
一个
minimax()
函数,应该看起来大致像你目前所拥有的(我没有详细检查正确性,可能有一些小错误,但总体上至少看起来接近正确)。这个函数只应该返回一个整数/浮点数/其他类型的值,即节点的值(如果已经足够深,则定义为评估函数的值,或者所有子节点的最大值/最小值(取决于哪位玩家将要移动))。 -
类似于
choose_move()
的函数,应该返回一个要执行的移动。它应该通过调用minimax()
来为所有子节点计算,然后返回导致子节点值最大的移动(建议随机打破平局)。
注意:你的代码中似乎也有一些错误,看起来返回得太频繁了。例如,对于最大化玩家,你在第一次看到score > best_val
时就已经返回了,而你应该继续循环遍历所有其他移动,以确定是否有任何移动可能有更高的分数。
对于最小化玩家的代码应该与最大化玩家的代码更加“对称”,现在看起来差异太大。
编辑:为了修复分数返回过快的问题,这行代码:
return best_move_q_1, best_move_q_2, score
应该简单地移到所有可能动作的循环之外。想法是,遍历所有动作,评估它们(通过递归minimax
调用),然后返回与最佳移动相关的移动和分数。这意味着它必须在动作循环之外,你不能在仍在这些循环内时就返回,因为那样你还没有完成所有动作的循环,可能会错过更好的选择。
在这种情况下,做法是简单地将那行代码向左移动4个缩进。它应该直接在for move_q_1 in moves_1:
行下方(在同一缩进级别),因为那是遍历所有移动的循环开始的地方。
然后,那行代码还应该更改为返回best_val
(所有子节点中最佳分数),而不是score
(最后一个子节点的评估)。
之后,not maximizing_player
情况的代码应该更改为与上面其他情况的代码更加相似。
然后,我刚刚注意到另一件事;接近顶部,你决定评估depth == 0
(或者游戏状态是否为终止状态)。然而,在递归调用中,你总是增加传递的深度级别。这看起来很奇怪(除非你在第一次调用时传递了一个负深度?)。你可能想要做以下之一:
-
在第一次调用minimax时,传递你想要搜索的最大深度(例如,3或5或其他)。然后,在递归调用minimax时,总是递减它,而不是递增(以确保它最终达到
depth=0
的点进行评估)。 -
不是在
depth == 0
时进行评估,而是在depth == max_depth
时进行评估,其中max_depth
再次是一个常数,如3或5或其他。然后,你对minimax的初始调用应该有depth=0
。
我没有详细检查是否还有其他错误,所以如果你觉得这还不够,请随时告诉我(或者尝试将你的代码与算法在其他地方的伪代码进行比较,看看差异在哪里,以及你是否能理解它们)。