C#国际象棋引擎错误走法求助:无法预见简单将杀
我基于SebLague Chess Challenge项目开发C#国际象棋引擎,实现了基础AlphaBeta Minimax算法和子力平衡评估函数。当前遇到两个核心问题:
- 搜索深度设为4时,引擎无法预判简单将杀
- 频繁走出弃子等怪异走法(比如选择移动车而不保护兵)
已知评估函数逻辑正常(白方优势返回正值,黑方优势返回负值),怀疑问题出在Search类或Think方法中,以下是相关代码:
Main类代码
public class TRACER : IChessBot { //Constant Variables private const int DEPTH = 4; //Search depth //Used classes Search findMove = new Search(); public Move Think(Board board, Timer timer) { //Variables int moveScore; //Score a certain move has int bestScore = int.MinValue; //Score of the current best move (low value on start to guarantee update) int nodeSearched = 0; //Number of nodes searched Move bestMove; //Current best move to be played //Generate all legal possible moves Move[] moves = board.GetLegalMoves(); bestMove = moves[0]; foreach (Move currentMove in moves) { //for every move -> make move virtually and evaluate how good it is board.MakeMove(currentMove); moveScore = findMove.MiniMaxAB(board, DEPTH, int.MinValue, int.MaxValue, board.IsWhiteToMove); board.UndoMove(currentMove); //if the score of this move is better than the current best -> switch if (moveScore > bestScore) { bestScore = moveScore; bestMove = currentMove; } nodeSearched++; Debug.WriteLine("currentMove:{0} | moveScore:{1}", currentMove, moveScore); } Debug.WriteLine("---- bestMove:{0} | bestScore:{1} ----", bestMove, bestScore); return bestMove; } }
Search类代码
namespace Chess_Challenge.src.TRACER { internal class Search { //used Classes Evaluation eval = new Evaluation(); //NegaMax AlphaBeta Pruning Algorith -> returns a high value for the current play (regardless of colour) public int MiniMaxAB(Board board, int depth, int alpha, int beta, bool maximizingPlayer) { //Variables int moveScore; //score of the current move //if depth is reached then evaluate position if (depth == 0) return eval.EvaluatePosition(board) * (maximizingPlayer ? -1 : 1); //if player is checkmated return low value (bad for white) or high value (bad for black) if (board.IsInCheckmate()) return board.IsWhiteToMove ? int.MinValue : int.MaxValue; //if game is a draw return equal if (board.IsDraw()) return 0; //Get all legal moves Move[] moves = board.GetLegalMoves(); //for every legal move get the best move for black and white if (maximizingPlayer) { moveScore = int.MinValue; foreach (Move move in moves) { board.MakeMove(move); moveScore = MiniMaxAB(board, depth - 1, alpha, beta, !maximizingPlayer); board.UndoMove(move); alpha = Math.Max(alpha, moveScore); if (beta <= alpha) break; } return moveScore; } else { moveScore = int.MaxValue; foreach (Move move in moves) { board.MakeMove(move); moveScore = MiniMaxAB(board, depth - 1, alpha, beta, !maximizingPlayer); board.UndoMove(move); beta = Math.Min(beta, moveScore); if (beta <= alpha) break; } return moveScore; } } } }
Evaluation类代码
namespace Chess_Challenge.src.TRACER { internal class Evaluation { // Piece values: ., P, K, B, R, Q, K private static readonly int[] mgPieceValues = { 0, 80, 335, 363, 460, 940, 100000}; private static readonly int[] egPieceValues = { 0, 100, 305, 333, 563, 950, 100000}; //Endgame Transition private int egT(Board board) { return BitOperations.PopCount(board.AllPiecesBitboard); } //Get the value of a single piece regarding all parameters .TODO public int EvaluatePosition(Board board) { int whiteMaterial = 0; int blackMaterial = 0; for (PieceType pieceType = PieceType.Pawn; pieceType <= PieceType.King; pieceType++) { whiteMaterial += mgPieceValues[(int)pieceType] * BitOperations.PopCount(board.GetPieceBitboard(pieceType, true)); blackMaterial += mgPieceValues[(int)pieceType] * BitOperations.PopCount(board.GetPieceBitboard(pieceType, false)); } return whiteMaterial - blackMaterial; } } }
核心问题分析
搜索函数递归逻辑错误
在MaximizingPlayer和MinimizingPlayer的循环中,每次递归返回的moveScore直接覆盖了当前值,没有执行取最大/最小的操作。比如MaximizingPlayer分支里,应该用Math.Max(moveScore, 递归结果)来更新当前最优分数,而非直接赋值,否则只会保留最后一个走法的分数,而非所有分支中的最优值,这直接导致引擎无法正确评估所有可能走法,出现怪异选择。终局判断顺序错误
当前代码先判断depth == 0再判断将杀/和棋,这会导致深度耗尽时,即使局面是将杀,也只会返回子力评估值而非将杀对应的极端分数,无法识别致命的将杀局面。Think方法调用参数错误
在Think方法中,执行board.MakeMove(currentMove)后,当前轮到对方玩家走棋,但调用MiniMaxAB时传入的maximizingPlayer参数是board.IsWhiteToMove(即对方玩家的颜色),这与搜索函数的逻辑不匹配,导致分数评估视角混乱。
修复建议
修正搜索函数循环逻辑
- MaximizingPlayer分支:
moveScore = int.MinValue; foreach (Move move in moves) { board.MakeMove(move); int score = MiniMaxAB(board, depth - 1, alpha, beta, !maximizingPlayer); board.UndoMove(move); moveScore = Math.Max(moveScore, score); alpha = Math.Max(alpha, moveScore); if (beta <= alpha) break; } - MinimizingPlayer分支同理,用
Math.Min更新moveScore:moveScore = int.MaxValue; foreach (Move move in moves) { board.MakeMove(move); int score = MiniMaxAB(board, depth - 1, alpha, beta, !maximizingPlayer); board.UndoMove(move); moveScore = Math.Min(moveScore, score); beta = Math.Min(beta, moveScore); if (beta <= alpha) break; }
- MaximizingPlayer分支:
调整终局判断顺序
将将杀、和棋的判断放在深度判断之前,确保终局状态优先被识别:// 先判断终局状态 if (board.IsInCheckmate()) return board.IsWhiteToMove ? int.MinValue : int.MaxValue; if (board.IsDraw()) return 0; // 再判断深度耗尽 if (depth == 0) return eval.EvaluatePosition(board) * (maximizingPlayer ? -1 : 1);修正Think方法调用参数
在Think方法中,执行board.MakeMove(currentMove)后,当前玩家是对方,因此调用MiniMaxAB时的maximizingPlayer参数应为!board.IsWhiteToMove,确保搜索视角正确。
内容的提问来源于stack exchange,提问作者Longfield Titans

