ConnectX的Minimax算法异常:无法实现基本阻挡操作求助
问题排查:Connect 4 N-step Lookahead Agent 无法正常工作
我正在学习Kaggle Learn的《游戏AI入门》课程,为N-step Lookahead练习编写了agent代码,但无法正常运行——甚至无法完成阻挡随机agent获胜这类简单操作。
我的Agent代码
def my_agent(obs, config): ########### # Imports # ########### import random import numpy as np #################### # Helper functions # #################### # Gets board at next step if agent drops piece in selected column def drop_piece(grid, col, mark, config): next_grid = grid.copy() for row in range(config.rows-1, -1 ,-1): if next_grid[row][col] == 0: break next_grid[row][col] == mark return next_grid # Helper function for get_heuristic: check if window meets heuristic conditions def check_window(window, num_discs, piece, config): return (window.count(piece) == num_discs and window.count(0) == config.inarow-num_discs) # Helper function for get_heuristic: counts number of windows satisfying specified heuristic conditions def count_windows(grid, num_discs, piece, config): num_windows = 0 # horizontal for row in range(config.rows): for col in range(config.columns-(config.inarow-1)): window = list(grid[row, col:col+config.inarow]) if check_window(window, num_discs, piece, config): num_windows += 1 # vertical for row in range(config.rows-(config.inarow-1)): for col in range(config.columns): window = list(grid[row:row+config.inarow, col]) if check_window(window, num_discs, piece, config): num_windows += 1 # positive diagonal (upper left part of board, extend right and downwards) for row in range(config.rows-(config.inarow-1)): for col in range(config.columns-(config.inarow-1)): window = list(grid[range(row, row+config.inarow), range(col, col+config.inarow)]) if check_window(window, num_discs, piece, config): num_windows += 1 # negative diagonal (lower left part of board, extend right and upwards) for row in range(config.inarow-1, config.rows): for col in range(config.columns-(config.inarow-1)): window = list(grid[range(row, row-config.inarow, -1), range(col, col+config.inarow)]) if check_window(window, num_discs, piece, config): num_windows += 1 return num_windows # Helper function for minimax: calculate heuristic for grid def get_heuristic(grid, mark, config): num_threes = count_windows(grid, 3, mark, config) num_fours = count_windows(grid, 4, mark, config) num_threes_opp = count_windows(grid, 3, mark%2+1, config) num_fours_opp = count_windows(grid, 4, mark%2+1, config) score = 1*num_threes + 1e5*num_fours - 100*num_threes_opp - 1000*num_fours_opp return score # Uses minimax to calculate value of dropping piece in selected column def score_move(grid, col, mark, config, nsteps): next_grid = drop_piece(grid, col, mark, config) score = minimax(next_grid, nsteps-1, False, mark, config) return score # Helper function for minimax: Check if agent or opponent has 4 in a row in the window def is_terminal_window(window, config): return window.count(1) == config.inarow or window.count(2) == config.inarow # Helper function for minimax: Check if game has ended def is_terminal_node(grid, config): # Check for draw if list(grid[0, :]).count(0) == 0: return True # Check for win: horizontal, vertical, or diagonal # horizontal for row in range(config.rows): for col in range(config.columns-(config.inarow-1)): window = list(grid[row, col:col+config.inarow]) if is_terminal_window(window, config): return True # vertical for row in range(config.rows-(config.inarow-1)): for col in range(config.columns): window = list(grid[row:row+config.inarow, col]) if is_terminal_window(window, config): return True # positive diagonal for row in range(config.rows-(config.inarow-1)): for col in range(config.columns-(config.inarow-1)): window = list(grid[range(row, row+config.inarow), range(col, col+config.inarow)]) if is_terminal_window(window, config): return True # negative diagonal for row in range(config.inarow-1, config.rows): for col in range(config.columns-(config.inarow-1)): window = list(grid[range(row, row-config.inarow, -1), range(col, col+config.inarow)]) if is_terminal_window(window, config): return True return False def minimax(node, depth, maximizingPlayer, mark, config): is_terminal = is_terminal_node(node, config) valid_moves = [c for c in range(config.columns) if node[0][c] == 0] if depth == 0 or is_terminal: return get_heuristic(node, mark, config) if maximizingPlayer: value = -np.Inf for col in valid_moves: child = drop_piece(node, col, mark, config) value = max(value, minimax(child, depth-1, False, mark, config)) return value else: value = np.Inf for col in valid_moves: child = drop_piece(node, col, mark%2+1, config) value = min(value, minimax(child, depth-1, True, mark, config)) return value ######################### # Agent makes selection # ######################### N_STEPS = 3 valid_moves = [col for col in range(config.columns) if obs.board[col] == 0] grid = np.asarray(obs.board).reshape(config.rows, config.columns) scores = dict(zip(valid_moves, [score_move(grid, col, obs.mark, config, N_STEPS) for col in valid_moves])) print(scores) max_cols = [key for key in scores.keys() if scores[key] == max(scores.values())] return random.choice(max_cols)
分数输出
{0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0, 5: 0.0, 6: 0.0} {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0, 5: 0.0, 6: 0.0} {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0, 5: 0.0, 6: 0.0} {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0, 5: 0.0, 6: 0.0} {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0, 5: 0.0, 6: 0.0} {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0, 5: 0.0, 6: 0.0} {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0, 5: 0.0, 6: 0.0} {0: -99.0, 1: -99.0, 2: -99.0, 3: -99.0, 4: -99.0, 5: -99.0, 6: -99.0} {0: -99.0, 1: -99.0, 2: -99.0, 3: -99.0, 4: -99.0, 5: -99.0, 6: -99.0} {0: -99.0, 1: -99.0, 2: -99.0, 3: -99.0, 4: -99.0, 5: -99.0, 6: -99.0} {0: -99.0, 1: -99.0, 2: -99.0, 3: -99.0, 4: -99.0, 5: -99.0} {0: -99.0, 1: -99.0, 2: -99.0, 3: -99.0, 4: -99.0, 5: -99.0}
我肯定犯了一个低级错误,但始终无法找出,能否帮忙排查?另外我是StackOverflow新手,若我的提问有问题请指出。
问题排查结果
核心错误:赋值运算符误用
在drop_piece函数中,你使用了比较运算符==而不是赋值运算符=:
next_grid[row][col] == mark # 错误:仅判断相等,不会修改棋盘
正确写法应该是:
next_grid[row][col] = mark # 正确:将棋子放置到对应位置
这个错误导致所有drop_piece调用都不会修改棋盘——返回的next_grid和输入的grid完全一致。因此,minimax算法实际上一直在评估同一个初始棋盘的分数,无法区分不同落子的优劣,最终只能随机选择列,自然无法完成阻挡对手的操作。
次要优化建议
- 导入语句位置:将
import random和import numpy as np移到函数外部,避免每次调用agent时重复导入,提升运行效率。 - 代码一致性:
minimax函数中valid_moves的判断逻辑与主逻辑一致,无需修改,但可以保持写法统一增强可读性。
修正后的drop_piece函数
def drop_piece(grid, col, mark, config): next_grid = grid.copy() for row in range(config.rows-1, -1 ,-1): if next_grid[row][col] == 0: break next_grid[row][col] = mark # 修正赋值运算符 return next_grid
修正后,agent应该能正确计算不同落子的分数,优先选择能阻挡对手或自身形成连子的列。
内容的提问来源于stack exchange,提问作者Andrew Loo
相关产品推荐
相关产品推荐

