当前位置:嗨网首页>书籍在线阅读

05-极小化极大算法

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

8.2.2 极小化极大算法

若要在双人、零和、全信息的对弈游戏(如井字棋、跳棋或国际象棋)中找到最佳走法,极小化极大策略是一种经典算法。它已经对其他类型的游戏进行了扩展和修改。极小化极大算法通常采用递归函数来实现,这时两个玩家要么是极大化玩家,要么是极小化玩家。

极大化玩家的目标是找到能获得最大收益的走法。但是,极大化玩家必须考虑极小化玩家的走法。在每次试图求出极大化玩家的最大收益后,递归地调用 minimax() 以求得对手的回手,也就是让极大化玩家的收益最小化的走法。这个过程一直来回进行(求最大值、求最小值、求最大值等),直到达到递归函数的基线条件。基线条件为终局(赢或平局)或达到最大搜索深度。

调用 minimax() 将返回极大化玩家的起始位置的评分。就 TTTBoard 类的 evaluate() 方法而言,如果双方的最佳走法将导致极大化玩家获胜,则返回1分。如果最佳走法将导致极大化玩家输棋,则返回−1分。如果最佳走法将导致平局,则返回0分。

这个分数将会在达到基线条件时返回。然后,再沿着达到基线条件的各层递归调用逐级向上返回。对于每次求最大值的递归调用,向上返回的是下一步走法的最佳评分。对于每次求最小值的递归调用,向上返回的是下一步走法的最差评分。这样,决策树就建立起来了。图8-2呈现了这样一棵决策树,有助于厘清还剩最后两步的一局棋向上返回评分的过程。

47.png

图8-2 一局井字棋的极小化极大决策树,该棋局还剩最后两步棋。为了让获胜的可能性最大化, 初始玩家O将选择在底部中心位置落子。箭头指明做出决策的位置

对于搜索空间太深而无法抵达终局的游戏(例如跳棋和国际象棋), minimax() 会在到达一定深度后停止。要搜索的棋步数深度有时被称为“层”(ply)。然后启动评分函数,采用启发法对棋局进行评分。游戏对初始玩家越有利,得分就越高。我们在介绍四子棋时将会再回来讨论这个概念,四子棋的搜索空间比井字棋的搜索空间大多了。

代码清单8-8中给出的就是 minimax() 的全部内容。

代码清单8-8 minimax.py

from __future__ import annotations
from board import Piece, Board, Move
# Find the best possible outcome for original player
def minimax(board: Board, maximizing: bool, original_player: Piece, max_depth: int = 8)
    -> float:
    # Base case – terminal position or maximum depth reached
    if board.is_win or board.is_draw or max_depth == 0:
        return board.evaluate(original_player)
    # Recursive case - maximize your gains or minimize the opponent's gains
    if maximizing:
        best_eval: float = float("-inf") # arbitrarily low starting point
       for move in board.legal_moves:
           result: float = minimax(board.move(move), False, original_player, max_depth - 1)
           best_eval = max(result, best_eval)
       return best_eval
    else: # minimizing
       worst_eval: float = float("inf")
       for move in board.legal_moves:
          result = minimax(board.move(move), True, original_player, max_depth - 1)
          worst_eval = min(result, worst_eval) 
       return worst_eval

在每次递归调用过程中,无论当前是极大化玩家还是极小化玩家,或者是对 original_player 的棋局进行评分,都需要跟踪记录棋局。 minimax() 的前几行代码负责处理基线条件:末端节点(赢、输或平局)或到达的最大深度。 minimax() 函数的其余部分是对递归情况的处理。

其中一种递归情况是求最大值,这时需要查找能够生成最高评分的走法。另一种递归情况是求最小值,这时查找的是导致最低评分的走法。这两种情况交替进行,直至抵达终局状态或最大深度(基线条件)。

不幸的是,只是原封不动地用 minimax() 无法找出给定棋局的最佳走法。它只能返回一个分值(一个 float 类型值),而无法给出生成该评分的最佳的第一步该怎么下。

于是我们要创建一个辅助函数 find_best_move() 来为某棋局中每一步合法的走法循环调用 minimax() ,以便找出评分最高的走法。我们可以将 find_best_move() 视作对 minimax() 的第一次求最大值调用,只是带上了初始的棋步而已。具体代码如代码清单8-9所示。

代码清单8-9 minimax.py(续)

# Find the best possible move in the current position 
# looking up to max_depth ahead
def find_best_move(board: Board, max_depth: int = 8) -> Move:
    best_eval: float = float("-inf")
    best_move: Move = Move(-1)
    for move in board.legal_moves:
        result: float = minimax(board.move(move), False, board.turn, max_depth)
        if result > best_eval:
            best_eval = result
            best_move = move
    return best_move

现在万事俱备,我们可以开始搜索任何井字棋局的最佳走法了。