Add alternate way to get legal moves
board.GetLegalMovesNonAlloc()
This commit is contained in:
parent
a56dca0165
commit
085baea286
3 changed files with 99 additions and 25 deletions
|
@ -18,6 +18,7 @@ namespace ChessChallenge.API
|
||||||
bool hasCachedMoves;
|
bool hasCachedMoves;
|
||||||
Move[] cachedLegalCaptureMoves;
|
Move[] cachedLegalCaptureMoves;
|
||||||
bool hasCachedCaptureMoves;
|
bool hasCachedCaptureMoves;
|
||||||
|
readonly Move[] movesDest;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new board. Note: this should not be used in the challenge,
|
/// Create a new board. Note: this should not be used in the challenge,
|
||||||
|
@ -29,6 +30,7 @@ namespace ChessChallenge.API
|
||||||
moveGen = new APIMoveGen();
|
moveGen = new APIMoveGen();
|
||||||
cachedLegalMoves = Array.Empty<Move>();
|
cachedLegalMoves = Array.Empty<Move>();
|
||||||
cachedLegalCaptureMoves = Array.Empty<Move>();
|
cachedLegalCaptureMoves = Array.Empty<Move>();
|
||||||
|
movesDest = new Move[APIMoveGen.MaxMoves];
|
||||||
|
|
||||||
// Init piece lists
|
// Init piece lists
|
||||||
List<PieceList> validPieceLists = new();
|
List<PieceList> validPieceLists = new();
|
||||||
|
@ -118,19 +120,35 @@ namespace ChessChallenge.API
|
||||||
|
|
||||||
if (!hasCachedMoves)
|
if (!hasCachedMoves)
|
||||||
{
|
{
|
||||||
cachedLegalMoves = moveGen.GenerateMoves(board, includeQuietMoves: true);
|
Span<Move> moveSpan = movesDest.AsSpan();
|
||||||
|
moveGen.GenerateMoves(ref moveSpan, board, includeQuietMoves: true);
|
||||||
|
cachedLegalMoves = moveSpan.ToArray();
|
||||||
hasCachedMoves = true;
|
hasCachedMoves = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cachedLegalMoves;
|
return cachedLegalMoves;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fills the given move span with legal moves, and slices it to the correct length.
|
||||||
|
/// Can choose to get only capture moves with the optional 'capturesOnly' parameter.
|
||||||
|
/// This gives the same result as the GetLegalMoves function, but allows you to be more
|
||||||
|
/// efficient with memory by allocating moves on the stack rather than the heap.
|
||||||
|
/// </summary>
|
||||||
|
public void GetLegalMovesNonAlloc(ref Span<Move> moveList, bool capturesOnly = false)
|
||||||
|
{
|
||||||
|
bool includeQuietMoves = !capturesOnly;
|
||||||
|
moveGen.GenerateMoves(ref moveList, board, includeQuietMoves);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Move[] GetLegalCaptureMoves()
|
Move[] GetLegalCaptureMoves()
|
||||||
{
|
{
|
||||||
if (!hasCachedCaptureMoves)
|
if (!hasCachedCaptureMoves)
|
||||||
{
|
{
|
||||||
cachedLegalCaptureMoves = moveGen.GenerateMoves(board, includeQuietMoves: false);
|
Span<Move> moveSpan = movesDest.AsSpan();
|
||||||
|
moveGen.GenerateMoves(ref moveSpan, board, includeQuietMoves: false);
|
||||||
|
cachedLegalCaptureMoves = moveSpan.ToArray();
|
||||||
hasCachedCaptureMoves = true;
|
hasCachedCaptureMoves = true;
|
||||||
}
|
}
|
||||||
return cachedLegalCaptureMoves;
|
return cachedLegalCaptureMoves;
|
||||||
|
|
|
@ -45,34 +45,32 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
// If only captures should be generated, this will have 1s only in positions of enemy pieces.
|
// If only captures should be generated, this will have 1s only in positions of enemy pieces.
|
||||||
// Otherwise it will have 1s everywhere.
|
// Otherwise it will have 1s everywhere.
|
||||||
ulong moveTypeMask;
|
ulong moveTypeMask;
|
||||||
API.Move[] moves;
|
|
||||||
|
|
||||||
public APIMoveGen()
|
public APIMoveGen()
|
||||||
{
|
{
|
||||||
board = new Board();
|
board = new Board();
|
||||||
moves = new API.Move[MaxMoves];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates list of legal moves in current position.
|
// Generates list of legal moves in current position.
|
||||||
// Quiet moves (non captures) can optionally be excluded. This is used in quiescence search.
|
// Quiet moves (non captures) can optionally be excluded. This is used in quiescence search.
|
||||||
public API.Move[] GenerateMoves(Board board, bool includeQuietMoves = true)
|
public void GenerateMoves(ref Span<API.Move> moves, Board board, bool includeQuietMoves = true)
|
||||||
{
|
{
|
||||||
this.board = board;
|
this.board = board;
|
||||||
generateNonCapture = includeQuietMoves;
|
generateNonCapture = includeQuietMoves;
|
||||||
|
|
||||||
Init();
|
Init();
|
||||||
|
|
||||||
GenerateKingMoves();
|
GenerateKingMoves(moves);
|
||||||
|
|
||||||
// Only king moves are valid in a double check position, so can return early.
|
// Only king moves are valid in a double check position, so can return early.
|
||||||
if (!inDoubleCheck)
|
if (!inDoubleCheck)
|
||||||
{
|
{
|
||||||
GenerateSlidingMoves();
|
GenerateSlidingMoves(moves);
|
||||||
GenerateKnightMoves();
|
GenerateKnightMoves(moves);
|
||||||
GeneratePawnMoves();
|
GeneratePawnMoves(moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
return moves.AsSpan().Slice(0, currMoveIndex).ToArray();
|
moves = moves.Slice(0, currMoveIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note, this will only return correct value after GenerateMoves() has been called in the current position
|
// Note, this will only return correct value after GenerateMoves() has been called in the current position
|
||||||
|
@ -123,7 +121,7 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
return apiMove;
|
return apiMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenerateKingMoves()
|
void GenerateKingMoves(Span<API.Move> moves)
|
||||||
{
|
{
|
||||||
ulong legalMask = ~(opponentAttackMap | friendlyPieces);
|
ulong legalMask = ~(opponentAttackMap | friendlyPieces);
|
||||||
ulong kingMoves = Bits.KingMoves[friendlyKingSquare] & legalMask & moveTypeMask;
|
ulong kingMoves = Bits.KingMoves[friendlyKingSquare] & legalMask & moveTypeMask;
|
||||||
|
@ -159,7 +157,7 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenerateSlidingMoves()
|
void GenerateSlidingMoves(Span<API.Move> moves)
|
||||||
{
|
{
|
||||||
// Limit movement to empty or enemy squares, and must block check if king is in check.
|
// Limit movement to empty or enemy squares, and must block check if king is in check.
|
||||||
ulong moveMask = emptyOrEnemySquares & checkRayBitmask & moveTypeMask;
|
ulong moveMask = emptyOrEnemySquares & checkRayBitmask & moveTypeMask;
|
||||||
|
@ -214,7 +212,7 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GenerateKnightMoves()
|
void GenerateKnightMoves(Span<API.Move> moves)
|
||||||
{
|
{
|
||||||
int friendlyKnightPiece = PieceHelper.MakePiece(PieceHelper.Knight, board.MoveColour);
|
int friendlyKnightPiece = PieceHelper.MakePiece(PieceHelper.Knight, board.MoveColour);
|
||||||
// bitboard of all non-pinned knights
|
// bitboard of all non-pinned knights
|
||||||
|
@ -234,7 +232,7 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GeneratePawnMoves()
|
void GeneratePawnMoves(Span<API.Move> moves)
|
||||||
{
|
{
|
||||||
int pushDir = board.IsWhiteToMove ? 1 : -1;
|
int pushDir = board.IsWhiteToMove ? 1 : -1;
|
||||||
int pushOffset = pushDir * 8;
|
int pushOffset = pushDir * 8;
|
||||||
|
@ -325,7 +323,7 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
int startSquare = targetSquare - pushOffset;
|
int startSquare = targetSquare - pushOffset;
|
||||||
if (!IsPinned(startSquare))
|
if (!IsPinned(startSquare))
|
||||||
{
|
{
|
||||||
GeneratePromotions(startSquare, targetSquare);
|
GeneratePromotions(moves, startSquare, targetSquare);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,7 +336,7 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
|
|
||||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||||
{
|
{
|
||||||
GeneratePromotions(startSquare, targetSquare);
|
GeneratePromotions(moves, startSquare, targetSquare);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,7 +347,7 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
|
|
||||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||||
{
|
{
|
||||||
GeneratePromotions(startSquare, targetSquare);
|
GeneratePromotions(moves, startSquare, targetSquare);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,7 +378,7 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GeneratePromotions(int startSquare, int targetSquare)
|
void GeneratePromotions(Span<API.Move> moves, int startSquare, int targetSquare)
|
||||||
{
|
{
|
||||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, Move.PromoteToQueenFlag, PieceHelper.Pawn);
|
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, Move.PromoteToQueenFlag, PieceHelper.Pawn);
|
||||||
// Don't generate non-queen promotions in q-search
|
// Don't generate non-queen promotions in q-search
|
||||||
|
|
|
@ -21,6 +21,40 @@ namespace ChessChallenge.Application
|
||||||
Console.WriteLine("Tests Finished");
|
Console.WriteLine("Tests Finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void RunPerft()
|
||||||
|
{
|
||||||
|
Warmer.Warm();
|
||||||
|
int[] depths = { 5, 5, 6, 5, 5, 4, 5, 4, 6, 6, 6, 7, 4, 5, 6, 5, 6, 6, 10, 7, 6, 5, 4, 5, 4, 6, 6, 9, 4, 5 };
|
||||||
|
ulong[] expectedNodes = { 4865609, 5617302, 11030083, 15587335, 89941194, 3894594, 193690690, 497787, 1134888, 1440467, 661072, 15594314, 1274206, 58773923, 3821001, 1004658, 217342, 92683, 5966690, 567584, 3114998, 42761834, 3050662, 10574719, 6871272, 71179139, 28859283, 7618365, 28181, 6323457 };
|
||||||
|
string[] fens = { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "2b1b3/1r1P4/3K3p/1p6/2p5/6k1/1P3p2/4B3 w - - 0 42", "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -", "r3k2r/pp3pp1/PN1pr1p1/4p1P1/4P3/3P4/P1P2PP1/R3K2R w KQkq - 4 4", "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8", "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -", "r3k1nr/p2pp1pp/b1n1P1P1/1BK1Pp1q/8/8/2PP1PPP/6N1 w kq - 0 1", "3k4/3p4/8/K1P4r/8/8/8/8 b - - 0 1", "8/8/1k6/2b5/2pP4/8/5K2/8 b - d3 0 1", "5k2/8/8/8/8/8/8/4K2R w K - 0 1", "3k4/8/8/8/8/8/8/R3K3 w Q - 0 1", "r3k2r/1b4bq/8/8/8/8/7B/R3K2R w KQkq - 0 1", "r3k2r/8/3Q4/8/8/5q2/8/R3K2R b KQkq - 0 1", "2K2r2/4P3/8/8/8/8/8/3k4 w - - 0 1", "8/8/1P2K3/8/2n5/1q6/8/5k2 b - - 0 1", "4k3/1P6/8/8/8/8/K7/8 w - - 0 1", "8/P1k5/K7/8/8/8/8/8 w - - 0 1", "K1k5/8/P7/8/8/8/8/8 w - - 0 1", "8/k1P5/8/1K6/8/8/8/8 w - - 0 1", "8/8/2k5/5q2/5n2/8/5K2/8 b - - 0 1", "r1bq2r1/1pppkppp/1b3n2/pP1PP3/2n5/2P5/P3QPPP/RNB1K2R w KQ a6 0 12", "r3k2r/pppqbppp/3p1n1B/1N2p3/1nB1P3/3P3b/PPPQNPPP/R3K2R w KQkq - 11 10", "4k2r/1pp1n2p/6N1/1K1P2r1/4P3/P5P1/1Pp4P/R7 w k - 0 6", "1Bb3BN/R2Pk2r/1Q5B/4q2R/2bN4/4Q1BK/1p6/1bq1R1rb w - - 0 1", "n1n5/PPPk4/8/8/8/8/4Kppp/5N1N b - - 0 1", "8/PPPk4/8/8/8/8/4Kppp/8 b - - 0 1", "8/2k1p3/3pP3/3P2K1/8/8/8/8 w - - 0 1", "3r4/2p1p3/8/1P1P1P2/3K4/5k2/8/8 b - - 0 1", "8/1p4p1/8/q1PK1P1r/3p1k2/8/4P3/4Q3 b - - 0 1" };
|
||||||
|
Console.WriteLine("Running perft...");
|
||||||
|
|
||||||
|
var board = new Chess.Board();
|
||||||
|
long timeTotal = 0;
|
||||||
|
for (int i = 0; i < fens.Length; i++)
|
||||||
|
{
|
||||||
|
board.LoadPosition(fens[i]);
|
||||||
|
boardAPI = new(board);
|
||||||
|
System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
|
||||||
|
ulong result = SearchStackalloc(depths[i]);
|
||||||
|
if (result != expectedNodes[i])
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sw.Stop();
|
||||||
|
timeTotal += sw.ElapsedMilliseconds;
|
||||||
|
Console.WriteLine(i + " successful: " + sw.ElapsedMilliseconds + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Console.WriteLine("Time Total: " + timeTotal + " ms.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static void TestMoveCreate()
|
static void TestMoveCreate()
|
||||||
{
|
{
|
||||||
Console.WriteLine("Testing move create");
|
Console.WriteLine("Testing move create");
|
||||||
|
@ -298,6 +332,30 @@ namespace ChessChallenge.Application
|
||||||
return numLocalNodes;
|
return numLocalNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ulong SearchStackalloc(int depth)
|
||||||
|
{
|
||||||
|
Span<API.Move> moves = stackalloc API.Move[128];
|
||||||
|
boardAPI.GetLegalMovesNonAlloc(ref moves);
|
||||||
|
|
||||||
|
if (depth == 1)
|
||||||
|
{
|
||||||
|
return (ulong)moves.Length;
|
||||||
|
}
|
||||||
|
ulong numLocalNodes = 0;
|
||||||
|
for (int i = 0; i < moves.Length; i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
boardAPI.MakeMove(moves[i]);
|
||||||
|
|
||||||
|
ulong numNodesFromThisPosition = SearchStackalloc(depth - 1);
|
||||||
|
numLocalNodes += numNodesFromThisPosition;
|
||||||
|
|
||||||
|
boardAPI.UndoMove(moves[i]);
|
||||||
|
|
||||||
|
}
|
||||||
|
return numLocalNodes;
|
||||||
|
}
|
||||||
|
|
||||||
static void Assert(bool condition, string msg)
|
static void Assert(bool condition, string msg)
|
||||||
{
|
{
|
||||||
if (!condition)
|
if (!condition)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue