Exemplo n.º 1
0
        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);
        }
Exemplo n.º 2
0
        /// <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);
        }
Exemplo n.º 3
0
        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);
        }
Exemplo n.º 4
0
        /// <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);
        }
Exemplo n.º 6
0
        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);
        }