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

可变棋盘尺寸与获胜条件下井字棋Minimax算法的性能优化

可变棋盘尺寸与获胜条件下井字棋Minimax算法的性能优化

嘿,我太懂你在大棋盘(比如8x12)和高k值连子规则下遇到的Minimax性能瓶颈了——随着棋盘规模扩大,搜索空间呈指数级爆炸,哪怕有Alpha-Beta剪枝也顶不住。咱们从底层逻辑到算法改进一步步来优化,帮你把AI的速度提上去:

一、先优化最耗时的胜负检测与评估函数

你现在的check_win每次都遍历整个棋盘,这在大棋盘上是巨大的性能浪费——胜负只可能由最新落子的位置产生,完全没必要扫遍所有格子。同时,当前的评估函数只有“胜/负/平”三种状态,导致必须搜很深才能做出决策,咱们一起改:

1. 仅检查最新落子的周边区域

修改check_win,让它只验证最新落子所在的行、列、两条对角线是否满足k连条件:

def check_win(board, player, k, last_move):
    r, c = last_move
    rows = len(board)
    cols = len(board[0])

    # 检查水平方向
    count = 1
    # 向左数
    i = c - 1
    while i >= 0 and board[r][i] == player:
        count += 1
        i -= 1
    # 向右数
    i = c + 1
    while i < cols and board[r][i] == player:
        count += 1
        i += 1
    if count >= k:
        return True

    # 检查垂直方向
    count = 1
    i = r - 1
    while i >= 0 and board[r][i] == player:
        count += 1
        i -= 1
    i = r + 1
    while i < rows and board[r][i] == player:
        count += 1
        i += 1
    if count >= k:
        return True

    # 检查正对角线(左上到右下)
    count = 1
    i, j = r - 1, c - 1
    while i >= 0 and j >= 0 and board[i][j] == player:
        count += 1
        i -= 1
        j -= 1
    i, j = r + 1, c + 1
    while i < rows and j < cols and board[i][j] == player:
        count += 1
        i += 1
        j += 1
    if count >= k:
        return True

    # 检查反对角线(右上到左下)
    count = 1
    i, j = r - 1, c + 1
    while i >= 0 and j < cols and board[i][j] == player:
        count += 1
        i -= 1
        j += 1
    i, j = r + 1, c - 1
    while i < rows and j >= 0 and board[i][j] == player:
        count += 1
        i += 1
        j -= 1
    if count >= k:
        return True

    return False

这样每次检测的时间复杂度从O(rows*cols)降到O(k),速度提升非常明显。

2. 增强评估函数的中间状态得分

当前评估函数只有极端值(胜/负),导致必须搜很深才能判断状态优劣。咱们可以计算玩家的潜在连子潜力,比如:

def evaluate_board(board, player, k):
    opponent = 3 - player
    player_score = 0
    opponent_score = 0

    # 遍历所有可能的连子窗口,计算得分
    rows = len(board)
    cols = len(board[0])

    # 水平窗口
    for r in range(rows):
        for c in range(cols - k + 1):
            window = board[r][c:c+k]
            p_count = window.count(player)
            o_count = window.count(opponent)
            if o_count == 0:
                player_score += p_count ** 2  # 比如3个连子得9分,2个得4分
            if p_count == 0:
                opponent_score += o_count ** 2

    # 垂直、对角线窗口同理,这里省略重复代码

    # 优先判断胜负
    if check_win(board, player, k, (-1,-1)):  # 这里可以结合最新落子优化,暂时简化
        return 1000
    if check_win(board, opponent, k, (-1,-1)):
        return -1000
    return player_score - opponent_score

这样即使搜索深度不够,AI也能优先选择更有潜力的位置,同时减少不必要的深度搜索。

二、优化Minimax的搜索效率

1. 迭代加深搜索(Iterative Deepening)

代替固定深度搜索,先从深度1开始搜索,记录最佳候选位置;然后深度2只搜索这些候选位置,以此类推,直到达到时间限制或最大深度。这样既能在有限时间内搜到尽可能深的结果,又能让Alpha-Beta剪枝更早触发:

def best_move(board, player, k, max_depth=4):
    best_move = (-1, -1)
    # 初始候选是所有有意义的位置
    candidates = get_relevant_moves(board, k)
    
    for depth in range(1, max_depth+1):
        best_val = -float('inf')
        new_candidates = []
        for (r,c) in candidates:
            board[r][c] = player
            move_val = minimax(board, depth-1, -float('inf'), float('inf'), False, player, k)
            board[r][c] = 0
            if move_val > best_val:
                best_val = move_val
                best_move = (r,c)
                new_candidates = [(r,c)]
            elif move_val == best_val:
                new_candidates.append((r,c))
        candidates = new_candidates
        if not candidates:
            break
    return best_move

2. 排序搜索节点(Move Ordering)

