Removed memory allocations from IsInCheckmate and IsInStalemate
This commit is contained in:
parent
a6c102ba30
commit
30644066b1
3 changed files with 154 additions and 38 deletions
|
@ -20,6 +20,8 @@ namespace ChessChallenge.API
|
||||||
bool hasCachedMoves;
|
bool hasCachedMoves;
|
||||||
Move[] cachedLegalCaptureMoves;
|
Move[] cachedLegalCaptureMoves;
|
||||||
bool hasCachedCaptureMoves;
|
bool hasCachedCaptureMoves;
|
||||||
|
bool hasCachedMoveCount;
|
||||||
|
int cachedMoveCount;
|
||||||
int depth;
|
int depth;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -154,6 +156,8 @@ namespace ChessChallenge.API
|
||||||
moveGen.GenerateMoves(ref moveSpan, board, includeQuietMoves: true);
|
moveGen.GenerateMoves(ref moveSpan, board, includeQuietMoves: true);
|
||||||
cachedLegalMoves = moveSpan.ToArray();
|
cachedLegalMoves = moveSpan.ToArray();
|
||||||
hasCachedMoves = true;
|
hasCachedMoves = true;
|
||||||
|
hasCachedMoveCount = true;
|
||||||
|
cachedMoveCount = moveSpan.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cachedLegalMoves;
|
return cachedLegalMoves;
|
||||||
|
@ -169,6 +173,8 @@ namespace ChessChallenge.API
|
||||||
{
|
{
|
||||||
bool includeQuietMoves = !capturesOnly;
|
bool includeQuietMoves = !capturesOnly;
|
||||||
moveGen.GenerateMoves(ref moveList, board, includeQuietMoves);
|
moveGen.GenerateMoves(ref moveList, board, includeQuietMoves);
|
||||||
|
hasCachedMoveCount = true;
|
||||||
|
cachedMoveCount = moveList.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -187,12 +193,12 @@ namespace ChessChallenge.API
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test if the player to move is in check in the current position.
|
/// Test if the player to move is in check in the current position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsInCheck() => board.IsInCheck();
|
public bool IsInCheck() => moveGen.IsInitialized ? moveGen.InCheck() : board.IsInCheck();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test if the current position is checkmate
|
/// Test if the current position is checkmate
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsInCheckmate() => IsInCheck() && GetLegalMoves().Length == 0;
|
public bool IsInCheckmate() => IsInCheck() && HasZeroLegalMoves();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test if the current position is a draw due stalemate, repetition, insufficient material, or 50-move rule.
|
/// Test if the current position is a draw due stalemate, repetition, insufficient material, or 50-move rule.
|
||||||
|
@ -202,11 +208,18 @@ namespace ChessChallenge.API
|
||||||
public bool IsDraw()
|
public bool IsDraw()
|
||||||
{
|
{
|
||||||
return IsFiftyMoveDraw() || IsInsufficientMaterial() || IsInStalemate() || IsRepeatedPosition();
|
return IsFiftyMoveDraw() || IsInsufficientMaterial() || IsInStalemate() || IsRepeatedPosition();
|
||||||
|
|
||||||
bool IsInStalemate() => !IsInCheck() && GetLegalMoves().Length == 0;
|
|
||||||
bool IsFiftyMoveDraw() => board.currentGameState.fiftyMoveCounter >= 100;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test if the current position is a draw due to stalemate
|
||||||
|
/// </summary>
|
||||||
|
public bool IsInStalemate() => !IsInCheck() && HasZeroLegalMoves();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test if the current position is a draw due to the fifty move rule
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFiftyMoveDraw() => board.currentGameState.fiftyMoveCounter >= 100;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test if the current position has occurred at least once before on the board.
|
/// Test if the current position has occurred at least once before on the board.
|
||||||
/// This includes both positions in the actual game, and positions reached by
|
/// This includes both positions in the actual game, and positions reached by
|
||||||
|
@ -362,6 +375,16 @@ namespace ChessChallenge.API
|
||||||
moveGen.NotifyPositionChanged();
|
moveGen.NotifyPositionChanged();
|
||||||
hasCachedMoves = false;
|
hasCachedMoves = false;
|
||||||
hasCachedCaptureMoves = false;
|
hasCachedCaptureMoves = false;
|
||||||
|
hasCachedMoveCount = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasZeroLegalMoves()
|
||||||
|
{
|
||||||
|
if (hasCachedMoveCount)
|
||||||
|
{
|
||||||
|
return cachedMoveCount == 0;
|
||||||
|
}
|
||||||
|
return moveGen.NoLegalMovesInPosition(board);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,8 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
board = new Board();
|
board = new Board();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsInitialized => hasInitializedCurrentPosition;
|
||||||
|
|
||||||
// Movegen needs to know when position has changed to allow for some caching optims in api
|
// Movegen needs to know when position has changed to allow for some caching optims in api
|
||||||
public void NotifyPositionChanged()
|
public void NotifyPositionChanged()
|
||||||
{
|
{
|
||||||
|
@ -64,6 +66,27 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
return opponentAttackMap;
|
return opponentAttackMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool NoLegalMovesInPosition(Board board)
|
||||||
|
{
|
||||||
|
Span<API.Move> moves = stackalloc API.Move[128];
|
||||||
|
generateNonCapture = true;
|
||||||
|
Init(board);
|
||||||
|
GenerateKingMoves(moves);
|
||||||
|
if (currMoveIndex > 0) { return false; }
|
||||||
|
|
||||||
|
if (!inDoubleCheck)
|
||||||
|
{
|
||||||
|
GenerateKnightMoves(moves);
|
||||||
|
if (currMoveIndex > 0) { return false; }
|
||||||
|
GeneratePawnMoves(moves);
|
||||||
|
if (currMoveIndex > 0) { return false; }
|
||||||
|
GenerateSlidingMoves(moves, true);
|
||||||
|
if (currMoveIndex > 0) { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// 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 void GenerateMoves(ref Span<API.Move> moves, Board board, bool includeQuietMoves = true)
|
public void GenerateMoves(ref Span<API.Move> moves, Board board, bool includeQuietMoves = true)
|
||||||
|
@ -187,7 +210,7 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenerateSlidingMoves(Span<API.Move> moves)
|
void GenerateSlidingMoves(Span<API.Move> moves, bool exitEarly = false)
|
||||||
{
|
{
|
||||||
// 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;
|
||||||
|
@ -218,6 +241,10 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
{
|
{
|
||||||
int targetSquare = BitBoardUtility.PopLSB(ref moveSquares);
|
int targetSquare = BitBoardUtility.PopLSB(ref moveSquares);
|
||||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, 0);
|
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, 0);
|
||||||
|
if (exitEarly)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,6 +264,10 @@ namespace ChessChallenge.Application.APIHelpers
|
||||||
{
|
{
|
||||||
int targetSquare = BitBoardUtility.PopLSB(ref moveSquares);
|
int targetSquare = BitBoardUtility.PopLSB(ref moveSquares);
|
||||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, 0);
|
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, 0);
|
||||||
|
if (exitEarly)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,12 @@ namespace ChessChallenge.Application
|
||||||
{
|
{
|
||||||
anyFailed = false;
|
anyFailed = false;
|
||||||
|
|
||||||
|
new SearchTest().Run(false);
|
||||||
|
new SearchTest().Run(true);
|
||||||
new SearchTest2().Run();
|
new SearchTest2().Run();
|
||||||
|
new SearchTest3().Run();
|
||||||
|
|
||||||
RepetitionTest();
|
RepetitionTest();
|
||||||
|
|
||||||
DrawTest();
|
DrawTest();
|
||||||
MoveGenTest();
|
MoveGenTest();
|
||||||
PieceListTest();
|
PieceListTest();
|
||||||
|
@ -29,9 +31,6 @@ namespace ChessChallenge.Application
|
||||||
MiscTest();
|
MiscTest();
|
||||||
TestBitboards();
|
TestBitboards();
|
||||||
TestMoveCreate();
|
TestMoveCreate();
|
||||||
new SearchTest().Run(false);
|
|
||||||
new SearchTest().Run(true);
|
|
||||||
|
|
||||||
|
|
||||||
if (runPerft)
|
if (runPerft)
|
||||||
{
|
{
|
||||||
|
@ -654,6 +653,69 @@ namespace ChessChallenge.Application
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SearchTest3
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
API.Board board;
|
||||||
|
int numCaptures;
|
||||||
|
int numChecks;
|
||||||
|
int numMates;
|
||||||
|
int nodes;
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Running misc search test");
|
||||||
|
Chess.Board b = new();
|
||||||
|
b.LoadPosition("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - ");
|
||||||
|
board = new API.Board(b);
|
||||||
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
Search(4, API.Move.NullMove);
|
||||||
|
sw.Stop();
|
||||||
|
Console.WriteLine("Test3 time: " + sw.ElapsedMilliseconds + " ms");
|
||||||
|
bool passed = nodes == 4085603 && numCaptures == 757163 && numChecks == 25523 && numMates == 43;
|
||||||
|
Assert(passed, "Test3 failed");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Search(int depth, API.Move prevMove)
|
||||||
|
{
|
||||||
|
|
||||||
|
Span<API.Move> moveSpan = stackalloc API.Move[256];
|
||||||
|
board.GetLegalMovesNonAlloc(ref moveSpan);
|
||||||
|
|
||||||
|
if (depth == 0)
|
||||||
|
{
|
||||||
|
if (prevMove.IsCapture)
|
||||||
|
{
|
||||||
|
numCaptures++;
|
||||||
|
}
|
||||||
|
if (board.IsInCheck())
|
||||||
|
{
|
||||||
|
numChecks++;
|
||||||
|
}
|
||||||
|
if (board.IsInCheckmate())
|
||||||
|
{
|
||||||
|
numMates++;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var move in moveSpan)
|
||||||
|
{
|
||||||
|
board.MakeMove(move);
|
||||||
|
Search(depth - 1, move);
|
||||||
|
board.UndoMove(move);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public class SearchTest2
|
public class SearchTest2
|
||||||
{
|
{
|
||||||
API.Board board;
|
API.Board board;
|
||||||
|
@ -778,7 +840,7 @@ namespace ChessChallenge.Application
|
||||||
this.useStackalloc = useStackalloc;
|
this.useStackalloc = useStackalloc;
|
||||||
Console.WriteLine("Running misc search test | stackalloc = " + useStackalloc);
|
Console.WriteLine("Running misc search test | stackalloc = " + useStackalloc);
|
||||||
Chess.Board b = new();
|
Chess.Board b = new();
|
||||||
b.LoadPosition("1r4k1/2P1r1pp/3p4/4n1Q1/1p6/2PB3P/P3pPP1/2B3K1 w - - 7 16");
|
b.LoadPosition("2rqk2r/5p1p/p2p1n2/1pPPn3/8/3B1QP1/PR1K1P1p/2B1R3 w k b6 0 28");
|
||||||
board = new API.Board(b);
|
board = new API.Board(b);
|
||||||
Search(4);
|
Search(4);
|
||||||
|
|
||||||
|
@ -791,19 +853,19 @@ namespace ChessChallenge.Application
|
||||||
|
|
||||||
numCalls++;
|
numCalls++;
|
||||||
var square = new Square(numCalls % 64);
|
var square = new Square(numCalls % 64);
|
||||||
miscSumTest += (int)boardAPI.GetPiece(square).PieceType;
|
miscSumTest += (int)board.GetPiece(square).PieceType;
|
||||||
miscSumTest += boardAPI.GetAllPieceLists()[numCalls % 12].Count;
|
miscSumTest += board.GetAllPieceLists()[numCalls % 12].Count;
|
||||||
miscSumTest += (long)(boardAPI.ZobristKey % 100);
|
miscSumTest += (long)(board.ZobristKey % 100);
|
||||||
miscSumTest += boardAPI.IsInCheckmate() ? 1 : 0;
|
miscSumTest += board.IsInCheckmate() ? 1 : 0;
|
||||||
|
|
||||||
if (numCalls % 6 == 0)
|
if (numCalls % 6 == 0)
|
||||||
{
|
{
|
||||||
miscSumTest += boardAPI.IsInCheck() ? 1 : 0;
|
miscSumTest += board.IsInCheck() ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numCalls % 18 == 0)
|
if (numCalls % 18 == 0)
|
||||||
{
|
{
|
||||||
miscSumTest += boardAPI.SquareIsAttackedByOpponent(square) ? 1 : 0;
|
miscSumTest += board.SquareIsAttackedByOpponent(square) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plyRemaining == 0)
|
if (plyRemaining == 0)
|
||||||
|
@ -814,10 +876,10 @@ namespace ChessChallenge.Application
|
||||||
|
|
||||||
if (numCalls % 3 == 0 && plyRemaining > 2)
|
if (numCalls % 3 == 0 && plyRemaining > 2)
|
||||||
{
|
{
|
||||||
if (boardAPI.TrySkipTurn())
|
if (board.TrySkipTurn())
|
||||||
{
|
{
|
||||||
Search(plyRemaining - 2);
|
Search(plyRemaining - 2);
|
||||||
boardAPI.UndoSkipTurn();
|
board.UndoSkipTurn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -826,19 +888,19 @@ namespace ChessChallenge.Application
|
||||||
if (useStackalloc)
|
if (useStackalloc)
|
||||||
{
|
{
|
||||||
Span<API.Move> moveSpan = stackalloc API.Move[256];
|
Span<API.Move> moveSpan = stackalloc API.Move[256];
|
||||||
boardAPI.GetLegalMovesNonAlloc(ref moveSpan);
|
board.GetLegalMovesNonAlloc(ref moveSpan);
|
||||||
moves = moveSpan.ToArray(); // (don't actually care about allocations here, just testing the func)
|
moves = moveSpan.ToArray(); // (don't actually care about allocations here, just testing the func)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
moves = boardAPI.GetLegalMoves();
|
moves = board.GetLegalMoves();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var move in moves)
|
foreach (var move in moves)
|
||||||
{
|
{
|
||||||
boardAPI.MakeMove(move);
|
board.MakeMove(move);
|
||||||
Search(plyRemaining - 1);
|
Search(plyRemaining - 1);
|
||||||
boardAPI.UndoMove(move);
|
board.UndoMove(move);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue