private static int GetPrincipalVariation(BoardState board, Move[] moves, int movesCount) { var entry = TranspositionTable.Get(board.Hash); if (entry.Flags == TranspositionTableEntryFlags.ExactScore && entry.IsKeyValid(board.Hash) && movesCount < SearchConstants.MaxDepth) { if (!board.IsMoveLegal(entry.BestMove)) { return(movesCount); } moves[movesCount] = entry.BestMove; board.MakeMove(entry.BestMove); var enemyColor = ColorOperations.Invert(board.ColorToMove); var king = board.Pieces[enemyColor][Piece.King]; var kingField = BitOperations.BitScan(king); if (board.IsFieldAttacked(enemyColor, (byte)kingField)) { board.UndoMove(entry.BestMove); return(movesCount); } movesCount = GetPrincipalVariation(board, moves, movesCount + 1); board.UndoMove(entry.BestMove); } return(movesCount); }
/// <summary> /// Gets the list pf PV nodes for the specified bitboard and color. Nodes count is limited by /// <see cref="AIConstants.MaxDepth"/> value to avoid infinite repetitions. /// </summary> /// <param name="bitboard">The initial bitboard.</param> /// <param name="color">The initial color.</param> /// <returns>The list of PV nodes.</returns> private PVNodesList GetPVNodes(Bitboard bitboard, Color color) { var pvNodes = new PVNodesList(); var boardHash = bitboard.GetHashForColor(color); while (_transpositionTable.Exists(boardHash) && pvNodes.Count < AIConstants.MaxDepth) { var pvNode = _transpositionTable.Get(boardHash); if (pvNode.BestMove == null) { break; } pvNodes.Add(pvNode.BestMove); bitboard = bitboard.Move(pvNode.BestMove); color = ColorOperations.Invert(color); boardHash = bitboard.GetHashForColor(color); } return(pvNodes); }
public static int FindBestMove(SearchContext context, int depth, int ply, int alpha, int beta, bool allowNullMove, bool friendlyKingInCheck) { if (context.Statistics.Nodes >= context.MaxNodesCount) { context.AbortSearch = true; return(0); } if (context.AbortSearch) { return(0); } context.Statistics.Nodes++; if (context.BoardState.Pieces[context.BoardState.ColorToMove][Piece.King] == 0) { context.Statistics.Leafs++; return(-EvaluationConstants.Checkmate + ply); } if (context.BoardState.IsThreefoldRepetition()) { context.Statistics.Leafs++; return(EvaluationConstants.ThreefoldRepetition); } if (context.BoardState.IsInsufficientMaterial()) { if (!friendlyKingInCheck && !context.BoardState.IsKingChecked(ColorOperations.Invert(context.BoardState.ColorToMove))) { context.Statistics.Leafs++; return(EvaluationConstants.InsufficientMaterial); } } if (context.BoardState.IsFiftyMoveRuleDraw()) { context.Statistics.Leafs++; if (context.BoardState.IsKingChecked(ColorOperations.Invert(context.BoardState.ColorToMove))) { return(EvaluationConstants.Checkmate + ply); } return(EvaluationConstants.ThreefoldRepetition); } if (depth <= 0) { context.Statistics.Leafs++; return(QuiescenceSearch.FindBestMove(context, depth, ply, alpha, beta)); } var originalAlpha = alpha; var pvNode = beta - alpha > 1; var entry = TranspositionTable.Get(context.BoardState.Hash); var hashMove = Move.Empty; if (entry.Flags != TranspositionTableEntryFlags.Invalid && entry.IsKeyValid(context.BoardState.Hash)) { #if DEBUG context.Statistics.TTHits++; #endif if (entry.Flags != TranspositionTableEntryFlags.AlphaScore) { var isMoveLegal = context.BoardState.IsMoveLegal(entry.BestMove); if (isMoveLegal) { hashMove = entry.BestMove; #if DEBUG context.Statistics.TTValidMoves++; #endif } #if DEBUG else { context.Statistics.TTInvalidMoves++; } #endif } if (entry.Depth >= depth) { switch (entry.Flags) { case TranspositionTableEntryFlags.AlphaScore: { if (entry.Score < beta) { beta = entry.Score; } break; } case TranspositionTableEntryFlags.ExactScore: { if (!pvNode) { entry.Score = (short)TranspositionTable.TTToRegularScore(entry.Score, ply); return(entry.Score); } break; } case TranspositionTableEntryFlags.BetaScore: { if (entry.Score > alpha) { alpha = entry.Score; } break; } } if (alpha >= beta) { context.Statistics.BetaCutoffs++; return(entry.Score); } } } #if DEBUG else { context.Statistics.TTNonHits++; } #endif if (NullWindowCanBeApplied(context.BoardState, depth, allowNullMove, pvNode, friendlyKingInCheck)) { context.BoardState.MakeNullMove(); var score = -FindBestMove(context, depth - 1 - SearchConstants.NullWindowDepthReduction, ply + 1, -beta, -beta + 1, false, false); context.BoardState.UndoNullMove(); if (score >= beta) { context.Statistics.BetaCutoffs++; return(score); } } if (IIDCanBeApplied(depth, entry.Flags, hashMove)) { FindBestMove(context, depth - 1 - SearchConstants.IIDDepthReduction, ply, alpha, beta, allowNullMove, friendlyKingInCheck); var iidEntry = TranspositionTable.Get(context.BoardState.Hash); if (iidEntry.IsKeyValid(context.BoardState.Hash)) { hashMove = iidEntry.BestMove; #if DEBUG context.Statistics.IIDHits++; #endif } } Span <Move> moves = stackalloc Move[SearchConstants.MaxMovesCount]; Span <short> moveValues = stackalloc short[SearchConstants.MaxMovesCount]; var bestMove = Move.Empty; var movesCount = 0; var loudMovesGenerated = false; var quietMovesGenerated = false; if (hashMove == Move.Empty) { movesCount = context.BoardState.GetLoudMoves(moves, 0); MoveOrdering.AssignLoudValues(context.BoardState, moves, moveValues, movesCount, depth, bestMove); loudMovesGenerated = true; context.Statistics.LoudMovesGenerated++; if (movesCount == 0) { movesCount = context.BoardState.GetQuietMoves(moves, 0); MoveOrdering.AssignQuietValues(context.BoardState, moves, moveValues, 0, movesCount, depth); quietMovesGenerated = true; context.Statistics.QuietMovesGenerated++; } } else { moves[0] = hashMove; moveValues[0] = MoveOrderingConstants.HashMove; movesCount = 1; } var pvs = true; for (var moveIndex = 0; moveIndex < movesCount; moveIndex++) { MoveOrdering.SortNextBestMove(moves, moveValues, movesCount, moveIndex); if (loudMovesGenerated && moves[moveIndex] == hashMove) { goto postLoopOperations; } if (loudMovesGenerated && !quietMovesGenerated && moveValues[moveIndex] < 100) { var loudMovesCount = movesCount; movesCount = context.BoardState.GetQuietMoves(moves, movesCount); MoveOrdering.AssignQuietValues(context.BoardState, moves, moveValues, loudMovesCount, movesCount, depth); MoveOrdering.SortNextBestMove(moves, moveValues, movesCount, moveIndex); quietMovesGenerated = true; context.Statistics.QuietMovesGenerated++; if (moves[moveIndex] == hashMove) { goto postLoopOperations; } } if (context.MoveRestrictions != null && ply == 0) { if (!context.MoveRestrictions.Contains(moves[moveIndex])) { continue; } } context.BoardState.MakeMove(moves[moveIndex]); var score = 0; var enemyKingInCheck = context.BoardState.IsKingChecked(context.BoardState.ColorToMove); if (pvs) { score = -FindBestMove(context, depth - 1, ply + 1, -beta, -alpha, allowNullMove, enemyKingInCheck); pvs = false; } else { var reducedDepth = depth; if (LMRCanBeApplied(depth, friendlyKingInCheck, enemyKingInCheck, moveIndex, moves)) { reducedDepth = LMRGetReducedDepth(depth, pvNode); } score = -FindBestMove(context, reducedDepth - 1, ply + 1, -alpha - 1, -alpha, allowNullMove, enemyKingInCheck); if (score > alpha) { score = -FindBestMove(context, depth - 1, ply + 1, -beta, -alpha, allowNullMove, enemyKingInCheck); } } context.BoardState.UndoMove(moves[moveIndex]); if (score > alpha) { alpha = score; bestMove = moves[moveIndex]; if (alpha >= beta) { if (moves[moveIndex].IsQuiet()) { KillerHeuristic.AddKillerMove(moves[moveIndex], context.BoardState.ColorToMove, depth); HistoryHeuristic.AddHistoryMove(context.BoardState.ColorToMove, moves[moveIndex].From, moves[moveIndex].To, depth); } #if DEBUG if (moveIndex == 0) { context.Statistics.BetaCutoffsAtFirstMove++; } else { context.Statistics.BetaCutoffsNotAtFirstMove++; } #endif context.Statistics.BetaCutoffs++; break; } } postLoopOperations: if (!loudMovesGenerated) { movesCount = context.BoardState.GetLoudMoves(moves, 0); MoveOrdering.AssignLoudValues(context.BoardState, moves, moveValues, movesCount, depth, bestMove); moveIndex = -1; loudMovesGenerated = true; context.Statistics.LoudMovesGenerated++; if (movesCount == 0) { movesCount = context.BoardState.GetQuietMoves(moves, 0); MoveOrdering.AssignQuietValues(context.BoardState, moves, moveValues, 0, movesCount, depth); quietMovesGenerated = true; context.Statistics.QuietMovesGenerated++; } } if (!quietMovesGenerated && moveIndex == movesCount - 1) { var loudMovesCount = movesCount; movesCount = context.BoardState.GetQuietMoves(moves, movesCount); MoveOrdering.AssignQuietValues(context.BoardState, moves, moveValues, loudMovesCount, movesCount, depth); quietMovesGenerated = true; context.Statistics.QuietMovesGenerated++; } } // Don't save invalid scores to the transposition table if (context.AbortSearch) { return(0); } // Don't add invalid move (done after checkmate) to prevent strange behaviors if (alpha == -(-EvaluationConstants.Checkmate + ply + 1)) { return(alpha); } // Return draw score or checkmate score as leafs if (alpha == -EvaluationConstants.Checkmate + ply + 2) { if (context.BoardState.IsKingChecked(context.BoardState.ColorToMove)) { return(alpha); } return(0); } if (entry.Age != context.TranspositionTableEntryAge || entry.Depth <= depth) { var valueToSave = alpha; var entryType = alpha <= originalAlpha ? TranspositionTableEntryFlags.AlphaScore : alpha >= beta ? TranspositionTableEntryFlags.BetaScore : TranspositionTableEntryFlags.ExactScore; if (entryType == TranspositionTableEntryFlags.ExactScore) { valueToSave = TranspositionTable.RegularToTTScore(alpha, ply); } TranspositionTable.Add(context.BoardState.Hash, new TranspositionTableEntry( context.BoardState.Hash, (short)valueToSave, bestMove, (byte)depth, entryType, (byte)context.TranspositionTableEntryAge) ); #if DEBUG if (entry.Flags != TranspositionTableEntryFlags.Invalid) { context.Statistics.TTReplacements++; } context.Statistics.TTAddedEntries++; #endif } return(alpha); }
/// <summary> /// Temporary method to calculating best move. /// </summary> /// <param name="color">The player color.</param> /// <param name="bitboard">The bitboard.</param> /// <param name="depth">The current depth.</param> /// <param name="bestMove">The best possible move from nested nodes.</param> /// <param name="stats">The AI stats.</param> /// <returns>The evaluation score of best move.</returns> public int Do(Color color, Bitboard bitboard, int depth, int alpha, int beta, AIStats stats) { var bestValue = AIConstants.InitialAlphaValue; var enemyColor = ColorOperations.Invert(color); var boardHash = bitboard.GetHashForColor(color); var originalAlpha = alpha; stats.TotalNodes++; if (bitboard.IsThreefoldRepetition()) { stats.EndNodes++; return(0); } if (_transpositionTable.Exists(boardHash)) { var transpositionNode = _transpositionTable.Get(boardHash); if (transpositionNode.Depth >= depth) { stats.TranspositionTableHits++; switch (transpositionNode.Type) { case ScoreType.Exact: { return(transpositionNode.Score); } case ScoreType.LowerBound: { alpha = Math.Max(alpha, transpositionNode.Score); break; } case ScoreType.UpperBound: { beta = Math.Min(beta, transpositionNode.Score); break; } } if (alpha >= beta) { return(transpositionNode.Score); } } } if (depth <= 0) { stats.EndNodes++; return(_quiescenceSearch.Do(color, bitboard, alpha, beta, stats)); } var whiteGeneratorMode = GetGeneratorMode(color, Color.White); var blackGeneratorMode = GetGeneratorMode(color, Color.Black); bitboard.Calculate(whiteGeneratorMode, blackGeneratorMode, false); if (bitboard.IsCheck(enemyColor)) { stats.EndNodes++; return(AIConstants.MateValue + depth); } Move bestMove = null; var availableMoves = SortMoves(color, bitboard, bitboard.Moves); var firstMove = true; foreach (var move in availableMoves) { var bitboardAfterMove = bitboard.Move(move); var nodeValue = 0; if (firstMove) { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, stats); firstMove = false; } else { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -alpha - 1, -alpha, stats); if (nodeValue > alpha && nodeValue < beta) { bitboardAfterMove = bitboard.Move(move); nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, stats); } } if (nodeValue > bestValue) { bestValue = nodeValue; bestMove = move; } alpha = Math.Max(nodeValue, alpha); if (alpha >= beta) { stats.AlphaBetaCutoffs++; break; } } if (bestValue == -(AIConstants.MateValue + depth - 1) && !bitboard.IsCheck(color)) { stats.EndNodes++; return(0); } var updateTranspositionNode = new TranspositionNode(); updateTranspositionNode.Score = bestValue; updateTranspositionNode.Depth = depth; updateTranspositionNode.BestMove = bestMove; if (bestValue <= originalAlpha) { updateTranspositionNode.Type = ScoreType.UpperBound; } else if (bestValue >= beta) { updateTranspositionNode.Type = ScoreType.LowerBound; } else { updateTranspositionNode.Type = ScoreType.Exact; } _transpositionTable.AddOrUpdate(boardHash, updateTranspositionNode); return(bestValue); }
/// <summary> /// Regular search, the core of AI algorithms. /// </summary> /// <param name="color">The player color.</param> /// <param name="bitboard">The bitboard.</param> /// <param name="depth">The current depth.</param> /// <param name="alpha">The alpha value.</param> /// <param name="beta">The beta value.</param> /// <param name="deadline">The deadline (time after which search is immediately terminated).</param> /// <param name="helper">The flag indicating whether the search is an helper or not.</param> /// <param name="stats">The AI stats.</param> /// <returns>The evaluation score of best move.</returns> public int Do(Color color, Bitboard bitboard, int depth, int alpha, int beta, long deadline, bool helper, AIStats stats) { var root = stats.TotalNodes == 0; var bestValue = AIConstants.InitialAlphaValue; var enemyColor = ColorOperations.Invert(color); var boardHash = bitboard.GetHashForColor(color); var originalAlpha = alpha; stats.TotalNodes++; if (bitboard.IsThreefoldRepetition()) { stats.EndNodes++; return(0); } #if TRANSPOSITION_TABLE if (_transpositionTable.Exists(boardHash)) { var transpositionNode = _transpositionTable.Get(boardHash); if (transpositionNode.Depth >= depth) { stats.TranspositionTableHits++; switch (transpositionNode.Type) { case ScoreType.Exact: { return(transpositionNode.Score); } case ScoreType.LowerBound: { alpha = Math.Max(alpha, transpositionNode.Score); break; } case ScoreType.UpperBound: { beta = Math.Min(beta, transpositionNode.Score); break; } } if (alpha >= beta) { return(transpositionNode.Score); } } } #endif if (depth <= 0) { stats.EndNodes++; #if QUIESCENCE_SEARCH return(_quiescenceSearch.Do(color, bitboard, alpha, beta, stats)); #else bitboard.Calculate(GeneratorMode.CalculateAttacks, false); return(bitboard.GetEvaluation()); #endif } var whiteGeneratorMode = GetGeneratorMode(color, Color.White); var blackGeneratorMode = GetGeneratorMode(color, Color.Black); bitboard.Calculate(whiteGeneratorMode, blackGeneratorMode, false); if (bitboard.IsCheck(enemyColor)) { stats.EndNodes++; return(AIConstants.MateValue + depth); } Move bestMove = null; var availableMoves = SortMoves(color, depth, bitboard, bitboard.Moves, helper); var firstMove = true; foreach (var move in availableMoves) { if (DateTime.Now.Ticks >= deadline) { break; } if (root) { if (_patternsDetector.IsPattern(bitboard, move)) { continue; } } var bitboardAfterMove = bitboard.Move(move); var nodeValue = 0; if (firstMove) { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, deadline, helper, stats); #if NEGASCOUT firstMove = false; #endif } else { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -alpha - 1, -alpha, deadline, helper, stats); if (nodeValue > alpha && nodeValue < beta) { bitboardAfterMove = bitboard.Move(move); nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, deadline, helper, stats); } } if (nodeValue > bestValue) { bestValue = nodeValue; bestMove = move; } alpha = Math.Max(nodeValue, alpha); if (alpha >= beta) { if (move is QuietMove) { _historyTable.AddKiller(color, depth, bestMove); _killerTable.AddKiller(depth, move); } #if ALPHABETA_PRUNNING stats.AlphaBetaCutoffs++; break; #endif } } if (bestValue == -(AIConstants.MateValue + depth - 1) && !bitboard.IsCheck(color)) { stats.EndNodes++; return(0); } var updateTranspositionNode = new TranspositionNode { Score = bestValue, Depth = depth, BestMove = bestMove, Type = GetTranspositionNodeType(originalAlpha, beta, bestValue) }; _transpositionTable.AddOrUpdate(boardHash, updateTranspositionNode); return(bestValue); }
public static int FindBestMove(SearchContext context, int depth, int ply, int alpha, int beta, bool allowNullMove, bool friendlyKingInCheck, int extensionsCount) { if (context.Statistics.Nodes >= context.MaxNodesCount) { context.AbortSearch = true; return(0); } if (context.AbortSearch) { return(0); } context.Statistics.Nodes++; if (context.BoardState.Pieces[context.BoardState.ColorToMove][Piece.King] == 0) { context.Statistics.Leafs++; return(SearchConstants.NoKingValue); } if (context.BoardState.IsKingChecked(ColorOperations.Invert(context.BoardState.ColorToMove))) { context.Statistics.Leafs++; return(-SearchConstants.NoKingValue); } if (context.BoardState.IsThreefoldRepetition()) { context.Statistics.Leafs++; return(EvaluationConstants.ThreefoldRepetition); } if (context.BoardState.IsInsufficientMaterial()) { var enemyColor = ColorOperations.Invert(context.BoardState.ColorToMove); if (!friendlyKingInCheck && !context.BoardState.IsKingChecked(enemyColor)) { context.Statistics.Leafs++; return(EvaluationConstants.InsufficientMaterial); } } if (context.BoardState.IsFiftyMoveRuleDraw()) { context.Statistics.Leafs++; return(EvaluationConstants.ThreefoldRepetition); } if (depth <= 0) { context.Statistics.Leafs++; return(QuiescenceSearch.FindBestMove(context, depth, ply, alpha, beta)); } var originalAlpha = alpha; var pvNode = beta - alpha > 1; var entry = TranspositionTable.Get(context.BoardState.Hash); var hashMove = Move.Empty; var bestMove = Move.Empty; if (entry.Flags != TranspositionTableEntryFlags.Invalid && entry.IsKeyValid(context.BoardState.Hash)) { #if DEBUG context.Statistics.TTHits++; #endif if (entry.Flags != TranspositionTableEntryFlags.AlphaScore) { var isMoveLegal = context.BoardState.IsMoveLegal(entry.BestMove); if (isMoveLegal) { hashMove = entry.BestMove; bestMove = entry.BestMove; #if DEBUG context.Statistics.TTValidMoves++; #endif } #if DEBUG else { context.Statistics.TTInvalidMoves++; } #endif } if (entry.Depth >= depth) { entry.Score = (short)TranspositionTable.TTToRegularScore(entry.Score, ply); switch (entry.Flags) { case TranspositionTableEntryFlags.AlphaScore: { if (entry.Score < beta) { beta = entry.Score; } break; } case TranspositionTableEntryFlags.ExactScore: { if (!pvNode || IterativeDeepening.IsScoreNearCheckmate(entry.Score)) { return(entry.Score); } break; } case TranspositionTableEntryFlags.BetaScore: { if (entry.Score > alpha) { alpha = entry.Score; } break; } } if (alpha >= beta) { context.Statistics.BetaCutoffs++; return(entry.Score); } } } #if DEBUG else { entry = TranspositionTableEntry.Empty; context.Statistics.TTNonHits++; } #endif if (RazoringCanBeApplied(depth, context.Statistics.Depth, friendlyKingInCheck, pvNode, alpha)) { var fastEvaluation = Evaluation.FastEvaluate(context.BoardState, context.Statistics.EvaluationStatistics); var margin = SearchConstants.RazoringMargin + (depth - SearchConstants.RazoringMinDepth) * SearchConstants.RazoringMarginMultiplier; var futileAlpha = alpha - margin; if (fastEvaluation < futileAlpha) { var result = QuiescenceSearch.FindBestMove(context, depth, ply, futileAlpha, futileAlpha + 1); if (result <= futileAlpha) { #if DEBUG context.Statistics.Razorings++; #endif return(futileAlpha); } #if DEBUG else { context.Statistics.RazoringsRejected++; } #endif } } if (StaticNullMoveCanBeApplied(depth, context.Statistics.Depth, friendlyKingInCheck, pvNode, beta)) { var fastEvaluation = Evaluation.FastEvaluate(context.BoardState, context.Statistics.EvaluationStatistics); var margin = SearchConstants.StaticNullMoveMargin + (depth - 1) * SearchConstants.StaticNullMoveMarginMultiplier; var score = fastEvaluation - margin; if (score >= beta) { #if DEBUG context.Statistics.StaticNullMovePrunes++; #endif return(score); } } if (NullMoveCanBeApplied(context.BoardState, depth, allowNullMove, pvNode, friendlyKingInCheck)) { context.BoardState.MakeNullMove(); var score = -FindBestMove(context, depth - 1 - NullMoveGetReduction(depth), ply + 1, -beta, -beta + 1, false, false, extensionsCount); context.BoardState.UndoNullMove(); if (score >= beta) { #if DEBUG context.Statistics.NullMovePrunes++; #endif return(score); } } if (IIDCanBeApplied(depth, entry.Flags, hashMove)) { FindBestMove(context, depth - 1 - SearchConstants.IIDDepthReduction, ply, alpha, beta, allowNullMove, friendlyKingInCheck, extensionsCount); var iidEntry = TranspositionTable.Get(context.BoardState.Hash); if (iidEntry.IsKeyValid(context.BoardState.Hash)) { hashMove = iidEntry.BestMove; #if DEBUG context.Statistics.IIDHits++; #endif } } var futilityPruningCanBeApplied = false; var futilityPruningEvaluation = 0; var futilityPruningMargin = 0; if (FutilityPruningCanBeApplied(depth, context.Statistics.Depth, friendlyKingInCheck, pvNode, alpha)) { futilityPruningCanBeApplied = true; futilityPruningEvaluation = Evaluation.FastEvaluate(context.BoardState, context.Statistics.EvaluationStatistics); futilityPruningMargin = SearchConstants.FutilityPruningMargin + (depth - 1) * SearchConstants.FutilityPruningMarginMultiplier; } Span <Move> moves = stackalloc Move[SearchConstants.MaxMovesCount]; Span <short> moveValues = stackalloc short[SearchConstants.MaxMovesCount]; var movesCount = 0; var loudMovesGenerated = false; var quietMovesGenerated = false; var evasionMask = ulong.MaxValue; if (friendlyKingInCheck && !context.BoardState.IsKingChecked(ColorOperations.Invert(context.BoardState.ColorToMove))) { var kingField = context.BoardState.Pieces[context.BoardState.ColorToMove][Piece.King]; var kingFieldIndex = BitOperations.BitScan(kingField); evasionMask = KnightMovesGenerator.GetMoves(kingFieldIndex) | QueenMovesGenerator.GetMoves(context.BoardState.OccupancySummary, kingFieldIndex); } if (hashMove == Move.Empty) { movesCount = context.BoardState.GetLoudMoves(moves, 0, evasionMask); MoveOrdering.AssignLoudValues(context.BoardState, moves, moveValues, movesCount, depth, Move.Empty); loudMovesGenerated = true; #if DEBUG context.Statistics.LoudMovesGenerated++; #endif if (movesCount == 0) { movesCount = context.BoardState.GetQuietMoves(moves, 0, evasionMask); MoveOrdering.AssignQuietValues(context.BoardState, moves, moveValues, 0, movesCount, ply); quietMovesGenerated = true; #if DEBUG context.Statistics.QuietMovesGenerated++; #endif } } else { moves[0] = hashMove; moveValues[0] = MoveOrderingConstants.HashMove; movesCount = 1; } var pvs = true; var bestScore = 0; var allMovesPruned = true; for (var moveIndex = 0; moveIndex < movesCount; moveIndex++) { if (LMPCanBeApplied(context, depth, friendlyKingInCheck, quietMovesGenerated, moveIndex, movesCount, pvNode)) { break; } MoveOrdering.SortNextBestMove(moves, moveValues, movesCount, moveIndex); if (loudMovesGenerated && moves[moveIndex] == hashMove) { goto postLoopOperations; } if (loudMovesGenerated && !quietMovesGenerated && moveValues[moveIndex] < 100) { var loudMovesCount = movesCount; movesCount = context.BoardState.GetQuietMoves(moves, movesCount, evasionMask); MoveOrdering.AssignQuietValues(context.BoardState, moves, moveValues, loudMovesCount, movesCount, ply); MoveOrdering.SortNextBestMove(moves, moveValues, movesCount, moveIndex); quietMovesGenerated = true; #if DEBUG context.Statistics.QuietMovesGenerated++; #endif if (moves[moveIndex] == hashMove) { goto postLoopOperations; } } if (context.MoveRestrictions != null && ply == 0) { if (!context.MoveRestrictions.Contains(moves[moveIndex])) { goto postLoopOperations; } } context.BoardState.MakeMove(moves[moveIndex]); var enemyKingInCheck = context.BoardState.IsKingChecked(context.BoardState.ColorToMove); var extension = GetExtensions(depth, extensionsCount, enemyKingInCheck); #if DEBUG context.Statistics.Extensions += extension; #endif if (futilityPruningCanBeApplied && FutilityPruningCanBeAppliedForMove(context, moves[moveIndex], enemyKingInCheck, pvs)) { var gain = FutilityPruningGetGain(context, moves[moveIndex]); if (futilityPruningEvaluation + futilityPruningMargin + gain <= alpha) { #if DEBUG context.Statistics.FutilityPrunes++; #endif context.BoardState.UndoMove(moves[moveIndex]); goto postLoopOperations; } } allMovesPruned = false; if (pvs) { bestScore = -FindBestMove(context, depth - 1 + extension, ply + 1, -beta, -alpha, allowNullMove, enemyKingInCheck, extensionsCount + extension); pvs = false; } else { var lmrReduction = 0; if (LMRCanBeApplied(context, depth, friendlyKingInCheck, enemyKingInCheck, moveIndex, moves, moveValues)) { lmrReduction = LMRGetReduction(pvNode, moveIndex); } var score = -FindBestMove(context, depth - lmrReduction - 1 + extension, ply + 1, -alpha - 1, -alpha, allowNullMove, enemyKingInCheck, extensionsCount + extension); if (score > alpha) { if (pvNode) { score = -FindBestMove(context, depth - 1 + extension, ply + 1, -beta, -alpha, allowNullMove, enemyKingInCheck, extensionsCount + extension); } else { if (lmrReduction != 0) { score = -FindBestMove(context, depth - 1 + extension, ply + 1, -beta, -alpha, allowNullMove, enemyKingInCheck, extensionsCount + extension); } } } if (score > bestScore) { bestScore = score; } } context.BoardState.UndoMove(moves[moveIndex]); if (bestScore > alpha) { alpha = bestScore; bestMove = moves[moveIndex]; if (alpha >= beta) { if (moves[moveIndex].IsQuiet()) { KillerHeuristic.AddKillerMove(moves[moveIndex], context.BoardState.ColorToMove, ply); HistoryHeuristic.AddHistoryMove(context.BoardState.ColorToMove, context.BoardState.PieceTable[moves[moveIndex].From], moves[moveIndex].To, depth); } #if DEBUG if (moveIndex == 0) { context.Statistics.BetaCutoffsAtFirstMove++; } else { context.Statistics.BetaCutoffsNotAtFirstMove++; } #endif context.Statistics.BetaCutoffs++; break; } } postLoopOperations: if (!loudMovesGenerated) { movesCount = context.BoardState.GetLoudMoves(moves, 0, evasionMask); MoveOrdering.AssignLoudValues(context.BoardState, moves, moveValues, movesCount, depth, hashMove); moveIndex = -1; loudMovesGenerated = true; #if DEBUG context.Statistics.LoudMovesGenerated++; #endif if (movesCount == 0) { movesCount = context.BoardState.GetQuietMoves(moves, 0, evasionMask); MoveOrdering.AssignQuietValues(context.BoardState, moves, moveValues, 0, movesCount, ply); quietMovesGenerated = true; #if DEBUG context.Statistics.QuietMovesGenerated++; #endif } } if (!quietMovesGenerated && moveIndex == movesCount - 1) { var loudMovesCount = movesCount; movesCount = context.BoardState.GetQuietMoves(moves, movesCount, evasionMask); MoveOrdering.AssignQuietValues(context.BoardState, moves, moveValues, loudMovesCount, movesCount, ply); quietMovesGenerated = true; #if DEBUG context.Statistics.QuietMovesGenerated++; #endif } } // Don't save invalid scores to the transposition table if (context.AbortSearch) { return(0); } if (allMovesPruned) { return(alpha); } // Don't add invalid move (done after checkmate) to prevent strange behaviors if (bestScore == -SearchConstants.NoKingValue) { return(bestScore); } // Return draw score or checkmate score as leafs if (bestScore == SearchConstants.NoKingValue) { if (friendlyKingInCheck) { return(-EvaluationConstants.Checkmate + ply); } return(0); } if (entry.Flags == TranspositionTableEntryFlags.Invalid || alpha != originalAlpha) { if (entry.Age != context.TranspositionTableEntryAge || entry.Depth <= depth) { var valueToSave = alpha; var entryType = alpha <= originalAlpha ? TranspositionTableEntryFlags.AlphaScore : alpha >= beta ? TranspositionTableEntryFlags.BetaScore : TranspositionTableEntryFlags.ExactScore; valueToSave = TranspositionTable.RegularToTTScore(alpha, ply); TranspositionTable.Add(context.BoardState.Hash, new TranspositionTableEntry( context.BoardState.Hash, (short)valueToSave, bestMove, (byte)depth, entryType, (byte)context.TranspositionTableEntryAge) ); #if DEBUG if (entry.Flags != TranspositionTableEntryFlags.Invalid) { context.Statistics.TTReplacements++; } context.Statistics.TTAddedEntries++; #endif } } return(bestScore); }