/// <summary>Performs the iterative deepening search.</summary> SearchResult Search(StateType initialState, SearchLimiter limiter, out Node <StateType, ActionType> solution, bool bidirectional) { if (limiter != null) { limiter.Start(); } solution = new Node <StateType, ActionType>(); for (DepthLimit = 1; limiter == null || !limiter.LimitReached; DepthLimit++) { SearchResult result = bidirectional ? base.FinishBidirectionalSearch(initialState, limiter, out solution) : base.FinishSearch(initialState, limiter, out solution); // if the search completed without reaching any limit, then we're done if (result != SearchResult.LimitReached) { return(result); } // if the depth limit is about to wrap around, there's no point in continuing if (DepthLimit == int.MaxValue) { return(SearchResult.LimitReached); } } return(SearchResult.LimitReached); }
/// <summary>This method does all of the work of running a forward-only search except for starting the limiter, if /// any. /// </summary> protected SearchResult FinishSearch(StateType initialState, SearchLimiter limiter, out Node <StateType, ActionType> solution) { solution = new Node <StateType, ActionType>(); IQueue <Node <StateType, ActionType> > fringe = CreateQueue(); Dictionary <StateType, Node <StateType, ActionType> > statesSeen = EliminateDuplicateStates ? new Dictionary <StateType, Node <StateType, ActionType> >() : null; bool limitHit = false; fringe.Enqueue(MakeNode(initialState)); while (fringe.Count != 0) { if (limiter != null && limiter.LimitReached) { limitHit = true; break; } Node <StateType, ActionType> node = fringe.Dequeue(); if (Problem.IsGoal(node.State)) { solution = node; return(SearchResult.Success); } limitHit |= TryEnqueueNodes(fringe, node, statesSeen, false); } return(limitHit ? SearchResult.LimitReached : SearchResult.Failed); }
/// <include file="documentation.xml" path="/AI/Search/ISearch/Search_State/*"/> public override SearchResult Search(StateType initialState, SearchLimiter limiter, out Node <StateType, ActionType> solution) { if (limiter != null) { limiter.Start(); } return(FinishSearch(initialState, limiter, out solution)); }
/// <include file="documentation.xml" path="/AI/Search/IBidirectionalGraphSearch/BidirectionalSearch_State/*"/> public override SearchResult BidirectionalSearch(StateType initialState, SearchLimiter limiter, out Node <StateType, ActionType> solution) { AssertBidirectionallySearchable(); if (limiter != null) { limiter.Start(); } return(FinishBidirectionalSearch(initialState, limiter, out solution)); }
/// <include file="documentation.xml" path="/AI/Search/GameSearchBase/IterativeDeepeningSearch/*"/> /// <include file="documentation.xml" path="/AI/Search/ISearch/SearchCommon/param[@name='limiter']"/> /// <include file="documentation.xml" path="/AI/Search/ISearch/Search_State/param[@name='initialState']"/> public SearchResult IterativeDeepeningSearch(StateType initialState, SearchLimiter limiter, out StateActionPair <StateType, ActionType> solution) { SearchResult result; int userDepthLimit = DepthLimit; // save the original depth limit so we can restore it later if (limiter == null) // if we have an no limit, we might as well do a regular search { DepthLimit = Infinite; // with unlimited depth because we have unlimited time result = Search(initialState, limiter, out solution); } else { PrepareToStartSearch(initialState); // otherwise, verify that the search is valid if (limiter != null) { limiter.Start(); } BeginIterativeDeepeningSearch(initialState); result = SearchResult.Failed; // assume that we couldn't complete a single iteration solution = new StateActionPair <StateType, ActionType>(); // gradually increase the depth limit, starting from 1 for (DepthLimit = 1; ; DepthLimit = DepthLimit == int.MaxValue ? Infinite : DepthLimit + 1) { // start a new search with the given depth limit, and run it until it completes or the time expires StateActionPair <StateType, ActionType> currentSolution; SearchResult currentResult = PerformSearch(initialState, limiter, out currentSolution); // Failed, in this case, means that the search couldn't complete because of the limiter, while LimitReached // means that the search completed but was limited by the depth limit if (currentResult == SearchResult.Failed) { break; } // the search completed, so store the result and solution result = currentResult; solution = currentSolution; // if the search was not limited by depth, increasing the depth won't help, so we're done if (currentResult != SearchResult.LimitReached) { break; } } EndIterativeDeepeningSearch(); } DepthLimit = userDepthLimit; // restore the previous depth limit return(result); }
/// <include file="documentation.xml" path="/AI/Search/ISearch/Search_State/*"/> public override SearchResult Search(StateType initialState, SearchLimiter limiter, out Node <StateType, ActionType> solution) { if (limiter != null) { limiter.Start(); } solution = new Node <StateType, ActionType>(); float costLimit = Problem.GetHeuristic(initialState); while (true) { IQueue <Node <StateType, ActionType> > fringe = CreateQueue(); Dictionary <StateType, Node <StateType, ActionType> > statesSeen = EliminateDuplicateStates ? new Dictionary <StateType, Node <StateType, ActionType> >() : null; float newLimit = float.PositiveInfinity; bool depthLimitHit = false; fringe.Enqueue(MakeNode(initialState)); while (fringe.Count != 0) { if (limiter != null && limiter.LimitReached) { return(SearchResult.LimitReached); } Node <StateType, ActionType> node = fringe.Dequeue(); if (Problem.IsGoal(node.State)) { solution = node; return(SearchResult.Success); } float cost = node.PathCost + node.HeuristicCost; if (cost <= costLimit) { depthLimitHit |= TryEnqueueNodes(fringe, node, statesSeen, false); } else if (cost < newLimit) { newLimit = cost; // the new limit is the lowest of those that exceeded the old limit } } if (float.IsInfinity(newLimit)) { return(depthLimitHit ? SearchResult.LimitReached : SearchResult.Failed); } costLimit = newLimit; } }
/// <include file="documentation.xml" path="/AI/Search/ISearch/Search_State/*"/> public sealed override SearchResult Search(StateType initialState, SearchLimiter limiter, out StateActionPair <StateType, ActionType> solution) { PrepareToStartSearch(initialState); if (limiter != null) { limiter.Start(); } // from PerformSearch(), Failed means that the limiter caused the search to abort, while LimitReached means that the // search completed, but was truncated by the depth limit. we'll convert Failed to LimitReached here. SearchResult result = PerformSearch(initialState, limiter, out solution); return(result == SearchResult.Failed ? SearchResult.LimitReached : result); }
/// <include file="documentation.xml" path="/AI/Search/GameSearchBase/PerformSearch/*"/> protected override SearchResult PerformSearch(StateType initialState, SearchLimiter limiter, out StateActionPair <StateType, ActionType> solution) { solution = new StateActionPair <StateType, ActionType>(); float bestUtility = float.NegativeInfinity; int player = Game.GetPlayerToMove(initialState); this.limiter = limiter; depthLimitHit = searchAborted = false; foreach (Move <StateType, ActionType> move in Game.GetSuccessors(initialState)) // for each move available { // get the estimated utilities of the move for all players float[] utilities = GetExpectedUtilities(move.State, 1); if (searchAborted) { return(SearchResult.Failed); // if the limiter caused an abort, just return immediately } // if the estimated utility of this move for the player moving at the root is the best yet, save it if (utilities[player] > bestUtility) { bestUtility = utilities[player]; solution = new StateActionPair <StateType, ActionType>(move.State, move.Action); if (bestUtility == MaxUtility) // if the given move is optimal, we don't need to search further { // if a terminal state was reached in the line of search that led to this maximal utility, then we don't // need another round of iterative deepening search because there's no doubt about this particular move, // and since this move is optimal, that's all that matters if (wasTerminal) { depthLimitHit = false; } break; } } } this.limiter = null; // release the limiter return(depthLimitHit ? SearchResult.LimitReached : SearchResult.Success); }
/// <include file="documentation.xml" path="/AI/Search/GameSearchBase/PerformSearch/*"/> protected override SearchResult PerformSearch(StateType initialState, SearchLimiter limiter, out StateActionPair <StateType, ActionType> solution) { solution = new StateActionPair <StateType, ActionType>(); // If we're performing an iterative deepening search, rootUtilities and rootSuccessors will be non-null. In that // case, we should use them to store utilities and retrieve successor nodes. The purpose is to allow some state, // in particular move ordering at the root, to be retained between iterations of the iterative deepening search. // The rootUtilities array is used to sort the rootSuccessors array so that the best moves from the previous // iteration can be tried first. // // TODO: should we extend this idea to store the best moves from each ply in the search? // TODO: we should probably also implement the killer heuristic and/or history heuristics (this requires some // more complex communication between the game and the search) // TODO: we should implement the scout part of negascout // this url is quite good: http://www.fierz.ch/strategy.htm if (rootUtilities != null) { for (int i = 0; i < rootUtilities.Length; i++) { rootUtilities[i] = float.PositiveInfinity; } } this.limiter = limiter; depthLimitHit = wasTerminal = searchAborted = false; float bestUtility = float.NegativeInfinity, best0 = float.NegativeInfinity, best1 = float.NegativeInfinity; int player = Game.GetPlayerToMove(initialState); int index = 0; // the index of the successor within rootSuccessor that we're currently examining foreach (Move <StateType, ActionType> move in rootSuccessors != null ? rootSuccessors : Game.GetSuccessors(initialState)) { // invoke the the alpha-beta search to estimate the utility value of the move's ending state float utility = GetUtilityEstimate(move.State, best0, best1, 1, player); // we'll use Failed to indicate that the limiter caused the search to abort if (searchAborted) { return(SearchResult.Failed); } // the alpha-beta search returns the utility of the move from the perspective of the player whose turn it is in // the state resulting from the move. so find out which player that is. int otherPlayer = Game.GetPlayerToMove(move.State); // if the player moving at the successor is not the same as the player moving at the root, then we need to // reverse the utility. however, if the move led to a chance node, represented as a "player" of -1, the utility // is already from the correct viewpoint, so we don't need to reverse it. if (player != otherPlayer && otherPlayer != -1) { utility = -utility; } // if we're doing an iterative deepening search, store the utility of this move if (rootUtilities != null) { rootUtilities[index++] = utility; } // if the move is better than whatever we've got so far, store the move as the new best move if (utility > bestUtility) { bestUtility = utility; // we'll store the best move in the "solution" parameter solution = new StateActionPair <StateType, ActionType>(move.State, move.Action); // also, if the move was so good that it got the maximum utility, there's no point in searching further, so // we'll end the search immediately if (utility == MaxUtility) { // if a terminal state was reached in the line of search that led to this maximal utility, then we don't // need another round of iterative deepening search because there's no doubt about this particular move, // and since this move is optimal, that's all that matters if (wasTerminal) { depthLimitHit = false; } break; } // update the alpha-beta values for the player at the root if (player == 0) { if (utility > best0) { best0 = utility; } } else { if (utility > best1) { best1 = utility; } } } } // if we're doing iterative deepening, sort the moves by utility if (rootSuccessors != null) { SortRootSuccessors(); } this.limiter = null; // release the limiter return(depthLimitHit ? SearchResult.LimitReached : SearchResult.Success); }
/// <include file="documentation.xml" path="/AI/Search/IBidirectionalGraphSearch/BidirectionalSearch/*"/> public SearchResult BidirectionalSearch(SearchLimiter limiter, out Node <StateType, ActionType> solution) { return(BidirectionalSearch(Problem.GetInitialState(), limiter, out solution)); }
/// <include file="documentation.xml" path="/AI/Search/ISearch/Search/*"/> public sealed override SearchResult Search(SearchLimiter limiter, out Node <StateType, ActionType> solution) { return(Search(Problem.GetInitialState(), limiter, out solution)); }
/// <include file="documentation.xml" path="/AI/Search/GameSearchBase/PerformSearch/*"/> protected abstract SearchResult PerformSearch(StateType initialState, SearchLimiter limiter, out StateActionPair <StateType, ActionType> solution);
/// <include file="documentation.xml" path="/AI/Search/GameSearchBase/IterativeDeepeningSearch/*"/> /// <include file="documentation.xml" path="/AI/Search/ISearch/SearchCommon/param[@name='limiter']"/> public SearchResult IterativeDeepeningSearch(SearchLimiter limiter, out StateActionPair <StateType, ActionType> solution) { return(IterativeDeepeningSearch(game.GetInitialState(), limiter, out solution)); }
/// <summary>This method does all of the work of running a bidirectional search except for ensuring that searching /// bidirectionally is safe and starting the limiter, if any. /// </summary> protected SearchResult FinishBidirectionalSearch(StateType initialState, SearchLimiter limiter, out Node <StateType, ActionType> solution) { solution = new Node <StateType, ActionType>(); // the queues into which nodes will be placed IQueue <Node <StateType, ActionType> > leftFringe, rightFringe; // dictionaries keeping track of states that have been visited before, and the best paths to them. Dictionary <StateType, Node <StateType, ActionType> > leftStates, rightStates; // initialize the search leftFringe = CreateQueue(); rightFringe = CreateQueue(); leftStates = new Dictionary <StateType, Node <StateType, ActionType> >(); rightStates = EliminateDuplicateStates ? new Dictionary <StateType, Node <StateType, ActionType> >() : null; leftFringe.Enqueue(MakeNode(initialState)); foreach (StateType goalState in BidiProblem.GetGoalStates()) { rightFringe.Enqueue(MakeNode(goalState)); } bool limitHit = false; // keeps track of whether the search has hit some limit preventing it from exploring further while (leftFringe.Count != 0 && rightFringe.Count != 0) // while there are still nodes in both open lists { if (limiter != null && limiter.LimitReached) { limitHit = true; break; } // get a node from the forward search Node <StateType, ActionType> leftNode = leftFringe.Dequeue(); if (Problem.IsGoal(leftNode.State)) // if it's a goal, we've found a forward-only solution, so return it { solution = leftNode; return(SearchResult.Success); } // this proceeds much like standard search... if (EliminateDuplicateStates) { limitHit |= TryEnqueueNodes(leftFringe, leftNode, leftStates, false); } else if (leftNode.Depth >= DepthLimit) { limitHit = true; } else { foreach (Node <StateType, ActionType> child in GetNodes(leftNode, null, false)) { leftFringe.Enqueue(child); Node <StateType, ActionType> existingChild; if (!leftStates.TryGetValue(child.State, out existingChild) || IsNodeBetter(child, existingChild)) { leftStates[child.State] = child; } } } // now take a node from the reverse search Node <StateType, ActionType> rightNode = rightFringe.Dequeue(); // if it has been visited by the forward search, we've found a link between the two if (leftStates.TryGetValue(rightNode.State, out leftNode)) { solution = BuildBidirectionalSolution(leftNode, rightNode); // convert it into a forward-only solution return(SearchResult.Success); // and return it } limitHit |= TryEnqueueNodes(rightFringe, rightNode, rightStates, true); } // if it gets here, we couldn't find a solution return(limitHit ? SearchResult.LimitReached : SearchResult.Failed); }
/// <include file="documentation.xml" path="/AI/Search/ISearch/Search/*"/> public sealed override SearchResult Search(SearchLimiter limiter, out StateActionPair <StateType, ActionType> solution) { return(Search(game.GetInitialState(), limiter, out solution)); }
/// <include file="documentation.xml" path="/AI/Search/ISearch/Search_State/*"/> public override SearchResult Search(StateType initialState, SearchLimiter limiter, out Node <StateType, ActionType> solution) { return(Search(initialState, limiter, out solution, false)); }
/// <include file="documentation.xml" path="/AI/Search/IBidirectionalGraphSearch/BidirectionalSearch_State/*"/> public abstract SearchResult BidirectionalSearch(StateType initialState, SearchLimiter limiter, out Node <StateType, ActionType> solution);