diff --git a/Rudim.Test/UnitTest/Board/TraversalTest.cs b/Rudim.Test/UnitTest/Board/TraversalTest.cs index b94639f..cffbd0e 100644 --- a/Rudim.Test/UnitTest/Board/TraversalTest.cs +++ b/Rudim.Test/UnitTest/Board/TraversalTest.cs @@ -13,10 +13,10 @@ public class TraversalTest // This helps keep track if certain optimizations are good enough to make up for the extra time spent // Compare time spent with and without the change before updating the keys [Theory] - [InlineData(Helpers.StartingFEN, 2170912, 7, 8)] - [InlineData(Helpers.EndgameFEN, 450006, 36, 9)] - [InlineData(Helpers.AdvancedMoveFEN, 4800762, 1750, 8)] - [InlineData(Helpers.KiwiPeteFEN, 11787207, -42, 8)] + [InlineData(Helpers.StartingFEN, 3513132, 7, 8)] + [InlineData(Helpers.EndgameFEN, 782171, 36, 9)] + [InlineData(Helpers.AdvancedMoveFEN, 6979392, 1750, 8)] + [InlineData(Helpers.KiwiPeteFEN, 15534609, -42, 8)] public void ShouldTraverseDeterministically(string position, int expectedNodes, int expectedScore, int depth) { Global.Reset(); diff --git a/Rudim/Common/TranspositionTable.cs b/Rudim/Common/TranspositionTable.cs index 980cd4b..7743369 100644 --- a/Rudim/Common/TranspositionTable.cs +++ b/Rudim/Common/TranspositionTable.cs @@ -1,4 +1,6 @@ +using Rudim.Board; using System; +using System.Collections.Generic; namespace Rudim.Common { @@ -51,6 +53,52 @@ public static void SubmitEntry(ulong hash, int score, int depth, Move bestMove, return; Entries[index] = new TranspositionTableEntry { Hash = hash, Score = score, Depth = depth, BestMove = bestMove, Type = entryType }; } + + public static List CollectPrincipalVariation(BoardState boardState) + { + List pv = new(); + while (true) + { + TranspositionTableEntry entry = Entries[boardState.BoardHash & (Capacity - 1)]; + if (entry == null || entry.Hash != boardState.BoardHash || entry.Type != TranspositionEntryType.Exact) + { + break; + } + pv.Add(entry.BestMove); + boardState.MakeMove(entry.BestMove); + } + + for (int i = pv.Count - 1; i >= 0; i--) + { + boardState.UnmakeMove(pv[i]); + } + return pv; + } + + public static int AdjustScore(int score, int ply) + { + if (!IsCloseToCheckmate(score)) + { + return score; + } + + return score + (score > 0 ? +ply : -ply); + } + + public static int RetrieveScore(int score, int ply) + { + if (!IsCloseToCheckmate(score)) + { + return score; + } + + return score + (score > 0 ? -ply : +ply); + } + + private static bool IsCloseToCheckmate(int score) + { + return Constants.MaxCentipawnEval - Math.Abs(score) <= Constants.MaxPly; + } } public class TranspositionTableEntry diff --git a/Rudim/Search/IterativeDeepening.cs b/Rudim/Search/IterativeDeepening.cs index 5f0df79..fdae597 100644 --- a/Rudim/Search/IterativeDeepening.cs +++ b/Rudim/Search/IterativeDeepening.cs @@ -2,7 +2,9 @@ using Rudim.CLI; using Rudim.Common; using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; namespace Rudim.Search @@ -37,7 +39,10 @@ public static void Search(BoardState boardState, int depth, CancellationToken ca if (debugMode) { - CliClient.WriteLine($"info depth {i} score cp {Score} nodes {nodesTraversed} time {time} nps {nps}"); + List pv = TranspositionTable.CollectPrincipalVariation(boardState); + string pvString = string.Join(' ', pv.Select(move => + move.Source.ToString() + move.Target.ToString() + move.GetPromotionChar())); + CliClient.WriteLine($"info depth {i} score cp {Score} nodes {nodesTraversed} time {time} nps {nps} pv {pvString}"); } } } diff --git a/Rudim/Search/Negamax.cs b/Rudim/Search/Negamax.cs index 467d8f2..9cc7732 100644 --- a/Rudim/Search/Negamax.cs +++ b/Rudim/Search/Negamax.cs @@ -28,11 +28,14 @@ public static int Search(BoardState boardState, int depth, CancellationToken can private static int Search(BoardState boardState, int depth, int alpha, int beta, CancellationToken cancellationToken) { + int ply = _searchDepth - depth; + Nodes++; + (bool hasValue, int ttScore, Move bestEvaluation) = TranspositionTable.GetEntry(boardState.BoardHash, alpha, beta, depth); if (hasValue) { BestMove = bestEvaluation; - return ttScore; + return TranspositionTable.RetrieveScore(ttScore, ply); } if (boardState.IsDraw()) @@ -42,10 +45,8 @@ private static int Search(BoardState boardState, int depth, int alpha, int beta, return Quiescence.Search(boardState, alpha, beta, cancellationToken); int originalAlpha = alpha; - int ply = _searchDepth - depth; bool foundPv = false; TranspositionEntryType entryType = TranspositionEntryType.Alpha; - Nodes++; boardState.GenerateMoves(); PopulateMoveScores(boardState, ply); @@ -70,7 +71,7 @@ private static int Search(BoardState boardState, int depth, int alpha, int beta, boardState.UnmakeMove(move); if (score >= beta) { - TranspositionTable.SubmitEntry(boardState.BoardHash, beta, depth, move, TranspositionEntryType.Beta); + TranspositionTable.SubmitEntry(boardState.BoardHash, TranspositionTable.AdjustScore(beta, ply), depth, move, TranspositionEntryType.Beta); return BetaCutoff(beta, move, ply); } if (score > alpha) @@ -90,7 +91,7 @@ private static int Search(BoardState boardState, int depth, int alpha, int beta, if (alpha != originalAlpha) BestMove = bestEvaluation; - TranspositionTable.SubmitEntry(boardState.BoardHash, alpha, depth, bestEvaluation, entryType); + TranspositionTable.SubmitEntry(boardState.BoardHash, TranspositionTable.AdjustScore(alpha, ply), depth, bestEvaluation, entryType); return alpha; }