Skip to content

Commit

Permalink
Remove QSearch
Browse files Browse the repository at this point in the history
  • Loading branch information
eduherminio committed Nov 19, 2024
1 parent 6783073 commit 1cc2477
Showing 1 changed file with 1 addition and 153 deletions.
154 changes: 1 addition & 153 deletions src/Lynx/Search/NegaMax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </param>
/// <returns></returns>
[SkipLocalsInit]
private int NegaMax(int depth, int ply, int alpha, int beta, bool parentWasNullMove = false)
{
var position = Game.CurrentPosition;
Expand Down Expand Up @@ -497,156 +496,5 @@ void RevertMove()
/// Defaults to the works possible score for Black, Int.MaxValue
/// </param>
/// <returns></returns>
[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<Move> 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<int> 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;
}

0 comments on commit 1cc2477

Please sign in to comment.