Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔍 Add capture history #634

Merged
merged 30 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f7d0f4c
Add basic capture history
eduherminio Jan 30, 2024
fb5ad1e
Replace MVV-LVA with only MVV, using SEE values as piece values
eduherminio Jan 30, 2024
75e238b
Use smaller values
eduherminio Jan 30, 2024
1e29df4
Increase MVV piece values to the hundreds order
eduherminio Jan 30, 2024
b7a0a67
Add capture history malus
eduherminio Jan 30, 2024
2064efd
Reduce MVV again
eduherminio Jan 30, 2024
8a01638
Fix capture malus impl
eduherminio Jan 30, 2024
1480181
Revert "Reduce MVV again"
eduherminio Jan 31, 2024
ecfb9eb
Increase MVV to thousands
eduherminio Jan 31, 2024
49b3c42
Add missing king MVV values
eduherminio Jan 31, 2024
8287cec
Back to MVV-LVA but with increased values
eduherminio Jan 31, 2024
bfd82f0
Pre-calculate captured piece
eduherminio Jan 31, 2024
724afad
Remove king as victim in MVV-LVA
eduherminio Jan 31, 2024
8c1d873
Remove also for white
eduherminio Jan 31, 2024
f161ea3
Revert "Remove also for white"
eduherminio Jan 31, 2024
eac33a9
Revert "Remove king as victim in MVV-LVA"
eduherminio Jan 31, 2024
84b8e4b
Refactored `MostValueableVictimLeastValuableAttacker` to show its tru…
eduherminio Jan 31, 2024
ceb6010
Don't clear capture history
eduherminio Jan 31, 2024
eae7283
Fix en-passant tests
eduherminio Jan 31, 2024
2680be9
'Age' capture history dividing it by two after every search instead o…
eduherminio Jan 31, 2024
2f33c3d
Revert "'Age' capture history dividing it by two after every search i…
eduherminio Jan 31, 2024
e3f8aad
Increment MVV-LVA so that capture history can only affect LVA or B-N …
eduherminio Jan 31, 2024
9f183be
Increment MVV-LVA so that capture history can only affect LVA or B-N …
eduherminio Jan 31, 2024
ec3ddeb
Revert "Increment MVV-LVA so that capture history can only affect LVA…
eduherminio Jan 31, 2024
04ab796
Revert "Increment MVV-LVA so that capture history can only affect LVA…
eduherminio Jan 31, 2024
13f1007
Remove dumb assignment
eduherminio Feb 1, 2024
1dd8dc5
Use MVV only
eduherminio Feb 1, 2024
44ebf49
Merge remote-tracking branch 'origin/main' into move-ordering/capture…
eduherminio Feb 1, 2024
9386ea6
Merge remote-tracking branch 'origin/main' into move-ordering/capture…
eduherminio Feb 1, 2024
bb9cc3c
Revert back to MVV-LVA
eduherminio Feb 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/Lynx/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,20 @@ public Engine(ChannelWriter<string> engineWriter)
_absoluteSearchCancellationTokenSource = new();
_engineWriter = engineWriter;

_historyMoves = new int[12][];
for (int i = 0; i < _historyMoves.Length; ++i)
_quietHistory = new int[12][];
for (int i = 0; i < _quietHistory.Length; ++i)
{
_historyMoves[i] = new int[64];
_quietHistory[i] = new int[64];
}

_captureHistory = new int[12][][];
for (int i = 0; i < 12; ++i)
{
_captureHistory[i] = new int[64][];
for (var j = 0; j < 64; ++j)
{
_captureHistory[i][j] = new int[12];
}
}

InitializeTT();
Expand Down
53 changes: 34 additions & 19 deletions src/Lynx/EvaluationConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@ public static class EvaluationConstants
/// </summary>
public static readonly int[][] LMRReductions = new int[Constants.AbsoluteMaxDepth][];

/// <summary>
/// [0, 4, 136, 276, 424, 580, 744, 916, 1096, 1284, 1480, 1684, 1896, 1896, 1896, 1896, ...]
/// </summary>
public static readonly int[] HistoryBonus = new int[Constants.AbsoluteMaxDepth];

