Пример #1
0
 private void SaveNode(ulong hash, TranspositionNode existingNode, TranspositionNode newNode)
 {
     if (existingNode != null)
     {
         transpositionTable.Update(hash, existingNode, newNode);
     }
     else
     {
         transpositionTable.Add(hash, newNode);
     }
 }
Пример #2
0
        private int Search(int depth, int ply, int alpha, int beta, PVList pvList, bool nullMoveReduction)
        {
            if (ct.IsCancellationRequested)
            {
                return(0);
            }

            searchStats.Nodes++;

            if (gameState.IsDraw())
            {
                int score = evaluator.GetDrawScore(engine.EngineColor);
                if (score > alpha)
                {
                    pvList.Replace(new PVList());
                }
                return(score);
            }

            if (gameState.IsCheck() && depth < ArtemisEngine.MAX_DEPTH)
            {
                //check extension
                depth += 1;
            }

            int   originalAlpha = alpha;
            ulong hash          = gameState.GetIrrevState().ZobristHash;
            TTHit ttHit         = transpositionTable.TryGetValue(hash, depth, alpha, beta, pvList);

            if (ttHit.HitType == HitType.Hit)
            {
                searchStats.TTHits++;
                return(ttHit.Score);
            }
            TranspositionNode ttNode = ttHit.TTNode;

            if (depth <= 0 || ply == ArtemisEngine.MAX_DEPTH)
            {
                int score = quietSearch.Search(alpha, beta);
                if (score > alpha)
                {
                    pvList.Replace(new PVList());
                }
                return(score);
            }


            Move   bestMove = null;
            bool   cutoff   = false;
            PVList newPV    = new PVList();
            bool   PVNode   = alpha != beta - 1;

            if (!PVNode && ttHit.HitType != HitType.AvoidNullMove &&
                !nullMoveReduction && engine.GameStage != GameStage.Endgame && !gameState.IsCheck())
            {
                //null move pruning
                gameState.MakeNullMove();
                int nextDepth = depth - 1 - config.NullMoveDepthReduction;
                int score     = -Search(nextDepth, ply + 1, -beta, -beta + 1, newPV, true);
                gameState.UnmakeNullMove();
                if (score >= beta)
                {
                    searchStats.NullMoveCutoffs++;
                    searchStats.AlphaBetaCutoffs++;
                    TranspositionNode newNode = new TranspositionNode(NodeType.CutNode, score, nextDepth + 1, null, null);
                    SaveNode(hash, ttNode, newNode);
                    //alpha-beta cutoff
                    return(beta);
                }
            }

            List <Move> moves  = gameState.GetMoves();
            Move        pvMove = null;

            if (PVNode)
            {
                if (currentPVNode != null)
                {
                    pvMove        = currentPVNode.Move;
                    currentPVNode = currentPVNode.Next;
                }
                else
                {
                    PVNode = false;
                }
            }
            Move hashMove = null;

            if (ttNode != null)
            {
                hashMove = ttNode.BestMove;
            }
            Move[] killers = killerMoves.GetKillerMoves(ply);
            moves = moves.OrderByDescending(m => moveEvaluator.EvaluateMove(m, pvMove, hashMove, killers).Score).ToList();

            int  originalLen          = moves.Count;
            int  moveCount            = 0;
            bool lmrCandidatePosition = depth >= 3 && !PVNode && !gameState.IsCheck();

            for (int i = 0; i < moves.Count && !cutoff; i++)
            {
                Move move = moves[i];
                gameState.MakeMove(move);
                if (move.IsLegal())
                {
                    int  nextDepth    = depth - 1;
                    bool lmrReduction = false;
                    if (lmrCandidatePosition && moveCount >= 4 && move.IsQuiet() && !gameState.IsCheck())
                    {
                        lmrReduction = true;
                        nextDepth--;
                        searchStats.LMRReductions++;
                    }

                    int score;
                    if (moveCount == 0 || searchDepth == 1)
                    {
                        score = -Search(nextDepth, ply + 1, -beta, -alpha, newPV, false);
                    }
                    else
                    {
                        ulong moveHash = gameState.GetIrrevState().ZobristHash;
                        if (!config.Multithreading || i >= originalLen || searchedNodes.TryAdd(moveHash, true))
                        {
                            score = -Search(nextDepth, ply + 1, -alpha - 1, -alpha, newPV, false);
                            searchedNodes.TryRemove(moveHash, out _);

                            if (score > alpha)
                            {
                                if (lmrReduction)
                                {
                                    nextDepth++;
                                }
                                score = -Search(nextDepth, ply + 1, -beta, -alpha, newPV, false);
                            }
                        }
                        else
                        {
                            moves.Add(move);
                            gameState.UnmakeMove(move);
                            continue;
                        }
                    }

                    if (score >= beta)
                    {
                        //alpha-beta cutoff
                        searchStats.AlphaBetaCutoffs++;
                        alpha    = beta;
                        bestMove = move;
                        cutoff   = true;
                        killerMoves.AddMove(move, ply);
                    }
                    else if (score > alpha)
                    {
                        alpha    = score;
                        bestMove = move;
                    }

                    moveCount++;
                }
                gameState.UnmakeMove(move);
            }

            if (moveCount == 0)
            {
                //player has no legal moves
                if (gameState.IsCheck())
                {
                    return(-PositionEvaluator.CHECKMATE_SCORE - depth);
                }
                else
                {
                    return(0);
                }
            }

            NodeType nodeType = GetNodeType(originalAlpha, beta, alpha);
            PVList   nodePV   = null;

            if (nodeType == NodeType.PVNode)
            {
                searchStats.PVNodes++;
                PVNode node = new PVNode(bestMove);
                newPV.AddFirst(node);
                nodePV = newPV;
                pvList.Replace(newPV);
            }
            TranspositionNode updatedNode = new TranspositionNode(nodeType, alpha, depth, bestMove, nodePV);

            SaveNode(hash, ttNode, updatedNode);

            return(alpha);
        }
Пример #3
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);
        }