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

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;
        }
    }
}

核心问题分析

  1. 搜索函数递归逻辑错误
    在MaximizingPlayer和MinimizingPlayer的循环中,每次递归返回的moveScore直接覆盖了当前值,没有执行取最大/最小的操作。比如MaximizingPlayer分支里,应该用Math.Max(moveScore, 递归结果)来更新当前最优分数,而非直接赋值,否则只会保留最后一个走法的分数,而非所有分支中的最优值,这直接导致引擎无法正确评估所有可能走法,出现怪异选择。

  2. 终局判断顺序错误
    当前代码先判断depth == 0再判断将杀/和棋,这会导致深度耗尽时,即使局面是将杀,也只会返回子力评估值而非将杀对应的极端分数,无法识别致命的将杀局面。

  3. Think方法调用参数错误
    在Think方法中,执行board.MakeMove(currentMove)后,当前轮到对方玩家走棋,但调用MiniMaxAB时传入的maximizingPlayer参数是board.IsWhiteToMove(即对方玩家的颜色),这与搜索函数的逻辑不匹配,导致分数评估视角混乱。

修复建议

  1. 修正搜索函数循环逻辑

    • 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;
      }
      
  2. 调整终局判断顺序
    将将杀、和棋的判断放在深度判断之前,确保终局状态优先被识别:

    // 先判断终局状态
    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);
    
  3. 修正Think方法调用参数
    在Think方法中,执行board.MakeMove(currentMove)后,当前玩家是对方,因此调用MiniMaxAB时的maximizingPlayer参数应为!board.IsWhiteToMove,确保搜索视角正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 09:35:57