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()