Skip to content

Commit

Permalink
⚡ Add a Board array to Position to track where pieces are indexed…
Browse files Browse the repository at this point in the history
… by square (#849)

* Keep track of pieces in an int[64] array as part of `Position`, saving the effort to find the captured piece
* Replace `FindCapturedPiece()` method with array lookup
  • Loading branch information
eduherminio authored Sep 17, 2024
1 parent d4811b9 commit eb9bf40
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ public MakeMovePosition(string fen) : this(FENParser.ParseFEN(fen))
{
}

public MakeMovePosition((BitBoard[] PieceBitBoards, BitBoard[] OccupancyBitBoards, Side Side, byte Castle, BoardSquare EnPassant,
public MakeMovePosition((BitBoard[] PieceBitBoards, BitBoard[] OccupancyBitBoards, int[] board, Side Side, byte Castle, BoardSquare EnPassant,
int HalfMoveClock/*, int FullMoveCounter*/) parsedFEN)
{
PieceBitBoards = parsedFEN.PieceBitBoards;
Expand Down
2 changes: 1 addition & 1 deletion src/Lynx.Benchmark/MakeUnmakeMove_integration_Benchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ public MakeMovePosition(string fen) : this(FENParser.ParseFEN(fen))
{
}

public MakeMovePosition((BitBoard[] PieceBitBoards, BitBoard[] OccupancyBitBoards, Side Side, byte Castle, BoardSquare EnPassant,
public MakeMovePosition((BitBoard[] PieceBitBoards, BitBoard[] OccupancyBitBoards, int[] board, Side Side, byte Castle, BoardSquare EnPassant,
int HalfMoveClock/*, int FullMoveCounter*/) parsedFEN)
{
PieceBitBoards = parsedFEN.PieceBitBoards;
Expand Down
20 changes: 12 additions & 8 deletions src/Lynx/FENParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Buffers;
using System.Runtime.CompilerServices;

using ParseResult = (ulong[] PieceBitBoards, ulong[] OccupancyBitBoards, Lynx.Model.Side Side, byte Castle, Lynx.Model.BoardSquare EnPassant,
using ParseResult = (ulong[] PieceBitBoards, ulong[] OccupancyBitBoards, int[] board, Lynx.Model.Side Side, byte Castle, Lynx.Model.BoardSquare EnPassant,
int HalfMoveClock/*, int FullMoveCounter*/);

namespace Lynx;
Expand All @@ -19,6 +19,8 @@ public static ParseResult ParseFEN(ReadOnlySpan<char> fen)

var pieceBitBoards = ArrayPool<BitBoard>.Shared.Rent(12);
var occupancyBitBoards = ArrayPool<BitBoard>.Shared.Rent(3);
var board = ArrayPool<int>.Shared.Rent(64);
Array.Fill(board, (int)Piece.None);

bool success;
Side side = Side.Both;
Expand All @@ -28,7 +30,7 @@ public static ParseResult ParseFEN(ReadOnlySpan<char> fen)

try
{
success = ParseBoard(fen, pieceBitBoards, occupancyBitBoards);
success = ParseBoard(fen, pieceBitBoards, occupancyBitBoards, board);

var unparsedStringAsSpan = fen[fen.IndexOf(' ')..];
Span<Range> parts = stackalloc Range[5];
Expand Down Expand Up @@ -63,12 +65,12 @@ public static ParseResult ParseFEN(ReadOnlySpan<char> fen)
}

return success
? (pieceBitBoards, occupancyBitBoards, side, castle, enPassant, halfMoveClock/*, fullMoveCounter*/)
? (pieceBitBoards, occupancyBitBoards, board, side, castle, enPassant, halfMoveClock/*, fullMoveCounter*/)
: throw new AssertException($"Error parsing {fen.ToString()}");
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool ParseBoard(ReadOnlySpan<char> fen, BitBoard[] pieceBitBoards, BitBoard[] occupancyBitBoards)
private static bool ParseBoard(ReadOnlySpan<char> fen, BitBoard[] pieceBitBoards, BitBoard[] occupancyBitBoards, int[] board)
{
bool success = true;
var rankIndex = 0;
Expand All @@ -83,7 +85,7 @@ private static bool ParseBoard(ReadOnlySpan<char> fen, BitBoard[] pieceBitBoards
{
var match = fen[..end];
ParseBoardSection(pieceBitBoards, rankIndex, match
ParseBoardSection(pieceBitBoards, board, rankIndex, match
#if DEBUG
, ref success
#endif
Expand All @@ -95,7 +97,7 @@ private static bool ParseBoard(ReadOnlySpan<char> fen, BitBoard[] pieceBitBoards
++rankIndex;
}
ParseBoardSection(pieceBitBoards, rankIndex, fen[..fen.IndexOf(' ')]
ParseBoardSection(pieceBitBoards, board, rankIndex, fen[..fen.IndexOf(' ')]
#if DEBUG
, ref success
#endif
Expand All @@ -104,7 +106,7 @@ private static bool ParseBoard(ReadOnlySpan<char> fen, BitBoard[] pieceBitBoards
return success;
static void ParseBoardSection(BitBoard[] pieceBitBoards, int rankIndex, ReadOnlySpan<char> boardfenSection
static void ParseBoardSection(BitBoard[] pieceBitBoards, int[] board, int rankIndex, ReadOnlySpan<char> boardfenSection
#if DEBUG
, ref bool success
#endif
Expand Down Expand Up @@ -135,7 +137,9 @@ static void ParseBoardSection(BitBoard[] pieceBitBoards, int rankIndex, ReadOnly

if (piece != Piece.None)
{
pieceBitBoards[(int)piece] = pieceBitBoards[(int)piece].SetBit(BitBoardExtensions.SquareIndex(rankIndex, fileIndex));
var square = BitBoardExtensions.SquareIndex(rankIndex, fileIndex);
pieceBitBoards[(int)piece] = pieceBitBoards[(int)piece].SetBit(square);
board[square] = (int)piece;
++fileIndex;
}
else
Expand Down
61 changes: 46 additions & 15 deletions src/Lynx/Model/Position.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public class Position : IDisposable
/// </summary>
public BitBoard[] OccupancyBitBoards { get; }

/// <summary>
/// Piece location indexed by square
/// </summary>
public int[] Board { get; }

public Side Side { get; private set; }

public BoardSquare EnPassant { get; private set; }
Expand All @@ -49,11 +54,12 @@ public Position(string fen) : this(FENParser.ParseFEN(fen))
{
}

public Position((BitBoard[] PieceBitBoards, BitBoard[] OccupancyBitBoards, Side Side, byte Castle, BoardSquare EnPassant,
public Position((BitBoard[] PieceBitBoards, BitBoard[] OccupancyBitBoards, int[] Board, Side Side, byte Castle, BoardSquare EnPassant,
int _/*, int FullMoveCounter*/) parsedFEN)
{
PieceBitBoards = parsedFEN.PieceBitBoards;
OccupancyBitBoards = parsedFEN.OccupancyBitBoards;
Board = parsedFEN.Board;
Side = parsedFEN.Side;
Castle = parsedFEN.Castle;
EnPassant = parsedFEN.EnPassant;
Expand All @@ -75,6 +81,9 @@ public Position(Position position)
OccupancyBitBoards = ArrayPool<BitBoard>.Shared.Rent(3);
Array.Copy(position.OccupancyBitBoards, OccupancyBitBoards, position.OccupancyBitBoards.Length);

Board = ArrayPool<int>.Shared.Rent(64);
Array.Copy(position.Board, Board, position.Board.Length);

Side = position.Side;
Castle = position.Castle;
EnPassant = position.EnPassant;
Expand Down Expand Up @@ -106,9 +115,11 @@ public GameState MakeMove(Move move)

PieceBitBoards[piece].PopBit(sourceSquare);
OccupancyBitBoards[oldSide].PopBit(sourceSquare);
Board[sourceSquare] = (int)Piece.None;

PieceBitBoards[newPiece].SetBit(targetSquare);
OccupancyBitBoards[oldSide].SetBit(targetSquare);
Board[targetSquare] = newPiece;

UniqueIdentifier ^=
ZobristTable.SideHash()
Expand Down Expand Up @@ -154,9 +165,11 @@ public GameState MakeMove(Move move)

PieceBitBoards[rookIndex].PopBit(rookSourceSquare);
OccupancyBitBoards[oldSide].PopBit(rookSourceSquare);
Board[rookSourceSquare] = (int)Piece.None;

PieceBitBoards[rookIndex].SetBit(rookTargetSquare);
OccupancyBitBoards[oldSide].SetBit(rookTargetSquare);
Board[rookTargetSquare] = rookIndex;

UniqueIdentifier ^=
ZobristTable.PieceHash(rookSourceSquare, rookIndex)
Expand All @@ -172,9 +185,11 @@ public GameState MakeMove(Move move)

PieceBitBoards[rookIndex].PopBit(rookSourceSquare);
OccupancyBitBoards[oldSide].PopBit(rookSourceSquare);
Board[rookSourceSquare] = (int)Piece.None;

PieceBitBoards[rookIndex].SetBit(rookTargetSquare);
OccupancyBitBoards[oldSide].SetBit(rookTargetSquare);
Board[rookTargetSquare] = rookIndex;

UniqueIdentifier ^=
ZobristTable.PieceHash(rookSourceSquare, rookIndex)
Expand All @@ -192,6 +207,7 @@ public GameState MakeMove(Move move)

PieceBitBoards[capturedPiece].PopBit(capturedSquare);
OccupancyBitBoards[oppositeSide].PopBit(capturedSquare);
Board[capturedSquare] = (int)Piece.None;
UniqueIdentifier ^= ZobristTable.PieceHash(capturedSquare, capturedPiece);

break;
Expand Down Expand Up @@ -231,6 +247,7 @@ public GameState MakeMoveCalculatingCapturedPiece(ref Move move)
int targetSquare = move.TargetSquare();
int piece = move.Piece();
int promotedPiece = move.PromotedPiece();
int capturedPiece = Board[targetSquare];

var newPiece = piece;
if (promotedPiece != default)
Expand All @@ -240,9 +257,11 @@ public GameState MakeMoveCalculatingCapturedPiece(ref Move move)

PieceBitBoards[piece].PopBit(sourceSquare);
OccupancyBitBoards[oldSide].PopBit(sourceSquare);
Board[sourceSquare] = (int)Piece.None;

PieceBitBoards[newPiece].SetBit(targetSquare);
OccupancyBitBoards[oldSide].SetBit(targetSquare);
Board[targetSquare] = newPiece;

UniqueIdentifier ^=
ZobristTable.SideHash()
Expand Down Expand Up @@ -274,9 +293,11 @@ public GameState MakeMoveCalculatingCapturedPiece(ref Move move)

PieceBitBoards[rookIndex].PopBit(rookSourceSquare);
OccupancyBitBoards[oldSide].PopBit(rookSourceSquare);
Board[rookSourceSquare] = (int)Piece.None;

PieceBitBoards[rookIndex].SetBit(rookTargetSquare);
OccupancyBitBoards[oldSide].SetBit(rookTargetSquare);
Board[rookTargetSquare] = rookIndex;

UniqueIdentifier ^=
ZobristTable.PieceHash(rookSourceSquare, rookIndex)
Expand All @@ -292,9 +313,11 @@ public GameState MakeMoveCalculatingCapturedPiece(ref Move move)

PieceBitBoards[rookIndex].PopBit(rookSourceSquare);
OccupancyBitBoards[oldSide].PopBit(rookSourceSquare);
Board[rookSourceSquare] = (int)Piece.None;

PieceBitBoards[rookIndex].SetBit(rookTargetSquare);
OccupancyBitBoards[oldSide].SetBit(rookTargetSquare);
Board[rookTargetSquare] = rookIndex;

UniqueIdentifier ^=
ZobristTable.PieceHash(rookSourceSquare, rookIndex)
Expand All @@ -312,6 +335,7 @@ public GameState MakeMoveCalculatingCapturedPiece(ref Move move)

PieceBitBoards[oppositePawnIndex].PopBit(capturedPawnSquare);
OccupancyBitBoards[oppositeSide].PopBit(capturedPawnSquare);
Board[capturedPawnSquare] = (int)Piece.None;
UniqueIdentifier ^= ZobristTable.PieceHash(capturedPawnSquare, oppositePawnIndex);
move = MoveExtensions.EncodeCapturedPiece(move, oppositePawnIndex);

Expand All @@ -321,21 +345,15 @@ public GameState MakeMoveCalculatingCapturedPiece(ref Move move)
{
if (move.IsCapture())
{
var oppositePawnIndex = (int)Piece.p - offset;

var limit = (int)Piece.K + oppositePawnIndex;
for (int pieceIndex = oppositePawnIndex; pieceIndex < limit; ++pieceIndex)
{
if (PieceBitBoards[pieceIndex].GetBit(targetSquare))
{
PieceBitBoards[pieceIndex].PopBit(targetSquare);
UniqueIdentifier ^= ZobristTable.PieceHash(targetSquare, pieceIndex);
move = MoveExtensions.EncodeCapturedPiece(move, pieceIndex);
break;
}
}
Debug.Assert(capturedPiece != (int)Piece.None, $"Expected piece at {targetSquare}, since it was supposed to be captured");

PieceBitBoards[capturedPiece].PopBit(targetSquare);
OccupancyBitBoards[oppositeSide].PopBit(targetSquare);
// No need to update Board here, it already has newPiece

move = MoveExtensions.EncodeCapturedPiece(move, capturedPiece);

UniqueIdentifier ^= ZobristTable.PieceHash(targetSquare, capturedPiece);
}

break;
Expand Down Expand Up @@ -381,18 +399,23 @@ public void UnmakeMove(Move move, GameState gameState)

PieceBitBoards[newPiece].PopBit(targetSquare);
OccupancyBitBoards[side].PopBit(targetSquare);
Board[targetSquare] = (int)Piece.None;

PieceBitBoards[piece].SetBit(sourceSquare);
OccupancyBitBoards[side].SetBit(sourceSquare);
Board[sourceSquare] = piece;

switch (move.SpecialMoveFlag())
{
case SpecialMoveType.None:
{
if (move.IsCapture())
{
PieceBitBoards[move.CapturedPiece()].SetBit(targetSquare);
var capturedPiece = move.CapturedPiece();

PieceBitBoards[capturedPiece].SetBit(targetSquare);
OccupancyBitBoards[oppositeSide].SetBit(targetSquare);
Board[targetSquare] = capturedPiece;
}

break;
Expand All @@ -405,9 +428,11 @@ public void UnmakeMove(Move move, GameState gameState)

PieceBitBoards[rookIndex].SetBit(rookSourceSquare);
OccupancyBitBoards[side].SetBit(rookSourceSquare);
Board[rookSourceSquare] = rookIndex;

PieceBitBoards[rookIndex].PopBit(rookTargetSquare);
OccupancyBitBoards[side].PopBit(rookTargetSquare);
Board[rookTargetSquare] = (int)Piece.None;

break;
}
Expand All @@ -419,9 +444,11 @@ public void UnmakeMove(Move move, GameState gameState)

PieceBitBoards[rookIndex].SetBit(rookSourceSquare);
OccupancyBitBoards[side].SetBit(rookSourceSquare);
Board[rookSourceSquare] = rookIndex;

PieceBitBoards[rookIndex].PopBit(rookTargetSquare);
OccupancyBitBoards[side].PopBit(rookTargetSquare);
Board[rookTargetSquare] = (int)Piece.None;

break;
}
Expand All @@ -437,6 +464,7 @@ public void UnmakeMove(Move move, GameState gameState)

PieceBitBoards[oppositePawnIndex].SetBit(capturedPawnSquare);
OccupancyBitBoards[oppositeSide].SetBit(capturedPawnSquare);
Board[capturedPawnSquare] = oppositePawnIndex;

break;
}
Expand Down Expand Up @@ -503,6 +531,7 @@ internal bool IsValid()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool WasProduceByAValidMove()
{
Debug.Assert(PieceBitBoards[(int)Piece.k - Utils.PieceOffset(Side)].CountBits() == 1);
var oppositeKingSquare = PieceBitBoards[(int)Piece.k - Utils.PieceOffset(Side)].GetLS1BIndex();

return !IsSquareAttacked(oppositeKingSquare, Side);
Expand Down Expand Up @@ -1253,6 +1282,8 @@ public void FreeResources()
{
ArrayPool<BitBoard>.Shared.Return(PieceBitBoards, clearArray: true);
ArrayPool<BitBoard>.Shared.Return(OccupancyBitBoards, clearArray: true);
// No need to clear, since we always have to initialize it to Piece.None after renting it anyway
ArrayPool<int>.Shared.Return(Board, clearArray: false);

_disposedValue = true;
}
Expand Down
Loading

0 comments on commit eb9bf40

Please sign in to comment.