From 8ab49996527a7d81add97dd9ca4e7ee0e353ebe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20C=C3=A1ceres?= Date: Thu, 21 Dec 2023 16:27:11 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AC=20Improve=20move=20generation:=20h?= =?UTF-8?q?ardcode=20castling=20moves=20and=20a=20few=20calculated=20varia?= =?UTF-8?q?bles=20(#541)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Lynx.Dev/Program.cs | 10 +- src/Lynx/Constants.cs | 449 ++++++++++++++++-- src/Lynx/Model/Position.cs | 2 +- src/Lynx/MoveGenerator.cs | 204 +++----- .../MoveGeneration/CastlingMoveTest.cs | 34 ++ .../GenerateCastlingMovesTest.cs | 40 +- 6 files changed, 546 insertions(+), 193 deletions(-) create mode 100644 tests/Lynx.Test/MoveGeneration/CastlingMoveTest.cs diff --git a/src/Lynx.Dev/Program.cs b/src/Lynx.Dev/Program.cs index 834394169..5c09b9105 100644 --- a/src/Lynx.Dev/Program.cs +++ b/src/Lynx.Dev/Program.cs @@ -400,9 +400,12 @@ static void _23_Castling_Moves() var position = new Position("rn2k2r/pppppppp/8/8/8/8/PPPPPPPP/RN2K2R w KQkq - 0 1"); position.Print(); - var moves = MoveGenerator.GenerateCastlingMovesForReference(position, Utils.PieceOffset(position.Side)).ToList(); + int index = 0; + var moves = new Move[Constants.MaxNumberOfPossibleMovesInAPosition]; - foreach (var move in moves) + MoveGenerator.GenerateCastlingMoves(ref index, moves, position); + + foreach (var move in moves[..index]) { Console.WriteLine(move); } @@ -689,7 +692,7 @@ static void ZobristTable() var pos = new Position(KillerPosition); var zobristTable = InitializeZobristTable(); var hash = CalculatePositionHash(zobristTable, pos); - var updatedHash = UpdatePositionHash(zobristTable, hash, MoveGenerator.GenerateAllMoves(pos).First()); + var updatedHash = UpdatePositionHash(zobristTable, hash, MoveGenerator.GenerateAllMoves(pos)[0]); Console.WriteLine(updatedHash); } @@ -1098,7 +1101,6 @@ static void TestMoveGen(string fen) Console.WriteLine($"Position\t{newPosition.FEN()}, Zobrist key {newPosition.UniqueIdentifier}"); Console.WriteLine($"Position\t{position.FEN()}, Zobrist key {position.UniqueIdentifier}"); - Console.WriteLine($"Unmaking {move.ToEPDString()} in\t{position.FEN()}"); //position.UnmakeMove(move, savedState); diff --git a/src/Lynx/Constants.cs b/src/Lynx/Constants.cs index 531f675b5..069f23099 100644 --- a/src/Lynx/Constants.cs +++ b/src/Lynx/Constants.cs @@ -73,14 +73,70 @@ public static class Constants public static readonly string[] Coordinates = [ - "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8", - "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", - "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6", - "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5", - "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4", - "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3", - "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2", - "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1" + "a8", + "b8", + "c8", + "d8", + "e8", + "f8", + "g8", + "h8", + "a7", + "b7", + "c7", + "d7", + "e7", + "f7", + "g7", + "h7", + "a6", + "b6", + "c6", + "d6", + "e6", + "f6", + "g6", + "h6", + "a5", + "b5", + "c5", + "d5", + "e5", + "f5", + "g5", + "h5", + "a4", + "b4", + "c4", + "d4", + "e4", + "f4", + "g4", + "h4", + "a3", + "b3", + "c3", + "d3", + "e3", + "f3", + "g3", + "h3", + "a2", + "b2", + "c2", + "d2", + "e2", + "f2", + "g2", + "h2", + "a1", + "b1", + "c1", + "d1", + "e1", + "f1", + "g1", + "h1" ]; /// @@ -94,8 +150,18 @@ public static class Constants /// public static readonly string[] UnicodePieces = [ - "♙", "♘", "♗", "♖", "♕", "♔", // White - "♟︎", "♞", "♝", "♜", "♛", "♚" // Black + "♙", + "♘", + "♗", + "♖", + "♕", + "♔", // White + "♟︎", + "♞", + "♝", + "♜", + "♛", + "♚" // Black ]; /// @@ -123,14 +189,70 @@ public static class Constants /// public static readonly int[] BishopRelevantOccupancyBits = [ - 6, 5, 5, 5, 5, 5, 5, 6, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 7, 7, 7, 7, 5, 5, - 5, 5, 7, 9, 9, 7, 5, 5, - 5, 5, 7, 9, 9, 7, 5, 5, - 5, 5, 7, 7, 7, 7, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 6, 5, 5, 5, 5, 5, 5, 6, + 6, + 5, + 5, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 7, + 7, + 7, + 7, + 5, + 5, + 5, + 5, + 7, + 9, + 9, + 7, + 5, + 5, + 5, + 5, + 7, + 9, + 9, + 7, + 5, + 5, + 5, + 5, + 7, + 7, + 7, + 7, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 5, + 5, + 5, + 5, + 6, ]; /// @@ -138,14 +260,70 @@ public static class Constants /// public static readonly int[] RookRelevantOccupancyBits = [ - 12, 11, 11, 11, 11, 11, 11, 12, - 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, - 12, 11, 11, 11, 11, 11, 11, 12 + 12, + 11, + 11, + 11, + 11, + 11, + 11, + 12, + 11, + 10, + 10, + 10, + 10, + 10, + 10, + 11, + 11, + 10, + 10, + 10, + 10, + 10, + 10, + 11, + 11, + 10, + 10, + 10, + 10, + 10, + 10, + 11, + 11, + 10, + 10, + 10, + 10, + 10, + 10, + 11, + 11, + 10, + 10, + 10, + 10, + 10, + 10, + 11, + 11, + 10, + 10, + 10, + 10, + 10, + 10, + 11, + 12, + 11, + 11, + 11, + 11, + 11, + 11, + 12 ]; /// @@ -308,6 +486,9 @@ public static class Constants public const string CaptureTrainPositionFEN = "r2q1rk1/bppb1pp1/p2p2np/2PPp3/1P2P1n1/P3BN2/2Q1BPPP/RN3RK1 w - - 2 15"; + public const int WhiteKingSourceSquare = (int)BoardSquare.e1; + public const int BlackKingSourceSquare = (int)BoardSquare.e8; + public const int WhiteShortCastleKingSquare = (int)BoardSquare.g1; public const int BlackShortCastleKingSquare = (int)BoardSquare.g8; public const int WhiteLongCastleKingSquare = (int)BoardSquare.c1; @@ -357,14 +538,70 @@ public static class Constants /// public static readonly byte[] CastlingRightsUpdateConstants = [ - 7, 15, 15, 15, 3, 15, 15, 11, - 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, - 13, 15, 15, 15, 12, 15, 15, 14 + 7, + 15, + 15, + 15, + 3, + 15, + 15, + 11, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 13, + 15, + 15, + 15, + 12, + 15, + 15, + 14 ]; /// @@ -377,26 +614,138 @@ public static class Constants public static readonly int[] Rank = [ - 7, 7, 7, 7, 7, 7, 7, 7, - 6, 6, 6, 6, 6, 6, 6, 6, - 5, 5, 5, 5, 5, 5, 5, 5, - 4, 4, 4, 4, 4, 4, 4, 4, - 3, 3, 3, 3, 3, 3, 3, 3, - 2, 2, 2, 2, 2, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0 + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 ]; public static readonly int[] File = [ - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7 + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 ]; public const int AbsoluteMaxDepth = 255; diff --git a/src/Lynx/Model/Position.cs b/src/Lynx/Model/Position.cs index 050823ea2..9256fe431 100644 --- a/src/Lynx/Model/Position.cs +++ b/src/Lynx/Model/Position.cs @@ -786,7 +786,7 @@ public static int EvaluateFinalPosition(int ply, bool isInCheck) endGameBonus += Configuration.EngineSettings.PassedPawnBonus[rank].EG; } - return (middleGameBonus, endGameBonus); ; + return (middleGameBonus, endGameBonus); } /// diff --git a/src/Lynx/MoveGenerator.cs b/src/Lynx/MoveGenerator.cs index ec8284f7a..b5956edf0 100644 --- a/src/Lynx/MoveGenerator.cs +++ b/src/Lynx/MoveGenerator.cs @@ -1,5 +1,6 @@ using Lynx.Model; using NLog; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace Lynx; @@ -10,31 +11,37 @@ public static class MoveGenerator private const int TRUE = 1; + public const int WhiteShortCastle = 8413116; + public const int WhiteLongCastle = 16801468; + public const int BlackShortCastle = 8434052; + public const int BlackLongCastle = 16822404; + /// /// Indexed by . /// Checks are not considered /// - private static readonly Func[] _pieceAttacks = new Func[] - { - (int origin, BitBoard _) => Attacks.PawnAttacks[(int)Side.White, origin], - (int origin, BitBoard _) => Attacks.KnightAttacks[origin], - Attacks.BishopAttacks, - Attacks.RookAttacks, - Attacks.QueenAttacks, - (int origin, BitBoard _) => Attacks.KingAttacks[origin], - - (int origin, BitBoard _) => Attacks.PawnAttacks[(int)Side.Black, origin], - (int origin, BitBoard _) => Attacks.KnightAttacks[origin], - Attacks.BishopAttacks, - Attacks.RookAttacks, - Attacks.QueenAttacks, - (int origin, BitBoard _) => Attacks.KingAttacks[origin], - }; + private static readonly Func[] _pieceAttacks = + [ + (int origin, BitBoard _) => Attacks.PawnAttacks[(int)Side.White, origin], + (int origin, BitBoard _) => Attacks.KnightAttacks[origin], + Attacks.BishopAttacks, + Attacks.RookAttacks, + Attacks.QueenAttacks, + (int origin, BitBoard _) => Attacks.KingAttacks[origin], + + (int origin, BitBoard _) => Attacks.PawnAttacks[(int)Side.Black, origin], + (int origin, BitBoard _) => Attacks.KnightAttacks[origin], + Attacks.BishopAttacks, + Attacks.RookAttacks, + Attacks.QueenAttacks, + (int origin, BitBoard _) => Attacks.KingAttacks[origin], + ]; /// /// Generates all psuedo-legal moves from , ordered by /// /// + /// /// Filters out all moves but captures /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -43,7 +50,7 @@ public static Move[] GenerateAllMoves(Position position, Move[]? movePool = null #if DEBUG if (position.Side == Side.Both) { - return Array.Empty(); + return []; } #endif @@ -53,7 +60,7 @@ public static Move[] GenerateAllMoves(Position position, Move[]? movePool = null var offset = Utils.PieceOffset(position.Side); GeneratePawnMoves(ref localIndex, movePool, position, offset, capturesOnly); - GenerateCastlingMoves(ref localIndex, movePool, position, offset); + GenerateCastlingMoves(ref localIndex, movePool, position); 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); @@ -155,31 +162,29 @@ internal static void GeneratePawnMoves(ref int localIndex, Move[] movePool, Posi /// Obvious moves that put the king in check have been discarded, but the rest still need to be discarded /// see FEN position "8/8/8/2bbb3/2bKb3/2bbb3/8/8 w - - 0 1", where 4 legal moves (corners) are found /// + /// /// - /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void GenerateCastlingMoves(ref int localIndex, Move[] movePool, Position position, int offset) + internal static void GenerateCastlingMoves(ref int localIndex, Move[] movePool, Position position) { - var piece = (int)Piece.K + offset; - var oppositeSide = (Side)Utils.OppositeSide(position.Side); - - int sourceSquare = position.PieceBitBoards[piece].GetLS1BIndex(); // There's for sure only one - - // Castles if (position.Castle != default) { if (position.Side == Side.White) { - bool ise1Attacked = Attacks.IsSquareAttackedBySide((int)BoardSquare.e1, position, oppositeSide); + bool ise1Attacked = Attacks.IsSquareAttackedBySide(Constants.WhiteKingSourceSquare, position, Side.Black); + if (((position.Castle & (int)CastlingRights.WK) != default) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.f1) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.g1) && !ise1Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.f1, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.g1, position, oppositeSide)) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.f1, position, Side.Black) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.g1, position, Side.Black)) { - movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, Constants.WhiteShortCastleKingSquare, piece, isShortCastle: TRUE); + movePool[localIndex++] = WhiteShortCastle; + + Debug.Assert(movePool[localIndex - 1] == MoveExtensions.Encode(Constants.WhiteKingSourceSquare, Constants.WhiteShortCastleKingSquare, (int)Piece.K + Utils.PieceOffset(position.Side), isShortCastle: TRUE), + "Wrong hardcoded white short castle move"); } if (((position.Castle & (int)CastlingRights.WQ) != default) @@ -187,23 +192,30 @@ internal static void GenerateCastlingMoves(ref int localIndex, Move[] movePool, && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.c1) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.b1) && !ise1Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.d1, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.c1, position, oppositeSide)) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.d1, position, Side.Black) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.c1, position, Side.Black)) { - movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, Constants.WhiteLongCastleKingSquare, piece, isLongCastle: TRUE); + movePool[localIndex++] = WhiteLongCastle; + + Debug.Assert(movePool[localIndex - 1] == MoveExtensions.Encode(Constants.WhiteKingSourceSquare, Constants.WhiteLongCastleKingSquare, (int)Piece.K + Utils.PieceOffset(position.Side), isLongCastle: TRUE), + "Wrong hardcoded white long castle move"); } } else { - bool ise8Attacked = Attacks.IsSquareAttackedBySide((int)BoardSquare.e8, position, oppositeSide); + bool ise8Attacked = Attacks.IsSquareAttackedBySide(Constants.BlackKingSourceSquare, position, Side.White); + if (((position.Castle & (int)CastlingRights.BK) != default) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.f8) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.g8) && !ise8Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.f8, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.g8, position, oppositeSide)) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.f8, position, Side.White) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.g8, position, Side.White)) { - movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, Constants.BlackShortCastleKingSquare, piece, isShortCastle: TRUE); + movePool[localIndex++] = BlackShortCastle; + + Debug.Assert(movePool[localIndex - 1] == MoveExtensions.Encode(Constants.BlackKingSourceSquare, Constants.BlackShortCastleKingSquare, (int)Piece.K + Utils.PieceOffset(position.Side), isShortCastle: TRUE), + "Wrong hardcoded black short castle move"); } if (((position.Castle & (int)CastlingRights.BQ) != default) @@ -211,10 +223,13 @@ internal static void GenerateCastlingMoves(ref int localIndex, Move[] movePool, && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.c8) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.b8) && !ise8Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.d8, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.c8, position, oppositeSide)) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.d8, position, Side.White) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.c8, position, Side.White)) { - movePool[localIndex++] = MoveExtensions.Encode(sourceSquare, Constants.BlackLongCastleKingSquare, piece, isLongCastle: TRUE); + movePool[localIndex++] = BlackLongCastle; + + Debug.Assert(movePool[localIndex - 1] == MoveExtensions.Encode(Constants.BlackKingSourceSquare, Constants.BlackLongCastleKingSquare, (int)Piece.K + Utils.PieceOffset(position.Side), isLongCastle: TRUE), + "Wrong hardcoded black long castle move"); } } } @@ -223,6 +238,7 @@ internal static void GenerateCastlingMoves(ref int localIndex, Move[] movePool, /// /// Generate Knight, Bishop, Rook and Queen moves /// + /// /// /// /// @@ -281,7 +297,7 @@ public static bool CanGenerateAtLeastAValidMove(Position position) || IsAnyPieceMoveValid((int)Piece.B + offset, position) || IsAnyPieceMoveValid((int)Piece.N + offset, position) || IsAnyPieceMoveValid((int)Piece.R + offset, position) - || IsAnyCastlingMoveValid(position, offset); + || IsAnyCastlingMoveValid(position); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -384,28 +400,23 @@ private static bool IsAnyPawnMoveValid(Position position, int offset) /// see FEN position "8/8/8/2bbb3/2bKb3/2bbb3/8/8 w - - 0 1", where 4 legal moves (corners) are found /// /// - /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsAnyCastlingMoveValid(Position position, int offset) + private static bool IsAnyCastlingMoveValid(Position position) { if (position.Castle != default) { - var piece = (int)Piece.K + offset; - var oppositeSide = (Side)Utils.OppositeSide(position.Side); - - int sourceSquare = position.PieceBitBoards[piece].GetLS1BIndex(); // There's for sure only one - if (position.Side == Side.White) { - bool ise1Attacked = Attacks.IsSquareAttackedBySide((int)BoardSquare.e1, position, oppositeSide); + bool ise1Attacked = Attacks.IsSquareAttackedBySide(Constants.WhiteKingSourceSquare, position, Side.Black); + if (((position.Castle & (int)CastlingRights.WK) != default) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.f1) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.g1) && !ise1Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.f1, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.g1, position, oppositeSide) - && IsValidMove(position, MoveExtensions.Encode(sourceSquare, Constants.WhiteShortCastleKingSquare, piece, isShortCastle: TRUE))) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.f1, position, Side.Black) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.g1, position, Side.Black) + && IsValidMove(position, WhiteShortCastle)) { return true; } @@ -415,23 +426,24 @@ private static bool IsAnyCastlingMoveValid(Position position, int offset) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.c1) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.b1) && !ise1Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.d1, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.c1, position, oppositeSide) - && IsValidMove(position, MoveExtensions.Encode(sourceSquare, Constants.WhiteLongCastleKingSquare, piece, isLongCastle: TRUE))) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.d1, position, Side.Black) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.c1, position, Side.Black) + && IsValidMove(position, WhiteLongCastle)) { return true; } } else { - bool ise8Attacked = Attacks.IsSquareAttackedBySide((int)BoardSquare.e8, position, oppositeSide); + bool ise8Attacked = Attacks.IsSquareAttackedBySide(Constants.BlackKingSourceSquare, position, Side.White); + if (((position.Castle & (int)CastlingRights.BK) != default) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.f8) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.g8) && !ise8Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.f8, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.g8, position, oppositeSide) - && IsValidMove(position, MoveExtensions.Encode(sourceSquare, Constants.BlackShortCastleKingSquare, piece, isShortCastle: TRUE))) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.f8, position, Side.White) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.g8, position, Side.White) + && IsValidMove(position, BlackShortCastle)) { return true; } @@ -441,9 +453,9 @@ private static bool IsAnyCastlingMoveValid(Position position, int offset) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.c8) && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.b8) && !ise8Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.d8, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.c8, position, oppositeSide) - && IsValidMove(position, MoveExtensions.Encode(sourceSquare, Constants.BlackLongCastleKingSquare, piece, isLongCastle: TRUE))) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.d8, position, Side.White) + && !Attacks.IsSquareAttackedBySide((int)BoardSquare.c8, position, Side.White) + && IsValidMove(position, BlackLongCastle)) { return true; } @@ -600,74 +612,6 @@ internal static IEnumerable GeneratePawnMovesForReference(Position positio } } - /// - /// Same as but returning them - /// - /// - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static IEnumerable GenerateCastlingMovesForReference(Position position, int offset) - { - var piece = (int)Piece.K + offset; - var oppositeSide = (Side)Utils.OppositeSide(position.Side); - - int sourceSquare = position.PieceBitBoards[piece].GetLS1BIndex(); // There's for sure only one - - // Castles - if (position.Castle != default) - { - if (position.Side == Side.White) - { - bool ise1Attacked = Attacks.IsSquareAttackedBySide((int)BoardSquare.e1, position, oppositeSide); - if (((position.Castle & (int)CastlingRights.WK) != default) - && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.f1) - && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.g1) - && !ise1Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.f1, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.g1, position, oppositeSide)) - { - yield return MoveExtensions.Encode(sourceSquare, Constants.WhiteShortCastleKingSquare, piece, isShortCastle: TRUE); - } - - if (((position.Castle & (int)CastlingRights.WQ) != default) - && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.d1) - && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.c1) - && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.b1) - && !ise1Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.d1, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.c1, position, oppositeSide)) - { - yield return MoveExtensions.Encode(sourceSquare, Constants.WhiteLongCastleKingSquare, piece, isLongCastle: TRUE); - } - } - else - { - bool ise8Attacked = Attacks.IsSquareAttackedBySide((int)BoardSquare.e8, position, oppositeSide); - if (((position.Castle & (int)CastlingRights.BK) != default) - && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.f8) - && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.g8) - && !ise8Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.f8, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.g8, position, oppositeSide)) - { - yield return MoveExtensions.Encode(sourceSquare, Constants.BlackShortCastleKingSquare, piece, isShortCastle: TRUE); - } - - if (((position.Castle & (int)CastlingRights.BQ) != default) - && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.d8) - && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.c8) - && !position.OccupancyBitBoards[(int)Side.Both].GetBit(BoardSquare.b8) - && !ise8Attacked - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.d8, position, oppositeSide) - && !Attacks.IsSquareAttackedBySide((int)BoardSquare.c8, position, oppositeSide)) - { - yield return MoveExtensions.Encode(sourceSquare, Constants.BlackLongCastleKingSquare, piece, isLongCastle: TRUE); - } - } - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static IEnumerable GenerateKingMoves(Position position, bool capturesOnly = false) { diff --git a/tests/Lynx.Test/MoveGeneration/CastlingMoveTest.cs b/tests/Lynx.Test/MoveGeneration/CastlingMoveTest.cs new file mode 100644 index 000000000..086b5d507 --- /dev/null +++ b/tests/Lynx.Test/MoveGeneration/CastlingMoveTest.cs @@ -0,0 +1,34 @@ +using Lynx.Model; +using NUnit.Framework; + +namespace Lynx.Test.MoveGeneration; +public class CastlingMoveTest +{ + [Test] + public void WhiteShortCastling() + { + Assert.AreEqual(MoveGenerator.WhiteShortCastle, + MoveExtensions.Encode(Constants.WhiteKingSourceSquare, Constants.WhiteShortCastleKingSquare, (int)Piece.K + Utils.PieceOffset(Side.White), isShortCastle: 1)); + } + + [Test] + public void WhiteLongCastling() + { + Assert.AreEqual(MoveGenerator.WhiteLongCastle, + MoveExtensions.Encode(Constants.WhiteKingSourceSquare, Constants.WhiteLongCastleKingSquare, (int)Piece.K + Utils.PieceOffset(Side.White), isLongCastle: 1)); + } + + [Test] + public void BlackShortCastling() + { + Assert.AreEqual(MoveGenerator.BlackShortCastle, + MoveExtensions.Encode(Constants.BlackKingSourceSquare, Constants.BlackShortCastleKingSquare, (int)Piece.K + Utils.PieceOffset(Side.Black), isShortCastle: 1)); + } + + [Test] + public void BlackLongCastling() + { + Assert.AreEqual(MoveGenerator.BlackLongCastle, + MoveExtensions.Encode(Constants.BlackKingSourceSquare, Constants.BlackLongCastleKingSquare, (int)Piece.K + Utils.PieceOffset(Side.Black), isLongCastle: 1)); + } +} diff --git a/tests/Lynx.Test/MoveGeneration/GenerateCastlingMovesTest.cs b/tests/Lynx.Test/MoveGeneration/GenerateCastlingMovesTest.cs index 2effb996d..8b124916c 100644 --- a/tests/Lynx.Test/MoveGeneration/GenerateCastlingMovesTest.cs +++ b/tests/Lynx.Test/MoveGeneration/GenerateCastlingMovesTest.cs @@ -15,7 +15,10 @@ public void ShortCastle(string fen) { var position = new Position(fen); - var moves = MoveGenerator.GenerateCastlingMovesForReference(position, Utils.PieceOffset(position.Side)); + int index = 0; + var moves = new Move[Constants.MaxNumberOfPossibleMovesInAPosition]; + + MoveGenerator.GenerateCastlingMoves(ref index, moves, position); Assert.True(1 == moves.Count(m => m.IsCastle())); Assert.True(1 == moves.Count(m => m.IsShortCastle())); @@ -32,7 +35,10 @@ public void LongCastle(string fen) { var position = new Position(fen); - var moves = MoveGenerator.GenerateCastlingMovesForReference(position, Utils.PieceOffset(position.Side)); + int index = 0; + var moves = new Move[Constants.MaxNumberOfPossibleMovesInAPosition]; + + MoveGenerator.GenerateCastlingMoves(ref index, moves, position); Assert.True(1 == moves.Count(m => m.IsCastle())); Assert.True(1 == moves.Count(m => m.IsLongCastle())); @@ -57,7 +63,10 @@ public void ShouldNotShortCastleWhenNoCastlingFlag(string fen) { var position = new Position(fen); - var moves = MoveGenerator.GenerateCastlingMovesForReference(position, Utils.PieceOffset(position.Side)); + int index = 0; + var moves = new Move[Constants.MaxNumberOfPossibleMovesInAPosition]; + + MoveGenerator.GenerateCastlingMoves(ref index, moves, position); Assert.IsEmpty(moves.Where(m => m.IsShortCastle())); } @@ -80,7 +89,10 @@ public void ShouldNotLongCastleWhenNoCastlingFlag(string fen) { var position = new Position(fen); - var moves = MoveGenerator.GenerateCastlingMovesForReference(position, Utils.PieceOffset(position.Side)); + int index = 0; + var moves = new Move[Constants.MaxNumberOfPossibleMovesInAPosition]; + + MoveGenerator.GenerateCastlingMoves(ref index, moves, position); Assert.IsEmpty(moves.Where(m => m.IsLongCastle())); } @@ -101,7 +113,10 @@ public void ShouldNotShortCastleWhenOccupiedSquares(string fen) { var position = new Position(fen); - var moves = MoveGenerator.GenerateCastlingMovesForReference(position, Utils.PieceOffset(position.Side)); + int index = 0; + var moves = new Move[Constants.MaxNumberOfPossibleMovesInAPosition]; + + MoveGenerator.GenerateCastlingMoves(ref index, moves, position); Assert.IsEmpty(moves.Where(m => m.IsShortCastle())); } @@ -127,7 +142,10 @@ public void ShouldNotLongCastleWhenOccupiedSquares(string fen) { var position = new Position(fen); - var moves = MoveGenerator.GenerateCastlingMovesForReference(position, Utils.PieceOffset(position.Side)); + int index = 0; + var moves = new Move[Constants.MaxNumberOfPossibleMovesInAPosition]; + + MoveGenerator.GenerateCastlingMoves(ref index, moves, position); Assert.IsEmpty(moves.Where(m => m.IsLongCastle())); } @@ -142,7 +160,10 @@ public void ShouldNotShortCastleWhenAttackedSquares(string fen) { var position = new Position(fen); - var moves = MoveGenerator.GenerateCastlingMovesForReference(position, Utils.PieceOffset(position.Side)); + int index = 0; + var moves = new Move[Constants.MaxNumberOfPossibleMovesInAPosition]; + + MoveGenerator.GenerateCastlingMoves(ref index, moves, position); Assert.IsEmpty(moves.Where(m => m.IsShortCastle())); } @@ -157,7 +178,10 @@ public void ShouldNotLongCastleWhenAttackedSquares(string fen) { var position = new Position(fen); - var moves = MoveGenerator.GenerateCastlingMovesForReference(position, Utils.PieceOffset(position.Side)); + int index = 0; + var moves = new Move[Constants.MaxNumberOfPossibleMovesInAPosition]; + + MoveGenerator.GenerateCastlingMoves(ref index, moves, position); Assert.IsEmpty(moves.Where(m => m.IsLongCastle())); }