Skip to content
6 changes: 6 additions & 0 deletions src/Lynx/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 2 additions & 0 deletions src/Lynx/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
34 changes: 32 additions & 2 deletions src/Lynx/Model/GameState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

/// <summary>
Expand Down
98 changes: 84 additions & 14 deletions src/Lynx/Model/Position.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class Position : IDisposable

public ulong[] NonPawnHash { get; private set; }

public ulong[] PieceUniqueIdentifiers { get; private set; }

/// <summary>
/// Use <see cref="Piece"/> as index
/// </summary>
Expand Down Expand Up @@ -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<ulong>.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]);
Expand All @@ -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<ulong>.Shared.Rent(12);
Array.Copy(position.PieceUniqueIdentifiers, PieceUniqueIdentifiers, position.PieceUniqueIdentifiers.Length);

PieceBitBoards = ArrayPool<BitBoard>.Shared.Rent(12);
Array.Copy(position.PieceBitBoards, PieceBitBoards, position.PieceBitBoards.Length);

Expand All @@ -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)]
Expand All @@ -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<ulong> 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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -416,6 +454,7 @@ public GameState MakeMove(Move move)

UniqueIdentifier ^= hashChange;
NonPawnHash[oldSide] ^= hashChange;
PieceUniqueIdentifiers[rookIndex] ^= hashChange;

break;
}
Expand All @@ -438,6 +477,7 @@ public GameState MakeMove(Move move)

UniqueIdentifier ^= hashChange;
NonPawnHash[oldSide] ^= hashChange;
PieceUniqueIdentifiers[rookIndex] ^= hashChange;

break;
}
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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;
Expand All @@ -607,6 +675,7 @@ public void UnMakeNullMove(GameState gameState)
{
Side = (Side)Utils.OppositeSide(Side);
EnPassant = gameState.EnPassant;

UniqueIdentifier = gameState.ZobristKey;

_incrementalEvalAccumulator = gameState.IncremetalEvalAccumulator;
Expand Down Expand Up @@ -1809,6 +1878,7 @@ public void FreeResources()
ArrayPool<BitBoard>.Shared.Return(PieceBitBoards, clearArray: true);
ArrayPool<BitBoard>.Shared.Return(OccupancyBitBoards, clearArray: true);
ArrayPool<ulong>.Shared.Return(NonPawnHash, clearArray: true);
ArrayPool<ulong>.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<int>.Shared.Return(Board, clearArray: false);
Expand Down
Loading