Interim refactoring of core AI search code to be more inspectable

This commit is contained in:
Lucas Scharenbroich 2016-12-10 21:56:27 -06:00
parent 5b45dfefa8
commit cfc2cda1a8
14 changed files with 516 additions and 23 deletions

83
AI.Test/AI.Test.csproj Normal file
View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>66b89e29-23e4-4d16-a3d5-e55f0eda4d87</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>AI.Test</RootNamespace>
<AssemblyName>AI.Test</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="UnitTest1.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

120
AI.Test/EightPuzzleBoard.cs Normal file
View File

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AI.Test
{
public class EightPuzzleBoard
{
protected static Random rng = new Random();
public enum Direction
{
LEFT,
RIGHT,
UP,
DOWN
}
private int[] board;
public EightPuzzleBoard()
: this(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 })
{
}
public EightPuzzleBoard(int[] aBoard)
{
this.board = aBoard;
}
public EightPuzzleBoard(EightPuzzleBoard aBoard)
{
this.board = (int[])aBoard.board.Clone();
}
public EightPuzzleBoard Scramble()
{
return Scramble(10);
}
public EightPuzzleBoard Scramble(int n)
{
var newPuzzle = new EightPuzzleBoard(this);
var direction = Enum.GetValues(typeof(Direction)).Cast<Direction>().ToList();
for (int i = 0; i < n; i++)
{
int j = rng.Next(direction.Count);
if (newPuzzle.CanMoveGap(direction[j]))
{
newPuzzle.MoveGap(direction[j]);
}
}
return newPuzzle;
}
private int[] ind2sub(int x)
{
if (x < 0 || x > 8)
{
return null;
}
return new int[] { x / 3, x % 3 };
}
protected int sub2ind(int x, int y)
{
return x * 3 + y;
}
public int this[int key]
{
get
{
return board[key];
}
set
{
board[key] = value;
}
}
private int GapPosition { get { return GetPositionOf(0); } }
public int CountMismatches(EightPuzzleBoard aBoard)
{
int count = 0;
for (int i = 0; i < 9; i++)
{
if (board[i] != aBoard[i] && board[i] != 0)
{
count++;
}
}
return count;
}
private int GetPositionOf(int val)
{
int retVal = -1;
for (int i = 0; i < 9; i++)
{
if (board[i] == val)
{
retVal = i;
}
}
return retVal;
}
public int[] Board { get { return board; } }
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("AI.Test")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AI.Test")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("66b89e29-23e4-4d16-a3d5-e55f0eda4d87")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

14
AI.Test/SearchTest.cs Normal file
View File

@ -0,0 +1,14 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AI.Test
{
[TestClass]
public class SearchTest
{
[TestMethod]
public void TestMethod1()
{
}
}
}

View File

@ -4,8 +4,18 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
public abstract class AbstractAISearch<A, S, T, C>
/// <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>
{
@ -19,10 +29,12 @@
// 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)
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; } }
@ -46,21 +58,34 @@
{
while (!fringe.Empty)
{
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))
var step = SearchStep(problem, fringe);
if (step.IsGoal)
{
return Solution(node);
return Solution(step.Node);
}
AddNodes(fringe, node, problem);
}
return Enumerable.Empty<T>();
}
public ISearchStepInfo<T> SearchStep(ISearchProblem<A, S, C> problem, IQueue<T> fringe)
{
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);
@ -73,10 +98,16 @@
public virtual IEnumerable<T> Search(ISearchProblem<A, S, C> problem, IQueue<T> fringe, S initialState)
{
fringe.Enqueue(expander.CreateNode(default(T), 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);
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpriteCompiler.AI
{
public abstract class AbstractStateSpaceSearch<A, S, T, C>
where T : ISearchNode<A, S, T, C>
where C : IPathCost<C>
{
private readonly INodeExpander<A, S, T, C> expander;
private readonly Func<IQueue<T>> fringe;
public AbstractStateSpaceSearch(INodeExpander<A, S, T, C> expander, Func<IQueue<T>> fringe)
{
this.expander = expander;
this.fringe = fringe;
}
public ISearchProblemInstance<A, S, T, C> Create(ISearchProblem<A, S, C> problem, S initialState)
{
return new SearchProblemInstance<A, S, T, C>(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;
}
}
}

View File

@ -32,5 +32,15 @@
{
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);
}
}
}

View File

@ -28,5 +28,15 @@ namespace SpriteCompiler.AI
{
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);
}
}
}

View File

