我们先以你的井字棋为例。
- 极小极大算法最适合玩家轮流行动的游戏,但可以调整为玩家每回合可进行多个移动的游戏。我们为简单起见假设为前者。在这种情况下,你不必在每个节点上存储“X要走”或“O要走”,因为可以通过节点深度的奇偶性(我距离顶部是偶数步还是奇数步)来确定。
- 从每个位置生成可能的移动需要知道谁要走(可以像之前一样确定),以及特定位置的合法移动规则。对于像井字棋这样简单的游戏,给定一个位置,只需枚举所有状态,其中包括当前位置的副本加上新的棋子,这些棋子属于当前玩家,并依次放置在每个空方格中。对于像黑白棋这样的游戏,您还必须检查每个放置是否遵循规则,并根据规则的后果更新最终位置(对于黑白棋,翻转一堆棋子的颜色)。通常,从您正在跟踪的每个有效位置开始,您会枚举所有可能放置新棋子的位置,并检查哪些位置符合规则集。
- 一般来说,您永远不会生成整个树,因为游戏树大小很容易超过地球的存储能力。您始终设置最大迭代深度。然后,终端节点是最大深度处的节点,或者没有合法移动存在的节点(对于井字棋,是一个每个方格都填满的棋盘)。您不会预先生成终端节点;它们在游戏树构建期间自然生成。井字棋足够简单,可以生成整个游戏树,但不要尝试将井字棋代码用于例如黑白棋之类的游戏。
- 查看您的伪代码:
max(a,b)是返回a或b中较大值的任何函数。这通常由数学库或类似库提供。
深度是您将搜索的最大深度。
您正在计算的启发式值是描述棋盘价值的某个数值。对于像井字棋这样简单到可以枚举整个游戏树的游戏,您可以将赢得分析的玩家的棋盘位置指定为1,将赢得另一个玩家的棋盘位置指定为-1,将任何不确定的位置指定为0。一般来说,您必须自己设计一个启发式算法或使用公认的算法。
您根据父节点在分析过程中即时生成节点。您的根节点始终是您正在进行分析的位置。
如果您还没有使用图形或树,我建议您先这样做;特别是,树基元对于解决此问题是至关重要的。
作为对该线程中一个关于确定给定节点轮到谁的评论的回答,我提供了这个伪Python代码:
who_started_first = None
class TreeNode:
def __init__(self, board_position = EMPTY_BOARD, depth = 0):
self.board_position = board_position
self.children = []
self.depth = depth
def construct_children(self, max_depth):
if max_depth > 0:
if who_started_first == COMPUTER:
to_move = (COMPUTER if self.depth % 2 == 0 else HUMAN)
elif who_started_first == HUMAN:
to_move = (HUMAN if self.depth % 2 == 0 else COMPUTER)
else:
raise ValueError('who_started_first invalid!')
for position in self.board_position.generate_all(to_move):
new_node = TreeNode(position, self.depth + 1)
self.children.append(new_node)
new_node.construct_children(max_depth - 1)
每个节点都能够记录自己距离“根”节点的绝对深度。当我们尝试确定如何为下一步生成棋盘位置时,我们会根据深度的奇偶性(
self.depth % 2
的结果)和我们谁先移动的记录来检查轮到谁移动了。