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)); } }
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); } }