namespace SpriteCompiler.AI { using System.Collections.Generic; using System.Linq; using Queue; /// /// An abstract description of a state-space search. Specific algorthims are determined by /// how the nodes are expanded, evaluated and enqueued. /// /// The description of the AI problem is delegated to the ISearchProblem interface. /// /// /// /// /// public abstract class AbstractSearchStrategy : ISearchStrategy where T : ISearchNode where C : ICost { // Conceptually the expander is responsible for two things: // // 1. Creating a node's successors via the Successor Function // and wrapping them in SearchNodes (computing path costs // and heuristics, as well). // // 2. Creating a search node from a state. This lets us // decouple the search algorithm from the state expansion private INodeExpander expander; public AbstractSearchStrategy(INodeExpander expander) { this.expander = expander; } public INodeExpander Expander { get { return expander; } set { expander = value; } } /// /// Helper method to walk a solution node to the root and then reverse the list /// /// /// public IEnumerable Solution(T node) { var sequence = new List(); for (var curr = node; curr != null; curr = curr.Parent) { sequence.Add(curr); } sequence.Reverse(); return sequence; } /// /// /// /// /// Must be initialized -- usually that means being empty /// /// public virtual IEnumerable Search(ISearchProblem problem, IQueue fringe, S initialState) { // Add the initial state to the fringe fringe.Enqueue(Expander.CreateNode(initialState)); // Search until success, or the search space is exhausted while (!fringe.Empty) { var node = fringe.Remove(); if (problem.IsGoal(node.State)) { return Solution(node); } AddNodes(fringe, node, problem); } return Enumerable.Empty(); } /// /// When it's time to actually expand a node and add the new states to the fringe, different /// algorithms can make different choices /// /// /// /// protected abstract void AddNodes(IQueue fringe, T node, ISearchProblem problem); } }