/// <summary> /// Recursively carries out a full width alpha beta search untill the bottom is reached. /// From then a Quiescent search is carried out to avoid horrison effects. /// </summary> /// <param name="alpha">This is the best score that can be forced by some means. Anything worth less than this is of no use, because there is a strategy that is known to result in a score of alpha.</param> /// <param name="beta">Beta is the worst thing that the opponent has to endure. If the search finds something that returns a score of beta or better, it's too good, so the side to move is not going to get a chance to use this strategy.</param> /// <param name="remainingDepth">Remaining depth to search before bottom is reached.</param> /// <param name="allowNullMove">Is a null move allowed at this search depth.</param> /// <param name="allowTranspotitionTable">Is it allowed to use the transpotition table to probe for scores.</param> /// <returns>The score of a board.</returns> private int AlphaBetaSearch(int alpha, int beta, int remainingDepth, bool allowNullMove, bool allowTranspotitionTable) { if (m_searchRollback) { return(beta); } //At interval check if time is up and rollback if so. if (m_nodesVisited == m_nextRollbackCheck) { if (AbortingSearch()) { m_alphaBetaTable.Close(); m_quiescentTable.Close(); m_searchRollback = true; return(beta); } m_nextRollbackCheck += ROLLBACK_INTERVAL_COUNT; } ++m_nodesVisited; int score = 0; Move pvMove = null; AlphaBetaFlag alphaBetaFlag = AlphaBetaFlag.AlphaScore; if (m_board.State.NonHitAndPawnMovesPlayed >= 100) { return(0); } if (m_board.BoardHistoryFrequency() >= 3) { return(0); } //don't allow transposition table before both players has passed the above repetetion draw checks. if (allowTranspotitionTable && (m_searchDepth - remainingDepth) > 0) { if (m_alphaBetaTable.ProbeScore(m_board.BoardHash(false), alpha, beta, remainingDepth, ref score)) { if (Math.Abs(score) != Math.Abs(m_evaluator.MateValue)) //don't probe a mate value as this actuall might be a mate far down in the tree, and we want to find the mate at lowest depth. { return(score); } } } if (remainingDepth == 0) { score = QuiescentSearch(alpha, beta); if (allowTranspotitionTable) { m_alphaBetaTable.RecordHash(m_board.BoardHash(false), AlphaBetaFlag.ExactScore, remainingDepth, score, pvMove); } return(score); } if (allowNullMove) { //Carry out a null move which is a move where nothing is actually moved. This makes the opponent move //twice in a row. The idea is then to search to a reduced depth and if nothing good come out of this //(for the opponent) the move is considered garbage and we cut it off. NullMove nullMove = new NullMove(m_board, m_evaluator); if (nullMove.Execute()) { int remDepth = (remainingDepth > NULL_REDUCTION) ? remainingDepth - 1 - NULL_REDUCTION : 0; score = -AlphaBetaSearch(-beta, -beta + 1, remDepth, false, false); nullMove.UnExecute(); if (score >= beta) { return(beta); } } } MoveOrganizer currentMoves = new MoveOrganizer(m_alphaBetaTable.ProbePvMove(m_board.BoardHash(false))); m_board.GeneratePseudoLegalMoves(currentMoves); currentMoves.Sort(m_captureMoveCompare); bool validMoveFound = false; foreach (Move move in currentMoves) { if (move.Execute(m_board)) { validMoveFound = true; if (pvMove == null) { score = -AlphaBetaSearch(-beta, -alpha, remainingDepth - 1, true, allowTranspotitionTable); } else { score = -AlphaBetaSearch(-alpha - 1, -alpha, remainingDepth - 1, true, false); if ((score > alpha) && (score < beta)) { score = -AlphaBetaSearch(-beta, -alpha, remainingDepth - 1, true, allowTranspotitionTable); } } move.UnExecute(m_board); if (score >= beta) { if (allowTranspotitionTable) { m_alphaBetaTable.RecordHash(m_board.BoardHash(false), AlphaBetaFlag.BetaScore, remainingDepth, beta, pvMove); } return(beta); } if (score > alpha) { alpha = score; pvMove = move; alphaBetaFlag = AlphaBetaFlag.ExactScore; } } } if (!validMoveFound) { if (m_board.ColorToPlayIsCheck()) { alpha = m_evaluator.MateValue; //Checkmate } else { alpha = 0; //Stalemate } } if (allowTranspotitionTable) { m_alphaBetaTable.RecordHash(m_board.BoardHash(false), alphaBetaFlag, remainingDepth, alpha, pvMove); } return(alpha); }
/// <summary> /// Carries out a narrow Quiescent search where only capure moves are searched. /// </summary> /// <param name="alpha">Current alpha value.</param> /// <param name="beta">Current beta value.</param> /// <returns>The score of a board.</returns> private int QuiescentSearch(int alpha, int beta) { if (m_searchRollback) { return(beta); } //At interval check if time is up and rollback if so. if (m_nodesVisited == m_nextRollbackCheck) { if (AbortingSearch()) { m_alphaBetaTable.Close(); m_quiescentTable.Close(); m_searchRollback = true; return(beta); } m_nextRollbackCheck += ROLLBACK_INTERVAL_COUNT; } ++m_nodesVisited; int score = 0; QuiescentFlag quiescentFlag = QuiescentFlag.AlphaScore; if (m_quiescentTable.ProbeScore(m_board.BoardHash(false), alpha, beta, ref score)) { return(score); } score = m_evaluator.EvaluatePosition(m_board); if (score >= beta) { m_quiescentTable.RecordHash(m_board.BoardHash(false), QuiescentFlag.BetaScore, beta); return(beta); } if (score > alpha) { alpha = score; quiescentFlag = QuiescentFlag.ExactScore; } MoveOrganizer currentMoves = new MoveOrganizer(); m_board.GeneratePseudoLegalMoves(currentMoves); currentMoves.Sort(m_captureMoveCompare); foreach (Move move in currentMoves) { if (!move.IsCaptureMove) { break; } if (move.Execute(m_board)) { score = -QuiescentSearch(-beta, -alpha); move.UnExecute(m_board); if (score >= beta) { m_quiescentTable.RecordHash(m_board.BoardHash(false), QuiescentFlag.BetaScore, beta); return(beta); } if (score > alpha) { alpha = score; quiescentFlag = QuiescentFlag.ExactScore; } } } m_quiescentTable.RecordHash(m_board.BoardHash(false), quiescentFlag, alpha); return(alpha); }
/// <summary> /// Starts a iterative depending search, returning the move engine is to play. /// </summary> /// <param name="board">Board to find move for.</param> /// <param name="moveOrganizer">move organizer to where best move is to be found in.</param> /// <param name="aimSearchTime">The search time to aim for. Actual search time might be between this and maxSearchTime.</param> /// <param name="maxSearchTime">The maximum amount of seconds to seach for move.</param> /// <param name="maxSearchDepth">Maximum depth for full width search.</param> /// <returns>Move considered best.</returns> public Move FindBestMove(Board board, MoveOrganizer moveOrganizer, TimeSpan aimSearchTime, TimeSpan maxSearchTime, int maxSearchDepth) { moveOrganizer.Sort(m_captureMoveCompare); List <ScoreMove> scoredMoves = new List <ScoreMove>(moveOrganizer.Count); foreach (Move move in moveOrganizer) { scoredMoves.Add(new ScoreMove(move)); } if (scoredMoves.Count == 1) { return(scoredMoves[0].Move); } m_alphaBetaTable.Open(); m_quiescentTable.Open(); m_searchRollback = m_externalAbort = false; m_board = board; m_searchStartTime = DateTime.Now; m_searchMaxTime = maxSearchTime; m_nodesVisited = 0; m_nextRollbackCheck = ROLLBACK_INTERVAL_COUNT; bool continueSearch = true; for (m_searchDepth = 0; m_searchDepth <= maxSearchDepth && continueSearch; ++m_searchDepth) { int movesSearched = 0; int alpha = m_evaluator.AlphaValue; int beta = -m_evaluator.AlphaValue; foreach (ScoreMove scoreMove in scoredMoves) { scoreMove.Move.Execute(m_board); if (movesSearched == 0) { scoreMove.TemporaryScore = -AlphaBetaSearch(-beta, -alpha, m_searchDepth, false, true); } else //search in the same way as we do with pv moves by narrowing together alpha and beta { scoreMove.TemporaryScore = -AlphaBetaSearch(-alpha - 1, -alpha, m_searchDepth, false, false); if ((scoreMove.TemporaryScore > alpha) && (scoreMove.TemporaryScore < beta)) { scoreMove.TemporaryScore = -AlphaBetaSearch(-beta, -alpha, m_searchDepth, false, true); } } scoreMove.Move.UnExecute(m_board); if (scoreMove.TemporaryScore > alpha) { alpha = scoreMove.TemporaryScore - 1; } if (m_searchRollback || m_externalAbort || (scoreMove.ValidScore >= -m_evaluator.MateValue)) { continueSearch = false; break; } ++movesSearched; } if (movesSearched == scoredMoves.Count) { foreach (ScoreMove scoreMove in scoredMoves) { scoreMove.ValidScore = scoreMove.TemporaryScore; } } scoredMoves.Sort(m_scoreMoveCompare); OutputWriter.Write("Depth: " + m_searchDepth + ", Score: " + scoredMoves[0].ValidScore); if ((DateTime.Now - m_searchStartTime) >= aimSearchTime) { continueSearch = false; } } OutputWriter.Write("Nodes: " + m_nodesVisited); OutputWriter.Write("Time: " + (DateTime.Now - m_searchStartTime).ToString()); if (m_externalAbort) { OutputWriter.Write("Search aborted"); return(null); } int canditates; for (canditates = 1; canditates < scoredMoves.Count; ++canditates) { if (m_scoreMoveCompare.Compare(scoredMoves[0], scoredMoves[canditates]) != 0) { break; } } Random selector = new Random(); int idx = selector.Next(canditates); OutputWriter.Write("Selecting: " + scoredMoves[idx].Move.From + " " + scoredMoves[idx].Move.To + " from " + scoredMoves.Count + " candidates"); return(scoredMoves[idx].Move); }