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

⚡ Split MoveGenerator.GenerateAllMoves and MoveGenerator.GenerateAllCaptures #483

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions src/Lynx.Dev/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ static void _54_ScoreMove()

var engine = new Engine(Channel.CreateBounded<string>(new BoundedChannelOptions(100) { SingleReader = true, SingleWriter = false }));
engine.SetGame(new(position));
foreach (var move in MoveGenerator.GenerateAllMoves(position, capturesOnly: true))
foreach (var move in MoveGenerator.GenerateAllCaptures(position))
{
Console.WriteLine($"{move} {engine.ScoreMove(move, default, default)}");
}
Expand All @@ -678,7 +678,7 @@ static void _54_ScoreMove()
position.Print();

engine.SetGame(new(position));
foreach (var move in MoveGenerator.GenerateAllMoves(position, capturesOnly: true))
foreach (var move in MoveGenerator.GenerateAllCaptures(position))
{
Console.WriteLine($"{move} {engine.ScoreMove(move, default, default)}");
}
Expand Down
174 changes: 154 additions & 20 deletions src/Lynx/MoveGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,43 @@ public static class MoveGenerator
};

/// <summary>
/// Generates all psuedo-legal moves from <paramref name="position"/>, ordered by <see cref="Move.Score(Position)"/>
/// Generates all psuedo-legal moves from <paramref name="position"/>
/// </summary>
/// <param name="position"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Move[] GenerateAllMoves(Position position, Move[]? movePool = null)
{
#if DEBUG
if (position.Side == Side.Both)
{
return Array.Empty<Move>();
}
#endif

movePool ??= new Move[Constants.MaxNumberOfPossibleMovesInAPosition];
int localIndex = 0;

var offset = Utils.PieceOffset(position.Side);

GeneratePawnMoves(ref localIndex, movePool, position, offset);
GenerateCastlingMoves(ref localIndex, movePool, position, offset);
GeneratePieceMoves(ref localIndex, movePool, (int)Piece.K + offset, position);
GeneratePieceMoves(ref localIndex, movePool, (int)Piece.N + offset, position);
GeneratePieceMoves(ref localIndex, movePool, (int)Piece.B + offset, position);
GeneratePieceMoves(ref localIndex, movePool, (int)Piece.R + offset, position);
GeneratePieceMoves(ref localIndex, movePool, (int)Piece.Q + offset, position);

return movePool[..localIndex];
}

/// <summary>
/// Generates all psuedo-legal captures from <paramref name="position"/>
/// </summary>
/// <param name="position"></param>
/// <param name="capturesOnly">Filters out all moves but captures</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Move[] GenerateAllMoves(Position position, Move[]? movePool = null, bool capturesOnly = false)
public static Move[] GenerateAllCaptures(Position position, Move[]? movePool = null)
{
#if DEBUG
if (position.Side == Side.Both)
Expand All @@ -52,19 +82,19 @@ public static Move[] GenerateAllMoves(Position position, Move[]? movePool = null

var offset = Utils.PieceOffset(position.Side);

GeneratePawnMoves(ref localIndex, movePool, position, offset, capturesOnly);
GeneratePawnCaptures(ref localIndex, movePool, position, offset);
GenerateCastlingMoves(ref localIndex, movePool, position, offset);
GeneratePieceMoves(ref localIndex, movePool, (int)Piece.K + offset, position, capturesOnly);
GeneratePieceMoves(ref localIndex, movePool, (int)Piece.N + offset, position, capturesOnly);
GeneratePieceMoves(ref localIndex, movePool, (int)Piece.B + offset, position, capturesOnly);
GeneratePieceMoves(ref localIndex, movePool, (int)Piece.R + offset, position, capturesOnly);
GeneratePieceMoves(ref localIndex, movePool, (int)Piece.Q + offset, position, capturesOnly);
GeneratePieceCaptures(ref localIndex, movePool, (int)Piece.K + offset, position);
GeneratePieceCaptures(ref localIndex, movePool, (int)Piece.N + offset, position);
GeneratePieceCaptures(ref localIndex, movePool, (int)Piece.B + offset, position);
GeneratePieceCaptures(ref localIndex, movePool, (int)Piece.R + offset, position);
GeneratePieceCaptures(ref localIndex, movePool, (int)Piece.Q + offset, position);

return movePool[..localIndex];
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void GeneratePawnMoves(ref int localIndex, Move[] movePool, Position position, int offset, bool capturesOnly = false)
internal static void GeneratePawnMoves(ref int localIndex, Move[] movePool, Position position, int offset)
{
int sourceSquare, targetSquare;

Expand Down Expand Up @@ -101,21 +131,19 @@ internal static void GeneratePawnMoves(ref int localIndex, Move[] movePool, Posi
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, singlePushSquare, piece, promotedPiece: (int)Piece.N + offset);
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, singlePushSquare, piece, promotedPiece: (int)Piece.B + offset);
}
else if (!capturesOnly)
else
{
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, singlePushSquare, piece);
}

// Double pawn push
// Inside of the if because singlePush square cannot be occupied either
if (!capturesOnly)

var doublePushSquare = sourceSquare + (2 * pawnPush);
if (!position.OccupancyBitBoards[2].GetBit(doublePushSquare)
&& ((sourceRank == 2 && position.Side == Side.Black) || (sourceRank == 7 && position.Side == Side.White)))
{
var doublePushSquare = sourceSquare + (2 * pawnPush);
if (!position.OccupancyBitBoards[2].GetBit(doublePushSquare)
&& ((sourceRank == 2 && position.Side == Side.Black) || (sourceRank == 7 && position.Side == Side.White)))
{
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, doublePushSquare, piece, isDoublePawnPush: TRUE);
}
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, doublePushSquare, piece, isDoublePawnPush: TRUE);
}
}

