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

井字棋Minimax结合Alpha-Beta剪枝与哈希表后单一场景失效问题

问题描述

Minimax函数在单独使用Alpha-Beta剪枝、哈希表缓存,或两者都不使用时均运行正常,但同时启用这两种优化后,仅在AI作为后手的某一特定场景下失效。

失效场景

落子顺序:

  • 人类(执X)落子:9、1、3、6
  • AI(执O)落子:5、7、2

最终棋盘状态:

X | O | X 
---+---+---
 4 | O | X 
---+---+---
 O | 8 | X

相关Minimax代码

def minimax(depth, alpha, beta):
    new_depth = depth + 1
    avails = core_func.availables()
    if new_depth % 2:  # maximizer
        if core_func.check_win():
            return -1  # previously minimizer
        elif not avails:
            return 0
        best_score = -float('inf')
        for c in avails:
            i = c - 1
            variables.grid[i] = variables.symbol
            t = tuple(variables.grid)
            if t not in variables.states:
                variables.states[t] = minimax(new_depth, alpha, beta)
            best_score = max(best_score, variables.states[t])
            alpha = max(alpha, best_score)
            variables.grid[i] = c
            if beta <= alpha: break
    else:  # minimizer
        if core_func.check_win():
            return 1  # previously maximizer
        elif not avails:
            return 0
        opponent = variables.valid_symbols - {variables.symbol}
        opponent = opponent.pop()
        best_score = float('inf')
        for c in avails:
            i = c - 1
            variables.grid[i] = opponent
            t = tuple(variables.grid)
            if t not in variables.states:
                variables.states[t] = minimax(new_depth, alpha, beta)
            best_score = min(best_score, variables.states[t])
            beta = min(beta, best_score)
            variables.grid[i] = c
            if beta <= alpha: break
    return best_score

完整可运行代码

core_func.py

import variables
import turns_func


def update_grid():
    print(f' {variables.grid[0]} | {variables.grid[1]} | {variables.grid[2]} \n'
          f'---+---+---\n'
          f' {variables.grid[3]} | {variables.grid[4]} | {variables.grid[5]} \n'
          f'---+---+---\n'
          f' {variables.grid[6]} | {variables.grid[7]} | {variables.grid[8]} \n')


def correct_input(message, lst):
    i = int(input(f'{message}: '))
    if i not in lst:
        print(f'input error: input is not valid. valid inputs: {lst}')
        return correct_input(message, lst)
    return i


def initialization():
    option = correct_input(variables.initialization_message, variables.valid_initials)
    if option in {1, 4, 5}:
        variables.turn = 'H'
    else:
        variables.turn = 'C'
    if option in {2, 4}:
        variables.difficulty = 'R'
    if option in {3, 5}:
        variables.difficulty = 'P'
    print('Coordinates of the grid:')
    update_grid()


def check_win():
    for x, y, z in (0, 1, 2), (3, 4, 5), (6, 7, 8), (0, 3, 6), (1, 4, 7), (2, 5, 8), (0, 4, 8), (2, 4, 6):
        if variables.grid[x] == variables.grid[y] == variables.grid[z]:
            return True
    return False


def availables():
    availables = []
    for c in variables.grid:
        if c not in variables.valid_symbols:
            availables.append(c)
    return availables


def get_coordinate():
    if not variables.difficulty:
        return turns_func.human_turn()
    elif variables.turn == 'H':
        variables.turn = 'C'
        return turns_func.human_turn()
    else:
        print(f'computer ({variables.symbol}):')
        variables.turn = 'H'
        if variables.difficulty == 'R':
            return turns_func.random_turn()
        else:
            return turns_func.perfect_turn()


def play():
    coordinate = get_coordinate()
    variables.grid[coordinate - 1] = variables.symbol
    update_grid()
    variables.moves += 1
    if check_win():
        print(f'\"{variables.symbol}\" wins in {variables.moves} moves!')
    elif not availables():
        print('\"Game Ties\"')
    else:
        changed = variables.valid_symbols - {variables.symbol}
        variables.symbol = changed.pop()
        play()

turns_func.py

import variables
import core_func
from random import choice


def human_turn():
    return core_func.correct_input(variables.get_coordinate_message + variables.symbol, core_func.availables())


def random_turn():
    return choice(core_func.availables())


# alpha - maximizer's max - it should be less than its parent node minimizer's min
# beta - minimizer's min - it should be greater than its parent node maximizer's max


