diff --git a/src/Lynx/Constants.cs b/src/Lynx/Constants.cs index e6b85e9a6..9985daf6f 100644 --- a/src/Lynx/Constants.cs +++ b/src/Lynx/Constants.cs @@ -536,6 +536,12 @@ public static class Constants public const int NonPawnCorrHistorySize = 16_384; public const int NonPawnCorrHistoryMask = NonPawnCorrHistorySize - 1; + public const int MinorCorrHistorySize = 16_384; + public const int MinorCorrHistoryMask = MinorCorrHistorySize - 1; + + public const int MajorCorrHistorySize = 16_384; + public const int MajorCorrHistoryMask = MajorCorrHistorySize - 1; + public const int CorrectionHistoryScale = 256; public const string NumberWithSignFormat = "+#;-#;0"; diff --git a/src/Lynx/Engine.cs b/src/Lynx/Engine.cs index 00c3823af..2b8e2e345 100644 --- a/src/Lynx/Engine.cs +++ b/src/Lynx/Engine.cs @@ -82,6 +82,8 @@ private void ResetEngine() Array.Clear(_pawnCorrHistory); Array.Clear(_nonPawnCorrHistory); + Array.Clear(_minorCorrHistory); + Array.Clear(_majorCorrHistory); // No need to clear killer move or pv table because they're cleared on every search (IDDFS) } diff --git a/src/Lynx/Model/GameState.cs b/src/Lynx/Model/GameState.cs index 4861b9071..ed1166363 100644 --- a/src/Lynx/Model/GameState.cs +++ b/src/Lynx/Model/GameState.cs @@ -12,6 +12,26 @@ public readonly struct GameState public readonly ulong NonPawnBlackKey; + #region PieceKeys + + public readonly ulong KnightWhiteKey; + + public readonly ulong KnightBlackKey; + + public readonly ulong BishopWhiteKey; + + public readonly ulong BishopBlackKey; + + public readonly ulong RookWhiteKey; + + public readonly ulong RookBlackKey; + + public readonly ulong QueenWhiteKey; + + public readonly ulong QueenBlackKey; + + #endregion + public readonly int IncremetalEvalAccumulator; public readonly int IncrementalPhaseAccumulator; @@ -22,14 +42,24 @@ public readonly struct GameState public readonly bool IsIncrementalEval; - public GameState( - ulong zobristKey, ulong kingPawnKey, ulong nonPawnWhiteKey, ulong nonPawnBlackKey, + public GameState(ulong zobristKey, + ulong kingPawnKey, ulong nonPawnWhiteKey, + ulong nonPawnBlackKey, ulong knightWhiteKey, ulong knightBlackKey, ulong bishopWhiteKey, ulong bishopBlackKey, + ulong rookWhiteKey, ulong rookBlackKey, ulong queenWhiteKey, ulong queenBlackKey, int incrementalEvalAccumulator, int incrementalPhaseAccumulator, BoardSquare enpassant, byte castle, bool isIncrementalEval) : this(zobristKey, incrementalEvalAccumulator, incrementalPhaseAccumulator, enpassant, castle, isIncrementalEval) { KingPawnKey = kingPawnKey; NonPawnWhiteKey = nonPawnWhiteKey; NonPawnBlackKey = nonPawnBlackKey; + KnightWhiteKey = knightWhiteKey; + KnightBlackKey = knightBlackKey; + BishopWhiteKey = bishopWhiteKey; + BishopBlackKey = bishopBlackKey; + RookWhiteKey = rookWhiteKey; + RookBlackKey = rookBlackKey; + QueenWhiteKey = queenWhiteKey; + QueenBlackKey = queenBlackKey; } /// diff --git a/src/Lynx/Model/Position.cs b/src/Lynx/Model/Position.cs index e792c5501..36a4d3ea2 100644 --- a/src/Lynx/Model/Position.cs +++ b/src/Lynx/Model/Position.cs @@ -23,6 +23,8 @@ public class Position : IDisposable public ulong[] NonPawnHash { get; private set; } + public ulong[] PieceUniqueIdentifiers { get; private set; } + /// /// Use as index /// @@ -81,7 +83,8 @@ public Position((BitBoard[] PieceBitBoards, BitBoard[] OccupancyBitBoards, int[] KingPawnUniqueIdentifier = ZobristTable.KingPawnHash(this); UniqueIdentifier = ZobristTable.PositionHash(this, KingPawnUniqueIdentifier, NonPawnHash[(int)Side.White], NonPawnHash[(int)Side.Black]); - + PieceUniqueIdentifiers = ArrayPool.Shared.Rent(12); + ZobristTable.PieceUniqueIdentifiers(this, PieceUniqueIdentifiers); Debug.Assert(UniqueIdentifier == ZobristTable.PositionHash(this)); Debug.Assert(ZobristTable.NonPawnSideHash(this, (int)Side.White) == NonPawnHash[(int)Side.White]); Debug.Assert(ZobristTable.NonPawnSideHash(this, (int)Side.Black) == NonPawnHash[(int)Side.Black]); @@ -103,6 +106,9 @@ public Position(Position position) NonPawnHash[(int)Side.White] = position.NonPawnHash[(int)Side.White]; NonPawnHash[(int)Side.Black] = position.NonPawnHash[(int)Side.Black]; + PieceUniqueIdentifiers = ArrayPool.Shared.Rent(12); + Array.Copy(position.PieceUniqueIdentifiers, PieceUniqueIdentifiers, position.PieceUniqueIdentifiers.Length); + PieceBitBoards = ArrayPool.Shared.Rent(12); Array.Copy(position.PieceBitBoards, PieceBitBoards, position.PieceBitBoards.Length); @@ -121,6 +127,18 @@ public Position(Position position) _incrementalPhaseAccumulator = position._incrementalPhaseAccumulator; } + public ulong MinorHash => + PieceUniqueIdentifiers[(int)Piece.N] + ^ PieceUniqueIdentifiers[(int)Piece.B] + ^ PieceUniqueIdentifiers[(int)Piece.n] + ^ PieceUniqueIdentifiers[(int)Piece.b]; + + public ulong MajorHash => + PieceUniqueIdentifiers[(int)Piece.R] + ^ PieceUniqueIdentifiers[(int)Piece.r] + ^ PieceUniqueIdentifiers[(int)Piece.Q] + ^ PieceUniqueIdentifiers[(int)Piece.q]; + #region Move making [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -129,17 +147,31 @@ public GameState MakeMove(Move move) Debug.Assert(ZobristTable.PositionHash(this) == UniqueIdentifier); Debug.Assert(ZobristTable.NonPawnSideHash(this, (int)Side.White) == NonPawnHash[(int)Side.White]); Debug.Assert(ZobristTable.NonPawnSideHash(this, (int)Side.Black) == NonPawnHash[(int)Side.Black]); - - byte castleCopy = Castle; - BoardSquare enpassantCopy = EnPassant; - ulong uniqueIdentifierCopy = UniqueIdentifier; - ulong kingPawnKeyUniqueIdentifierCopy = KingPawnUniqueIdentifier; - ulong nonPawnWhiteHashCopy = NonPawnHash[(int)Side.White]; - ulong nonPawnBlackHashCopy = NonPawnHash[(int)Side.Black]; - int incrementalEvalAccumulatorCopy = _incrementalEvalAccumulator; - int incrementalPhaseAccumulatorCopy = _incrementalPhaseAccumulator; - // We also save a copy of _isIncrementalEval, so that current move doesn't affect 'sibling' moves exploration - bool isIncrementalEvalCopy = _isIncrementalEval; + Debug.Assert(ZobristTable.MinorHash(this) == MinorHash); + Debug.Assert(ZobristTable.MajorHash(this) == MajorHash); + + +#if DEBUG + Span arr = stackalloc ulong[12]; + ZobristTable.PieceUniqueIdentifiers(this, arr); + + Debug.Assert(arr[(int)Piece.N] == PieceUniqueIdentifiers[(int)Piece.N]); + Debug.Assert(arr[(int)Piece.B] == PieceUniqueIdentifiers[(int)Piece.B]); + Debug.Assert(arr[(int)Piece.R] == PieceUniqueIdentifiers[(int)Piece.R]); + Debug.Assert(arr[(int)Piece.Q] == PieceUniqueIdentifiers[(int)Piece.Q]); + //Debug.Assert(arr[(int)Piece.K] == PieceUniqueIdentifiers[(int)Piece.K]); + Debug.Assert(arr[(int)Piece.n] == PieceUniqueIdentifiers[(int)Piece.n]); + Debug.Assert(arr[(int)Piece.b] == PieceUniqueIdentifiers[(int)Piece.b]); + Debug.Assert(arr[(int)Piece.r] == PieceUniqueIdentifiers[(int)Piece.r]); + Debug.Assert(arr[(int)Piece.q] == PieceUniqueIdentifiers[(int)Piece.q]); + //Debug.Assert(arr[(int)Piece.k] == PieceUniqueIdentifiers[(int)Piece.k]); + +#endif + // No need to make copies of value type, and reference ones are copied inside of the constructor + var gameState = new GameState(UniqueIdentifier, KingPawnUniqueIdentifier, NonPawnHash[(int)Side.White], NonPawnHash[(int)Side.Black], + PieceUniqueIdentifiers[(int)Piece.N], PieceUniqueIdentifiers[(int)Piece.n], PieceUniqueIdentifiers[(int)Piece.B], PieceUniqueIdentifiers[(int)Piece.b], + PieceUniqueIdentifiers[(int)Piece.R], PieceUniqueIdentifiers[(int)Piece.r], PieceUniqueIdentifiers[(int)Piece.Q], PieceUniqueIdentifiers[(int)Piece.q], + _incrementalEvalAccumulator, _incrementalPhaseAccumulator, EnPassant, Castle, _isIncrementalEval); var oldSide = (int)Side; var offset = Utils.PieceOffset(oldSide); @@ -170,13 +202,15 @@ public GameState MakeMove(Move move) var targetPieceHash = ZobristTable.PieceHash(targetSquare, newPiece); var fullPieceMovementHash = sourcePieceHash ^ targetPieceHash; - UniqueIdentifier ^= ZobristTable.SideHash() ^ fullPieceMovementHash ^ ZobristTable.EnPassantHash((int)EnPassant) // We clear the existing enpassant square, if any ^ ZobristTable.CastleHash(Castle); // We clear the existing castle rights + PieceUniqueIdentifiers[piece] ^= sourcePieceHash; + PieceUniqueIdentifiers[newPiece] ^= targetPieceHash; + if (piece == (int)Piece.P || piece == (int)Piece.p) { KingPawnUniqueIdentifier ^= sourcePieceHash; // We remove pawn from start square @@ -245,6 +279,7 @@ public GameState MakeMove(Move move) var capturedPieceHash = ZobristTable.PieceHash(capturedSquare, capturedPiece); UniqueIdentifier ^= capturedPieceHash; + PieceUniqueIdentifiers[capturedPiece] ^= capturedPieceHash; // Kings can't be captured if (capturedPiece == (int)Piece.P || capturedPiece == (int)Piece.p) @@ -294,6 +329,7 @@ public GameState MakeMove(Move move) UniqueIdentifier ^= hashChange; NonPawnHash[oldSide] ^= hashChange; + PieceUniqueIdentifiers[rookIndex] ^= hashChange; _incrementalEvalAccumulator -= PSQT(0, sameSideBucket, rookIndex, rookSourceSquare); _incrementalEvalAccumulator -= PSQT(1, opposideSideBucket, rookIndex, rookSourceSquare); @@ -322,6 +358,7 @@ public GameState MakeMove(Move move) UniqueIdentifier ^= hashChange; NonPawnHash[oldSide] ^= hashChange; + PieceUniqueIdentifiers[rookIndex] ^= hashChange; _incrementalEvalAccumulator -= PSQT(0, sameSideBucket, rookIndex, rookSourceSquare); _incrementalEvalAccumulator -= PSQT(1, opposideSideBucket, rookIndex, rookSourceSquare); @@ -372,6 +409,7 @@ public GameState MakeMove(Move move) ulong capturedPieceHash = ZobristTable.PieceHash(capturedSquare, capturedPiece); UniqueIdentifier ^= capturedPieceHash; + PieceUniqueIdentifiers[capturedPiece] ^= capturedPieceHash; // Kings can't be captured if (capturedPiece == (int)Piece.P || capturedPiece == (int)Piece.p) @@ -416,6 +454,7 @@ public GameState MakeMove(Move move) UniqueIdentifier ^= hashChange; NonPawnHash[oldSide] ^= hashChange; + PieceUniqueIdentifiers[rookIndex] ^= hashChange; break; } @@ -438,6 +477,7 @@ public GameState MakeMove(Move move) UniqueIdentifier ^= hashChange; NonPawnHash[oldSide] ^= hashChange; + PieceUniqueIdentifiers[rookIndex] ^= hashChange; break; } @@ -474,11 +514,29 @@ public GameState MakeMove(Move move) Debug.Assert(ZobristTable.PositionHash(this) == UniqueIdentifier); Debug.Assert(ZobristTable.NonPawnSideHash(this, (int)Side.White) == NonPawnHash[(int)Side.White]); Debug.Assert(ZobristTable.NonPawnSideHash(this, (int)Side.Black) == NonPawnHash[(int)Side.Black]); + Debug.Assert(ZobristTable.MinorHash(this) == MinorHash); + Debug.Assert(ZobristTable.MajorHash(this) == MajorHash); + +#if DEBUG + arr = stackalloc ulong[12]; + ZobristTable.PieceUniqueIdentifiers(this, arr); + + Debug.Assert(arr[(int)Piece.N] == PieceUniqueIdentifiers[(int)Piece.N]); + Debug.Assert(arr[(int)Piece.B] == PieceUniqueIdentifiers[(int)Piece.B]); + Debug.Assert(arr[(int)Piece.R] == PieceUniqueIdentifiers[(int)Piece.R]); + Debug.Assert(arr[(int)Piece.Q] == PieceUniqueIdentifiers[(int)Piece.Q]); + //Debug.Assert(arr[(int)Piece.K] == PieceUniqueIdentifiers[(int)Piece.K]); + Debug.Assert(arr[(int)Piece.n] == PieceUniqueIdentifiers[(int)Piece.n]); + Debug.Assert(arr[(int)Piece.b] == PieceUniqueIdentifiers[(int)Piece.b]); + Debug.Assert(arr[(int)Piece.r] == PieceUniqueIdentifiers[(int)Piece.r]); + Debug.Assert(arr[(int)Piece.q] == PieceUniqueIdentifiers[(int)Piece.q]); + //Debug.Assert(arr[(int)Piece.k] == PieceUniqueIdentifiers[(int)Piece.k]); +#endif // KingPawn hash assert won't work due to PassedPawnBonusNoEnemiesAheadBonus //Debug.Assert(ZobristTable.PawnKingHash(this) != _kingPawnUniqueIdentifier && WasProduceByAValidMove()); - return new GameState(uniqueIdentifierCopy, kingPawnKeyUniqueIdentifierCopy, nonPawnWhiteHashCopy, nonPawnBlackHashCopy, incrementalEvalAccumulatorCopy, incrementalPhaseAccumulatorCopy, enpassantCopy, castleCopy, isIncrementalEvalCopy); + return gameState; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -578,10 +636,20 @@ public void UnmakeMove(Move move, GameState gameState) // Updating saved values Castle = gameState.Castle; EnPassant = gameState.EnPassant; + UniqueIdentifier = gameState.ZobristKey; KingPawnUniqueIdentifier = gameState.KingPawnKey; NonPawnHash[(int)Side.White] = gameState.NonPawnWhiteKey; NonPawnHash[(int)Side.Black] = gameState.NonPawnBlackKey; + PieceUniqueIdentifiers[(int)Piece.N] = gameState.KnightWhiteKey; + PieceUniqueIdentifiers[(int)Piece.n] = gameState.KnightBlackKey; + PieceUniqueIdentifiers[(int)Piece.B] = gameState.BishopWhiteKey; + PieceUniqueIdentifiers[(int)Piece.b] = gameState.BishopBlackKey; + PieceUniqueIdentifiers[(int)Piece.R] = gameState.RookWhiteKey; + PieceUniqueIdentifiers[(int)Piece.r] = gameState.RookBlackKey; + PieceUniqueIdentifiers[(int)Piece.Q] = gameState.QueenWhiteKey; + PieceUniqueIdentifiers[(int)Piece.q] = gameState.QueenBlackKey; + _incrementalEvalAccumulator = gameState.IncremetalEvalAccumulator; _incrementalPhaseAccumulator = gameState.IncrementalPhaseAccumulator; _isIncrementalEval = gameState.IsIncrementalEval; @@ -607,6 +675,7 @@ public void UnMakeNullMove(GameState gameState) { Side = (Side)Utils.OppositeSide(Side); EnPassant = gameState.EnPassant; + UniqueIdentifier = gameState.ZobristKey; _incrementalEvalAccumulator = gameState.IncremetalEvalAccumulator; @@ -1809,6 +1878,7 @@ public void FreeResources() ArrayPool.Shared.Return(PieceBitBoards, clearArray: true); ArrayPool.Shared.Return(OccupancyBitBoards, clearArray: true); ArrayPool.Shared.Return(NonPawnHash, clearArray: true); + ArrayPool.Shared.Return(PieceUniqueIdentifiers, clearArray: true); // No need to clear, since we always have to initialize it to Piece.None after renting it anyway #pragma warning disable S3254 // Default parameter values should not be passed as arguments ArrayPool.Shared.Return(Board, clearArray: false); diff --git a/src/Lynx/Search/Helpers.cs b/src/Lynx/Search/Helpers.cs index 4480c1738..142515722 100644 --- a/src/Lynx/Search/Helpers.cs +++ b/src/Lynx/Search/Helpers.cs @@ -114,6 +114,7 @@ private void UpdateCorrectionHistory(Position position, int evaluationDelta, int var scaledBonus = evaluationDelta * Constants.CorrectionHistoryScale; var weight = 2 * Math.Min(16, depth + 1); + // Pawn correction history var pawnHash = position.KingPawnUniqueIdentifier ^ ZobristTable.PieceHash(position.WhiteKingSquare, (int)Piece.K) ^ ZobristTable.PieceHash(position.BlackKingSquare, (int)Piece.k); @@ -124,9 +125,9 @@ private void UpdateCorrectionHistory(Position position, int evaluationDelta, int Debug.Assert(pawnCorrHistIndex < (ulong)_pawnCorrHistory.Length); ref var pawnCorrHistEntry = ref _pawnCorrHistory[pawnCorrHistIndex]; - pawnCorrHistEntry = UpdateCorrectionHistory(pawnCorrHistEntry, scaledBonus, weight); + // Non-pawn correction history - side to move var nonPawnSTMIndex = position.NonPawnHash[side] & Constants.NonPawnCorrHistoryMask; var nonPawnCorrHistSTMIndex = @@ -137,9 +138,9 @@ private void UpdateCorrectionHistory(Position position, int evaluationDelta, int Debug.Assert(nonPawnCorrHistSTMIndex < (ulong)_nonPawnCorrHistory.Length); ref var nonPawnSTMCorrHistEntry = ref _nonPawnCorrHistory[nonPawnCorrHistSTMIndex]; - nonPawnSTMCorrHistEntry = UpdateCorrectionHistory(nonPawnSTMCorrHistEntry, scaledBonus, weight); + // Non-pawn correction history - not side to move var nonPawnNoSTMIndex = position.NonPawnHash[oppositeSide] & Constants.NonPawnCorrHistoryMask; var nonPawnNoSTMCorrHistIndex = (nonPawnNoSTMIndex * 2 * 2) @@ -152,6 +153,27 @@ private void UpdateCorrectionHistory(Position position, int evaluationDelta, int nonPawnNoSTMCorrHistEntry = UpdateCorrectionHistory(nonPawnNoSTMCorrHistEntry, scaledBonus, weight); + // Minor correction history + var minorHash = position.MinorHash; + var minorIndex = minorHash & Constants.MinorCorrHistoryMask; + + var minorCorrHistIndex = (2 * minorIndex) + side; + Debug.Assert(minorCorrHistIndex < (ulong)_minorCorrHistory.Length); + + ref var minorCorrHistEntry = ref _minorCorrHistory[minorCorrHistIndex]; + minorCorrHistEntry = UpdateCorrectionHistory(minorCorrHistEntry, scaledBonus, weight); + + // Minor correction history + var majorHash = position.MajorHash; + var majorIndex = majorHash & Constants.MajorCorrHistoryMask; + + var majorCorrHistIndex = (2 * majorIndex) + side; + Debug.Assert(majorCorrHistIndex < (ulong)_majorCorrHistory.Length); + + ref var majorCorrHistEntry = ref _majorCorrHistory[majorCorrHistIndex]; + majorCorrHistEntry = UpdateCorrectionHistory(majorCorrHistEntry, scaledBonus, weight); + + // Common update logic [MethodImpl(MethodImplOptions.AggressiveInlining)] static int UpdateCorrectionHistory(int previousCorrectedScore, int scaledBonus, int weight) { @@ -176,6 +198,7 @@ private int CorrectStaticEvaluation(Position position, int staticEvaluation) var side = (ulong)position.Side; var oppositeSide = Utils.OppositeSide((int)side); + // Pawn correction history var pawnHash = position.KingPawnUniqueIdentifier ^ ZobristTable.PieceHash(position.WhiteKingSquare, (int)Piece.K) ^ ZobristTable.PieceHash(position.BlackKingSquare, (int)Piece.k); @@ -187,6 +210,7 @@ private int CorrectStaticEvaluation(Position position, int staticEvaluation) var pawnCorrHist = _pawnCorrHistory[pawnCorrHistIndex]; + // Non-pawn correction history - side to move var nonPawnSTMoveIndex = position.NonPawnHash[side] & Constants.NonPawnCorrHistoryMask; var nonPawnSTMoveCorrHistIndex = (nonPawnSTMoveIndex * 2 * 2) @@ -197,6 +221,7 @@ private int CorrectStaticEvaluation(Position position, int staticEvaluation) var nonPawnSTMCorrHist = _nonPawnCorrHistory[nonPawnSTMoveCorrHistIndex]; + // Non-pawn correction history - not side to move var nonPawnNoSTMIndex = position.NonPawnHash[oppositeSide] & Constants.NonPawnCorrHistoryMask; var nonPawnNoSTMCorrHistIndex = (nonPawnNoSTMIndex * 2 * 2) @@ -207,7 +232,26 @@ private int CorrectStaticEvaluation(Position position, int staticEvaluation) var nonPawnNoSTMCorrHist = _nonPawnCorrHistory[nonPawnNoSTMCorrHistIndex]; - var correction = pawnCorrHist + nonPawnSTMCorrHist + nonPawnNoSTMCorrHist; + // Minor correction history + var minorHash = position.MinorHash; + var minorIndex = minorHash & Constants.MinorCorrHistoryMask; + + var minorCorrHistIndex = (2 * minorIndex) + side; + Debug.Assert(minorCorrHistIndex < (ulong)_minorCorrHistory.Length); + + var minorCorrHist = _minorCorrHistory[minorCorrHistIndex]; + + // Major correction history + var majorHash = position.MajorHash; + var majorIndex = majorHash & Constants.MajorCorrHistoryMask; + + var majorCorrHistIndex = (2 * majorIndex) + side; + Debug.Assert(majorCorrHistIndex < (ulong)_majorCorrHistory.Length); + + var majorCorrHist = _majorCorrHistory[majorCorrHistIndex]; + + // Aggregated correction + var correction = pawnCorrHist + nonPawnSTMCorrHist + nonPawnNoSTMCorrHist + minorCorrHist + majorCorrHist; var correctStaticEval = staticEvaluation + (correction / (Constants.CorrectionHistoryScale * 3)); return Math.Clamp(correctStaticEval, EvaluationConstants.MinStaticEval, EvaluationConstants.MaxStaticEval); diff --git a/src/Lynx/Search/IDDFS.cs b/src/Lynx/Search/IDDFS.cs index b4404acd7..70f41c2ba 100644 --- a/src/Lynx/Search/IDDFS.cs +++ b/src/Lynx/Search/IDDFS.cs @@ -48,11 +48,23 @@ public sealed partial class Engine private readonly int[] _pawnCorrHistory = GC.AllocateArray(Constants.PawnCorrHistorySize * 2, pinned: true); /// - /// x 2 x 2 - /// Side hash x side to move x piece hash side + /// x 2 x 2 + /// Side non-pawn hash x side to move x piece hash side /// private readonly int[] _nonPawnCorrHistory = GC.AllocateArray(Constants.NonPawnCorrHistorySize * 2 * 2, pinned: true); + /// + /// x 2 + /// Minor hash x side to move + /// + private readonly int[] _minorCorrHistory = GC.AllocateArray(Constants.MinorCorrHistorySize * 2, pinned: true); + + /// + /// x 2 + /// Major hash x side to move + /// + private readonly int[] _majorCorrHistory = GC.AllocateArray(Constants.MajorCorrHistorySize * 2, pinned: true); + /// /// 12 x 64 /// piece x target square diff --git a/src/Lynx/ZobristTable.cs b/src/Lynx/ZobristTable.cs index 3c2f3e81f..693a00244 100644 --- a/src/Lynx/ZobristTable.cs +++ b/src/Lynx/ZobristTable.cs @@ -193,6 +193,83 @@ public static ulong NonPawnSideHash(Position position, int side) return nonPawnSideHash; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong MinorHash(Position position) + { + ulong minorHash = 0; + + for (int pieceIndex = (int)Piece.N; pieceIndex <= (int)Piece.B; ++pieceIndex) + { + var whiteBitboard = position.PieceBitBoards[pieceIndex]; + while (whiteBitboard != default) + { + whiteBitboard = whiteBitboard.WithoutLS1B(out var pieceSquareIndex); + + minorHash ^= PieceHash(pieceSquareIndex, pieceIndex); + } + + var blackBitboard = position.PieceBitBoards[pieceIndex + 6]; + while (blackBitboard != default) + { + blackBitboard = blackBitboard.WithoutLS1B(out var pieceSquareIndex); + + minorHash ^= PieceHash(pieceSquareIndex, pieceIndex + 6); + } + } + + return minorHash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong MajorHash(Position position) + { + ulong majorHash = 0; + + for (int pieceIndex = (int)Piece.R; pieceIndex <= (int)Piece.Q; ++pieceIndex) + { + var whiteBitboard = position.PieceBitBoards[pieceIndex]; + while (whiteBitboard != default) + { + whiteBitboard = whiteBitboard.WithoutLS1B(out var pieceSquareIndex); + + majorHash ^= PieceHash(pieceSquareIndex, pieceIndex); + } + + var blackBitboard = position.PieceBitBoards[pieceIndex + 6]; + while (blackBitboard != default) + { + blackBitboard = blackBitboard.WithoutLS1B(out var pieceSquareIndex); + + majorHash ^= PieceHash(pieceSquareIndex, pieceIndex + 6); + } + } + + return majorHash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void PieceUniqueIdentifiers(Position position, Span pieceIdentifiers) + { + for (int pieceIndex = (int)Piece.N; pieceIndex <= (int)Piece.K; ++pieceIndex) + { + var whiteBitboard = position.PieceBitBoards[pieceIndex]; + while (whiteBitboard != default) + { + whiteBitboard = whiteBitboard.WithoutLS1B(out var pieceSquareIndex); + + pieceIdentifiers[pieceIndex] ^= PieceHash(pieceSquareIndex, pieceIndex); + } + + var blackBitboard = position.PieceBitBoards[pieceIndex + 6]; + while (blackBitboard != default) + { + blackBitboard = blackBitboard.WithoutLS1B(out var pieceSquareIndex); + + pieceIdentifiers[pieceIndex + 6] ^= PieceHash(pieceSquareIndex, pieceIndex + 6); + } + } + } + /// /// Initializes Zobrist table (long[64][12]) ///