mirror of
https://github.com/lscharen/iigs-sprite-compiler.git
synced 2024-12-10 04:51:02 +00:00
Significant refactoring and addition of more test cases to exercise the base AI seach code
This commit is contained in:
parent
e4ae474a67
commit
ac9850998b
@ -8,7 +8,7 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>AI.Test</RootNamespace>
|
||||
<AssemblyName>AI.Test</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
@ -16,6 +16,7 @@
|
||||
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
|
||||
<IsCodedUITest>False</IsCodedUITest>
|
||||
<TestProjectType>UnitTest</TestProjectType>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@ -54,6 +55,12 @@
|
||||
<Compile Include="SearchTest.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SpriteCompiler\SpriteCompiler.csproj">
|
||||
<Project>{56f54ca9-17c1-45c6-915b-6fabf4dae5d0}</Project>
|
||||
<Name>SpriteCompiler</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Choose>
|
||||
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
|
||||
<ItemGroup>
|
||||
|
@ -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<Direction>().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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Direction, EightPuzzleBoard, EightPuzzleNode, IntegerCost>
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestMethod1()
|
||||
public EightPuzzleNode(EightPuzzleNode node, EightPuzzleBoard state) : base(node, state)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class EightPuzzleGoalTest : IGoalTest<EightPuzzleBoard>
|
||||
{
|
||||
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<Direction, EightPuzzleBoard>
|
||||
{
|
||||
public IEnumerable<Tuple<Direction, EightPuzzleBoard>> Successors(EightPuzzleBoard board)
|
||||
{
|
||||
foreach (var direction in Enum.GetValues(typeof(Direction)).Cast<Direction>())
|
||||
{
|
||||
if (board.CanMoveGap(direction))
|
||||
{
|
||||
yield return Tuple.Create(direction, new EightPuzzleBoard(board).MoveGap(direction));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class EightPuzzleStepCost : IStepCostFunction<Direction, EightPuzzleBoard, IntegerCost>
|
||||
{
|
||||
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<EightPuzzleBoard, IntegerCost>
|
||||
{
|
||||
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<EightPuzzleBoard, IntegerCost>
|
||||
{
|
||||
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<A, S, T, C> : NodeExpanderDelegator<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : ICost<C>
|
||||
{
|
||||
private readonly IDictionary<string, int> metrics = new Dictionary<string, int>();
|
||||
private readonly INodeExpander<A, S, T, C> expander;
|
||||
|
||||
public InstrumentedNodeExpander(INodeExpander<A, S, T, C> expander)
|
||||
: base(expander)
|
||||
{
|
||||
ClearMetrics();
|
||||
}
|
||||
|
||||
public override IEnumerable<T> Expand(ISearchProblem<A, S, C> 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<Direction, EightPuzzleBoard, EightPuzzleNode, IntegerCost>
|
||||
{
|
||||
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<Direction, EightPuzzleBoard, IntegerCost> problem_none;
|
||||
private ISearchProblem<Direction, EightPuzzleBoard, IntegerCost> problem_h1;
|
||||
private ISearchProblem<Direction, EightPuzzleBoard, IntegerCost> 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<Direction, EightPuzzleBoard, IntegerCost>(goalTest, stepCost, successorFn);
|
||||
problem_h1 = new SearchProblem<Direction, EightPuzzleBoard, IntegerCost>(goalTest, stepCost, successorFn, heuristic1);
|
||||
problem_h2 = new SearchProblem<Direction, EightPuzzleBoard, IntegerCost>(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<Direction, EightPuzzleBoard, EightPuzzleNode, IntegerCost>(new EightPuzzleNodeExpander());
|
||||
var treeSearch = new TreeSearch<Direction, EightPuzzleBoard, EightPuzzleNode, IntegerCost>(expander);
|
||||
var ids = new IterativeDeepeningSearch<Direction, EightPuzzleBoard, EightPuzzleNode, IntegerCost>(treeSearch, dmax);
|
||||
var aStarH1 = new AStarSearch<Direction, EightPuzzleBoard, EightPuzzleNode, IntegerCost>(treeSearch, new QueueAdapter<EightPuzzleNode, IntegerCost>());
|
||||
var aStarH2 = new IterativeDeepeningAStarSearch<Direction, EightPuzzleBoard, EightPuzzleNode, IntegerCost>(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("+--+---------+--------+--------++---------+--------+--------+");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses Newton iteration to solve for the effective branching factor
|
||||
/// </summary>
|
||||
/// <param name="n">number of nodes expanded</param>
|
||||
/// <param name="d">depth of the solution</param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -8,7 +8,7 @@ namespace SpriteCompiler.AI
|
||||
{
|
||||
public class AStarComparator<A, S, T, C> : IComparer<T>
|
||||
where T : HeuristicSearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>, new()
|
||||
where C : ICost<C>, new()
|
||||
{
|
||||
public int Compare(T x, T y)
|
||||
{
|
||||
|
@ -1,15 +1,12 @@
|
||||
namespace SpriteCompiler.AI
|
||||
{
|
||||
public class AStarSearch<A, S, T, C> : BestFirstSearch<A, S, T, C>
|
||||
where T : HeuristicSearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>, new()
|
||||
{
|
||||
public AStarSearch(AbstractAISearch<A, S, T, C> search)
|
||||
: base(search) // , new AStarComparator<A, S, T, C>())
|
||||
{
|
||||
}
|
||||
using Queue;
|
||||
|
||||
public AStarSearch(AbstractAISearch<A, S, T, C> search, IQueue<T> fringe)
|
||||
public class AStarSearch<A, S, T, C> : BestFirstSearch<A, S, T, C>
|
||||
where T : IHeuristicSearchNode<A, S, T, C>
|
||||
where C : ICost<C>, new()
|
||||
{
|
||||
public AStarSearch(ISearchStrategy<A, S, T, C> search, IQueue<T> fringe)
|
||||
: base(search, fringe)
|
||||
{
|
||||
}
|
||||
|
@ -1,113 +0,0 @@
|
||||
namespace SpriteCompiler.AI
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="A"></typeparam>
|
||||
/// <typeparam name="S"></typeparam>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="C"></typeparam>
|
||||
public abstract class AbstractAISearch<A, S, T, C> : ISearch<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>
|
||||
{
|
||||
// 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<A, S, T, C> expander;
|
||||
private readonly IQueue<T> fringe;
|
||||
|
||||
public AbstractAISearch(INodeExpander<A, S, T, C> expander, IQueue<T> fringe)
|
||||
{
|
||||
this.expander = expander;
|
||||
this.fringe = fringe;
|
||||
}
|
||||
|
||||
public INodeExpander<A, S, T, C> Expander { get { return expander; } set { expander = value; } }
|
||||
|
||||
public IEnumerable<T> Solution(T node)
|
||||
{
|
||||
var sequence = new List<T>();
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
sequence.Add(node);
|
||||
node = node.Parent;
|
||||
}
|
||||
|
||||
sequence.Reverse();
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public IEnumerable<T> ExtendSearch(ISearchProblem<A, S, C> problem)
|
||||
{
|
||||
while (!fringe.Empty)
|
||||
{
|
||||
var step = SearchStep(problem, fringe);
|
||||
if (step.IsGoal)
|
||||
{
|
||||
return Solution(step.Node);
|
||||
}
|
||||
}
|
||||
|
||||
return Enumerable.Empty<T>();
|
||||
}
|
||||
|
||||
public ISearchStepInfo<T> SearchStep(ISearchProblem<A, S, C> 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<T>(node, Solution(node));
|
||||
}
|
||||
|
||||
AddNodes(fringe, node, problem);
|
||||
|
||||
return new SearchStepInfo<T>(node, null);
|
||||
}
|
||||
|
||||
public IEnumerable<T> Expand(ISearchProblem<A, S, C> problem, T node)
|
||||
{
|
||||
return expander.Expand(problem, node);
|
||||
}
|
||||
|
||||
public bool IsFailure(IEnumerable<T> solution)
|
||||
{
|
||||
return !solution.Any();
|
||||
}
|
||||
|
||||
public virtual IEnumerable<T> Search(ISearchProblem<A, S, C> problem, IQueue<T> fringe, S initialState)
|
||||
{
|
||||
InitializeSearch(fringe, initialState);
|
||||
return ExtendSearch(problem, fringe);
|
||||
}
|
||||
|
||||
public void InitializeSearch(IQueue<T> fringe, S initialState)
|
||||
{
|
||||
fringe.Clear();
|
||||
fringe.Enqueue(expander.CreateNode(default(T), initialState));
|
||||
}
|
||||
|
||||
protected abstract void AddNodes(IQueue<T> fringe, T node, ISearchProblem<A, S, C> problem);
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
|
||||
public abstract class AbstractSearchNode<A, S, T, C> : ISearchNode<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>, new()
|
||||
where C : ICost<C>, 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; } }
|
||||
}
|
||||
}
|
||||
|
95
SpriteCompiler/AI/AbstractSearchStrategy.cs
Normal file
95
SpriteCompiler/AI/AbstractSearchStrategy.cs
Normal file
@ -0,0 +1,95 @@
|
||||
namespace SpriteCompiler.AI
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Queue;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="A"></typeparam>
|
||||
/// <typeparam name="S"></typeparam>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="C"></typeparam>
|
||||
public abstract class AbstractSearchStrategy<A, S, T, C> : ISearchStrategy<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : ICost<C>
|
||||
{
|
||||
// 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<A, S, T, C> expander;
|
||||
|
||||
public AbstractSearchStrategy(INodeExpander<A, S, T, C> expander)
|
||||
{
|
||||
this.expander = expander;
|
||||
}
|
||||
|
||||
public INodeExpander<A, S, T, C> Expander { get { return expander; } set { expander = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to walk a solution node to the root and then reverse the list
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<T> Solution(T node)
|
||||
{
|
||||
var sequence = new List<T>();
|
||||
|
||||
for (var curr = node; node != null; node = node.Parent)
|
||||
{
|
||||
sequence.Add(curr);
|
||||
}
|
||||
|
||||
sequence.Reverse();
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="problem"></param>
|
||||
/// <param name="fringe">Must be initialize -- usually that means being empty</param>
|
||||
/// <param name="initialState"></param>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<T> Search(ISearchProblem<A, S, C> problem, IQueue<T> 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<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When it's time to actually expand a node and add the new states to the fringe, different
|
||||
/// algorhtms can make different choices
|
||||
/// </summary>
|
||||
/// <param name="fringe"></param>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="problem"></param>
|
||||
protected abstract void AddNodes(IQueue<T> fringe, T node, ISearchProblem<A, S, C> problem);
|
||||
}
|
||||
}
|
@ -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<A, S, T, C>
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Queue;
|
||||
|
||||
public abstract class AbstractStateSpaceSearch<A, S, T, C> : ISearch<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>
|
||||
where C : ICost<C>
|
||||
{
|
||||
private readonly INodeExpander<A, S, T, C> expander;
|
||||
protected readonly ISearchStrategy<A, S, T, C> strategy;
|
||||
private readonly Func<IQueue<T>> fringe;
|
||||
|
||||
public AbstractStateSpaceSearch(INodeExpander<A, S, T, C> expander, Func<IQueue<T>> fringe)
|
||||
public AbstractStateSpaceSearch(ISearchStrategy<A, S, T, C> strategy, Func<IQueue<T>> fringe)
|
||||
{
|
||||
this.expander = expander;
|
||||
this.fringe = fringe;
|
||||
this.strategy = strategy;
|
||||
this.fringe = fringe;
|
||||
}
|
||||
|
||||
public ISearchProblemInstance<A, S, T, C> Create(ISearchProblem<A, S, C> problem, S initialState)
|
||||
public virtual IEnumerable<T> Search(ISearchProblem<A, S, C> problem, S initialState)
|
||||
{
|
||||
return new SearchProblemInstance<A, S, T, C>(problem, fringe(), initialState);
|
||||
return strategy.Search(problem, fringe(), initialState);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ISearchProblemInstance<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerable<T> FindSolution();
|
||||
|
||||
/// <summary>
|
||||
/// Take a single step of the searth to allow introspection of the search process
|
||||
/// </summary>
|
||||
/// <param name="problem"></param>
|
||||
/// <returns></returns>
|
||||
ISearchStepInfo<T> Step();
|
||||
|
||||
/// <summary>
|
||||
/// Provide a set of callbacks to watch the execution of a search
|
||||
/// </summary>
|
||||
// void Trace();
|
||||
}
|
||||
|
||||
public class SearchProblemInstance<A, S, T, C> : ISearchProblemInstance<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>
|
||||
{
|
||||
private readonly ISearchProblem<A, S, C> problem;
|
||||
private readonly S initialState;
|
||||
private readonly IQueue<T> fringe;
|
||||
|
||||
public SearchProblemInstance(ISearchProblem<A, S, C> problem, IQueue<T> fringe, S initialState)
|
||||
{
|
||||
this.problem = problem;
|
||||
this.fringe = fringe;
|
||||
this.initialState = initialState;
|
||||
}
|
||||
|
||||
public IEnumerable<T> FindSolution()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ISearchStepInfo<T> Step()
|
||||
{
|
||||
var node = fringe.Remove();
|
||||
|
||||
if (problem.IsGoal(node.State))
|
||||
{
|
||||
return new SearchStepInfo<T>(node, Solution(node));
|
||||
}
|
||||
|
||||
AddNodes(fringe, node, problem);
|
||||
|
||||
return new SearchStepInfo<T>(node, null);
|
||||
}
|
||||
|
||||
public IEnumerable<T> Solution(T node)
|
||||
{
|
||||
var sequence = new List<T>();
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
sequence.Add(node);
|
||||
node = node.Parent;
|
||||
}
|
||||
|
||||
sequence.Reverse();
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,14 @@
|
||||
namespace SpriteCompiler.AI
|
||||
{
|
||||
using Adapters;
|
||||
using System.Collections.Generic;
|
||||
using Queue;
|
||||
|
||||
public class BestFirstSearch<A, S, T, C> : ISearch<A, S, T, C>
|
||||
public class BestFirstSearch<A, S, T, C> : AbstractStateSpaceSearch<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>
|
||||
where C : ICost<C>
|
||||
{
|
||||
protected readonly AbstractAISearch<A, S, T, C> search;
|
||||
protected readonly IQueue<T> fringe;
|
||||
|
||||
public BestFirstSearch(AbstractAISearch<A, S, T, C> search, IQueue<T> fringe)
|
||||
public BestFirstSearch(ISearchStrategy<A, S, T, C> strategy, IQueue<T> fringe)
|
||||
: base(strategy, () => fringe)
|
||||
{
|
||||
this.search = search;
|
||||
this.fringe = fringe;
|
||||
}
|
||||
|
||||
public BestFirstSearch(AbstractAISearch<A, S, T, C> search)
|
||||
{
|
||||
this.search = search;
|
||||
this.fringe = new QueueAdapter<T, C>();
|
||||
}
|
||||
|
||||
public IEnumerable<T> Search(ISearchProblem<A, S, C> problem, S initialState)
|
||||
{
|
||||
fringe.Clear();
|
||||
return search.Search(problem, fringe, initialState);
|
||||
}
|
||||
|
||||
public IEnumerable<T> ExtendSearch(ISearchProblem<A, S, C> problem)
|
||||
{
|
||||
return search.ExtendSearch(problem, fringe);
|
||||
}
|
||||
|
||||
public void InitializeSearch(S initialState)
|
||||
{
|
||||
search.InitializeSearch(fringe, initialState);
|
||||
}
|
||||
|
||||
public ISearchStepInfo<T> SearchStep(ISearchProblem<A,S,C> problem)
|
||||
{
|
||||
return search.SearchStep(problem, fringe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,18 +7,31 @@
|
||||
/// </summary>
|
||||
public class CostNodeLimiter<T, C> : INodeLimiter<T, C>
|
||||
where T : ISearchNode<C>
|
||||
where C : IPathCost<C>, new()
|
||||
where C : ICost<C>, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<A, S, T, C> : ISearch<A, S, T, C>
|
||||
using Queue;
|
||||
|
||||
public class DepthFirstSearch<A, S, T, C> : AbstractStateSpaceSearch<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>, new()
|
||||
where C : ICost<C>, new()
|
||||
{
|
||||
protected readonly AbstractAISearch<A, S, T, C> search;
|
||||
protected readonly IQueue<T> fringe = new Lifo<T>();
|
||||
|
||||
public DepthFirstSearch(AbstractAISearch<A, S, T, C> search)
|
||||
public DepthFirstSearch(ISearchStrategy<A, S, T, C> strategy)
|
||||
: base(strategy, () => new LIFO<T>())
|
||||
{
|
||||
this.search = search;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<T> Search(ISearchProblem<A, S, C> problem, S initialState)
|
||||
{
|
||||
fringe.Clear();
|
||||
return search.Search(problem, fringe, initialState);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<T> ExtendSearch(ISearchProblem<A, S, C> problem)
|
||||
{
|
||||
return search.ExtendSearch(problem, fringe);
|
||||
}
|
||||
|
||||
public void InitializeSearch(S initialState)
|
||||
{
|
||||
search.InitializeSearch(fringe, initialState);
|
||||
}
|
||||
|
||||
public ISearchStepInfo<T> SearchStep(ISearchProblem<A, S, C> problem)
|
||||
{
|
||||
return search.SearchStep(problem, fringe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace SpriteCompiler.AI
|
||||
{
|
||||
public class DepthLimitedNodeExpander<A, S, T, C> : NodeExpanderDelegator<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>, new()
|
||||
where C : ICost<C>, new()
|
||||
{
|
||||
private readonly INodeLimiter<T, C> limit;
|
||||
private bool cutoffOccured = false;
|
||||
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Depth-first search with a cutoff
|
||||
/// </summary>
|
||||
public class DepthLimitedSearch<A, S, T, C> : DepthFirstSearch<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>, new()
|
||||
where C : ICost<C>, new()
|
||||
{
|
||||
protected readonly INodeLimiter<T, C> limit;
|
||||
|
||||
public DepthLimitedSearch(AbstractAISearch<A, S, T, C> search, INodeLimiter<T, C> limit)
|
||||
: base(search)
|
||||
public DepthLimitedSearch(ISearchStrategy<A, S, T, C> strategy, INodeLimiter<T, C> limit)
|
||||
: base(strategy)
|
||||
{
|
||||
this.limit = limit;
|
||||
}
|
||||
@ -24,32 +21,26 @@ namespace SpriteCompiler.AI
|
||||
public override IEnumerable<T> Search(ISearchProblem<A, S, C> 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<A, S, T, C>(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<T> ExtendSearch(ISearchProblem<A, S, C> problem)
|
||||
{
|
||||
return base.ExtendSearch(problem);
|
||||
}
|
||||
|
||||
public bool IsCutoff(IEnumerable<T> result)
|
||||
|
19
SpriteCompiler/AI/DepthNodeLimiter.cs
Normal file
19
SpriteCompiler/AI/DepthNodeLimiter.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace SpriteCompiler.AI
|
||||
{
|
||||
public class DepthNodeLimiter<T, C> : INodeLimiter<T, C>
|
||||
where T : ISearchNode<C>
|
||||
where C : ICost<C>, new()
|
||||
{
|
||||
private readonly int maxDepth;
|
||||
|
||||
public DepthNodeLimiter(int maxDepth)
|
||||
{
|
||||
this.maxDepth = maxDepth;
|
||||
}
|
||||
|
||||
public bool Cutoff(T node)
|
||||
{
|
||||
return node.Depth >= maxDepth;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SpriteCompiler.AI
|
||||
namespace SpriteCompiler.AI
|
||||
{
|
||||
public class GraphSearch<A, S, T, C> : AbstractAISearch<A, S, T, C>
|
||||
using System.Collections.Generic;
|
||||
using Queue;
|
||||
|
||||
public class GraphSearch<A, S, T, C> : AbstractSearchStrategy<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>
|
||||
where C : ICost<C>
|
||||
{
|
||||
private readonly ISet<S> closed = new HashSet<S>();
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,14 @@
|
||||
{
|
||||
using System;
|
||||
|
||||
public class HeuristicSearchNode<A, S, T, C> : AbstractSearchNode<A, S, T, C>, ISearchNode<A, S, T, C>
|
||||
where T : HeuristicSearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>, new()
|
||||
public interface IHeuristicSearchNode<A, S, T, C> : ISearchNode<A, S, T, C> where C : ICost<C>
|
||||
{
|
||||
C EstCost { get; }
|
||||
}
|
||||
|
||||
public class HeuristicSearchNode<A, S, T, C> : AbstractSearchNode<A, S, T, C>, IHeuristicSearchNode<A, S, T, C>
|
||||
where T : IHeuristicSearchNode<A, S, T, C>
|
||||
where C : ICost<C>, 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
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
using System;
|
||||
|
||||
public interface IHeuristicFunction<S, C> where C : IPathCost<C>
|
||||
public interface IHeuristicFunction<S, C> where C : ICost<C>
|
||||
{
|
||||
C Eval(S state);
|
||||
}
|
||||
|
@ -11,9 +11,11 @@
|
||||
/// </summary>
|
||||
public interface INodeExpander<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>
|
||||
where C : ICost<C>
|
||||
{
|
||||
IEnumerable<T> Expand(ISearchProblem<A, S, C> problem, T node);
|
||||
|
||||
T CreateNode(T parent, S state);
|
||||
T CreateNode(S state);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
public interface INodeLimiter<T, C>
|
||||
where T : ISearchNode<C>
|
||||
where C : IPathCost<C>, new()
|
||||
where C : ICost<C>, new()
|
||||
{
|
||||
bool Cutoff(T node);
|
||||
}
|
||||
|
@ -2,8 +2,13 @@
|
||||
{
|
||||
using System;
|
||||
|
||||
public interface IPathCost<C> : IComparable<C>
|
||||
public interface ICost<C> : IComparable<C>
|
||||
{
|
||||
C Add(C value);
|
||||
|
||||
// Number theoretic values, i.e. C + ZERO = C, C * ONE = C
|
||||
C Zero();
|
||||
C One();
|
||||
C Maximum();
|
||||
}
|
||||
}
|
||||
|
@ -4,23 +4,11 @@
|
||||
|
||||
public interface ISearch<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>
|
||||
where C : ICost<C>
|
||||
{
|
||||
/// 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<T> Search(ISearchProblem<A, S, C> 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<T> ExtendSearch(ISearchProblem<A, S, C> problem);
|
||||
|
||||
void InitializeSearch(S initialState);
|
||||
ISearchStepInfo<T> SearchStep(ISearchProblem<A, S, C> problem);
|
||||
IEnumerable<T> Search(ISearchProblem<A, S, C> problem, S initialState);
|
||||
}
|
||||
}
|
||||
|
@ -9,18 +9,23 @@
|
||||
/// <typeparam name="S">State of the search</typeparam>
|
||||
/// <typeparam name="T">Type of the parent</typeparam>
|
||||
/// <typeparam name="C">Cost type</typeparam>
|
||||
public interface ISearchNode<A, S, T, C> : ISearchNode<C> where C : IPathCost<C>
|
||||
public interface ISearchNode<A, S, T, C> : ISearchNode<C> where C : ICost<C>
|
||||
{
|
||||
A Action { get; set; }
|
||||
S State { get; }
|
||||
T Parent { get; }
|
||||
}
|
||||
|
||||
public interface ISearchNode<C>
|
||||
/// <summary>
|
||||
/// Simplest representation of a seach node that just has the costs. This interface
|
||||
/// is useful for certain evaluation functions
|
||||
/// </summary>
|
||||
/// <typeparam name="C"></typeparam>
|
||||
public interface ISearchNode<C> where C : ICost<C>
|
||||
{
|
||||
C PathCost { get; }
|
||||
C StepCost { get; set; }
|
||||
int Depth { get; }
|
||||
C EstCost { get; }
|
||||
int Depth { get; }
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
public interface ISearchProblem<A, S, C>
|
||||
where C : IComparable<C>
|
||||
where C : ICost<C>
|
||||
{
|
||||
IEnumerable<Tuple<A, S>> Successors(S state);
|
||||
bool IsGoal(S state);
|
||||
|
18
SpriteCompiler/AI/ISearchStrategy.cs
Normal file
18
SpriteCompiler/AI/ISearchStrategy.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace SpriteCompiler.AI
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Queue;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public interface ISearchStrategy<A, S, T, C>
|
||||
where T : ISearchNode<A, S, T, C>
|
||||
where C : ICost<C>
|
||||
{
|
||||
IEnumerable<T> Search(ISearchProblem<A, S, C> problem, IQueue<T> fringe, S initialState);
|
||||
INodeExpander<A, S, T, C> Expander { get; set; }
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
using System;
|
||||
|
||||
public interface IStepCostFunction<A, S, C> where C : IPathCost<C>
|
||||
public interface IStepCostFunction<A, S, C> where C : ICost<C>
|
||||
{
|
||||
C StepCost(S fromState, A action, S toState);
|
||||
}
|
||||
|
@ -7,16 +7,17 @@ namespace SpriteCompiler.AI
|
||||
|
||||
public abstract class InformedNodeExpander<A, S, T, C> : INodeExpander<A, S, T, C>
|
||||
where T : HeuristicSearchNode<A, S, T, C>
|
||||
where C : IPathCost<C>, new()
|
||||
where C : ICost<C>, new()
|
||||
{
|
||||
public abstract T CreateNode(T parent, S state);
|
||||
public abstract T CreateNode(S state);
|
||||
|
||||
public IEnumerable<T> Expand(ISearchProblem<A, S, C> 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
|
||||