public static long Qdivide(ChessBoard chessBoard, int depth) { ThreadData.StartPly(); MoveGenerator.GenerateMoves(ThreadData, chessBoard); long counter = 0; while (ThreadData.HasNext()) { var move = ThreadData.Next(); if (depth == 1) { Console.WriteLine(new MoveWrapper(move) + ": " + 1); counter++; continue; } chessBoard.DoMove(move); var divideCounter = Qperft(chessBoard, depth - 1); counter += divideCounter; chessBoard.UndoMove(move); Console.WriteLine(new MoveWrapper(move) + ": " + divideCounter); } return(counter); }
public static void PerftDivide(Board b, int depth) { var startTime = DateTime.Now; var moves = MoveGenerator.GenerateMoves(b, true); int total = 0; int i = 0; int moveCount = 0; foreach (var m in moves) { if (!b.MakeMove(m)) { continue; } i++; var nodes = Perft(b, depth - 1); moveCount++; total += nodes; b.UnMakeMove(); Console.WriteLine(String.Format("{0}: {1}", m.ToAlegbraicNotation(b), nodes)); } Console.WriteLine("Moves: {0}", moveCount); Console.WriteLine("Total: {0}", total); var endTime = DateTime.Now; Console.WriteLine("Completed in {0}ms", (endTime - startTime).TotalMilliseconds); }
private static int perft(ChessBoard cb, ThreadData threadData, int depth) { threadData.StartPly(); MoveGenerator.GenerateMoves(threadData, cb); MoveGenerator.GenerateAttacks(threadData, cb); if (depth == 0) { threadData.EndPly(); return(1); } var counter = 0; while (threadData.HasNext()) { var move = threadData.Next(); if (!cb.IsLegal(move)) { continue; } cb.DoMove(move); counter += perft(cb, threadData, depth - 1); cb.UndoMove(move); } threadData.EndPly(); return(counter); }
public void FoolsCheckMateTest() { // generate moves var moves = new List <Move>(4) { new Move(EPieces.WhitePawn, ESquare.f2, ESquare.f3), new Move(EPieces.BlackPawn, ESquare.e7, ESquare.e5, EMoveType.Doublepush, EPieces.NoPiece), new Move(EPieces.WhitePawn, ESquare.g2, ESquare.g4, EMoveType.Doublepush, EPieces.NoPiece), new Move(EPieces.BlackQueen, ESquare.d8, ESquare.h4) }; // construct game and start a new game var game = new Game(); game.NewGame(); // make the moves necessary to create a mate foreach (var move in moves) { Assert.True(game.MakeMove(move)); } // verify in check is actually true Assert.True(game.Position.InCheck); var mg = new MoveGenerator(game.Position); // generate all legal moves mg.GenerateMoves(true); // verify that no legal moves actually exists. Assert.Empty(mg.Moves); }
private static char GetCheckChar(this IPosition pos) { var mg = new MoveGenerator(pos); mg.GenerateMoves(); return(mg.Moves.Count > 0 ? '+' : '#'); }
public override void DoTurn() { var cells = GameBoard.Instance.GetCells(); cells.RemoveAll(c => c.piece == null); cells.RemoveAll(c => c.piece.color != color); while (cells.Count > 0) { var cell = cells.RandomItem(); cells.Remove(cell); List <Move> moves = MoveGenerator.GenerateMoves(cell); while (moves.Count > 0) { var move = moves.RandomItem(); moves.Remove(move); turn = new Turn(cell, GameBoard.Instance.GetCell(move.targetPosition)) { extraCaptures = move.extraCaptures }; OnTurnCompleted?.Invoke(turn); return; } } // There are no possible moves for any of the pieces }
public void MoveGeneratorDoesNotChangeHashKey() { var b = new Board(); var expected = TranspositionTable.GetHashKeyForPosition(b); MoveGenerator.GenerateMoves(b); Assert.Equal(expected, b.HashKey); }
public void PerfTLevel1() { var testBoard = new Board(); var result = MoveGenerator.GenerateMoves(testBoard); Assert.Equal(20, result.Count); }
public void FirstMove() { Board board = new Board(new Player[10, 10]); MoveGenerator moveGenerator = new MoveGenerator(board); Assert.Collection(moveGenerator.GenerateMoves(), p => { Assert.Equal(board.FlattenedIndex((5, 5)), p); });
public static (bool, Move) Locate(this Move move, IPosition pos) { // force position to contain the latest moves for the position moves to be searched in var mg = new MoveGenerator(pos); mg.GenerateMoves(); var element = mg.Moves.FirstOrDefault(x => x.GetFromSquare() == move.GetFromSquare() && x.GetToSquare() == move.GetToSquare()); return(element == null ? (false, EmptyMove) : (true, element)); }
//TJ: setting the right flags after parsing the move from a string without having a board instance as context isn't possible and so we add the flags here public Move WithFlags(Move move) { foreach (var candidate in _moveGen.GenerateMoves(_board, true)) { if (candidate.StartSquare == move.StartSquare && candidate.TargetSquare == move.TargetSquare && candidate.PromotionPieceType == move.PromotionPieceType) { return(candidate); } } //not found? well... that's... kinda bad throw new Exception(move.Name + " not valid in the given position!"); }
public void HashKeyRestoredAfterUnMakeMove() { var testBoard = new Board(); var moves = MoveGenerator.GenerateMoves(testBoard); foreach (var m in moves) { var expected = testBoard.HashKey; if (!testBoard.MakeMove(m)) { continue; } testBoard.UnMakeMove(); Assert.Equal(expected, testBoard.HashKey); } }
private void TryMakeMove(Coord startSquare, Coord targetSquare) { var startIndex = BoardRepresentation.IndexFromCoord(startSquare); var targetIndex = BoardRepresentation.IndexFromCoord(targetSquare); var moveIsLegal = false; var chosenMove = new Move(); var moveGenerator = new MoveGenerator(); var wantsKnightPromotion = Input.GetKey(KeyCode.LeftAlt); var legalMoves = moveGenerator.GenerateMoves(board); for (var i = 0; i < legalMoves.Count; i++) { var legalMove = legalMoves[i]; if (legalMove.StartSquare == startIndex && legalMove.TargetSquare == targetIndex) { if (legalMove.IsPromotion) { if (legalMove.MoveFlag == Move.Flag.PromoteToQueen && wantsKnightPromotion) { continue; } if (legalMove.MoveFlag != Move.Flag.PromoteToQueen && !wantsKnightPromotion) { continue; } } moveIsLegal = true; chosenMove = legalMove; // Debug.Log (legalMove.PromotionPieceType); break; } } if (moveIsLegal) { ChoseMove(chosenMove); currentState = InputState.None; } else { CancelPieceSelection(); } }
Result GetGameState() { MoveGenerator moveGenerator = new MoveGenerator(); var moves = moveGenerator.GenerateMoves(board); // Look for mate/stalemate if (moves.Count == 0) { if (moveGenerator.InCheck()) { return((board.WhiteToMove) ? Result.WhiteIsMated : Result.BlackIsMated); } return(Result.Stalemate); } // Fifty move rule if (board.fiftyMoveCounter >= 100) { return(Result.FiftyMoveRule); } // Threefold repetition int repCount = board.RepetitionPositionHistory.Count((x => x == board.ZobristKey)); if (repCount == 3) { return(Result.Repetition); } // Look for insufficient material (not all cases implemented yet) int numPawns = board.pawns[Board.WhiteIndex].Count + board.pawns[Board.BlackIndex].Count; int numRooks = board.rooks[Board.WhiteIndex].Count + board.rooks[Board.BlackIndex].Count; int numQueens = board.queens[Board.WhiteIndex].Count + board.queens[Board.BlackIndex].Count; int numKnights = board.knights[Board.WhiteIndex].Count + board.knights[Board.BlackIndex].Count; int numBishops = board.bishops[Board.WhiteIndex].Count + board.bishops[Board.BlackIndex].Count; if (numPawns + numRooks + numQueens == 0) { if (numKnights == 1 || numBishops == 1) { return(Result.InsufficientMaterial); } } return(Result.Playing); }
public void HighlightLegalMoves(Board board, Coord fromSquare) { if (showLegalMoves) { var moves = moveGenerator.GenerateMoves(board); for (var i = 0; i < moves.Count; i++) { var move = moves[i]; if (move.StartSquare == BoardRepresentation.IndexFromCoord(fromSquare)) { var coord = BoardRepresentation.CoordFromIndex(move.TargetSquare); SetSquareColour(coord, boardTheme.lightSquares.legal, boardTheme.darkSquares.legal); } } } }
// zasady WDZ Result GetGameState() { MoveGenerator moveGenerator = new MoveGenerator(); var moves = moveGenerator.GenerateMoves(board); // Mat if (moves.Count == 0) { if (moveGenerator.InCheck()) { return((board.WhiteToMove) ? Result.WhiteIsMated : Result.BlackIsMated); } return(Result.Stalemate); } // Zasada 50 if (board.fiftyMoveCounter >= 100) { return(Result.FiftyMoveRule); } // Zasada Powtórzeń int repCount = board.RepetitionPositionHistory.Count((x => x == board.ZobristKey)); if (repCount == 3) { return(Result.Repetition); } int numPawns = board.pawns[Board.WhiteIndex].Count + board.pawns[Board.BlackIndex].Count; int numRooks = board.rooks[Board.WhiteIndex].Count + board.rooks[Board.BlackIndex].Count; int numQueens = board.queens[Board.WhiteIndex].Count + board.queens[Board.BlackIndex].Count; int numKnights = board.knights[Board.WhiteIndex].Count + board.knights[Board.BlackIndex].Count; int numBishops = board.bishops[Board.WhiteIndex].Count + board.bishops[Board.BlackIndex].Count; if (numPawns + numRooks + numQueens == 0) { if (numKnights == 1 || numBishops == 1) { return(Result.InsufficientMaterial); } } return(Result.Playing); }
public void GetHashKeyAgreesWithMakeMove1() { var testBoard = new Board(); var moves = MoveGenerator.GenerateMoves(testBoard); foreach (var m in moves) { var expected = testBoard.HashKey; if (!testBoard.MakeMove(m)) { continue; } testBoard.UnMakeMove(); Assert.Equal(expected, testBoard.HashKey); } var result = testBoard.HashKey; }
private void AttemptMove(Cell cell) { // Generate a set of all valid moves for the starting piece var moves = MoveGenerator.GenerateMoves(turn.start); var validMove = moves.Where(m => m.targetPosition == cell.position).FirstOrDefault(); // If the move we want to take is one of the valid moves, we can continue if (validMove != null) { // Moving to an empty space is fine. Capturing is only allowed if the piece is of opposite color to you. if (cell.piece == null || (cell.piece != null && cell.piece.color != color)) { turn.end = cell; OnTurnCompleted?.Invoke(turn); turnManager.selectedCell = null; } } }
public static int Divide(ChessBoard cb, ThreadData threadData, int depth) { threadData.StartPly(); MoveGenerator.GenerateMoves(threadData, cb); MoveGenerator.GenerateAttacks(threadData, cb); var counter = 0; while (threadData.HasNext()) { var move = threadData.Next(); cb.DoMove(move); var divideCounter = perft(cb, threadData, depth - 1); counter += divideCounter; cb.UndoMove(move); Console.WriteLine(new MoveWrapper(move) + ": " + divideCounter); } return(counter); }
public override void Render(Batcher batcher, Camera camera) { if (GameBoard.Instance.Layout == null) { return; } if (previousSelectedCell != TurnManager.Instance.selectedCell) { previousSelectedCell = TurnManager.Instance.selectedCell; if (previousSelectedCell != null && previousSelectedCell.piece != null && previousSelectedCell.piece.color == TurnManager.Instance.CurrentPlayer.color) { possibleMoves = MoveGenerator.GenerateMoves(previousSelectedCell); } } DrawMouseOverlay(batcher, camera); DrawPossibleMoves(batcher); }
public static void PerftDivideParallel(Board b, int depth) { var startTime = DateTime.Now; var moves = MoveGenerator.GenerateMoves(b); var legalMoves = new List <PerfTMove>(); //before we parallelize, let's remove any illegal moves foreach (var m in moves) { if (!b.MakeMove(m)) { continue; } legalMoves.Add(new PerfTMove { Move = m, Nodes = 0 }); b.UnMakeMove(); } Parallel.ForEach(legalMoves, (m) => { Board newBoard = new Board(b); if (!newBoard.MakeMove(m.Move)) { return; } var nodes = Perft(newBoard, depth - 1); m.Nodes += nodes; newBoard.UnMakeMove(); Console.WriteLine(String.Format("{0}: {1}", m.Move.ToAlegbraicNotation(newBoard), nodes)); }); Console.WriteLine("Moves: {0}", legalMoves.Count); Console.WriteLine("Total: {0}", legalMoves.Sum(x => x.Nodes)); var endTime = DateTime.Now; Console.WriteLine("Completed in {0}ms", (endTime - startTime).TotalMilliseconds); }
static int Perft(Board b, int depth) { int nodes = 0; if (depth == 0) { return(1); } var moves = MoveGenerator.GenerateMoves(b); var nMoves = moves.Count; foreach (var m in moves) { if (b.MakeMove(m)) { nodes += Perft(b, depth - 1); b.UnMakeMove(); } } return(nodes); }
private static long Qperft(ChessBoard chessBoard, int depth) { ThreadData.StartPly(); MoveGenerator.GenerateMoves(ThreadData, chessBoard); MoveGenerator.GenerateAttacks(ThreadData, chessBoard); long counter = 0; if (depth == 1) { while (ThreadData.HasNext()) { if (chessBoard.IsLegal(ThreadData.Next())) { counter++; } } ThreadData.EndPly(); return(counter); } while (ThreadData.HasNext()) { var move = ThreadData.Next(); if (!chessBoard.IsLegal(move)) { continue; } chessBoard.DoMove(move); counter += Qperft(chessBoard, depth - 1); chessBoard.UndoMove(move); } ThreadData.EndPly(); return(counter); }
public static Dictionary <string, double> LoadFens(string fileName, bool containsResult, bool includingCheck) { Console.WriteLine("Loading " + fileName); var fens = new Dictionary <string, double>(); var checkCount = 0; var checkmate = 0; var stalemate = 0; try { foreach (var line in File.ReadLines(fileName)) { double score = 0; string fenString; if (containsResult) { var scoreString = GetScoreStringFromLine(line); fenString = GetFenStringFromLine(line); if (scoreString.Equals("\"1/2-1/2\";")) { score = 0.5; } else if (scoreString.Equals("\"1-0\";")) { score = 1; } else if (scoreString.Equals("\"0-1\";")) { score = 0; } else { throw new Exception("Unknown result: " + scoreString); } } else { fenString = line; } ChessBoardUtil.SetFen(fenString, Cb); if (Cb.CheckingPieces == 0) { ThreadData.StartPly(); MoveGenerator.GenerateAttacks(ThreadData, Cb); MoveGenerator.GenerateMoves(ThreadData, Cb); if (ThreadData.HasNext()) { fens.Add(fenString, score); } else { stalemate++; } ThreadData.EndPly(); } else { checkCount++; if (!includingCheck) { continue; } ThreadData.StartPly(); MoveGenerator.GenerateAttacks(ThreadData, Cb); MoveGenerator.GenerateMoves(ThreadData, Cb); if (ThreadData.HasNext()) { fens.Add(fenString, score); } else { checkmate++; } ThreadData.EndPly(); } //line = br.readLine(); } } catch (IOException e) { throw new Exception("", e); } Console.WriteLine("In check : " + checkCount); Console.WriteLine("Checkmate : " + checkmate); Console.WriteLine("Stalemate : " + stalemate); return(fens); }
public int SearchRoot(int alpha, int beta, int depth) { PvLength[0] = 0; if (MyGameState.GameBoard.History.IsGameDrawn(MyBoard.HashKey)) { return(CurrentDrawScore); } if (RootMoves == null) { RootMoves = MoveGenerator .GenerateMoves(MyBoard) .OrderByDescending(m => OrderRootMove(m)) .ToList(); } int score; Move bestMove = null, lastMove = null; bool inCheck = MyBoard.InCheck(MyBoard.SideToMove); foreach (var m in RootMoves) { if (!Make(m)) { continue; } if (depth > 0) { if (bestMove == null) { score = -Search(-beta, -alpha, depth - 1); } else { score = -Search(-alpha - 1, -alpha, depth - 1); if (score > alpha) { score = -Search(-beta, -alpha, depth - 1); } } } else { score = -Quiesce(-beta, -alpha); } TakeBack(); if (MyGameState.TimeUp) { return(alpha); } if (score >= beta) { //we want to try this move first next time NewRootMove(m); PvLength[0] = 1; PrincipalVariation[0, 0] = m; return(score); } if (score > alpha) { alpha = score; bestMove = m; // PV Node //update the PV UpdatePv(bestMove); TranspositionTable.Instance.Store(MyBoard.HashKey, m, depth, alpha, EntryType.PV); } lastMove = m; } //check for mate if (lastMove == null) { //we can't make a move. check for mate or stalemate. if (inCheck) { return(-10000 + Ply); } else { return(CurrentDrawScore); } } if (bestMove != null) { if (bestMove != RootMoves[0]) { NewRootMove(bestMove); } } return(alpha); }
public int Search(int alpha, int beta, int depth) { Metrics.Nodes++; PvLength[Ply] = Ply; var inCheck = MyBoard.InCheck(MyBoard.SideToMove); int ext = inCheck ? 1 : 0; if (MyGameState.GameBoard.History.IsPositionDrawn(MyBoard.HashKey)) { return(CurrentDrawScore); } if (Ply >= MAX_DEPTH) { TakeBack(); return(Evaluator.Evaluate(MyBoard, -10000, 10000)); } if ((Metrics.Nodes & 65535) == 65535) { Interrupt(); if (MyGameState.TimeUp) { return(alpha); } } Move bestMove = null; if (depth + ext <= 0) { return(Quiesce(alpha, beta)); } //first let's look for a transposition var entry = TranspositionTable.Instance.Read(MyBoard.HashKey); if (entry != null) { //we have a hit from the TTable if (entry.Depth >= depth) { if (entry.Type == (byte)EntryType.CUT && entry.Score >= beta) { return(beta); } else if (entry.Type == (byte)EntryType.ALL && entry.Score <= alpha) { return(alpha); } else if (entry.Type == (byte)EntryType.PV) { return(entry.Score); } } } int mateThreat = 0; var myPieceCount = MyBoard.PieceCount(MyBoard.SideToMove); //next try a Null Move if (Ply > 0 && depth > 1 && alpha == beta - 1 && !inCheck && !MyBoard.History[Ply - 1].IsNullMove && myPieceCount > 0 && (myPieceCount > 2 || depth < 7)) { Metrics.NullMoveTries++; MakeNullMove(); var nullReductionDepth = depth > 6 ? 4 : 3; int nmScore; if (depth - nullReductionDepth - 1 > 0) { nmScore = -Search(-beta, 1 - beta, depth - nullReductionDepth - 1); } else { nmScore = -Quiesce(-beta, 1 - beta); } UnmakeNullMove(); if (MyGameState.TimeUp) { return(alpha); } if (nmScore >= beta) { Metrics.NullMoveFailHigh++; TranspositionTable.Instance.Store(MyBoard.HashKey, null, depth, nmScore, EntryType.CUT); return(nmScore); } } var moves = MoveGenerator .GenerateMoves(MyBoard) .OrderByDescending((m) => OrderMove(m, entry)); Move lastMove = null; int lmr = 0, nonCaptureMoves = 0, movesSearched = 0; foreach (var m in moves) { bool fprune = false; int score; if (!Make(m)) { continue; } var justGaveCheck = MyBoard.InCheck(MyBoard.SideToMove); var capture = ((m.Bits & (byte)MoveBits.Capture) != 0); if (!capture && (entry == null || entry.MoveValue != m.Value)) // don't count the hash move as a non-capture { ++nonCaptureMoves; // while it might not be a capture, the point } // here is to start counting after generated captures var passedpawnpush = (m.Bits & (byte)MoveBits.Pawn) > 0 && (Evaluator.PassedPawnMask[MyBoard.SideToMove ^ 1, m.To] & MyBoard.Pawns[MyBoard.SideToMove]) == 0; //LATE MOVE REDUCTIONS if (ext == 0 && //no extension !inCheck && //i am not in check at this node !justGaveCheck && //the move we just made does not check the opponent mateThreat == 0 && //no mate threat detected !passedpawnpush && //do not reduce/prune passed pawn pushes nonCaptureMoves > 0) //start reducing after the winning captures { if (depth > 2) { lmr = movesSearched > 2 ? 2 : 1; // start reducing depth if we aren't finding anything useful } //FUTILITY PRUNING else if (depth < 3 && alpha > -9900 && beta < 9900) { if (depth == 2 && -Evaluator.EvaluateMaterial(MyBoard) + Evaluator.PieceValues[(int)Piece.Rook] <= alpha) { Metrics.EFPrune++; fprune = true; } else if (depth == 1 && -Evaluator.EvaluateMaterial(MyBoard) + Evaluator.PieceValues[(int)Piece.Knight] <= alpha) { Metrics.FPrune++; fprune = true; } } } if (!fprune) { //if we don't yet have a move, then search full window (PV Node) if (bestMove == null) { score = -Search(-beta, -alpha, depth - 1 - lmr + ext); } else //otherwise, use a zero window { //zero window search score = -Search(-alpha - 1, -alpha, depth - 1 - lmr + ext); if (score > alpha) { //this move might be better than our current best move //we have to research with full window score = -Search(-beta, -alpha, depth - 1 - lmr + ext); if (score > alpha && lmr > 0) { //let's research again without the lmr Metrics.LMRResearch++; score = -Search(-beta, -alpha, depth - 1); } } } } else { score = -Quiesce(-beta, -alpha); } TakeBack(); ++movesSearched; if (MyGameState.TimeUp) { return(alpha); } if (score >= beta) { SearchFailHigh(m, score, depth, entry); if (lastMove == null) { Metrics.FirstMoveFailHigh++; } return(score); } if (score > alpha) { alpha = score; bestMove = m; // PV Node //update the PV UpdatePv(bestMove); //Add to hashtable TranspositionTable.Instance.Store( MyBoard.HashKey, bestMove, depth, alpha, TranspositionTableEntry.EntryType.PV); } lastMove = m; } //check for mate if (lastMove == null) { //we can't make a move. check for mate or stalemate. if (inCheck) { return(-10000 + Ply); } else { return(CurrentDrawScore); } } if (bestMove == null) { //ALL NODE TranspositionTable.Instance.Store( MyBoard.HashKey, null, depth, alpha, TranspositionTableEntry.EntryType.ALL); } return(alpha); }
private static char GetCheckChar(this MoveGenerator moveGenerator) { moveGenerator.GenerateMoves(); return(moveGenerator.Moves.Count > 0 ? '+' : '#'); }
static string NotationFromMove(Board board, Move move) { MoveGenerator moveGen = new MoveGenerator(); int movePieceType = Piece.PieceType(board.Square[move.StartSquare]); int capturedPieceType = Piece.PieceType(board.Square[move.TargetSquare]); if (move.MoveFlag == Move.Flag.Castling) { int delta = move.TargetSquare - move.StartSquare; if (delta == 2) { return("O-O"); } else if (delta == -2) { return("O-O-O"); } } string moveNotation = GetSymbolFromPieceType(movePieceType); // check if any ambiguity exists in notation (e.g if e2 can be reached via Nfe2 and Nbe2) if (movePieceType != Piece.Pawn && movePieceType != Piece.King) { var allMoves = moveGen.GenerateMoves(board); foreach (Move altMove in allMoves) { if (altMove.StartSquare != move.StartSquare && altMove.TargetSquare == move.TargetSquare) // if moving to same square from different square { if (Piece.PieceType(board.Square[altMove.StartSquare]) == movePieceType) // same piece type { int fromFileIndex = BoardRepresentation.FileIndex(move.StartSquare); int alternateFromFileIndex = BoardRepresentation.FileIndex(altMove.StartSquare); int fromRankIndex = BoardRepresentation.RankIndex(move.StartSquare); int alternateFromRankIndex = BoardRepresentation.RankIndex(altMove.StartSquare); if (fromFileIndex != alternateFromFileIndex) // pieces on different files, thus ambiguity can be resolved by specifying file { moveNotation += BoardRepresentation.fileNames[fromFileIndex]; break; // ambiguity resolved } else if (fromRankIndex != alternateFromRankIndex) { moveNotation += BoardRepresentation.rankNames[fromRankIndex]; break; // ambiguity resolved } } } } } if (capturedPieceType != 0) // add 'x' to indicate capture { if (movePieceType == Piece.Pawn) { moveNotation += BoardRepresentation.fileNames[BoardRepresentation.FileIndex(move.StartSquare)]; } moveNotation += "x"; } else // check if capturing ep { if (move.MoveFlag == Move.Flag.EnPassantCapture) { moveNotation += BoardRepresentation.fileNames[BoardRepresentation.FileIndex(move.StartSquare)] + "x"; } } moveNotation += BoardRepresentation.fileNames[BoardRepresentation.FileIndex(move.TargetSquare)]; moveNotation += BoardRepresentation.rankNames[BoardRepresentation.RankIndex(move.TargetSquare)]; // add promotion piece if (move.IsPromotion) { int promotionPieceType = move.PromotionPieceType; moveNotation += "=" + GetSymbolFromPieceType(promotionPieceType); } board.MakeMove(move, inSearch: true); var legalResponses = moveGen.GenerateMoves(board); // add check/mate symbol if applicable if (moveGen.InCheck()) { if (legalResponses.Count == 0) { moveNotation += "#"; } else { moveNotation += "+"; } } board.UnmakeMove(move, inSearch: true); return(moveNotation); }
public static int CalculateBestMove(ChessBoard cb, ThreadData threadData, int ply, int depth, int alpha, int beta, int nullMoveCounter) { if (!IsRunning) { return(ChessConstants.ScoreNotRunning); } if (EngineConstants.Assert) { Assert.IsTrue(depth >= 0); Assert.IsTrue(alpha >= Util.ShortMin && alpha <= Util.ShortMax); Assert.IsTrue(beta >= Util.ShortMin && beta <= Util.ShortMax); } var alphaOrig = alpha; // get extensions depth += Extensions(cb); /* mate-distance pruning */ if (EngineConstants.EnableMateDistancePruning) { alpha = Math.Max(alpha, Util.ShortMin + ply); beta = Math.Min(beta, Util.ShortMax - ply - 1); if (alpha >= beta) { return(alpha); } } // TODO JITWatch unpredictable branch if (depth == 0) { return(QuiescenceUtil.CalculateBestMove(cb, threadData, alpha, beta)); } /* transposition-table */ var ttEntry = TtUtil.GetEntry(cb.ZobristKey); var score = ttEntry.GetScore(ply); if (ttEntry.Key != 0) { if (!EngineConstants.TestTtValues) { if (ttEntry.Depth >= depth) { switch (ttEntry.Flag) { case TtUtil.FlagExact: return(score); case TtUtil.FlagLower: if (score >= beta) { return(score); } break; case TtUtil.FlagUpper: if (score <= alpha) { return(score); } break; } } } } if (Statistics.Enabled) { Statistics.AbNodes++; } var eval = Util.ShortMin; var isPv = beta - alpha != 1; if (!isPv && cb.CheckingPieces == 0) { eval = EvalUtil.GetScore(cb, threadData); /* use tt value as eval */ if (EngineConstants.UseTtScoreAsEval) { if (TtUtil.CanRefineEval(ttEntry, eval, score)) { eval = score; } } /* static null move pruning */ if (EngineConstants.EnableStaticNullMove && depth < StaticNullmoveMargin.Length) { if (eval - StaticNullmoveMargin[depth] >= beta) { if (Statistics.Enabled) { Statistics.StaticNullMoved[depth]++; } return(eval); } } /* razoring */ if (EngineConstants.EnableRazoring && depth < RazoringMargin.Length && Math.Abs(alpha) < EvalConstants.ScoreMateBound) { if (eval + RazoringMargin[depth] < alpha) { score = QuiescenceUtil.CalculateBestMove(cb, threadData, alpha - RazoringMargin[depth], alpha - RazoringMargin[depth] + 1); if (score + RazoringMargin[depth] <= alpha) { if (Statistics.Enabled) { Statistics.Razored[depth]++; } return(score); } } } /* null-move */ if (EngineConstants.EnableNullMove) { if (nullMoveCounter < 2 && eval >= beta && MaterialUtil.HasNonPawnPieces(cb.MaterialKey, cb.ColorToMove)) { cb.DoNullMove(); // TODO less reduction if stm (other side) has only 1 major piece var reduction = depth / 4 + 3 + Math.Min((eval - beta) / 80, 3); score = depth - reduction <= 0 ? -QuiescenceUtil.CalculateBestMove(cb, threadData, -beta, -beta + 1) : -CalculateBestMove(cb, threadData, ply + 1, depth - reduction, -beta, -beta + 1, nullMoveCounter + 1); cb.UndoNullMove(); if (score >= beta) { if (Statistics.Enabled) { Statistics.NullMoveHit++; } return(score); } if (Statistics.Enabled) { Statistics.NullMoveMiss++; } } } } var parentMove = ply == 0 ? 0 : threadData.Previous(); var bestMove = 0; var bestScore = Util.ShortMin - 1; var ttMove = 0; var counterMove = 0; var killer1Move = 0; var killer2Move = 0; var movesPerformed = 0; threadData.StartPly(); var phase = PhaseTt; while (phase <= PhaseQuiet) { switch (phase) { case PhaseTt: if (ttEntry.Key != 0) { ttMove = ttEntry.Move; if (cb.IsValidMove(ttMove)) { threadData.AddMove(ttMove); } // else { // throw new RuntimeException("invalid tt-move found: " + new MoveWrapper(ttMove)); // } } break; case PhaseAttacking: MoveGenerator.GenerateAttacks(threadData, cb); threadData.SetMvvlvaScores(); threadData.Sort(); break; case PhaseKiller1: killer1Move = threadData.GetKiller1(ply); if (killer1Move != 0 && killer1Move != ttMove && cb.IsValidMove(killer1Move)) { threadData.AddMove(killer1Move); break; } phase++; goto case PhaseKiller2; case PhaseKiller2: killer2Move = threadData.GetKiller2(ply); if (killer2Move != 0 && killer2Move != ttMove && cb.IsValidMove(killer2Move)) { threadData.AddMove(killer2Move); break; } phase++; goto case PhaseCounter; case PhaseCounter: counterMove = threadData.GetCounter(cb.ColorToMove, parentMove); if (counterMove != 0 && counterMove != ttMove && counterMove != killer1Move && counterMove != killer2Move && cb.IsValidMove(counterMove)) { threadData.AddMove(counterMove); break; } phase++; goto case PhaseQuiet; case PhaseQuiet: MoveGenerator.GenerateMoves(threadData, cb); threadData.SetHhScores(cb.ColorToMove); threadData.Sort(); break; } while (threadData.HasNext()) { var move = threadData.Next(); switch (phase) { case PhaseQuiet when move == ttMove || move == killer1Move || move == killer2Move || move == counterMove || !cb.IsLegal(move): case PhaseAttacking when move == ttMove || !cb.IsLegal(move): continue; } // pruning allowed? if (!isPv && cb.CheckingPieces == 0 && movesPerformed > 0 && threadData.GetMoveScore() < 100 && !cb.IsDiscoveredMove(MoveUtil.GetFromIndex(move))) { if (phase == PhaseQuiet) { /* late move pruning */ if (EngineConstants.EnableLmp && depth <= 4 && movesPerformed >= depth * 3 + 3) { if (Statistics.Enabled) { Statistics.Lmped[depth]++; } continue; } /* futility pruning */ if (EngineConstants.EnableFutilityPruning && depth < FutilityMargin.Length) { if (!MoveUtil.IsPawnPush78(move)) { if (eval == Util.ShortMin) { eval = EvalUtil.GetScore(cb, threadData); } if (eval + FutilityMargin[depth] <= alpha) { if (Statistics.Enabled) { Statistics.Futile[depth]++; } continue; } } } } /* SEE Pruning */ else if (EngineConstants.EnableSeePruning && depth <= 6 && phase == PhaseAttacking && SeeUtil.GetSeeCaptureScore(cb, move) < -20 * depth * depth) { continue; } } cb.DoMove(move); movesPerformed++; /* draw check */ if (cb.IsRepetition(move) || MaterialUtil.IsDrawByMaterial(cb)) { score = EvalConstants.ScoreDraw; } else { score = alpha + 1; // initial is above alpha var reduction = 1; if (depth > 2 && movesPerformed > 1 && MoveUtil.IsQuiet(move) && !MoveUtil.IsPawnPush78(move)) { reduction = LmrTable[Math.Min(depth, 63)][Math.Min(movesPerformed, 63)]; if (threadData.GetMoveScore() > 40) { reduction -= 1; } if (move == killer1Move || move == killer2Move || move == counterMove) { reduction -= 1; } if (!isPv) { reduction += 1; } reduction = Math.Min(depth - 1, Math.Max(reduction, 1)); } /* LMR */ if (EngineConstants.EnableLmr && reduction != 1) { score = -CalculateBestMove(cb, threadData, ply + 1, depth - reduction, -alpha - 1, -alpha, 0); } /* PVS */ if (EngineConstants.EnablePvs && score > alpha && movesPerformed > 1) { score = -CalculateBestMove(cb, threadData, ply + 1, depth - 1, -alpha - 1, -alpha, 0); } /* normal bounds */ if (score > alpha) { score = -CalculateBestMove(cb, threadData, ply + 1, depth - 1, -beta, -alpha, 0); } } cb.UndoMove(move); if (score > bestScore) { bestScore = score; bestMove = move; if (ply == 0 && IsRunning) { threadData.SetBestMove(cb, bestMove, alphaOrig, beta, bestScore, depth); } alpha = Math.Max(alpha, score); if (alpha >= beta) { if (Statistics.Enabled) { Statistics.FailHigh[Math.Min(movesPerformed - 1, Statistics.FailHigh.Length - 1)]++; } /* killer and history */ if (MoveUtil.IsQuiet(move) && cb.CheckingPieces == 0) { threadData.AddCounterMove(cb.ColorToMove, parentMove, move); threadData.AddKillerMove(move, ply); threadData.AddHhValue(cb.ColorToMove, move, depth); } phase += 10; break; } } if (MoveUtil.IsQuiet(move)) { threadData.AddBfValue(cb.ColorToMove, move, depth); } } phase++; } threadData.EndPly(); /* checkmate or stalemate */ if (movesPerformed == 0) { if (cb.CheckingPieces == 0) { if (Statistics.Enabled) { Statistics.StaleMateCount++; } return(EvalConstants.ScoreDraw); } if (Statistics.Enabled) { Statistics.MateCount++; } return(Util.ShortMin + ply); } if (EngineConstants.Assert) { Assert.IsTrue(bestMove != 0); } // set tt-flag var flag = TtUtil.FlagExact; if (bestScore >= beta) { flag = TtUtil.FlagLower; } else if (bestScore <= alphaOrig) { flag = TtUtil.FlagUpper; } if (IsRunning) { TtUtil.AddValue(cb.ZobristKey, bestScore, ply, depth, flag, bestMove); } Statistics.SetBestMove(cb, bestMove, ttMove, ttEntry, flag, counterMove, killer1Move, killer2Move); if (EngineConstants.TestTtValues) { SearchTestUtil.TestTtValues(score, bestScore, depth, bestMove, flag, ttEntry, ply); } return(bestScore); }