static EvaluationConstants()
Expand Down Expand Up @@ -284,34 +287,46 @@ static EvaluationConstants()
}
}

#pragma warning disable IDE0055 // Discard formatting in this region

/// <summary>
/// MVV LVA [attacker,victim] [12,64]
/// MVV LVA [attacker,victim] 12x11
/// Original based on
/// https://github.com/maksimKorzh/chess_programming/blob/master/src/bbc/move_ordering_intro/bbc.c#L2406
/// (Victims) Pawn Knight Bishop Rook Queen King
/// (Attackers)
/// Pawn 105 205 305 405 505 605
/// Knight 104 204 304 404 504 604
/// Bishop 103 203 303 403 503 603
/// Rook 102 202 302 402 502 602
/// Queen 101 201 301 401 501 601
/// King 100 200 300 400 500 600
/// Pawn 105 205 305 405 505 0
/// Knight 104 204 304 404 504 0
/// Bishop 103 203 303 403 503 0
/// Rook 102 202 302 402 502 0
/// Queen 101 201 301 401 501 0
/// King 100 200 300 400 500 0
/// </summary>
public static readonly int[][] MostValueableVictimLeastValuableAttacker =
[ // P N B R Q K p n b r q k
/* P */ [ 0, 0, 0, 0, 0, 0, 1500, 4000, 4500, 5500, 11500 ], // 0],
/* N */ [ 0, 0, 0, 0, 0, 0, 1400, 3900, 4400, 5400, 11400 ], // 0],
/* B */ [ 0, 0, 0, 0, 0, 0, 1300, 3800, 4300, 5300, 11300 ], // 0],
/* R */ [ 0, 0, 0, 0, 0, 0, 1200, 3700, 4200, 5200, 11200 ], // 0],
/* Q */ [ 0, 0, 0, 0, 0, 0, 1100, 3600, 4100, 5100, 11100 ], // 0],
/* K */ [ 0, 0, 0, 0, 0, 0, 1000, 3500, 4001, 5000, 11000 ], // 0],
/* p */ [1500, 4000, 4500, 5500, 11500, 0, 0, 0, 0, 0, 0 ], // 0],
/* n */ [1400, 3900, 4400, 5400, 11400, 0, 0, 0, 0, 0, 0 ], // 0],
/* b */ [1300, 3800, 4300, 5300, 11300, 0, 0, 0, 0, 0, 0 ], // 0],
/* r */ [1200, 3700, 4200, 5200, 11200, 0, 0, 0, 0, 0, 0 ], // 0],
/* q */ [1100, 3600, 4100, 5100, 11100, 0, 0, 0, 0, 0, 0 ], // 0],
/* k */ [1000, 3500, 4001, 5000, 11000, 0, 0, 0, 0, 0, 0 ], // 0]
];

public static readonly int[] MVV_PieceValues =
[
[105, 205, 305, 405, 505, 605, 105, 205, 305, 405, 505, 605],
[104, 204, 304, 404, 504, 604, 104, 204, 304, 404, 504, 604],
[103, 203, 303, 403, 503, 603, 103, 203, 303, 403, 503, 603],
[102, 202, 302, 402, 502, 602, 102, 202, 302, 402, 502, 602],
[101, 201, 301, 401, 501, 601, 101, 201, 301, 401, 501, 601],
[100, 200, 300, 400, 500, 600, 100, 200, 300, 400, 500, 600],
[105, 205, 305, 405, 505, 605, 105, 205, 305, 405, 505, 605],
[104, 204, 304, 404, 504, 604, 104, 204, 304, 404, 504, 604],
[103, 203, 303, 403, 503, 603, 103, 203, 303, 403, 503, 603],
[102, 202, 302, 402, 502, 602, 102, 202, 302, 402, 502, 602],
[101, 201, 301, 401, 501, 601, 101, 201, 301, 401, 501, 601],
[100, 200, 300, 400, 500, 600, 100, 200, 300, 400, 500, 600]
1000, 3500, 4000, 5000, 11000, 0,
1000, 3500, 4000, 5000, 11000, 0,
0
];

#pragma warning restore IDE0055

/// <summary>
/// Base absolute checkmate evaluation value. Actual absolute evaluations are lower than this one by a number of <see cref="Position.DepthCheckmateFactor"/>
/// </summary>
Expand Down
17 changes: 12 additions & 5 deletions src/Lynx/Search/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public sealed partial class Engine
private const int MaxValue = short.MaxValue;

/// <summary>
/// Returns the score evaluation of a move taking into account <see cref="_isScoringPV"/>, <paramref name="bestMoveTTCandidate"/>, <see cref="EvaluationConstants.MostValueableVictimLeastValuableAttacker"/>, <see cref="_killerMoves"/> and <see cref="_historyMoves"/>
/// Returns the score evaluation of a move taking into account <see cref="_isScoringPV"/>, <paramref name="bestMoveTTCandidate"/>, <see cref="EvaluationConstants.MostValueableVictimLeastValuableAttacker"/>, <see cref="_killerMoves"/> and <see cref="_quietHistory"/>
/// </summary>
/// <param name="move"></param>
/// <param name="depth"></param>
Expand Down Expand Up @@ -99,12 +99,19 @@ internal int ScoreMove(Move move, int depth, bool isNotQSearch, ShortMove bestMo

if (isCapture)
{

var baseCaptureScore = (isPromotion || move.IsEnPassant() || SEE.IsGoodCapture(Game.CurrentPosition, move))
? EvaluationConstants.GoodCaptureMoveBaseScoreValue
: EvaluationConstants.BadCaptureMoveBaseScoreValue;

return baseCaptureScore + EvaluationConstants.MostValueableVictimLeastValuableAttacker[move.Piece()][move.CapturedPiece()];
var piece = move.Piece();
var capturedPiece = move.CapturedPiece();

Debug.Assert(capturedPiece != (int)Piece.K && capturedPiece != (int)Piece.k, $"{move.UCIString()} capturing king is generated in position {Game.CurrentPosition.FEN()}");

return baseCaptureScore
+ EvaluationConstants.MostValueableVictimLeastValuableAttacker[piece][capturedPiece]
//+ EvaluationConstants.MVV_PieceValues[capturedPiece]
+ _captureHistory[piece][move.TargetSquare()][capturedPiece];
}

if (isPromotion)
Expand All @@ -131,7 +138,7 @@ internal int ScoreMove(Move move, int depth, bool isNotQSearch, ShortMove bestMo
}

// History move or 0 if not found
return EvaluationConstants.BaseMoveScore + _historyMoves[move.Piece()][move.TargetSquare()];
return EvaluationConstants.BaseMoveScore + _quietHistory[move.Piece()][move.TargetSquare()];
}

return EvaluationConstants.BaseMoveScore;
Expand Down Expand Up @@ -351,7 +358,7 @@ internal void PrintHistoryMoves()