把最有希望的走法(比如能形成潜在连子的位置)放在搜索队列前面,这样Alpha-Beta剪枝能更早剪掉无用分支。比如先评估所有空位置的得分,按得分从高到低排序后再搜索:

def get_sorted_moves(board, player, k):
    moves = []
    for r in range(len(board)):
        for c in range(len(board[0])):
            if board[r][c] == 0:
                board[r][c] = player
                score = evaluate_board(board, player, k)
                board[r][c] = 0
                moves.append((-score, r, c))  # 负号是为了升序排序相当于降序
    moves.sort()
    return [(r,c) for (s,r,c) in moves]

# 在minimax和best_move中使用这个排序后的列表,代替原有的双重循环

3. 置换表(Transposition Table)

用哈希表缓存已经计算过的棋盘状态的得分,避免重复计算相同状态。因为不同的走法序列可能到达同一个棋盘状态,缓存后直接复用结果:

transposition_table = {}

def minimax(board, depth, alpha, beta, is_maximizing_player, player, k):
    # 把棋盘转换成可哈希的键
    board_key = tuple(tuple(row) for row in board)
    if (board_key, depth, is_maximizing_player) in transposition_table:
        return transposition_table[(board_key, depth, is_maximizing_player)]
    
    score = evaluate_board(board, player, k)
    if score == 1000 or score == -1000 or depth == 0:
        transposition_table[(board_key, depth, is_maximizing_player)] = score
        return score

    # 后续的minimax逻辑不变,最后把结果存入缓存
    if is_maximizing_player:
        best = -float('inf')
        for r,c in get_sorted_moves(board, player, k):
            board[r][c] = player
            best = max(best, minimax(board, depth-1, alpha, beta, False, player, k))
            board[r][c] = 0
            alpha = max(alpha, best)
            if beta <= alpha:
                break
    else:
        best = float('inf')
        opponent = 3 - player
        for r,c in get_sorted_moves(board, opponent, k):
            board[r][c] = opponent
            best = min(best, minimax(board, depth-1, alpha, beta, True, player, k))
            board[r][c] = 0
            beta = min(beta, best)
            if beta <= alpha:
                break
    
    transposition_table[(board_key, depth, is_maximizing_player)] = best
    return best

三、缩小搜索范围,只关注有意义的位置

在大棋盘上,大部分空位置离已有棋子很远,完全不可能形成k连,直接忽略这些位置:

def get_relevant_moves(board, k):
    relevant = set()
    rows = len(board)
    cols = len(board[0])
    # 遍历所有已有棋子,把周围k范围内的空位置加入候选
    for r in range(rows):
        for c in range(cols):
            if board[r][c] != 0:
                # 上下左右、对角线的k范围内
                for dr in range(-k+1, k):
                    for dc in range(-k+1, k):
                        nr = r + dr
                        nc = c + dc
                        if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] == 0:
                            relevant.add((nr, nc))
    # 如果没有已有棋子(开局),返回所有位置
    return list(relevant) if relevant else [(r,c) for r in range(rows) for c in range(cols) if board[r][c] == 0]

用这个函数代替原有的所有空位置搜索,能大幅减少搜索节点数。

四、改进并行化方案

你之前的多线程方案有线程安全问题(多个线程修改同一个棋盘),而且Python的GIL限制了CPU密集型任务的多线程性能,改用多进程+棋盘复制:

from concurrent.futures import ProcessPoolExecutor

def evaluate_move(args):
    board_copy, depth, alpha, beta, is_maximizing, player, k, r, c = args
    board_copy[r][c] = player if is_maximizing else 3 - player
    return minimax(board_copy, depth-1, alpha, beta, not is_maximizing, player, k)

def parallel_best_move(board, player, k, depth=4):
    best_val = -float('inf')
    best_move = (-1,-1)
    moves = get_relevant_moves(board, k)
    # 为每个移动复制一份棋盘
    args_list = []
    for r,c in moves:
        board_copy = [row.copy() for row in board]
        args_list.append((board_copy, depth, -float('inf'), float('inf'), False, player, k, r, c))
    
    with ProcessPoolExecutor() as executor:
        results = executor.map(evaluate_move, args_list)
    
    for idx, val in enumerate(results):
        r,c = moves[idx]
        if val > best_val:
            best_val = val
            best_move = (r,c)
    return best_move

总结优化优先级

  1. 先改胜负检测为仅检查最新落子周边,这是见效最快的优化;
  2. 然后用缩小搜索范围减少节点数;
  3. 接着加入节点排序和置换表,提升Alpha-Beta剪枝效率;
  4. 最后考虑迭代加深和多进程并行,进一步压榨性能。

按照这个顺序优化,你会发现大棋盘下的AI速度会有质的提升,同时决策质量也不会下降太多。

备注:内容来源于stack exchange,提问作者Lumo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 14:55:30