Beispiel #1
0
        public int GoPerft(Position b, int depth)
        {
            if (depth <= 0)
            {
                return(1);
            }

            var ret = 0;

            if (_perftTable != null && _perftTable.TryGetEntry(b.ZobristHash, b.GamePly, out int count))
            {
                return(count);
            }

            List <Move> moves = _moveListCache.Get(depth);

            moves.Clear();

            _moveGenerator.Generate(moves, b);

            var tmpPosition = _positionCache.Get(depth);

            foreach (var move in moves)
            {
                var nextPosition = Position.MakeMove(tmpPosition, move, b);

                // check move legality if using a pseudolegal move generator
                if (!_moveGenerator.OnlyLegalMoves && nextPosition.MovedIntoCheck())
                {
                    continue;
                }

                ret += GoPerft(nextPosition, depth - 1);
            }

            if (_perftTable != null)
            {
                _perftTable.SetEntry(b.ZobristHash, b.GamePly, ret);
            }

            return(ret);
        }
Beispiel #2
0
        public static bool TryGetMoveFromCoordinateString(
            IMoveGenerator moveGenerator,
            Position b,
            string coordinateString,
            out Move move)
        {
            List <Move> moves = new List <Move>();

            moveGenerator.Generate(moves, b);

            foreach (var tmpMove in moves)
            {
                var potentialCoordinateString = CoordinateStringFromMove(tmpMove);
                if (!string.Equals(potentialCoordinateString, coordinateString, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                // if a pseudolegal move generator, then we need to make sure that the move we're attempting is even legal
                if (!moveGenerator.OnlyLegalMoves)
                {
                    var testingBoard = Position.MakeMove(new Position(), tmpMove, b);

                    // if we moved into check, clearly it was an invalid move
                    if (testingBoard.MovedIntoCheck())
                    {
                        continue;
                    }
                }

                move = tmpMove;
                return(true);
            }

            move = default; // in practice, this is fine, because callers should never use this if returning false
            return(false);
        }
Beispiel #3
0
        public Score InnerSearch(Position position, int depth, Score alpha, Score beta, int ply)
        {
            _statistics.QSearchNonRootNodes++;

            _statistics.MaxPly = Math.Max(_statistics.MaxPly, ply);
            _statistics.Evaluations++;
            var standPatEval = _evaluator.Evaluate(position);

            // correct so side to move wants to maximize value
            if (position.SideToMove == Color.Black)
            {
                standPatEval = -standPatEval;
            }

            if (standPatEval >= beta)
            {
                return(standPatEval);
            }

            bool raisedAlpha = false;

            // TODO: what to do if standpat does/doesn't raise alpha???
            if (standPatEval > alpha)
            {
                raisedAlpha = true;
                alpha       = standPatEval;
            }

            // for now, let's do this to prevent qsearch explosion
            // TODO: should we do something in order to return a conservatively low value?
            if (depth > MaxDepth)
            {
                return(standPatEval);
            }

            var moves = _moveListCache.Get(ply);

            moves.Clear();

            bool moveGenIsStrictlyLegal;
            IEnumerable <Move> moveEnumerator;

            if (position.InCheck())
            {
                // if we're in check, we need to try all moves
                _moveGenerator.Generate(moves, position);
                moveGenIsStrictlyLegal = _moveGenerator.OnlyLegalMoves;
                moveEnumerator         = _checkEvasionsMoveOrderer.Order(moves, position);
            }
            else
            {
                // if not in check, then only look at captures and promotions
                _moveGenerator.Generate(moves, position);
                moveGenIsStrictlyLegal = _moveGenerator.OnlyLegalMoves;

                // since we don't have a qsearch movegen, we need to remove the non-applicable moves
                for (int i = moves.Count - 1; i >= 0; i--)
                {
                    var move = moves[i];
                    if (!move.MoveType.HasFlag(MoveType.Capture) && !move.MoveType.HasFlag(MoveType.Promotion))
                    {
                        moves.QuickRemoveAt(i);
                    }
                }

                // TODO: we should use SEE to determine which moves to keep!

                moveEnumerator = _quiescenceMoveOrderer.Order(moves, position);
            }

            int moveNumber           = 0;
            var cachedPositionObject = _positionCache.Get(ply);

            foreach (var move in moveEnumerator)
            {
                var nextPosition = Position.MakeMove(cachedPositionObject, move, position);

                if (!moveGenIsStrictlyLegal && nextPosition.MovedIntoCheck())
                {
                    continue;
                }

                moveNumber++;

                var eval = -InnerSearch(nextPosition, depth + 1, -beta, -alpha, ply + 1);

                if (eval >= beta)
                {
                    _statistics.QSearchCutNodes++;
                    _statistics.QSearchCutMoveMisses += moveNumber - 1; // don't include the current move in the move misses calculation
                    return(eval);
                }

                if (eval > alpha)
                {
                    raisedAlpha = true;
                    alpha       = eval;
                }
            }

            // TODO: if there were no legal moves, then maybe we should do a terminal check? it's dangerous not to, but maybe kinda slow to do this

            if (raisedAlpha)
            {
                // TODO: eventually commit to triangular pv table? Maybe?
                _statistics.QSearchPVNodes++;
            }
            else
            {
                _statistics.QSearchAllNodes++;
            }

            return(alpha);
        }
Beispiel #4
0
        public (Move move, Statistics statistics) Search(Position position, ITimeStrategy timeStrategy, Statistics.PrintInfoDelegate printInfoDelegate)
        {
            // setup
            _timeStrategy = timeStrategy;
            _enteredCount = 0;
            _statistics   = new Statistics();
            _statistics.Timer.Start();
            _statistics.SideCalculating = position.SideToMove;
            _pvTable = new TriangularPVTable(); // TODO: should we be passing this in instead?
            _qSearch.StartSearch(_timeStrategy, _pvTable, _statistics);

            Move bestMove = Move.Null;
            var  moveList = new List <Move>();

            _moveGenerator.Generate(moveList, position);

            var cachedPositionObject = new Position();

            if (!_moveGenerator.OnlyLegalMoves)
            {
                // remove all illegal moves
                for (int i = moveList.Count - 1; i >= 0; i--)
                {
                    var move            = moveList[i];
                    var testingPosition = Position.MakeMove(cachedPositionObject, move, position);
                    if (testingPosition.MovedIntoCheck())
                    {
                        moveList.QuickRemoveAt(i);
                    }
                }
            }

            var scoredMoveList = new List <ScoredMove>();

            foreach (var move in moveList)
            {
                scoredMoveList.Add(new ScoredMove(move, 0));
            }

            // TODO: instead of looping here, why don't we only loop in InnerSearch and get the best value from the PV table?
            // That would simplify things a lot.
            // However, if we have aspiration windows and we get a beta cutoff, how do we retrieve the best move? Or is that even required?
            // The PV table would probably need to handle that case.
            for (int depth = 1;; depth++)
            {
                _statistics.NormalNonLeafNodes++;

                Score alpha = Score.MinValue;
                Score beta  = Score.MaxValue;

                for (int i = 0; i < scoredMoveList.Count; i++)
                {
                    var move = scoredMoveList[i].Move;

                    var nextPosition = Position.MakeMove(cachedPositionObject, move, position);
                    _pvTable.Add(move, 0);

                    _statistics.CurrentDepth = depth;
                    var nextEval = -InnerSearch(nextPosition, depth - 1, false, -beta, -alpha, 1);
                    if (nextEval == alpha)
                    {
                        nextEval -= 1;
                    }

                    scoredMoveList[i] = new ScoredMove(move, nextEval);

                    if (timeStrategy.ShouldStop(_statistics))
                    {
                        _statistics.Timer.Stop();
                        return(bestMove, _statistics);
                    }

                    if (nextEval > alpha)
                    {
                        alpha    = nextEval;
                        bestMove = move; // this is safe, because we search the best move from last pass first in the next pass
                        _pvTable.Commit(0);
                        _statistics.BestLine = _pvTable.GetBestLine();
                        if (position.SideToMove == Color.White)
                        {
                            _statistics.CurrentScore = alpha;
                        }
                        else
                        {
                            _statistics.CurrentScore = -alpha;
                        }

                        printInfoDelegate(_statistics);
                    }
                }

                // if we don't do this, we'll never return from a terminal position (with no moves)
                if (timeStrategy.ShouldStop(_statistics))
                {
                    _statistics.Timer.Stop();
                    return(bestMove, _statistics);
                }

                // sort the moves for next pass
                scoredMoveList.Sort((m1, m2) => (int)(m2.Score - m1.Score));
            }
        }
Beispiel #5
0
        public (Move move, Statistics statistics) Search(Position position, ITimeStrategy timeStrategy, Statistics.PrintInfoDelegate printInfoDelegate)
        {
            // setup
            _timeStrategy = timeStrategy;
            _enteredCount = 0;
            _statistics   = new Statistics();
            _statistics.Timer.Start();
            _statistics.SideCalculating = position.SideToMove;
            _pvTable = new TriangularPVTable(); // TODO: should we be passing this in instead?
            _qSearch.StartSearch(_timeStrategy, _pvTable, _statistics);

            Move bestMove = Move.Null;
            var  moveList = new List <Move>();

            _moveGenerator.Generate(moveList, position);

            // TODO: instead of looping here, why don't we only loop in InnerSearch and get the best value from the PV table?
            // That would simplify things a lot.
            // However, if we have aspiration windows and we get a beta cutoff, how do we retrieve the best move? Or is that even required?
            // The PV table would probably need to handle that case.
            var tmpBestMove = Move.Null;

            for (int depth = 1;; depth++)
            {
                _statistics.NormalNonLeafNodes++;

                Score alpha = Score.MinValue;
                Score beta  = Score.MaxValue;

                var cachedPositionObject = new Position();
                foreach (var move in moveList)
                {
                    var nextPosition = Position.MakeMove(cachedPositionObject, move, position);

                    if (!_moveGenerator.OnlyLegalMoves && nextPosition.MovedIntoCheck())
                    {
                        continue;
                    }

                    _pvTable.Add(move, 0);

                    _statistics.CurrentDepth = depth;
                    var nextEval = -InnerSearch(nextPosition, depth - 1, -beta, -alpha, 1);

                    if (timeStrategy.ShouldStop(_statistics))
                    {
                        _statistics.Timer.Stop();
                        return(bestMove, _statistics);
                    }

                    if (nextEval > alpha)
                    {
                        alpha       = nextEval;
                        tmpBestMove = move;
                        _pvTable.Commit(0);
                    }
                }

                // only committing best move after a full search
                // TODO: this will go away once we're no longer doing a search at this level
                bestMove             = tmpBestMove;
                _statistics.BestLine = _pvTable.GetBestLine();
                if (position.SideToMove == Color.White)
                {
                    _statistics.CurrentScore = alpha;
                }
                else
                {
                    _statistics.CurrentScore = -alpha;
                }

                // if we don't do this, we'll never return from a terminal position (with no moves)
                if (timeStrategy.ShouldStop(_statistics))
                {
                    _statistics.Timer.Stop();
                    return(bestMove, _statistics);
                }

                // if we didn't return, let's print some info!
                printInfoDelegate(_statistics);
            }
        }