@ -20,5 +20,7 @@
*/
IEnumerable<T> ExtendSearch(ISearchProblem<A, S, C> problem);
void InitializeSearch(S initialState);
ISearchStepInfo<T> SearchStep(ISearchProblem<A, S, C> problem);
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpriteCompiler.AI
{
public interface ISearchStepInfo<T>
{
bool IsGoal { get; }
T Node { get; }
IEnumerable<T> Solution { get; }
}
public class SearchStepInfo<T> : ISearchStepInfo<T>
{
private readonly T node;
private readonly IEnumerable<T> solution;
public SearchStepInfo(T node, IEnumerable<T> solution)
{
this.solution = solution;
this.node = node;
}
public IEnumerable<T> Solution { get { return solution; } }
public bool IsGoal { get { return solution != null; } }
public T Node { get { return node; } }
}
}

View File

@ -41,5 +41,15 @@ namespace SpriteCompiler.AI
{
throw new NotImplementedException();
}
public void InitializeSearch(S initialState)
{
search.InitializeSearch(initialState);
}
public ISearchStepInfo<T> SearchStep(ISearchProblem<A, S, C> problem)
{
return search.SearchStep(problem);
}
}
}

View File

@ -22,5 +22,15 @@
return new AStarSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(strategy);
}
public static ISearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost> Create(int maxCycles)
{
var expander = new SpriteGeneratorNodeExpander();
//var strategy = new TreeSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(expander);
var strategy = new GraphSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(expander);
var maxCost = (IntegerPathCost)maxCycles;
return new IterativeDeepeningAStarSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(strategy, maxCost);
}
}
}

View File

@ -49,7 +49,9 @@
var mask = new List<byte>();
var filename = (string)null;
Color? maskColor = null;
int? maxCycles = null;
var sprite = new List<SpriteByte>();
bool verbose = false;
var p = new FluentCommandLineParser();
@ -62,6 +64,12 @@
p.Setup<string>('i', "image")
.Callback(_ => filename = _);
p.Setup<string>('l', "limit")
.Callback(_ => maxCycles = int.Parse(_));
p.Setup<string>('v', "verbose")
.Callback(_ => verbose = true);
p.Setup<string>("bg-color")
.Callback(_ => maskColor = Color.FromArgb(0xFF, Color.FromArgb(Convert.ToInt32(_, 16))));
@ -72,6 +80,15 @@
Console.WriteLine("Input filename is " + filename);
Console.WriteLine("Image mask color is " + (maskColor.HasValue ? maskColor.ToString() : "(none)"));
// Set the global state
var problem = SpriteGeneratorSearchProblem.CreateSearchProblem();
var search = maxCycles.HasValue ?
SpriteGeneratorSearchProblem.Create(maxCycles.Value) :
SpriteGeneratorSearchProblem.Create();
SpriteGeneratorState initialState = null;
// Handle the difference command line cases
if (!String.IsNullOrEmpty(filename))
{
var palette = new Dictionary<Color, int>();
@ -149,23 +166,38 @@
sprite.Add(new SpriteByte(data_byte, mask_byte, offset));
}
}
}
}
// Set the global state
var problem = SpriteGeneratorSearchProblem.CreateSearchProblem();
var search = SpriteGeneratorSearchProblem.Create();
if (data.Count == mask.Count)
initialState = SpriteGeneratorState.Init(sprite);
}
else if (data.Count == mask.Count)
{
var solution = search.Search(problem, SpriteGeneratorState.Init(data, mask));
WriteOutSolution(solution);
initialState = SpriteGeneratorState.Init(data, mask);
}
else
{
var solution = search.Search(problem, SpriteGeneratorState.Init(data));
WriteOutSolution(solution);
initialState = SpriteGeneratorState.Init(data);
}
IEnumerable<SpriteGeneratorSearchNode> solution = null;
if (verbose)
{
search.InitializeSearch(initialState);
while (true)
{
var step = search.SearchStep(problem);
if (step.IsGoal)
{
break;
}
}
}
else
{
solution = search.Search(problem, initialState);
}
WriteOutSolution(solution);
}
}
}

View File

@ -66,6 +66,7 @@
<Compile Include="AI\IHeuristicFunction.cs" />
<Compile Include="AI\InformedNodeExpander.cs" />
<Compile Include="AI\INodeLimiter.cs" />
<Compile Include="AI\ISearchStepInfo.cs" />
<Compile Include="AI\ISuccessorFunction.cs" />
<Compile Include="AI\IGoalTest.cs" />
<Compile Include="AI\INodeExpander.cs" />