def minimax(depth, alpha, beta):
    new_depth = depth + 1
    avails = core_func.availables()
    if new_depth % 2:  # maximizer
        if core_func.check_win():
            return -1  # previously minimizer
        elif not avails:
            return 0
        best_score = -float('inf')
        for c in avails:
            i = c - 1
            variables.grid[i] = variables.symbol
            t = tuple(variables.grid)
            if t not in variables.states:
                variables.states[t] = minimax(new_depth, alpha, beta)
            best_score = max(best_score, variables.states[t])
            alpha = max(alpha, best_score)
            variables.grid[i] = c
            if beta <= alpha: break
    else:  # minimizer
        if core_func.check_win():
            return 1  # previously maximizer
        elif not avails:
            return 0
        opponent = variables.valid_symbols - {variables.symbol}
        opponent = opponent.pop()
        best_score = float('inf')
        for c in avails:
            i = c - 1
            variables.grid[i] = opponent
            t = tuple(variables.grid)
            if t not in variables.states:
                variables.states[t] = minimax(new_depth, alpha, beta)
            best_score = min(best_score, variables.states[t])
            beta = min(beta, best_score)
            variables.grid[i] = c
            if beta <= alpha: break
    return best_score


def perfect_turn():
    max_score = -float('inf')
    coordinate = None
    for c in variables.grid:
        if c in variables.valid_symbols: continue
        i = c - 1
        variables.grid[i] = variables.symbol
        score = minimax(1, -float('inf'), float('inf'))
        variables.grid[i] = c
        if score > max_score:
            max_score = score
            coordinate = c
    return coordinate

variables.py

initialization_message = ('1. Play with Human\n'
                          'Play with computer:\n'
                          'For computer going first, Difficulties:\n'
                          '\t2. Random\n'
                          '\t3. Perfect\n'
                          'For computer going second, Difficulties:\n'
                          '\t4. Random\n'
                          '\t5. Perfect\n'
                          'Choose an option. Enter the number')

get_coordinate_message = 'Input a coordinate for '

valid_initials = (1, 2, 3, 4, 5)
grid = [1, 2, 3, 4, 5, 6, 7, 8, 9]
valid_symbols = {'X', 'O'}
symbol = 'X'
difficulty = None
turn = None
moves = 0
states = {}

main.py

import core_func

core_func.initialization()
core_func.play()
问题原因

哈希表缓存仅以棋盘布局作为键存储评分,但忽略了当前搜索节点的alpha/beta约束和玩家身份。Alpha-Beta剪枝的结果高度依赖搜索路径中的alpha、beta值,相同棋盘布局在不同的约束条件下可能触发不同的剪枝逻辑,直接复用缓存的评分会导致错误的剪枝决策。

在失效场景中,AI评估剩余落子(4和8)时,缓存中存储了该棋盘在其他alpha/beta条件下的评分,导致当前搜索错误触发剪枝,无法找到正确的落子选择,最终出现失效情况。

解决方案

修改缓存键的构成,将棋盘布局、当前玩家身份(maximizer/minimizer)、alpha值、beta值共同作为缓存键,确保只有在完全相同的搜索上下文下才复用缓存结果。同时,每次AI决策前清空缓存,避免不同回合的搜索上下文互相干扰。

修改后的Minimax函数

def minimax(depth, alpha, beta):
    new_depth = depth + 1
    is_maximizer = new_depth % 2 == 1
    avails = core_func.availables()
    
    # 构建包含完整搜索上下文的缓存键
    board_state = tuple(variables.grid)
    cache_key = (board_state, is_maximizer, alpha, beta)
    
    # 优先从缓存获取结果
    if cache_key in variables.states:
        return variables.states[cache_key]
    
    if is_maximizer:  # maximizer逻辑
        if core_func.check_win():
            result = -1
        elif not avails:
            result = 0
        else:
            best_score = -float('inf')
            for c in avails:
                i = c - 1
                variables.grid[i] = variables.symbol
                score = minimax(new_depth, alpha, beta)
                best_score = max(best_score, score)
                alpha = max(alpha, best_score)
                variables.grid[i] = c
                if beta <= alpha:
                    break
            result = best_score
    else:  # minimizer逻辑
        if core_func.check_win():
            result = 1
        elif not avails:
            result = 0
        else:
            opponent = variables.valid_symbols - {variables.symbol}
            opponent = opponent.pop()
            best_score = float('inf')
            for c in avails:
                i = c - 1
                variables.grid[i] = opponent
                score = minimax(new_depth, alpha, beta)
                best_score = min(best_score, score)
                beta = min(beta, best_score)
                variables.grid[i] = c
                if beta <= alpha:
                    break
            result = best_score
    
    # 将结果存入缓存
    variables.states[cache_key] = result
    return result

修改后的perfect_turn函数

def perfect_turn():
    max_score = -float('inf')
    coordinate = None
    # 清空缓存,避免上一回合的结果干扰当前决策
    variables.states.clear()
    for c in variables.grid:
        if c in variables.valid_symbols:
            continue
        i = c - 1
        variables.grid[i] = variables.symbol
        score = minimax(1, -float('inf'), float('inf'))
        variables.grid[i] = c
        if score > max_score:
            max_score = score
            coordinate = c
    return coordinate

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 19:29:49