diff --git a/src/Lynx/Search/NegaMax.cs b/src/Lynx/Search/NegaMax.cs index 8808cf7bf..fdc40321b 100644 --- a/src/Lynx/Search/NegaMax.cs +++ b/src/Lynx/Search/NegaMax.cs @@ -19,7 +19,6 @@ public sealed partial class Engine /// Best score Side's to move's opponent can achieve, assuming best play by Side to move. /// /// - [SkipLocalsInit] private int NegaMax(int depth, int ply, int alpha, int beta, bool parentWasNullMove = false) { var position = Game.CurrentPosition; @@ -497,156 +496,5 @@ void RevertMove() /// Defaults to the works possible score for Black, Int.MaxValue /// /// - [SkipLocalsInit] - public int QuiescenceSearch(int ply, int alpha, int beta) - { - var position = Game.CurrentPosition; - - _absoluteSearchCancellationTokenSource.Token.ThrowIfCancellationRequested(); - _searchCancellationTokenSource.Token.ThrowIfCancellationRequested(); - - if (ply >= Configuration.EngineSettings.MaxDepth) - { - _logger.Info("Max depth {0} reached", Configuration.EngineSettings.MaxDepth); - return position.StaticEvaluation(Game.HalfMovesWithoutCaptureOrPawnMove).Score; - } - - var pvIndex = PVTable.Indexes[ply]; - var nextPvIndex = PVTable.Indexes[ply + 1]; - _pVTable[pvIndex] = _defaultMove; // Nulling the first value before any returns - - var ttProbeResult = _tt.ProbeHash(position, 0, ply, alpha, beta); - if (ttProbeResult.Score != EvaluationConstants.NoHashEntry) - { - return ttProbeResult.Score; - } - ShortMove ttBestMove = ttProbeResult.BestMove; - - _maxDepthReached[ply] = ply; - - var staticEval = ttProbeResult.NodeType != NodeType.Unknown - ? ttProbeResult.StaticEval - : position.StaticEvaluation(Game.HalfMovesWithoutCaptureOrPawnMove).Score; - - Game.UpdateStaticEvalInStack(ply, staticEval); - - // Beta-cutoff (updating alpha after this check) - if (staticEval >= beta) - { - PrintMessage(ply - 1, "Pruning before starting quiescence search"); - return staticEval; - } - - // Better move - if (staticEval > alpha) - { - alpha = staticEval; - } - - Span moves = stackalloc Move[Constants.MaxNumberOfPossibleMovesInAPosition]; - var pseudoLegalMoves = MoveGenerator.GenerateAllCaptures(position, moves); - if (pseudoLegalMoves.Length == 0) - { - // Checking if final position first: https://github.com/lynx-chess/Lynx/pull/358 - return staticEval; - } - - var nodeType = NodeType.Alpha; - Move? bestMove = null; - int bestScore = staticEval; - - bool isAnyCaptureValid = false; - - Span moveScores = stackalloc int[pseudoLegalMoves.Length]; - for (int i = 0; i < pseudoLegalMoves.Length; ++i) - { - moveScores[i] = ScoreMove(pseudoLegalMoves[i], ply, isNotQSearch: false, ttBestMove); - } - - for (int i = 0; i < pseudoLegalMoves.Length; ++i) - { - // Incremental move sorting, inspired by https://github.com/jw1912/Chess-Challenge and suggested by toanth - // There's no need to sort all the moves since most of them don't get checked anyway - // So just find the first unsearched one with the best score and try it - for (int j = i + 1; j < pseudoLegalMoves.Length; j++) - { - if (moveScores[j] > moveScores[i]) - { - (moveScores[i], moveScores[j], pseudoLegalMoves[i], pseudoLegalMoves[j]) = (moveScores[j], moveScores[i], pseudoLegalMoves[j], pseudoLegalMoves[i]); - } - } - - var move = pseudoLegalMoves[i]; - - // 🔍 QSearch SEE pruning: pruning bad captures - if (moveScores[i] < EvaluationConstants.PromotionMoveScoreValue && moveScores[i] >= EvaluationConstants.BadCaptureMoveBaseScoreValue) - { - continue; - } - - var gameState = position.MakeMove(move); - if (!position.WasProduceByAValidMove()) - { - position.UnmakeMove(move, gameState); - continue; - } - - ++_nodes; - isAnyCaptureValid = true; - - PrintPreMove(position, ply, move, isQuiescence: true); - - // No need to check for threefold or 50 moves repetitions, since we're only searching captures, promotions, and castles - Game.UpdateMoveinStack(ply, move); - -#pragma warning disable S2234 // Arguments should be passed in the same order as the method parameters - int score = -QuiescenceSearch(ply + 1, -beta, -alpha); -#pragma warning restore S2234 // Arguments should be passed in the same order as the method parameters - position.UnmakeMove(move, gameState); - - PrintMove(position, ply, move, score, isQuiescence: true); - - if (score > bestScore) - { - bestScore = score; - - // Beta-cutoff - if (score >= beta) - { - PrintMessage($"Pruning: {move} is enough to discard this line"); - - _tt.RecordHash(position, staticEval, 0, ply, bestScore, NodeType.Beta, bestMove); - - return bestScore; // The refutation doesn't matter, since it'll be pruned - } - - // Improving alpha - if (score > alpha) - { - alpha = score; - bestMove = move; - - _pVTable[pvIndex] = move; - CopyPVTableMoves(pvIndex + 1, nextPvIndex, Configuration.EngineSettings.MaxDepth - ply - 1); - - nodeType = NodeType.Exact; - } - } - } - - if (!isAnyCaptureValid - && !MoveGenerator.CanGenerateAtLeastAValidMove(position)) // Bad captures can be pruned, so all moves need to be generated for now - { - Debug.Assert(bestMove is null); - - var finalEval = Position.EvaluateFinalPosition(ply, position.IsInCheck()); - _tt.RecordHash(position, staticEval, 0, ply, finalEval, NodeType.Exact); - - return finalEval; - } - - _tt.RecordHash(position, staticEval, 0, ply, bestScore, nodeType, bestMove); - - return bestScore; - } + public int QuiescenceSearch(int ply, int alpha, int beta) => 0; }