diff --git a/src/Lynx/Model/Game.cs b/src/Lynx/Model/Game.cs index 1ac772fa6..3d74c1af0 100644 --- a/src/Lynx/Model/Game.cs +++ b/src/Lynx/Model/Game.cs @@ -118,43 +118,20 @@ public bool Update50movesRule(Move moveToPlay, bool isCapture) /// /// Basic algorithm described in https://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf - /// Appart from that, tests for three fold repetition if is true, two otherwise /// - /// Whether real threefold repetition is required, 'two-fold' will be checked otherwise. Usual strategy is to do threefold only for pv nodes /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsThreefoldRepetition(bool requiresThreefold) + public bool IsThreefoldRepetition() { var currentHash = CurrentPosition.UniqueIdentifier; // [Count - 1] would be the last one, we want to start searching 2 ealier and finish HalfMovesWithoutCaptureOrPawnMove earlier var limit = Math.Max(0, PositionHashHistory.Count - 1 - HalfMovesWithoutCaptureOrPawnMove); - - if (!requiresThreefold) + for (int i = PositionHashHistory.Count - 3; i >= limit; i -= 2) { - for (int i = PositionHashHistory.Count - 3; i >= limit; i -= 2) + if (currentHash == PositionHashHistory[i]) { - if (currentHash == PositionHashHistory[i]) - { - return true; - } - } - } - else - { - for (int i = PositionHashHistory.Count - 3; i >= limit; i -= 2) - { - if (currentHash == PositionHashHistory[i]) - { - if (requiresThreefold) - { - requiresThreefold = false; - } - else - { - return true; - } - } + return true; } } diff --git a/src/Lynx/Search/NegaMax.cs b/src/Lynx/Search/NegaMax.cs index fd5dbeb9c..03259e715 100644 --- a/src/Lynx/Search/NegaMax.cs +++ b/src/Lynx/Search/NegaMax.cs @@ -235,7 +235,7 @@ private int NegaMax(int depth, int ply, int alpha, int beta, bool parentWasNullM Game.PositionHashHistory.Add(position.UniqueIdentifier); int evaluation; - if (canBeRepetition && (Game.IsThreefoldRepetition(pvNode) || Game.Is50MovesRepetition())) + if (canBeRepetition && (Game.IsThreefoldRepetition() || Game.Is50MovesRepetition())) { evaluation = 0; diff --git a/tests/Lynx.Test/BestMove/RegressionTest.cs b/tests/Lynx.Test/BestMove/RegressionTest.cs index d1a1e1c07..8ed41c6c8 100644 --- a/tests/Lynx.Test/BestMove/RegressionTest.cs +++ b/tests/Lynx.Test/BestMove/RegressionTest.cs @@ -249,6 +249,8 @@ public void InvalidPV(string positionCommand) engine.AdjustPosition(positionCommand); var bestMove = engine.BestMove(new GoCommand($"go depth {5}")); + Assert.Zero(bestMove.Evaluation); + Assert.AreEqual(1, bestMove.Moves.Count); Assert.AreEqual("b8c7", bestMove.BestMove.UCIString()); } @@ -392,38 +394,4 @@ public void PawnlessEndgames(string fen, string[]? allowedUCIMoveString, string[ { TestBestMove(fen, allowedUCIMoveString, excludedUCIMoveString); } - - [Test] - public void FalseThreefoldRepetitionDetected() - { - const string positionCommand = - "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK1NR w KQq - 0 1 " + - "moves b1c3 c7c5 d2d4 c5d4 d1d4 b8c6 d4a4 d7d5 g1f3 g8f6 c1g5 c8d7 g5f6 e7f6 e1c1 c6b4 a4b3 d7e6 d1d4 " + - "d8b6 a2a3 b4c6 b3b6 a7b6 d4d2 f8c5 e2e3 c6e7 h1d1 c5d6 c3b5 e8d7 b5d6 d7d6 f3d4 h8c8 e3e4 c8c5 b2b4 c5c4 " + - "e4d5 e7d5 c1b2 g7g6 d4b5 d6c6 b5d4 c6d7 d4b5 d7c6 b5d4 c6d6 d4b5 d6e5 d2e2 e5f4 e2d2 f4e5 d2e2 e5f4 e2d2 " + - "d5e7 b5d6 c4c7 d1e1 f4g4 c2c3 g4g5 d6e4 g5h6 e4f6 h6g7 f6e4 h7h6 g2g3 e7d5 d2d4 d5f6 e4d6 c7d7 e1e2 a8d8 " + - "d6b5 e6d5 h2h4 d8c8 e2e1 c8d8 e1e3 d8c8"; - - var engine = GetEngine(); - - engine.AdjustPosition(positionCommand); - - var bestMove = engine.BestMove(new GoCommand("go depth 1")); - Assert.Less(bestMove.Evaluation, -150); - - engine.NewGame(); - engine.AdjustPosition(positionCommand); - bestMove = engine.BestMove(new GoCommand("go depth 5")); - Assert.Less(bestMove.Evaluation, -150); - - engine.NewGame(); - engine.AdjustPosition(positionCommand); - bestMove = engine.BestMove(new GoCommand("go depth 2")); - Assert.Less(bestMove.Evaluation, -150); - - engine.NewGame(); - engine.AdjustPosition(positionCommand); - bestMove = engine.BestMove(new GoCommand("go depth 10")); - Assert.Less(bestMove.Evaluation, -150); - } } diff --git a/tests/Lynx.Test/GameTest.cs b/tests/Lynx.Test/GameTest.cs index 95f690918..62b30de1a 100644 --- a/tests/Lynx.Test/GameTest.cs +++ b/tests/Lynx.Test/GameTest.cs @@ -27,19 +27,19 @@ public void IsThreefoldRepetition_1() Assert.DoesNotThrow(() => game.MakeMove(repeatedMoves[2])); game.MakeMove(repeatedMoves[3]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); game.MakeMove(repeatedMoves[4]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); game.MakeMove(repeatedMoves[5]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); game.MakeMove(repeatedMoves[6]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); game.MakeMove(repeatedMoves[7]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); } [Test] @@ -66,46 +66,19 @@ public void IsThreefoldRepetition_2() Assert.DoesNotThrow(() => game.MakeMove(repeatedMoves[2])); game.MakeMove(repeatedMoves[3]); - Assert.True(game.IsThreefoldRepetition(false)); - Assert.False(game.IsThreefoldRepetition(true)); + Assert.True(game.IsThreefoldRepetition()); game.MakeMove(repeatedMoves[4]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); game.MakeMove(repeatedMoves[5]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); game.MakeMove(repeatedMoves[6]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); game.MakeMove(repeatedMoves[7]); - Assert.True(game.IsThreefoldRepetition(false)); - Assert.True(game.IsThreefoldRepetition(true)); - } - - [Test] - public void EvaluateFinalPosition_Threefold_PVNode() - { - const string winningPosition = "7k/8/5KR1/8/8/8/5R2/8 w - - 0 1"; - - var game = new Game(winningPosition); - var repeatedMoves = new List - { - MoveExtensions.Encode((int)BoardSquare.f2, (int)BoardSquare.e2, (int)Piece.R), - MoveExtensions.Encode((int)BoardSquare.h8, (int)BoardSquare.h7, (int)Piece.k), - MoveExtensions.Encode((int)BoardSquare.e2, (int)BoardSquare.f2, (int)Piece.R), - MoveExtensions.Encode((int)BoardSquare.h7, (int)BoardSquare.h8, (int)Piece.k), - MoveExtensions.Encode((int)BoardSquare.f2, (int)BoardSquare.e2, (int)Piece.R), - MoveExtensions.Encode((int)BoardSquare.h8, (int)BoardSquare.h7, (int)Piece.k), - MoveExtensions.Encode((int)BoardSquare.e2, (int)BoardSquare.f2, (int)Piece.R), - MoveExtensions.Encode((int)BoardSquare.h7, (int)BoardSquare.h8, (int)Piece.k), // Triple position repetition - }; - - repeatedMoves.ForEach(move => Assert.DoesNotThrow(() => game.MakeMove(move))); - - Assert.AreEqual(repeatedMoves.Count + 1, game.PositionHashHistory.Count); - Assert.True(game.IsThreefoldRepetition(requiresThreefold: true)); - Assert.True(game.IsThreefoldRepetition(requiresThreefold: false)); + Assert.True(game.IsThreefoldRepetition()); } [Test] @@ -134,18 +107,18 @@ public void IsThreefoldRepetition_CastleRightsRemoval() Assert.DoesNotThrow(() => game.MakeMove(repeatedMoves[2])); game.MakeMove(repeatedMoves[3]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); Assert.DoesNotThrow(() => game.MakeMove(repeatedMoves[4])); Assert.DoesNotThrow(() => game.MakeMove(repeatedMoves[5])); game.MakeMove(repeatedMoves[6]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); Assert.DoesNotThrow(() => game.MakeMove(repeatedMoves[6])); game.MakeMove(repeatedMoves[7]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); // Position with castling rights, lost in move Ke1d1 winningPosition = new Position("1n2k2r/8/8/8/8/8/4PPPP/1N2K2R w Kk - 0 1"); @@ -170,7 +143,7 @@ public void IsThreefoldRepetition_CastleRightsRemoval() } game.MakeMove(repeatedMoves[^1]); - Assert.False(game.IsThreefoldRepetition(false)); // Same position, but white not can't castle + Assert.False(game.IsThreefoldRepetition()); // Same position, but white not can't castle #if DEBUG Assert.AreEqual(repeatedMoves.Count, game.MoveHistory.Count); @@ -183,7 +156,7 @@ public void IsThreefoldRepetition_CastleRightsRemoval() Assert.DoesNotThrow(() => game.MakeMove(repeatedMoves[5])); game.MakeMove(repeatedMoves[6]); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); } [Test] diff --git a/tests/Lynx.Test/OnlineTablebaseProberTest.cs b/tests/Lynx.Test/OnlineTablebaseProberTest.cs index 52fd3e5b4..8f6144f97 100644 --- a/tests/Lynx.Test/OnlineTablebaseProberTest.cs +++ b/tests/Lynx.Test/OnlineTablebaseProberTest.cs @@ -387,7 +387,7 @@ public async Task RootSearch_ForceThreefoldRepetitionWhenLosing() Assert.AreEqual("h8g7", result.BestMove.UCIString()); game.MakeMove(result.BestMove); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); // Using local method due to async Span limitation static Game ParseGame() @@ -413,7 +413,7 @@ public async Task RootSearch_ForceThreefoldRepetitionWhenBlessedLosing() Assert.AreEqual("h8g7", result.BestMove.UCIString()); game.MakeMove(result.BestMove); - Assert.True(game.IsThreefoldRepetition(false)); + Assert.True(game.IsThreefoldRepetition()); // Using local method due to async Span limitation static Game ParseGame()