for (int i = 0; i < 12; ++i)
{
var tmp = _historyMoves[i];
var tmp = _quietHistory[i];
for (int j = 0; j < 64; ++i)
{
var item = tmp[j];
Expand Down
13 changes: 10 additions & 3 deletions src/Lynx/Search/IDDFS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ public sealed partial class Engine
/// <summary>
/// 12x64
/// </summary>
private readonly int[][] _historyMoves;
private readonly int[][] _quietHistory;

/// <summary>
/// 12x64x12
/// </summary>
private readonly int[][][] _captureHistory;

private readonly int[] _maxDepthReached = new int[Constants.AbsoluteMaxDepth];
private TranspositionTable _tt = [];
Expand Down Expand Up @@ -298,10 +303,12 @@ private int CheckPonderHit(ref SearchResult? lastSearchResult, int depth)
Array.Clear(_killerMoves[2]);
Debug.Assert(_killerMoves.Length == 3);

for (int i = 0; i < _historyMoves.Length; i++)
for (int i = 0; i < _quietHistory.Length; i++)
{
Array.Clear(_historyMoves[i]);
Array.Clear(_quietHistory[i]);
}

// Not clearing _captureHistory on purpose
}

return depth;
Expand Down
43 changes: 36 additions & 7 deletions src/Lynx/Search/NegaMax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ private int NegaMax(int depth, int ply, int alpha, int beta, bool parentWasNullM
}

// -= history/(maxHistory/2)
reduction -= 2 * _historyMoves[move.Piece()][move.TargetSquare()] / Configuration.EngineSettings.History_MaxMoveValue;
reduction -= 2 * _quietHistory[move.Piece()][move.TargetSquare()] / Configuration.EngineSettings.History_MaxMoveValue;

// Don't allow LMR to drop into qsearch or increase the depth
// depth - 1 - depth +2 = 1, min depth we want
Expand Down Expand Up @@ -338,29 +338,58 @@ private int NegaMax(int depth, int ply, int alpha, int beta, bool parentWasNullM
{
PrintMessage($"Pruning: {move} is enough");

if (!move.IsCapture())
if (move.IsCapture())
{
var piece = move.Piece();
var targetSquare = move.TargetSquare();
var capturedPiece = move.CapturedPiece();

_captureHistory[piece][targetSquare][capturedPiece] = ScoreHistoryMove(
_captureHistory[piece][targetSquare][capturedPiece],
EvaluationConstants.HistoryBonus[depth]);

// 🔍 Capture history penalty/malus
// When a capture fails high, penalize previous visited captures
for (int i = 0; i < visitedMovesCounter - 1; ++i)
{
var visitedMove = visitedMoves[i];

if (visitedMove.IsCapture())
{
var visitedMovePiece = visitedMove.Piece();
var visitedMoveTargetSquare = visitedMove.TargetSquare();
var visitedMoveCapturedPiece = visitedMove.CapturedPiece();

_captureHistory[visitedMovePiece][visitedMoveTargetSquare][visitedMoveCapturedPiece] = ScoreHistoryMove(
_captureHistory[visitedMovePiece][visitedMoveTargetSquare][visitedMoveCapturedPiece],
-EvaluationConstants.HistoryBonus[depth]);
}
}
}
else
{
// 🔍 Quiet history moves
// Doing this only in beta cutoffs (instead of when eval > alpha) was suggested by Sirius author
var piece = move.Piece();
var targetSquare = move.TargetSquare();

_historyMoves[piece][targetSquare] = ScoreHistoryMove(
_historyMoves[piece][targetSquare],
_quietHistory[piece][targetSquare] = ScoreHistoryMove(
_quietHistory[piece][targetSquare],
EvaluationConstants.HistoryBonus[depth]);

// 🔍 History penalty/malus
// 🔍 Quiet history penalty/malus
// When a quiet move fails high, penalize previous visited quiet moves
for (int i = 0; i < visitedMovesCounter - 1; ++i)
{
var visitedMove = visitedMoves[i];

if (!visitedMove.IsCapture())
{
var visitedMovePiece = visitedMove.Piece();
var visitedMoveTargetSquare = visitedMove.TargetSquare();

_historyMoves[visitedMovePiece][visitedMoveTargetSquare] = ScoreHistoryMove(
_historyMoves[visitedMovePiece][visitedMoveTargetSquare],
_quietHistory[visitedMovePiece][visitedMoveTargetSquare] = ScoreHistoryMove(
_quietHistory[visitedMovePiece][visitedMoveTargetSquare],
-EvaluationConstants.HistoryBonus[depth]);
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Lynx.Test/Model/MoveScoreTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@ public void MoveScoreEnPassant(string fen, string moveWithHighestScore)
var allMoves = MoveGenerator.GenerateAllMoves(engine.Game.CurrentPosition).OrderByDescending(move => engine.ScoreMove(move, default, default)).ToList();

Assert.AreEqual(moveWithHighestScore, allMoves[0].UCIString());
Assert.AreEqual(EvaluationConstants.GoodCaptureMoveBaseScoreValue + EvaluationConstants.MostValueableVictimLeastValuableAttacker[0][0], engine.ScoreMove(allMoves[0], default, default));
Assert.AreEqual(EvaluationConstants.GoodCaptureMoveBaseScoreValue + EvaluationConstants.MostValueableVictimLeastValuableAttacker[0][6], engine.ScoreMove(allMoves[0], default, default));
}
}
Loading