Expand Down Expand Up @@ -228,7 +256,7 @@ internal static void GenerateCastlingMoves(ref int localIndex, Move[] movePool,
/// <param name="capturesOnly"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void GeneratePieceMoves(ref int localIndex, Move[] movePool, int piece, Position position, bool capturesOnly = false)
internal static void GeneratePieceMoves(ref int localIndex, Move[] movePool, int piece, Position position)
{
var bitboard = position.PieceBitBoards[piece];
int sourceSquare, targetSquare;
Expand All @@ -250,14 +278,120 @@ internal static void GeneratePieceMoves(ref int localIndex, Move[] movePool, int
{
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, targetSquare, piece, isCapture: TRUE);
}
else if (!capturesOnly)
else
{
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, targetSquare, piece);
}
}
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void GeneratePawnCaptures(ref int localIndex, Move[] movePool, Position position, int offset)
{
int sourceSquare, targetSquare;

var piece = (int)Piece.P + offset;
var pawnPush = +8 - ((int)position.Side * 16); // position.Side == Side.White ? -8 : +8
int oppositeSide = Utils.OppositeSide(position.Side); // position.Side == Side.White ? (int)Side.Black : (int)Side.White
var bitboard = position.PieceBitBoards[piece];

while (bitboard != default)
{
sourceSquare = bitboard.GetLS1BIndex();
bitboard.ResetLS1B();

var sourceRank = (sourceSquare >> 3) + 1;

#if DEBUG
if (sourceRank == 1 || sourceRank == 8)
{
_logger.Warn("There's a non-promoted {0} pawn in rank {1}", position.Side, sourceRank);
continue;
}
#endif

// Pawn pushes
var singlePushSquare = sourceSquare + pawnPush;
if (!position.OccupancyBitBoards[2].GetBit(singlePushSquare))
{
// Single pawn push
var targetRank = (singlePushSquare >> 3) + 1;
if (targetRank == 1 || targetRank == 8) // Promotion
{
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, singlePushSquare, piece, promotedPiece: (int)Piece.Q + offset);
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, singlePushSquare, piece, promotedPiece: (int)Piece.R + offset);
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, singlePushSquare, piece, promotedPiece: (int)Piece.N + offset);
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, singlePushSquare, piece, promotedPiece: (int)Piece.B + offset);
}
}

var attacks = Attacks.PawnAttacks[(int)position.Side, sourceSquare];

// En passant
if (position.EnPassant != BoardSquare.noSquare && attacks.GetBit(position.EnPassant))
// We assume that position.OccupancyBitBoards[oppositeOccupancy].GetBit(targetSquare + singlePush) == true
{
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, (int)position.EnPassant, piece, isCapture: TRUE, isEnPassant: TRUE);
}

// Captures
var attackedSquares = attacks & position.OccupancyBitBoards[oppositeSide];
while (attackedSquares != default)
{
targetSquare = attackedSquares.GetLS1BIndex();
attackedSquares.ResetLS1B();

var targetRank = (targetSquare >> 3) + 1;
if (targetRank == 1 || targetRank == 8) // Capture with promotion
{
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, targetSquare, piece, promotedPiece: (int)Piece.Q + offset, isCapture: TRUE);
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, targetSquare, piece, promotedPiece: (int)Piece.R + offset, isCapture: TRUE);
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, targetSquare, piece, promotedPiece: (int)Piece.N + offset, isCapture: TRUE);
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, targetSquare, piece, promotedPiece: (int)Piece.B + offset, isCapture: TRUE);
}
else
{
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, targetSquare, piece, isCapture: TRUE);
}
}
}
}

/// <summary>
/// Generate Knight, Bishop, Rook and Queen moves
/// </summary>
/// <param name="piece"><see cref="Piece"/></param>
/// <param name="position"></param>
/// <param name="capturesOnly"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void GeneratePieceCaptures(ref int localIndex, Move[] movePool, int piece, Position position)
{
var bitboard = position.PieceBitBoards[piece];
int sourceSquare, targetSquare;

while (bitboard != default)
{
sourceSquare = bitboard.GetLS1BIndex();
bitboard.ResetLS1B();

var attacks = _pieceAttacks[piece](sourceSquare, position.OccupancyBitBoards[(int)Side.Both])
& ~position.OccupancyBitBoards[(int)position.Side];

while (attacks != default)
{
targetSquare = attacks.GetLS1BIndex();
attacks.ResetLS1B();

if (position.OccupancyBitBoards[(int)Side.Both].GetBit(targetSquare))
{
movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, targetSquare, piece, isCapture: TRUE);
}
}
}
}

/// <summary>
/// Generates all psuedo-legal moves from <paramref name="position"/>, ordered by <see cref="Move.Score(Position)"/>
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Lynx/Search/NegaMax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ public int QuiescenceSearch(int ply, int alpha, int beta)
alpha = staticEvaluation;
}

var pseudoLegalMoves = MoveGenerator.GenerateAllMoves(position, Game.MovePool, capturesOnly: true);
var pseudoLegalMoves = MoveGenerator.GenerateAllCaptures(position, Game.MovePool);
if (pseudoLegalMoves.Length == 0)
{
// Checking if final position first: https://github.com/lynx-chess/Lynx/pull/358
Expand Down
Loading