Esempio n. 1
0
        private void StorePVinTT(Move[] pv, int depth)
        {
            Board position = new Board(_root);

            for (int ply = 0; ply < pv.Length; ply++)
            {
                Move move = pv[ply];
                Transpositions.Store(position.ZobristHash, --depth, ply, SearchWindow.Infinite, Score, move);
                position.Play(move);
            }
        }
Esempio n. 2
0
        private (int Score, Move[] PV) EvalPositionTT(Board position, int ply, int depth, SearchWindow window)
        {
            if (Transpositions.GetScore(position.ZobristHash, depth, ply, window, out int ttScore))
            {
                return(ttScore, Array.Empty <Move>());
            }

            var result = EvalPosition(position, ply, depth, window);

            Transpositions.Store(position.ZobristHash, depth, ply, window, result.Score, result.PV.Length > 0 ? result.PV[0] : default);
            return(result);
        }
Esempio n. 3
0
        internal static IEnumerable <(Move Move, Board Board)> Play(Board position, int depth, KillerMoves killers, History history)
        {
            //1. Is there a known best move for this position? (PV Node)
            if (Transpositions.GetBestMove(position, out Move bestMove))
            {
                var nextPosition = new Board(position, bestMove);
                yield return(bestMove, nextPosition);
            }

            //2. Try all captures ordered by Mvv-Lva
            foreach (var capture in MoveList.SortedCaptures(position))
            {
                var nextPosition = new Board(position, capture);
                if (!nextPosition.IsChecked(position.SideToMove))
                {
                    yield return(capture, nextPosition);
                }
            }

            //3. Play quiet moves that have caused a beta cutoff elsewhere if available
            foreach (Move killer in killers.Get(depth))
            {
                if (position[killer.ToSquare] != Piece.None || !position.IsPlayable(killer))
                {
                    continue;
                }

                var nextPosition = new Board(position, killer);
                if (!nextPosition.IsChecked(position.SideToMove))
                {
                    yield return(killer, nextPosition);
                }
            }

            //4. Play the remaining quiet moves ordered by history
            foreach (var move in MoveList.SortedQuiets(position, history))
            {
                if (killers.Contains(depth, move))
                {
                    continue;
                }

                var nextPosition = new Board(position, move);
                if (!nextPosition.IsChecked(position.SideToMove))
                {
                    yield return(move, nextPosition);
                }
            }
        }
Esempio n. 4
0
        private (int Score, Move[] PV) EvalPosition(Board position, int ply, int depth, SearchWindow window)
        {
            if (depth <= 0)
            {
                _mobilityBonus = Evaluation.ComputeMobility(position);
                return(QEval(position, ply, window), Array.Empty <Move>());
            }

            NodesVisited++;
            if (Aborted)
            {
                return(0, Array.Empty <Move>());
            }

            Color color     = position.SideToMove;
            bool  isChecked = position.IsChecked(color);
            //if the previous iteration found a mate we check the first few plys without null move to try and find the shortest mate or escape
            bool allowNullMove = Evaluation.IsCheckmate(Score) ? (ply > Depth / 4) : true;

            //should we try null move pruning?
            if (allowNullMove && depth >= 2 && !isChecked && window.CanFailHigh(color))
            {
                const int R = 2;
                //evaluate the position at reduced depth with a null-window around beta
                SearchWindow beta = window.GetUpperBound(color);
                //skip making a move
                Board nullChild = Playmaker.PlayNullMove(position);
                (int score, _) = EvalPositionTT(nullChild, ply + 1, depth - R - 1, beta);
                //is the evaluation "too good" despite null-move? then don't waste time on a branch that is likely going to fail-high
                //if the static eval look much worse the alpha also skip it
                if (window.FailHigh(score, color))
                {
                    return(score, Array.Empty <Move>());
                }
            }

            //do a regular expansion...
            Move[] pv            = Array.Empty <Move>();
            int    expandedNodes = 0;

            foreach ((Move move, Board child) in Playmaker.Play(position, depth, _killers, _history))
            {
                expandedNodes++;
                bool interesting = expandedNodes == 1 || isChecked || child.IsChecked(child.SideToMove);

                //some near the leaves that appear hopeless can be skipped without evaluation
                if (depth <= 4 && !interesting)
                {
                    //if the static eval look much worse the alpha also skip it
                    int futilityMargin = (int)color * depth * MAX_GAIN_PER_PLY;
                    if (window.FailLow(child.Score + futilityMargin, color))
                    {
                        continue;
                    }
                }

                //moves after the PV node are unlikely to raise alpha.
                //avoid a full evaluation by searching with a null-sized window around alpha first
                //...we expect it to fail low but if it does not we have to research it!
                if (depth >= 2 && expandedNodes > 1)
                {
                    //non-tactical late moves are searched at a reduced depth to make this test even faster!
                    int R = (interesting || expandedNodes < 4) ? 0 : 2;
                    (int score, _) = EvalPositionTT(child, ply + 1, depth - R - 1, window.GetLowerBound(color));
                    if (window.FailLow(score, color))
                    {
                        continue;
                    }
                }

                //this move is expected to raise alpha so we search it at full depth!
                var eval = EvalPositionTT(child, ply + 1, depth - 1, window);
                if (window.FailLow(eval.Score, color))
                {
                    _history.Bad(position, move, depth);
                    continue;
                }

                //the position has a new best move and score!
                Transpositions.Store(position.ZobristHash, depth, ply, window, eval.Score, move);
                //set the PV to this move, followed by the PV of the childnode
                pv = Merge(move, eval.PV);
                //...and maybe we even get a beta cutoff
                if (window.Cut(eval.Score, color))
                {
                    //we remember killers like hat!
                    if (position[move.ToSquare] == Piece.None)
                    {
                        _history.Good(position, move, depth);
                        _killers.Add(move, depth);
                    }

                    return(GetScore(window, color), pv);
                }
            }

            //checkmate or draw?
            if (expandedNodes == 0)
            {
                return(position.IsChecked(color) ? Evaluation.Checkmate(color, ply) : 0, Array.Empty <Move>());
            }

            return(GetScore(window, color), pv);
        }