Skip to content

Commit

Permalink
Split MoveGenerator.GenerateAllMoves and `MoveGenerator.GenerateAll…
Browse files Browse the repository at this point in the history
…Captures'
  • Loading branch information
eduherminio committed Oct 31, 2023
1 parent 6155f47 commit 69f9678
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 23 deletions.
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

0 comments on commit 69f9678

Please sign in to comment.