diff --git a/AI.Test/AI.Test.csproj b/AI.Test/AI.Test.csproj
index c21903c..7828f92 100644
--- a/AI.Test/AI.Test.csproj
+++ b/AI.Test/AI.Test.csproj
@@ -8,7 +8,7 @@
Properties
AI.Test
AI.Test
- v4.5
+ v4.5.2
512
{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
10.0
@@ -16,6 +16,7 @@
$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
False
UnitTest
+
true
@@ -54,6 +55,12 @@
+
+
+ {56f54ca9-17c1-45c6-915b-6fabf4dae5d0}
+ SpriteCompiler
+
+
diff --git a/AI.Test/EightPuzzleBoard.cs b/AI.Test/EightPuzzleBoard.cs
index b894141..71227bb 100644
--- a/AI.Test/EightPuzzleBoard.cs
+++ b/AI.Test/EightPuzzleBoard.cs
@@ -6,17 +6,47 @@ using System.Threading.Tasks;
namespace AI.Test
{
+ public static class DirectionExtensions
+ {
+ public static bool CanMove(this Direction direction, int p)
+ {
+ switch (direction)
+ {
+ case Direction.LEFT: return (p % 3) != 0;
+ case Direction.RIGHT: return (p % 3) != 2;
+ case Direction.UP: return (p > 2);
+ case Direction.DOWN: return (p < 6);
+ }
+
+ throw new ArgumentException();
+ }
+
+ public static int MoveFrom(this Direction direction, int p)
+ {
+ switch (direction)
+ {
+ case Direction.LEFT: return p - 1;
+ case Direction.RIGHT: return p + 1;
+ case Direction.UP: return p - 3;
+ case Direction.DOWN: return p + 3;
+ }
+
+ throw new ArgumentException();
+ }
+ }
+
+ public enum Direction
+ {
+ LEFT,
+ RIGHT,
+ UP,
+ DOWN
+ }
+
public class EightPuzzleBoard
{
protected static Random rng = new Random();
- public enum Direction
- {
- LEFT,
- RIGHT,
- UP,
- DOWN
- }
private int[] board;
@@ -35,6 +65,8 @@ namespace AI.Test
this.board = (int[])aBoard.board.Clone();
}
+ public int[] Board { get { return board; } }
+
public EightPuzzleBoard Scramble()
{
return Scramble(10);
@@ -45,12 +77,13 @@ namespace AI.Test
var newPuzzle = new EightPuzzleBoard(this);
var direction = Enum.GetValues(typeof(Direction)).Cast().ToList();
- for (int i = 0; i < n; i++)
+ for (int i = 0; i < n;)
{
int j = rng.Next(direction.Count);
if (newPuzzle.CanMoveGap(direction[j]))
{
newPuzzle.MoveGap(direction[j]);
+ i += 1;
}
}
@@ -113,8 +146,51 @@ namespace AI.Test
return retVal;
}
- public int[] Board { get { return board; } }
+ public int[] GetLocationOf(int val)
+ {
+ return ind2sub(GetPositionOf(val));
+ }
+ public EightPuzzleBoard MoveGap(Direction direction)
+ {
+ var pos1 = GapPosition;
+ if (direction.CanMove(pos1))
+ {
+ var pos2 = direction.MoveFrom(pos1);
+ Swap(pos1, pos2);
+ }
+ return this;
+ }
+
+ private void Swap(int pos1, int pos2)
+ {
+ var val = this[pos1];
+ this[pos1] = this[pos2];
+ this[pos2] = val;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return (this == obj) || board.SequenceEqual(((EightPuzzleBoard)obj).board);
+ }
+
+ public override int GetHashCode()
+ {
+ return board.GetHashCode();
+ }
+
+ public bool CanMoveGap(Direction where)
+ {
+ return where.CanMove(GetPositionOf(0));
+ }
+
+ public override string ToString()
+ {
+ return
+ board[0] + " " + board[1] + " " + board[2] + Environment.NewLine +
+ board[3] + " " + board[4] + " " + board[5] + Environment.NewLine +
+ board[6] + " " + board[7] + " " + board[8];
+ }
}
}
diff --git a/AI.Test/SearchTest.cs b/AI.Test/SearchTest.cs
index cd81834..93cb137 100644
--- a/AI.Test/SearchTest.cs
+++ b/AI.Test/SearchTest.cs
@@ -1,14 +1,270 @@
using System;
+using System.Linq;
+using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SpriteCompiler.AI;
+using System.Diagnostics;
+using SpriteCompiler.Adapters;
namespace AI.Test
{
- [TestClass]
- public class SearchTest
+ public class EightPuzzleNode : HeuristicSearchNode
{
- [TestMethod]
- public void TestMethod1()
+ public EightPuzzleNode(EightPuzzleNode node, EightPuzzleBoard state) : base(node, state)
{
}
}
+
+ public class EightPuzzleGoalTest : IGoalTest
+ {
+ private readonly EightPuzzleBoard goal;
+
+ public EightPuzzleGoalTest(EightPuzzleBoard goal)
+ {
+ this.goal = new EightPuzzleBoard(goal);
+ }
+
+ public bool IsGoal(EightPuzzleBoard state)
+ {
+ return state.Equals(goal);
+ }
+ }
+
+ public class EightPuzzleSuccessorFunction : ISuccessorFunction
+ {
+ public IEnumerable> Successors(EightPuzzleBoard board)
+ {
+ foreach (var direction in Enum.GetValues(typeof(Direction)).Cast())
+ {
+ if (board.CanMoveGap(direction))
+ {
+ yield return Tuple.Create(direction, new EightPuzzleBoard(board).MoveGap(direction));
+ }
+ }
+ }
+ }
+
+ public class EightPuzzleStepCost : IStepCostFunction
+ {
+ private static readonly IntegerCost UNIT_STEP_COST = (IntegerCost) 1;
+
+ public IntegerCost StepCost(EightPuzzleBoard fromState, Direction action, EightPuzzleBoard toState)
+ {
+ return UNIT_STEP_COST;
+ }
+ }
+
+ public class ManhattanHeuristic : IHeuristicFunction
+ {
+ private readonly EightPuzzleBoard goal;
+
+ public ManhattanHeuristic(EightPuzzleBoard goal)
+ {
+ this.goal = new EightPuzzleBoard(goal);
+ }
+
+ public IntegerCost Eval(EightPuzzleBoard state)
+ {
+ int cost = 0;
+
+ for (int i = 1; i < 9; i++)
+ {
+ var goalLocation = goal.GetLocationOf(i);
+ var stateLocation = state.GetLocationOf(i);
+
+ cost += Math.Abs(goalLocation[0] - stateLocation[0]);
+ cost += Math.Abs(goalLocation[1] - stateLocation[1]);
+ }
+
+ return cost;
+ }
+ }
+
+ public class MisplacedHeuristic : IHeuristicFunction
+ {
+ private readonly EightPuzzleBoard goal;
+
+ public MisplacedHeuristic(EightPuzzleBoard goal)
+ {
+ this.goal = new EightPuzzleBoard(goal);
+ }
+
+ public IntegerCost Eval(EightPuzzleBoard state)
+ {
+ return goal.CountMismatches(state);
+ }
+ }
+
+ public static class IntrumentedParameters
+ {
+ public const string NODES_EXPANDED = "nodesExpanded";
+ }
+
+ public class InstrumentedNodeExpander : NodeExpanderDelegator
+ where T : ISearchNode
+ where C : ICost
+ {
+ private readonly IDictionary metrics = new Dictionary();
+ private readonly INodeExpander expander;
+
+ public InstrumentedNodeExpander(INodeExpander expander)
+ : base(expander)
+ {
+ ClearMetrics();
+ }
+
+ public override IEnumerable Expand(ISearchProblem problem, T node)
+ {
+ metrics[IntrumentedParameters.NODES_EXPANDED] += 1;
+ return base.Expand(problem, node);
+ }
+
+ public int this[string key]
+ {
+ get
+ {
+ return metrics[key];
+ }
+ }
+
+ public void ClearMetrics()
+ {
+ metrics[IntrumentedParameters.NODES_EXPANDED] = 0;
+ }
+ }
+
+
+ public class EightPuzzleNodeExpander : InformedNodeExpander
+ {
+ public override EightPuzzleNode CreateNode(EightPuzzleNode parent, EightPuzzleBoard state)
+ {
+ return new EightPuzzleNode(parent, state);
+ }
+
+ public override EightPuzzleNode CreateNode(EightPuzzleBoard state)
+ {
+ return CreateNode(null, state);
+ }
+ }
+
+ [TestClass]
+ public class SearchTest
+ {
+ // These are the three search problem to run using IDS, A*(h1) and A*(h2)
+ private ISearchProblem problem_none;
+ private ISearchProblem problem_h1;
+ private ISearchProblem problem_h2;
+
+ // Define the goal state
+ private EightPuzzleBoard goal = new EightPuzzleBoard(new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 });
+
+ [TestInitialize]
+ public void SetUp()
+ {
+ // These objects define the abstract search problem
+ var goalTest = new EightPuzzleGoalTest(goal);
+ var stepCost = new EightPuzzleStepCost();
+ var successorFn = new EightPuzzleSuccessorFunction();
+ var heuristic1 = new MisplacedHeuristic(goal);
+ var heuristic2 = new ManhattanHeuristic(goal);
+
+ // Create three search problem objects. One without a heuristic and two with the different
+ // heuristics
+ problem_none = new SearchProblem(goalTest, stepCost, successorFn);
+ problem_h1 = new SearchProblem(goalTest, stepCost, successorFn, heuristic1);
+ problem_h2 = new SearchProblem(goalTest, stepCost, successorFn, heuristic2);
+ }
+
+ [TestMethod]
+ public void TestMethod1()
+ {
+ int N = 1;
+ int dmax = 3;
+
+ // Now we define the search algorithm and the type of node expansion. Russell & Norvig discuss
+ // two type of expansion strategies: tree search and graph search. One will avoid cycles in the search
+ // space and the other will not.
+ //
+ // They state that a tree search was used to generate Figure 4.8;
+ var expander = new InstrumentedNodeExpander(new EightPuzzleNodeExpander());
+ var treeSearch = new TreeSearch(expander);
+ var ids = new IterativeDeepeningSearch(treeSearch, dmax);
+ var aStarH1 = new AStarSearch(treeSearch, new QueueAdapter());
+ var aStarH2 = new IterativeDeepeningAStarSearch(treeSearch, (IntegerCost)dmax);
+
+ // Depth runs from 0 to dmax
+ int[,] d = new int[dmax + 2, 3];
+ int[,] n = new int[dmax + 2, 3];
+
+ for (int i = 0; i < N; i++)
+ {
+ // Invoke the search on the problem with a particular starting state
+ var initialState = goal.Scramble(dmax);
+
+ /*
+ {
+ expander.ClearMetrics();
+ var solution = ids.Search(problem_none, initialState);
+ System.Diagnostics.Trace.WriteLine("IDS Solution has " + solution.Count() + " nodes and expanded " + expander[IntrumentedParameters.NODES_EXPANDED] + " nodes");
+ d[solution.Count(), 0] += 1;
+ n[solution.Count(), 0] += expander[IntrumentedParameters.NODES_EXPANDED];
+ }
+
+ {
+ expander.ClearMetrics();
+ var solution = aStarH1.Search(problem_h1, initialState);
+ System.Diagnostics.Trace.WriteLine("A* (h1) Solution has " + solution.Count() + " nodes and expanded " + expander[IntrumentedParameters.NODES_EXPANDED] + " nodes");
+ d[solution.Count(), 1] += 1;
+ n[solution.Count(), 1] += expander[IntrumentedParameters.NODES_EXPANDED];
+ }
+ */
+ {
+ expander.ClearMetrics();
+ var solution = aStarH2.Search(problem_h2, initialState);
+ System.Diagnostics.Trace.WriteLine("A* (h2) Solution has " + solution.Count() + " nodes and expanded " + expander[IntrumentedParameters.NODES_EXPANDED] + " nodes");
+ d[solution.Count(), 2] += 1;
+ n[solution.Count(), 2] += expander[IntrumentedParameters.NODES_EXPANDED];
+ }
+ }
+
+ Trace.WriteLine("| Search Cost Branching Factor |");
+ Trace.WriteLine("+--+---------+--------+--------++---------+--------+--------+");
+ Trace.WriteLine("| d| IDS | A*(h1) | A*(h2) || IDS | A*(h1) | A*(h2) |");
+ Trace.WriteLine("+--+---------+--------+--------++---------+--------+--------+");
+
+ for (int i = 0; i <= dmax + 1; i++)
+ {
+ var bf0 = ComputeBranchingFactor((float)n[i, 0] / (float)d[i, 0], i);
+ var bf1 = ComputeBranchingFactor((float)n[i, 1] / (float)d[i, 1], i);
+ var bf2 = ComputeBranchingFactor((float)n[i, 2] / (float)d[i, 2], i);
+
+ Trace.WriteLine(String.Format("|{0,2}|{1,-8} |{2,7} |{3,7} ||{4,8:0.00} |{5,7:0.00} |{6,7:0.00} |", i,
+ n[i, 0] / Math.Max(d[i, 0], 1), n[i, 1] / Math.Max(d[i, 1], 1), n[i, 2] / Math.Max(d[i, 2], 1), bf0, bf1, bf2));
+ }
+
+ Trace.WriteLine("+--+---------+--------+--------++---------+--------+--------+");
+ }
+
+ ///
+ /// Uses Newton iteration to solve for the effective branching factor
+ ///
+ /// number of nodes expanded
+ /// depth of the solution
+ ///
+ private float ComputeBranchingFactor(float n, float d)
+ {
+ float x = 3.0f; // Initial guess
+
+ for (int i = 0; i < 20; i++)
+ {
+ float f = (float)Math.Pow(x, d + 1.0f) - 1.0f - x * n + n;
+ float df = (d + 1.0f) * (float)Math.Pow(x, d) - n;
+
+ x = x - (f / df);
+ }
+
+ return x;
+ }
+
+ }
}
diff --git a/SpriteCompiler.sln b/SpriteCompiler.sln
index a61dd02..720fdb2 100644
--- a/SpriteCompiler.sln
+++ b/SpriteCompiler.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.40629.0
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpriteCompiler", "SpriteCompiler\SpriteCompiler.csproj", "{56F54CA9-17C1-45C6-915B-6FABF4DAE5D0}"
EndProject
diff --git a/SpriteCompiler/AI/AStarComparer.cs b/SpriteCompiler/AI/AStarComparer.cs
index d86c9d1..eb983cd 100644
--- a/SpriteCompiler/AI/AStarComparer.cs
+++ b/SpriteCompiler/AI/AStarComparer.cs
@@ -8,7 +8,7 @@ namespace SpriteCompiler.AI
{
public class AStarComparator : IComparer
where T : HeuristicSearchNode
- where C : IPathCost, new()
+ where C : ICost, new()
{
public int Compare(T x, T y)
{
diff --git a/SpriteCompiler/AI/AStarSearch.cs b/SpriteCompiler/AI/AStarSearch.cs
index 5e5aa93..6020fbf 100644
--- a/SpriteCompiler/AI/AStarSearch.cs
+++ b/SpriteCompiler/AI/AStarSearch.cs
@@ -1,15 +1,12 @@
namespace SpriteCompiler.AI
{
- public class AStarSearch : BestFirstSearch
- where T : HeuristicSearchNode
- where C : IPathCost, new()
- {
- public AStarSearch(AbstractAISearch search)
- : base(search) // , new AStarComparator())
- {
- }
+ using Queue;
- public AStarSearch(AbstractAISearch search, IQueue fringe)
+ public class AStarSearch : BestFirstSearch
+ where T : IHeuristicSearchNode
+ where C : ICost, new()
+ {
+ public AStarSearch(ISearchStrategy search, IQueue fringe)
: base(search, fringe)
{
}
diff --git a/SpriteCompiler/AI/AbstractAISearch.cs b/SpriteCompiler/AI/AbstractAISearch.cs
deleted file mode 100644
index 13e9480..0000000
--- a/SpriteCompiler/AI/AbstractAISearch.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-namespace SpriteCompiler.AI
-{
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Linq;
-
- ///
- /// An abstract description of a state-space search. Specific algorthims are determined by
- /// how the nodes are expanded, eveanluated and enqueued.
- ///
- /// The description of the AI problem is delegated to the ISearchProblem interface.
- ///
- ///
- ///
- ///
- ///
- public abstract class AbstractAISearch : ISearch
- where T : ISearchNode
- where C : IPathCost
- {
- // 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;
- private readonly IQueue fringe;
-
- public AbstractAISearch(INodeExpander expander, IQueue fringe)
- {
- this.expander = expander;
- this.fringe = fringe;
- }
-
- public INodeExpander Expander { get { return expander; } set { expander = value; } }
-
- public IEnumerable Solution(T node)
- {
- var sequence = new List();
-
- while (node != null)
- {
- sequence.Add(node);
- node = node.Parent;
- }
-
- sequence.Reverse();
-
- return sequence;
- }
-
- public IEnumerable ExtendSearch(ISearchProblem problem)
- {
- while (!fringe.Empty)
- {
- var step = SearchStep(problem, fringe);
- if (step.IsGoal)
- {
- return Solution(step.Node);
- }
- }
-
- return Enumerable.Empty();
- }
-
- public ISearchStepInfo SearchStep(ISearchProblem problem)
- {
- var node = fringe.Remove();
-
- #if DEBUG
- Console.WriteLine(string.Format("Removed {0} from the queue with g = {1}, c(n, n') = {2}", node.State, node.PathCost, node.StepCost));
- #endif
-
- if (problem.IsGoal(node.State))
- {
- return new SearchStepInfo(node, Solution(node));
- }
-
- AddNodes(fringe, node, problem);
-
- return new SearchStepInfo(node, null);
- }
-
- public IEnumerable Expand(ISearchProblem problem, T node)
- {
- return expander.Expand(problem, node);
- }
-
- public bool IsFailure(IEnumerable solution)
- {
- return !solution.Any();
- }
-
- public virtual IEnumerable Search(ISearchProblem problem, IQueue fringe, S initialState)
- {
- InitializeSearch(fringe, initialState);
- return ExtendSearch(problem, fringe);
- }
-
- public void InitializeSearch(IQueue fringe, S initialState)
- {
- fringe.Clear();
- fringe.Enqueue(expander.CreateNode(default(T), initialState));
- }
-
- protected abstract void AddNodes(IQueue fringe, T node, ISearchProblem problem);
- }
-}
diff --git a/SpriteCompiler/AI/AbstractSearchNode.cs b/SpriteCompiler/AI/AbstractSearchNode.cs
index bc10533..631f93e 100644
--- a/SpriteCompiler/AI/AbstractSearchNode.cs
+++ b/SpriteCompiler/AI/AbstractSearchNode.cs
@@ -5,7 +5,7 @@
public abstract class AbstractSearchNode : ISearchNode
where T : ISearchNode
- where C : IPathCost, new()
+ where C : ICost, new()
{
protected readonly S state;
protected readonly T parent;
@@ -30,6 +30,8 @@
public int Depth { get { return depth; } }
public S State { get { return state; } }
+ public C EstCost { get { return PathCost; } }
+
public C StepCost
{
get
@@ -43,7 +45,5 @@
pathCost = HasParent ? parent.PathCost.Add(value) : value;
}
}
-
- virtual public C EstCost { get { return PathCost; } }
}
}
diff --git a/SpriteCompiler/AI/AbstractSearchStrategy.cs b/SpriteCompiler/AI/AbstractSearchStrategy.cs
new file mode 100644
index 0000000..e4c4abf
--- /dev/null
+++ b/SpriteCompiler/AI/AbstractSearchStrategy.cs
@@ -0,0 +1,95 @@
+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; node != null; node = node.Parent)
+ {
+ sequence.Add(curr);
+ }
+
+ sequence.Reverse();
+
+ return sequence;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// Must be initialize -- 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
+ /// algorhtms can make different choices
+ ///
+ ///
+ ///
+ ///
+ protected abstract void AddNodes(IQueue fringe, T node, ISearchProblem problem);
+ }
+}
diff --git a/SpriteCompiler/AI/AbstractStateSpaceSearch.cs b/SpriteCompiler/AI/AbstractStateSpaceSearch.cs
index 745ed78..6839c8e 100644
--- a/SpriteCompiler/AI/AbstractStateSpaceSearch.cs
+++ b/SpriteCompiler/AI/AbstractStateSpaceSearch.cs
@@ -1,103 +1,25 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SpriteCompiler.AI
+namespace SpriteCompiler.AI
{
- public abstract class AbstractStateSpaceSearch
+ using System;
+ using System.Collections.Generic;
+ using Queue;
+
+ public abstract class AbstractStateSpaceSearch : ISearch
where T : ISearchNode
- where C : IPathCost
+ where C : ICost
{
- private readonly INodeExpander expander;
+ protected readonly ISearchStrategy strategy;
private readonly Func> fringe;
- public AbstractStateSpaceSearch(INodeExpander expander, Func> fringe)
+ public AbstractStateSpaceSearch(ISearchStrategy strategy, Func> fringe)
{
- this.expander = expander;
- this.fringe = fringe;
+ this.strategy = strategy;
+ this.fringe = fringe;
}
- public ISearchProblemInstance Create(ISearchProblem problem, S initialState)
+ public virtual IEnumerable Search(ISearchProblem problem, S initialState)
{
- return new SearchProblemInstance(problem, fringe(), initialState);
+ return strategy.Search(problem, fringe(), initialState);
}
}
-
- public interface ISearchProblemInstance
- where T : ISearchNode
- where C : IPathCost
- {
- ///
- /// Tries to find a solution to the search problem. Returns null if no solution can be found.
- /// This method can be called repeatedly to try to find additional, sub optimal solutions if
- /// supported.
- ///
- ///
- IEnumerable FindSolution();
-
- ///
- /// Take a single step of the searth to allow introspection of the search process
- ///
- ///
- ///
- ISearchStepInfo Step();
-
- ///
- /// Provide a set of callbacks to watch the execution of a search
- ///
- // void Trace();
- }
-
- public class SearchProblemInstance : ISearchProblemInstance
- where T : ISearchNode
- where C : IPathCost
- {
- private readonly ISearchProblem problem;
- private readonly S initialState;
- private readonly IQueue fringe;
-
- public SearchProblemInstance(ISearchProblem problem, IQueue fringe, S initialState)
- {
- this.problem = problem;
- this.fringe = fringe;
- this.initialState = initialState;
- }
-
- public IEnumerable FindSolution()
- {
- throw new NotImplementedException();
- }
-
- public ISearchStepInfo Step()
- {
- var node = fringe.Remove();
-
- if (problem.IsGoal(node.State))
- {
- return new SearchStepInfo(node, Solution(node));
- }
-
- AddNodes(fringe, node, problem);
-
- return new SearchStepInfo(node, null);
- }
-
- public IEnumerable Solution(T node)
- {
- var sequence = new List();
-
- while (node != null)
- {
- sequence.Add(node);
- node = node.Parent;
- }
-
- sequence.Reverse();
-
- return sequence;
- }
-
- }
}
diff --git a/SpriteCompiler/AI/BestFirstSearch.cs b/SpriteCompiler/AI/BestFirstSearch.cs
index bc2752a..99f0698 100644
--- a/SpriteCompiler/AI/BestFirstSearch.cs
+++ b/SpriteCompiler/AI/BestFirstSearch.cs
@@ -1,46 +1,14 @@
namespace SpriteCompiler.AI
{
- using Adapters;
- using System.Collections.Generic;
+ using Queue;
- public class BestFirstSearch : ISearch
+ public class BestFirstSearch : AbstractStateSpaceSearch
where T : ISearchNode
- where C : IPathCost
+ where C : ICost
{
- protected readonly AbstractAISearch search;
- protected readonly IQueue fringe;
-
- public BestFirstSearch(AbstractAISearch search, IQueue fringe)
+ public BestFirstSearch(ISearchStrategy strategy, IQueue fringe)
+ : base(strategy, () => fringe)
{
- this.search = search;
- this.fringe = fringe;
- }
-
- public BestFirstSearch(AbstractAISearch search)
- {
- this.search = search;
- this.fringe = new QueueAdapter();
- }
-
- public IEnumerable Search(ISearchProblem problem, S initialState)
- {
- fringe.Clear();
- return search.Search(problem, fringe, initialState);
- }
-
- public IEnumerable ExtendSearch(ISearchProblem problem)
- {
- return search.ExtendSearch(problem, fringe);
- }
-
- public void InitializeSearch(S initialState)
- {
- search.InitializeSearch(fringe, initialState);
- }
-
- public ISearchStepInfo SearchStep(ISearchProblem problem)
- {
- return search.SearchStep(problem, fringe);
}
}
}
diff --git a/SpriteCompiler/AI/CostNodeLimiter.cs b/SpriteCompiler/AI/CostNodeLimiter.cs
index e24fc20..8237a31 100644
--- a/SpriteCompiler/AI/CostNodeLimiter.cs
+++ b/SpriteCompiler/AI/CostNodeLimiter.cs
@@ -7,18 +7,31 @@
///
public class CostNodeLimiter : INodeLimiter
where T : ISearchNode
- where C : IPathCost, new()
+ where C : ICost, new()
{
private readonly C maxCost;
+ private C nextCost;
- public CostNodeLimiter(C maxCost)
+ public CostNodeLimiter(C maxCost, C infinity)
{
this.maxCost = maxCost;
+ this.nextCost = infinity;
}
+ public C NextCost { get { return nextCost; } }
+
public bool Cutoff(T node)
{
- return node.PathCost.CompareTo(maxCost) >= 0;
+ // If we find a value that exceeds the current maximum, return false,
+ // but keep track of the smallest value that is larger than the maximum
+ // cost.
+ if (node.PathCost.CompareTo(maxCost) > 0)
+ {
+ nextCost = (node.PathCost.CompareTo(nextCost) < 0) ? node.PathCost : nextCost;
+ return true;
+ }
+
+ return false;
}
}
}
diff --git a/SpriteCompiler/AI/DepthFirstSearch.cs b/SpriteCompiler/AI/DepthFirstSearch.cs
index 339796e..48b0b12 100644
--- a/SpriteCompiler/AI/DepthFirstSearch.cs
+++ b/SpriteCompiler/AI/DepthFirstSearch.cs
@@ -1,42 +1,14 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SpriteCompiler.AI
+namespace SpriteCompiler.AI
{
- public class DepthFirstSearch : ISearch
+ using Queue;
+
+ public class DepthFirstSearch : AbstractStateSpaceSearch
where T : ISearchNode
- where C : IPathCost, new()
+ where C : ICost, new()
{
- protected readonly AbstractAISearch search;
- protected readonly IQueue fringe = new Lifo();
-
- public DepthFirstSearch(AbstractAISearch search)
+ public DepthFirstSearch(ISearchStrategy strategy)
+ : base(strategy, () => new LIFO())
{
- this.search = search;
- }
-
- public virtual IEnumerable Search(ISearchProblem problem, S initialState)
- {
- fringe.Clear();
- return search.Search(problem, fringe, initialState);
- }
-
- public virtual IEnumerable ExtendSearch(ISearchProblem problem)
- {
- return search.ExtendSearch(problem, fringe);
- }
-
- public void InitializeSearch(S initialState)
- {
- search.InitializeSearch(fringe, initialState);
- }
-
- public ISearchStepInfo SearchStep(ISearchProblem problem)
- {
- return search.SearchStep(problem, fringe);
}
}
}
diff --git a/SpriteCompiler/AI/DepthLimitedNodeExpander.cs b/SpriteCompiler/AI/DepthLimitedNodeExpander.cs
index 5f538f7..9c7b1ed 100644
--- a/SpriteCompiler/AI/DepthLimitedNodeExpander.cs
+++ b/SpriteCompiler/AI/DepthLimitedNodeExpander.cs
@@ -8,7 +8,7 @@ namespace SpriteCompiler.AI
{
public class DepthLimitedNodeExpander : NodeExpanderDelegator
where T : ISearchNode
- where C : IPathCost, new()
+ where C : ICost, new()
{
private readonly INodeLimiter limit;
private bool cutoffOccured = false;
diff --git a/SpriteCompiler/AI/DepthLimitedSearch.cs b/SpriteCompiler/AI/DepthLimitedSearch.cs
index de3587d..44c116a 100644
--- a/SpriteCompiler/AI/DepthLimitedSearch.cs
+++ b/SpriteCompiler/AI/DepthLimitedSearch.cs
@@ -1,22 +1,19 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SpriteCompiler.AI
+namespace SpriteCompiler.AI
{
+ using System.Collections.Generic;
+ using System.Linq;
+
///
/// Depth-first search with a cutoff
///
public class DepthLimitedSearch : DepthFirstSearch
where T : ISearchNode
- where C : IPathCost, new()
+ where C : ICost, new()
{
protected readonly INodeLimiter limit;
- public DepthLimitedSearch(AbstractAISearch search, INodeLimiter limit)
- : base(search)
+ public DepthLimitedSearch(ISearchStrategy strategy, INodeLimiter limit)
+ : base(strategy)
{
this.limit = limit;
}
@@ -24,32 +21,26 @@ namespace SpriteCompiler.AI
public override IEnumerable Search(ISearchProblem problem, S initialState)
{
// Save the old node expander
- var oldExpander = search.Expander;
+ var oldExpander = strategy.Expander;
// Wrap the expander with a depth-limied expanded in order to
// terminate the search
var expander = new DepthLimitedNodeExpander(oldExpander, limit);
- search.Expander = expander;
+ strategy.Expander = expander;
// Run the search
var solution = base.Search(problem, initialState);
// Restore the old expander
- search.Expander = oldExpander;
+ strategy.Expander = oldExpander;
// Check to see we failed and if the reason for failing was not reaching the cutoff depth.
- if (search.IsFailure(solution) && expander.CutoffOccured)
+ if (!solution.Any() && expander.CutoffOccured)
{
return null;
}
return solution;
-
- }
-
- public override IEnumerable ExtendSearch(ISearchProblem problem)
- {
- return base.ExtendSearch(problem);
}
public bool IsCutoff(IEnumerable result)
diff --git a/SpriteCompiler/AI/DepthNodeLimiter.cs b/SpriteCompiler/AI/DepthNodeLimiter.cs
new file mode 100644
index 0000000..7d5d94d
--- /dev/null
+++ b/SpriteCompiler/AI/DepthNodeLimiter.cs
@@ -0,0 +1,19 @@
+namespace SpriteCompiler.AI
+{
+ public class DepthNodeLimiter : INodeLimiter
+ where T : ISearchNode
+ where C : ICost, new()
+ {
+ private readonly int maxDepth;
+
+ public DepthNodeLimiter(int maxDepth)
+ {
+ this.maxDepth = maxDepth;
+ }
+
+ public bool Cutoff(T node)
+ {
+ return node.Depth >= maxDepth;
+ }
+ }
+}
diff --git a/SpriteCompiler/AI/GraphSearch.cs b/SpriteCompiler/AI/GraphSearch.cs
index 9cd2319..32b4583 100644
--- a/SpriteCompiler/AI/GraphSearch.cs
+++ b/SpriteCompiler/AI/GraphSearch.cs
@@ -1,11 +1,11 @@
-using System;
-using System.Collections.Generic;
-
-namespace SpriteCompiler.AI
+namespace SpriteCompiler.AI
{
- public class GraphSearch : AbstractAISearch
+ using System.Collections.Generic;
+ using Queue;
+
+ public class GraphSearch : AbstractSearchStrategy
where T : ISearchNode
- where C : IPathCost
+ where C : ICost
{
private readonly ISet closed = new HashSet();
@@ -31,7 +31,7 @@ namespace SpriteCompiler.AI
if (!closed.Contains(node.State))
{
closed.Add(node.State);
- fringe.AddRange(Expand(problem, node));
+ fringe.AddRange(Expander.Expand(problem, node));
}
}
}
diff --git a/SpriteCompiler/AI/HeuristicSearchNode.cs b/SpriteCompiler/AI/HeuristicSearchNode.cs
index 471ae9b..a3e5c25 100644
--- a/SpriteCompiler/AI/HeuristicSearchNode.cs
+++ b/SpriteCompiler/AI/HeuristicSearchNode.cs
@@ -2,9 +2,14 @@
{
using System;
- public class HeuristicSearchNode : AbstractSearchNode, ISearchNode
- where T : HeuristicSearchNode
- where C : IPathCost, new()
+ public interface IHeuristicSearchNode : ISearchNode where C : ICost
+ {
+ C EstCost { get; }
+ }
+
+ public class HeuristicSearchNode : AbstractSearchNode, IHeuristicSearchNode
+ where T : IHeuristicSearchNode
+ where C : ICost, new()
{
public HeuristicSearchNode(T node, S state)
: base(node, state)
@@ -14,7 +19,7 @@
public C Heuristic { get; set; }
- public override C EstCost
+ public C EstCost
{
get
{
diff --git a/SpriteCompiler/AI/IHeuristicFunction.cs b/SpriteCompiler/AI/IHeuristicFunction.cs
index afcf070..4630b56 100644
--- a/SpriteCompiler/AI/IHeuristicFunction.cs
+++ b/SpriteCompiler/AI/IHeuristicFunction.cs
@@ -2,7 +2,7 @@
{
using System;
- public interface IHeuristicFunction where C : IPathCost
+ public interface IHeuristicFunction where C : ICost
{
C Eval(S state);
}
diff --git a/SpriteCompiler/AI/INodeExpander.cs b/SpriteCompiler/AI/INodeExpander.cs
index 6809603..f57b715 100644
--- a/SpriteCompiler/AI/INodeExpander.cs
+++ b/SpriteCompiler/AI/INodeExpander.cs
@@ -11,9 +11,11 @@
///
public interface INodeExpander
where T : ISearchNode
- where C : IPathCost
+ where C : ICost
{
IEnumerable Expand(ISearchProblem problem, T node);
+
T CreateNode(T parent, S state);
+ T CreateNode(S state);
}
}
diff --git a/SpriteCompiler/AI/INodeLimiter.cs b/SpriteCompiler/AI/INodeLimiter.cs
index f836816..d386a28 100644
--- a/SpriteCompiler/AI/INodeLimiter.cs
+++ b/SpriteCompiler/AI/INodeLimiter.cs
@@ -2,7 +2,7 @@
{
public interface INodeLimiter
where T : ISearchNode
- where C : IPathCost, new()
+ where C : ICost, new()
{
bool Cutoff(T node);
}
diff --git a/SpriteCompiler/AI/IPathCost.cs b/SpriteCompiler/AI/IPathCost.cs
index cbfb127..56b53dd 100644
--- a/SpriteCompiler/AI/IPathCost.cs
+++ b/SpriteCompiler/AI/IPathCost.cs
@@ -2,8 +2,13 @@
{
using System;
- public interface IPathCost : IComparable
+ public interface ICost : IComparable
{
C Add(C value);
+
+ // Number theoretic values, i.e. C + ZERO = C, C * ONE = C
+ C Zero();
+ C One();
+ C Maximum();
}
}
diff --git a/SpriteCompiler/AI/ISearch.cs b/SpriteCompiler/AI/ISearch.cs
index 3c80d12..5279f63 100644
--- a/SpriteCompiler/AI/ISearch.cs
+++ b/SpriteCompiler/AI/ISearch.cs
@@ -4,23 +4,11 @@
public interface ISearch
where T : ISearchNode
- where C : IPathCost
+ where C : ICost
{
/// Perform a new search on the specified search problem using the given
/// initial state as a starting point. The method will return an empty
/// list on failure.
- IEnumerable Search(ISearchProblem problem, S initialState);
-
- /**
- * Continues to run a search after a solution is found. This can
- * be useful for enumerating over all the solutions to a search problem.
- *
- * @param problem
- * @return Sequence of search nodes desribing a solution
- */
- IEnumerable ExtendSearch(ISearchProblem problem);
-
- void InitializeSearch(S initialState);
- ISearchStepInfo SearchStep(ISearchProblem problem);
+ IEnumerable Search(ISearchProblem problem, S initialState);
}
}
diff --git a/SpriteCompiler/AI/ISearchNode.cs b/SpriteCompiler/AI/ISearchNode.cs
index ed6844e..f600fc6 100644
--- a/SpriteCompiler/AI/ISearchNode.cs
+++ b/SpriteCompiler/AI/ISearchNode.cs
@@ -9,18 +9,23 @@
/// State of the search
/// Type of the parent
/// Cost type
- public interface ISearchNode : ISearchNode where C : IPathCost
+ public interface ISearchNode : ISearchNode where C : ICost
{
A Action { get; set; }
S State { get; }
T Parent { get; }
}
- public interface ISearchNode
+ ///
+ /// Simplest representation of a seach node that just has the costs. This interface
+ /// is useful for certain evaluation functions
+ ///
+ ///
+ public interface ISearchNode where C : ICost
{
C PathCost { get; }
C StepCost { get; set; }
- int Depth { get; }
C EstCost { get; }
+ int Depth { get; }
}
}
diff --git a/SpriteCompiler/AI/ISearchProblem.cs b/SpriteCompiler/AI/ISearchProblem.cs
index 739f0e7..469666c 100644
--- a/SpriteCompiler/AI/ISearchProblem.cs
+++ b/SpriteCompiler/AI/ISearchProblem.cs
@@ -4,7 +4,7 @@
using System.Collections.Generic;
public interface ISearchProblem
- where C : IComparable
+ where C : ICost
{
IEnumerable> Successors(S state);
bool IsGoal(S state);
diff --git a/SpriteCompiler/AI/ISearchStrategy.cs b/SpriteCompiler/AI/ISearchStrategy.cs
new file mode 100644
index 0000000..0cea487
--- /dev/null
+++ b/SpriteCompiler/AI/ISearchStrategy.cs
@@ -0,0 +1,18 @@
+namespace SpriteCompiler.AI
+{
+ using System.Collections.Generic;
+ using Queue;
+
+ ///
+ /// A search strategy defines how a state space is explored by determining which
+ /// nodes in the fringe are expanded next. Two common search strategies are
+ /// Tree Search and Graph Search.
+ ///
+ public interface ISearchStrategy
+ where T : ISearchNode
+ where C : ICost
+ {
+ IEnumerable Search(ISearchProblem problem, IQueue fringe, S initialState);
+ INodeExpander Expander { get; set; }
+ }
+}
diff --git a/SpriteCompiler/AI/IStepCostFunction.cs b/SpriteCompiler/AI/IStepCostFunction.cs
index 5efaccf..9bc4be2 100644
--- a/SpriteCompiler/AI/IStepCostFunction.cs
+++ b/SpriteCompiler/AI/IStepCostFunction.cs
@@ -2,7 +2,7 @@
{
using System;
- public interface IStepCostFunction where C : IPathCost
+ public interface IStepCostFunction where C : ICost
{
C StepCost(S fromState, A action, S toState);
}
diff --git a/SpriteCompiler/AI/InformedNodeExpander.cs b/SpriteCompiler/AI/InformedNodeExpander.cs
index 39e8223..23b3e7d 100644
--- a/SpriteCompiler/AI/InformedNodeExpander.cs
+++ b/SpriteCompiler/AI/InformedNodeExpander.cs
@@ -7,16 +7,17 @@ namespace SpriteCompiler.AI
public abstract class InformedNodeExpander : INodeExpander
where T : HeuristicSearchNode
- where C : IPathCost, new()
+ where C : ICost, new()
{
public abstract T CreateNode(T parent, S state);
+ public abstract T CreateNode(S state);
public IEnumerable Expand(ISearchProblem problem, T node)
{
var successors = problem.Successors(node.State);
// Debug
- #if DEBUG
+ #if VERBOSE_DEBUG
Console.WriteLine(String.Format("There are {0} successors for {1}", successors.Count(), node));
Console.WriteLine(String.Format("This node has a current path cost of {0}", node.PathCost));
#endif
@@ -31,7 +32,7 @@ namespace SpriteCompiler.AI
next.StepCost = problem.StepCost(node.State, action, state);
next.Heuristic = problem.Heuristic(state);
-#if DEBUG
+#if False
Console.WriteLine(" Action = " + next.Action + ", g(n') = " + next.PathCost + ", h(n') = " + next.Heuristic);
#endif
yield return next;
diff --git a/SpriteCompiler/AI/IntegerPathCost.cs b/SpriteCompiler/AI/IntegerPathCost.cs
new file mode 100644
index 0000000..963b45c
--- /dev/null
+++ b/SpriteCompiler/AI/IntegerPathCost.cs
@@ -0,0 +1,72 @@
+using System;
+
+namespace SpriteCompiler.AI
+{
+ ///
+ /// Helper class to implement simple integer path costs
+ ///
+ public sealed class IntegerCost : ICost
+ {
+ private readonly int value;
+
+ public IntegerCost()
+ : this(0)
+ {
+ }
+
+ private IntegerCost(int value)
+ {
+ this.value = value;
+ }
+
+ public static implicit operator int(IntegerCost obj)
+ {
+ return obj.value;
+ }
+
+ public static implicit operator IntegerCost(int value)
+ {
+ return new IntegerCost(value);
+ }
+
+ public IntegerCost Add(IntegerCost other)
+ {
+ return value + other.value;
+ }
+
+ public override int GetHashCode()
+ {
+ return value.GetHashCode();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return value.Equals(((IntegerCost)obj).value);
+ }
+
+ public int CompareTo(IntegerCost other)
+ {
+ return value.CompareTo(other.value);
+ }
+
+ public override string ToString()
+ {
+ return value.ToString();
+ }
+
+ public IntegerCost Zero()
+ {
+ return 0;
+ }
+
+ public IntegerCost One()
+ {
+ return 1;
+ }
+
+ public IntegerCost Maximum()
+ {
+ return int.MaxValue;
+ }
+ }
+}
diff --git a/SpriteCompiler/AI/IterativeDeepeningAStarSearch.cs b/SpriteCompiler/AI/IterativeDeepeningAStarSearch.cs
index b35925d..eb9bd05 100644
--- a/SpriteCompiler/AI/IterativeDeepeningAStarSearch.cs
+++ b/SpriteCompiler/AI/IterativeDeepeningAStarSearch.cs
@@ -7,13 +7,15 @@ using System.Threading.Tasks;
namespace SpriteCompiler.AI
{
public class IterativeDeepeningAStarSearch : ISearch
- where T : ISearchNode
- where C : IPathCost, new()
+ where T : IHeuristicSearchNode
+ where C : ICost, new()
{
- protected readonly AbstractAISearch search;
+ private static readonly C Cost = new C();
+
+ protected readonly ISearchStrategy search;
protected readonly C limit;
- public IterativeDeepeningAStarSearch(AbstractAISearch search, C limit)
+ public IterativeDeepeningAStarSearch(ISearchStrategy search, C limit)
{
this.search = search;
this.limit = limit;
@@ -21,35 +23,31 @@ namespace SpriteCompiler.AI
public IEnumerable Search(ISearchProblem problem, S initialState)
{
- C bound = new C();
+ C bound = Cost.Zero();
while (bound.CompareTo(limit) < 0)
{
- var dls = new DepthLimitedSearch(search, new CostNodeLimiter(bound));
+ var limiter = new CostNodeLimiter(bound, Cost.Maximum());
+ var dls = new DepthLimitedSearch(search, limiter);
var result = dls.Search(problem, initialState);
+ // If there was no cutoff, return the solution (or lack thereof)
if (!dls.IsCutoff(result))
{
return result;
}
+
+ // If the cost did not change, throw exception
+ if (bound.Equals(limiter.NextCost))
+ {
+ throw new ApplicationException("IDA*: Bound did not increase after depth-limited search");
+ }
+
+ // Otherwise, increase the cutoff to the next value
+ bound = limiter.NextCost;
}
// An empty list signals failure
return Enumerable.Empty();
}
-
- public IEnumerable ExtendSearch(ISearchProblem problem)
- {
- throw new NotImplementedException();
- }
-
- public void InitializeSearch(S initialState)
- {
- search.InitializeSearch(initialState);
- }
-
- public ISearchStepInfo SearchStep(ISearchProblem problem)
- {
- return search.SearchStep(problem);
- }
}
}
diff --git a/SpriteCompiler/AI/IterativeDeepeningSearch.cs b/SpriteCompiler/AI/IterativeDeepeningSearch.cs
new file mode 100644
index 0000000..fd64e9f
--- /dev/null
+++ b/SpriteCompiler/AI/IterativeDeepeningSearch.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SpriteCompiler.AI
+{
+ public class IterativeDeepeningSearch : ISearch
+ where T : ISearchNode
+ where C : ICost, new()
+ {
+ private readonly ISearchStrategy search;
+ private readonly int limit;
+
+ public IterativeDeepeningSearch(ISearchStrategy search, int limit)
+ {
+ this.search = search;
+ this.limit = limit;
+ }
+
+ public IEnumerable Search(ISearchProblem problem, S initialState)
+ {
+ for (int depth = 1; depth <= limit; depth++)
+ {
+ var dls = new DepthLimitedSearch(search, new DepthNodeLimiter(depth));
+ var result = dls.Search(problem, initialState);
+
+ if (!dls.IsCutoff(result))
+ {
+ return result;
+ }
+ }
+
+ return Enumerable.Empty();
+ }
+ }
+}
diff --git a/SpriteCompiler/AI/NodeExpanderDelegator.cs b/SpriteCompiler/AI/NodeExpanderDelegator.cs
index 576fabe..835a671 100644
--- a/SpriteCompiler/AI/NodeExpanderDelegator.cs
+++ b/SpriteCompiler/AI/NodeExpanderDelegator.cs
@@ -4,7 +4,7 @@
public class NodeExpanderDelegator : INodeExpander
where T : ISearchNode
- where C : IPathCost