You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python国际象棋引擎搜索函数深度4时触发pop空列表错误求助

国际象棋引擎搜索函数IndexError问题排查

问题背景

我为Python国际象棋引擎编写了以下搜索函数:

def search(pos: position.Position, depth: int, alpha: int, beta: int, side_to_move: chess.Color, root: bool = False):
    global nodes, best_score, best_move, max_score, min_score, killers
    bestMove = None
    """Search the position to a given depth."""
    
    if depth == 0 or pos.board.chess_board().is_game_over():
        nodes += 1
        return evaluate.evaluate(pos, side_to_move), best_move
    
    for move in pos.board.chess_board().legal_moves:
        if stop_search.search_has_stopped():
            return best_score, best_move
        
        if prune(pos, move, alpha, beta, side_to_move, depth):
            continue
        
        pos.board.chess_board().push(move)
        score, _ = search(pos, depth - 1, -beta, -alpha, not side_to_move, root=False)
        score = -score
        pos.board.chess_board().pop()
        
        if score > best_score:
            best_score = score
            best_move = move
        
        if score > max_score:
            max_score = score
        if score < min_score:
            min_score = score
        
        if score > alpha:
            alpha = score
            bestMove = move
            if score >= beta:
                best_move = bestMove
                return beta, bestMove
        else:
            killers.append(move)
        
    return alpha, bestMove

运行时,搜索深度1-3结果正常,但深度设为4时触发如下异常:

File "path/to/search.py", line 66, in search
    pos.board.chess_board().pop()
  File "path/to/chess/__init__.py", line 2267, in pop
    move = self.move_stack.pop()
           ^^^^^^^^^^^^^^^^^^^^^
IndexError: pop from empty list

已确认默认合法棋步生成器生成的move均合法,推测棋步push操作未成功,需要排查原因。

问题根源与修复方案

1. 提前return导致push/pop不匹配

循环中有两处提前return的逻辑,会直接跳过后续的pop()操作,但之前已经执行了push(move),导致棋盘的move_stack中残留未弹出的棋步。递归层级增加到4时,栈状态的混乱累积到触发空栈pop错误:

  • 停止搜索时直接return
  • beta剪枝时直接return

修复: 提前return前必须先弹出当前push的棋步:

  • 处理停止搜索的情况:
if stop_search.search_has_stopped():
    pos.board.chess_board().pop()
    return best_score, best_move
  • 处理beta剪枝的情况:
if score >= beta:
    pos.board.chess_board().pop()
    best_move = bestMove
    return beta, bestMove

2. 重复调用chess_board()的风险

每次调用pos.board.chess_board()如果返回的是新的棋盘实例,那么push()和pop()会作用在不同对象上,导致栈状态完全不匹配。

修复: 在函数开头获取棋盘实例的局部引用,全程使用该引用操作:

def search(pos: position.Position, depth: int, alpha: int, beta: int, side_to_move: chess.Color, root: bool = False):
    global nodes, best_score, best_move, max_score, min_score, killers
    bestMove = None
    board = pos.board.chess_board()  # 存储局部引用
    """Search the position to a given depth."""
    
    if depth == 0 or board.is_game_over():
        nodes += 1
        return evaluate.evaluate(pos, side_to_move), best_move
    
    for move in board.legal_moves:
        if stop_search.search_has_stopped():
            return best_score, best_move
        
        if prune(pos, move, alpha, beta, side_to_move, depth):
            continue
        
        board.push(move)
        score, _ = search(pos, depth - 1, -beta, -alpha, not side_to_move, root=False)
        score = -score
        board.pop()
        # 后续逻辑保持不变,将所有pos.board.chess_board()替换为board

3. 全局变量的干扰

函数依赖多个全局变量(如best_move、killers),递归过程中不同层级的调用会互相覆盖这些变量,可能导致逻辑混乱,间接影响栈操作的正确性。建议将这些状态封装到类中,或改为通过函数参数/返回值传递局部状态,避免全局变量的副作用。

4. 验证prune函数逻辑

虽然已确认合法棋步,但prune函数如果存在逻辑错误,可能在某些边界场景下误判,但只要未执行push()就不会影响栈。可以在prune函数前后添加日志,记录被剪枝的棋步,排除异常情况。

内容的提问来源于stack exchange,提问作者Boo Who

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 15:15:27