diff --git a/SpriteCompiler.Test/HeuristicTests.cs b/SpriteCompiler.Test/HeuristicTests.cs new file mode 100644 index 0000000..e1e2d83 --- /dev/null +++ b/SpriteCompiler.Test/HeuristicTests.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SpriteCompiler.Problem; +using SpriteCompiler.AI; +using System.Diagnostics; +using System.Collections.Generic; +using FluentAssertions; + +namespace SpriteCompiler.Test +{ + [TestClass] + public class HeuristicTests + { + private SpriteGeneratorHeuristicFunction heuristic = new SpriteGeneratorHeuristicFunction(); + + [TestMethod] + public void TestSmallGap() + { + // Create a test with $XX -- -- $XX with the Accumulator loaded with $XX. Optimal code is + // ^ + // STA 3,s + // PHA = 7 cycles + + var state = new SpriteGeneratorState(new[] { new SpriteByte(0x11, 0), new SpriteByte(0x11, 3)}) + { + A = Register.Constant(0x0011), + S = Register.INITIAL_OFFSET, + P = SpriteGeneratorState.LONG_I + }; + + var h = heuristic.Eval(state); + + h.Should().BeLessOrEqualTo(7); + } + } +} diff --git a/SpriteCompiler.Test/SpriteCompiler.Test.csproj b/SpriteCompiler.Test/SpriteCompiler.Test.csproj index 92e471a..97dd1ed 100644 --- a/SpriteCompiler.Test/SpriteCompiler.Test.csproj +++ b/SpriteCompiler.Test/SpriteCompiler.Test.csproj @@ -35,7 +35,17 @@ 4 + + ..\packages\FluentAssertions.4.17.0\lib\net45\FluentAssertions.dll + True + + + ..\packages\FluentAssertions.4.17.0\lib\net45\FluentAssertions.Core.dll + True + + + @@ -50,6 +60,7 @@ + @@ -60,6 +71,9 @@ SpriteCompiler + + + diff --git a/SpriteCompiler.Test/packages.config b/SpriteCompiler.Test/packages.config new file mode 100644 index 0000000..32e1eb0 --- /dev/null +++ b/SpriteCompiler.Test/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/SpriteCompiler/AI/AbstractAISearch.cs b/SpriteCompiler/AI/AbstractAISearch.cs index 3476114..99f06ab 100644 --- a/SpriteCompiler/AI/AbstractAISearch.cs +++ b/SpriteCompiler/AI/AbstractAISearch.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Diagnostics; using System.Linq; public abstract class AbstractAISearch @@ -46,6 +47,7 @@ while (!fringe.Empty) { var node = fringe.Remove(); + Console.WriteLine(string.Format("Removed {0} from the queue with g = {1}, c(n, n') = {2}", node.State, node.PathCost, node.StepCost)); if (problem.IsGoal(node.State)) { diff --git a/SpriteCompiler/AI/AbstractSearchNode.cs b/SpriteCompiler/AI/AbstractSearchNode.cs index ab76569..bc10533 100644 --- a/SpriteCompiler/AI/AbstractSearchNode.cs +++ b/SpriteCompiler/AI/AbstractSearchNode.cs @@ -43,5 +43,7 @@ pathCost = HasParent ? parent.PathCost.Add(value) : value; } } + + virtual public C EstCost { get { return PathCost; } } } } diff --git a/SpriteCompiler/AI/HeuristicSearchNode.cs b/SpriteCompiler/AI/HeuristicSearchNode.cs index ef4ab9d..471ae9b 100644 --- a/SpriteCompiler/AI/HeuristicSearchNode.cs +++ b/SpriteCompiler/AI/HeuristicSearchNode.cs @@ -9,8 +9,17 @@ public HeuristicSearchNode(T node, S state) : base(node, state) { + Heuristic = new C(); } public C Heuristic { get; set; } + + public override C EstCost + { + get + { + return PathCost.Add(Heuristic); + } + } } } diff --git a/SpriteCompiler/AI/ISearchNode.cs b/SpriteCompiler/AI/ISearchNode.cs index 8cb500a..41435f0 100644 --- a/SpriteCompiler/AI/ISearchNode.cs +++ b/SpriteCompiler/AI/ISearchNode.cs @@ -12,6 +12,7 @@ public interface ISearchNode : ISearchNode where C : IPathCost { A Action { get; set; } + C PathCost { get; } C StepCost { get; set; } int Depth { get; } S State { get; } @@ -20,6 +21,6 @@ public interface ISearchNode { - C PathCost { get; } + C EstCost { get; } } } diff --git a/SpriteCompiler/AI/InformedNodeExpander.cs b/SpriteCompiler/AI/InformedNodeExpander.cs index d41e3f6..4db5129 100644 --- a/SpriteCompiler/AI/InformedNodeExpander.cs +++ b/SpriteCompiler/AI/InformedNodeExpander.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; namespace SpriteCompiler.AI { + using System.Linq; + public abstract class InformedNodeExpander : INodeExpander where T : HeuristicSearchNode where C : IPathCost, new() @@ -11,16 +13,24 @@ namespace SpriteCompiler.AI public IEnumerable Expand(ISearchProblem problem, T node) { - foreach (var successor in problem.Successors(node.State)) + var successors = problem.Successors(node.State); + + // 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)); + + foreach (var successor in successors) { var action = successor.Item1; var state = successor.Item2; var next = CreateNode(node, state); - - next.Action = action; + + next.Action = action; next.StepCost = problem.StepCost(node.State, action, state); next.Heuristic = problem.Heuristic(state); + Console.WriteLine(" Action = " + next.Action + ", g(n') = " + next.PathCost + ", h(n') = " + next.Heuristic); + yield return next; } } diff --git a/SpriteCompiler/AI/TreeSearch.cs b/SpriteCompiler/AI/TreeSearch.cs index a28483d..de72b5e 100644 --- a/SpriteCompiler/AI/TreeSearch.cs +++ b/SpriteCompiler/AI/TreeSearch.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; namespace SpriteCompiler.AI { diff --git a/SpriteCompiler/Adapters/QueueAdapter.cs b/SpriteCompiler/Adapters/QueueAdapter.cs index d645fde..288faed 100644 --- a/SpriteCompiler/Adapters/QueueAdapter.cs +++ b/SpriteCompiler/Adapters/QueueAdapter.cs @@ -20,7 +20,8 @@ { foreach (var item in items) { - queue.Enqueue(item, item.PathCost); + Console.WriteLine("Enqueuing " + item + " with cost " + item.EstCost); + queue.Enqueue(item, item.EstCost); } } @@ -31,7 +32,7 @@ public void Enqueue(T item) { - queue.Enqueue(item, item.PathCost); + queue.Enqueue(item, item.EstCost); } public T Remove() diff --git a/SpriteCompiler/Problem/CodeSequence.cs b/SpriteCompiler/Problem/CodeSequence.cs index 176116b..395e632 100644 --- a/SpriteCompiler/Problem/CodeSequence.cs +++ b/SpriteCompiler/Problem/CodeSequence.cs @@ -22,6 +22,9 @@ namespace SpriteCompiler.Problem // Function to generate a new state based on the code's operation public abstract SpriteGeneratorState Apply(SpriteGeneratorState state); + // Funtion to emit the source code + public abstract string Emit(); + // Helper function for ToString implementations protected string FormatLine(string label, string opcode, string operand, string comment) { @@ -49,6 +52,11 @@ namespace SpriteCompiler.Problem } public override string ToString() + { + return (offset == 0) ? "TSC" : ("ADC #" + offset.ToString() + " / TSC"); + } + + public override string Emit() { if (offset == 0) { @@ -74,6 +82,11 @@ namespace SpriteCompiler.Problem } public override string ToString() + { + return "SEP #$10"; + } + + public override string Emit() { return FormatLine("", "SEP", "#$10", "3 cycles"); } @@ -89,6 +102,11 @@ namespace SpriteCompiler.Problem } public override string ToString() + { + return "REP #$10"; + } + + public override string Emit() { return FormatLine("", "REP", "#$10", "3 cycles"); } @@ -111,6 +129,11 @@ namespace SpriteCompiler.Problem } public override string ToString() + { + return "LDA #$" + value.ToString("X2") + " / STA " + offset.ToString("X2") + ",s"; + } + + public override string Emit() { return String.Join("\n", FormatLine("", "LDA", "#$" + value.ToString("X2"), "2 cycles"), @@ -119,6 +142,32 @@ namespace SpriteCompiler.Problem } } + public sealed class STACK_REL_16_BIT_STORE : CodeSequence + { + private readonly ushort value; + private readonly byte offset; + + public STACK_REL_16_BIT_STORE(ushort value, byte offset) : base(5) { this.value = value; this.offset = offset; } + + public override SpriteGeneratorState Apply(SpriteGeneratorState state) + { + return state.Clone(_ => + { + _.RemoveWord((ushort)(offset + _.S.Value)); + }); + } + + public override string ToString() + { + return "STA " + offset.ToString("X2") + ",s"; + } + + public override string Emit() + { + return FormatLine("", "STA", offset.ToString("X2") + ",s", "5 cycles"); + } + } + public sealed class STACK_REL_16_BIT_IMMEDIATE_STORE : CodeSequence { private readonly ushort value; @@ -136,6 +185,11 @@ namespace SpriteCompiler.Problem } public override string ToString() + { + return "LDA #$" + value.ToString("X4") + " / STA " + offset.ToString("X2") + ",s"; + } + + public override string Emit() { return String.Join("\n", FormatLine("", "LDA", "#$" + value.ToString("X4"), "3 cycles"), @@ -144,6 +198,36 @@ namespace SpriteCompiler.Problem } } + public sealed class LOAD_16_BIT_IMMEDIATE_AND_PUSH : CodeSequence + { + private readonly ushort value; + + public LOAD_16_BIT_IMMEDIATE_AND_PUSH(ushort value) : base(7) { this.value = value; } + + public override SpriteGeneratorState Apply(SpriteGeneratorState state) + { + return state.Clone(_ => + { + _.A = _.A.LoadConstant(value); + _.RemoveWord((ushort)(_.S.Value - 1)); + _.S = _.S.Add(-2); + }); + } + + public override string ToString() + { + return "LDA #$" + value.ToString("X4") + " / PHA"; + } + + public override string Emit() + { + return String.Join("\n", + FormatLine("", "LDA", "#$" + value.ToString("X4"), "3 cycles"), + FormatLine("", "PHA", "", "4 cycles") + ); + } + } + public sealed class PEA : CodeSequence { private readonly ushort value; @@ -154,16 +238,43 @@ namespace SpriteCompiler.Problem { return state.Clone(_ => { - _.S.Add(-2); _.RemoveWord((ushort)(_.S.Value - 1)); + _.S = _.S.Add(-2); }); } public override string ToString() + { + return "PEA $" + value.ToString("X4"); + } + + public override string Emit() { return FormatLine("", "PEA", "$" + value.ToString("X4"), "5 cycles"); } } + public sealed class PHA : CodeSequence + { + public PHA() : base(4) { } + public override SpriteGeneratorState Apply(SpriteGeneratorState state) + { + return state.Clone(_ => + { + _.RemoveWord((ushort)(_.S.Value - 1)); + _.S = _.S.Add(-2); + }); + } + + public override string ToString() + { + return "PHA"; + } + + public override string Emit() + { + return FormatLine("", "PHA", "", "4 cycles"); + } + } } diff --git a/SpriteCompiler/Problem/Register.cs b/SpriteCompiler/Problem/Register.cs index b37724f..4751593 100644 --- a/SpriteCompiler/Problem/Register.cs +++ b/SpriteCompiler/Problem/Register.cs @@ -36,6 +36,11 @@ return new Register(Value + offset, Tag); } + public static Register Constant(int value) + { + return new Register(value, DataType.LITERAL); + } + public Register LoadConstant(int value) { return new Register(value, DataType.LITERAL); @@ -50,7 +55,18 @@ public override string ToString() { - return string.Format("{0} ({1})", Tag, Value.ToString("X4")); + switch (Tag) + { + default: + case DataType.UNINITIALIZED: + return " ----"; + + case DataType.SCREEN_OFFSET: + return "*" + Value.ToString("X4"); + + case DataType.LITERAL: + return " " + Value.ToString("X4"); + } } public override bool Equals(object obj) diff --git a/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs b/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs index 1e7469c..c6d0c0d 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs @@ -2,9 +2,28 @@ { using SpriteCompiler.AI; using System.Linq; + using System; public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction { + private static int SpanAndGapCost(int stack, int start, int end, int next) + { + var len = end - start + 1; + + // If the span is within 255 bytes of the stack, there is no + // gap penalty and we base the cost off of sta xx,s instructions + + var h1 = SpanAndGapCost(start, end, next); + var h2 = int.MaxValue; + + if (stack <= end && (end - stack) < 256) + { + h2 = 5 * (len / 2) + 4 * (len % 2); + } + + return Math.Min(h1, h2); + } + private static int SpanAndGapCost(int start, int end, int next) { // [start, end] is the span @@ -22,12 +41,10 @@ public IntegerPathCost Eval(SpriteGeneratorState state) { - // An admissible heuistic calculates a cost based on the gaps and runs in a sprite + // An admissible heuistic that calculates a cost based on the gaps and runs in a sprite // // An even-length run can be done, at best in 4 cycles/word // An odd-length run is even + 3 cycles/byte - // - // Each gap needs at least 5 cycles to cover (ADC # / TCS) var count = state.Bytes.Count; @@ -35,6 +52,7 @@ var offsets = state.Bytes.Select(x => x.Offset).OrderBy(x => x).ToList(); var start = offsets[0]; + var stack = state.S.Value; var curr = start; var cost = 0; @@ -49,7 +67,14 @@ } // Calculate the estimate cost - cost += SpanAndGapCost(start, prev, curr); + if (state.S.IsScreenOffset) + { + cost += SpanAndGapCost(stack, start, prev, curr); + } + else + { + cost += SpanAndGapCost(start, prev, curr); + } // Start a new sppan start = curr; diff --git a/SpriteCompiler/Problem/SpriteGeneratorState.cs b/SpriteCompiler/Problem/SpriteGeneratorState.cs index c356467..66f3f22 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorState.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorState.cs @@ -42,6 +42,11 @@ P = other.P; } + public override string ToString() + { + return String.Format("A = {0:X4}, X = {1}, Y = {2}, S = {3}, D = {4}, P = {5:X2}", A, X, Y, S, D, P); + } + public void RemoveWord(ushort offset) { var total = Bytes.RemoveAll(x => x.Offset == offset || x.Offset == (offset + 1)); @@ -72,6 +77,10 @@ return other; } + // A better state representation would be to have an array of offsets and a static + // data and mask array. Then the state is just the locations and registers, rather + // than a full copy of the data + public List Bytes { get; private set; } public bool IsEmpty { get { return Bytes.Count == 0; } } @@ -87,6 +96,9 @@ public byte P { get; set; } + public const byte LONG_A = 0x10; + public const byte LONG_I = 0x20; + public override bool Equals(object obj) { return Equals(obj as SpriteGeneratorState); diff --git a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs index 69600e4..892f515 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs @@ -5,6 +5,42 @@ using System.Collections.Generic; using System.Linq; + public static class StateHelpers + { + public static byte? TryGetStackByte(this SpriteGeneratorState state, IDictionary data) + { + SpriteByte top; + if (state.S.IsScreenOffset && data.TryGetValue((ushort)state.S.Value, out top)) + { + return top.Data; + } + + return null; + } + + public static ushort? TryGetStackWord(this SpriteGeneratorState state, IDictionary data) + { + return TryGetStackWord(state, data, 0); + } + + public static ushort? TryGetStackWord(this SpriteGeneratorState state, IDictionary data, int offset) + { + SpriteByte high; + SpriteByte low; + if (state.S.IsScreenOffset && (state.S.Value + offset) > 0 && data.TryGetValue((ushort)(state.S.Value + offset), out high) && data.TryGetValue((ushort)(state.S.Value + offset - 1), out low)) + { + return (ushort)(low.Data + (high.Data << 8)); + } + + return null; + } + + public static Tuple Apply(this SpriteGeneratorState state, CodeSequence code) + { + return Tuple.Create(code, code.Apply(state)); + } + } + public sealed class SpriteGeneratorSuccessorFunction : ISuccessorFunction { public IEnumerable> Successors(SpriteGeneratorState state) @@ -27,48 +63,80 @@ // a. If no registers are 8-bit, LDA #Imm/STA 0,s (8 cycles, sets Acc) // b. If any reg is already 8-bit, LDA #imm/PHA (6 cycles) // - // We al - var actions = new List(); + // We always try to return actions that write data since that moves us toward the goal state + + // Make it more convenient to get data by offset (this will probably be the representation of the state, eventually) var bytes = state.Bytes.ToDictionary(x => x.Offset, x => x); - // If the accumulator holds an offset then we could move to any byte position. - if (state.A.IsScreenOffset && !state.S.IsScreenOffset) + // Get the current byte and current word that exist at the current stack location + var topByte = state.TryGetStackByte(bytes); + var topWord = state.TryGetStackWord(bytes); + var nextWord = state.TryGetStackWord(bytes, -2); // Also get the next value below the current word + + // We can always perform a PEA regardless of the register widths + if (topWord.HasValue) { - foreach (var datum in state.Bytes) + yield return state.Apply(new PEA(topWord.Value)); + + // If any of the registers happen to match the value, we can do an optimized PHA/X/Y/D operations. Any one + // PHx is as good as another and cannot affect the state, so just pick the first one. + if (state.LongA) { - actions.Add(new MOVE_STACK(datum.Offset - state.A.Value)); + if (state.A.IsLiteral && state.A.Value == topWord.Value) + { + yield return state.Apply(new PHA()); + } + else + { + yield return state.Apply(new LOAD_16_BIT_IMMEDIATE_AND_PUSH(topWord.Value)); + } + + //else if (state.X.IsLiteral && state.X.Value == topWord.Value) { } + //else if (state.Y.IsLiteral && state.Y.Value == topWord.Value) { } + //else if (state.D.IsLiteral && state.D.Value == topWord.Value) { } + + // If the top two workd match, it might be worthwhile to load the accumulator to start immediate PHAs } } - // If the accumulator and stack are both initialized, only propose moves to locations - // before and after the current 256 byte stack-relative window - if (state.A.IsScreenOffset && state.S.IsScreenOffset) + // If there is a valid byte, then we can look for an 8-bit push, or an immediate mode LDA #XX/STA 0,s + if (topByte.HasValue) { - var addr = state.S.Value; - foreach (var datum in state.Bytes.Where(x => (x.Offset - addr) > 255 || (x.Offset - addr) < 0)) + if (!state.LongA) { - actions.Add(new MOVE_STACK(datum.Offset - state.A.Value)); + yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(topByte.Value, 0)); } } - // If the stack is valid on a word (consecutive bytes), when we can alway do a PEA - if (state.S.IsScreenOffset && state.S.Value > 0) + // If the accumulator holds an offset then we could move to any byte position, but it is only beneficial to + // move to the first or last byte of each span. So , take the first byte and then look for any + if (state.A.IsScreenOffset && !state.S.IsScreenOffset && state.LongA) { - var addr = state.S.Value; - if (bytes.ContainsKey((ushort)addr) && bytes.ContainsKey((ushort)(addr - 1))) + for (var i = 0; i < state.Bytes.Count; i++) { - var high = bytes[(ushort)addr].Data; - var low = bytes[(ushort)(addr - 1)].Data; + if (i == 0) + { + yield return state.Apply(new MOVE_STACK(state.Bytes[i].Offset - state.A.Value)); + continue; + } - var word = (ushort)(low + (high << 8)); - actions.Add(new PEA(word)); + if (i == state.Bytes.Count - 1) + { + yield return state.Apply(new MOVE_STACK(state.Bytes[i].Offset - state.A.Value)); + continue; + } + + if ((state.Bytes[i].Offset - state.Bytes[i-1].Offset) > 1) + { + yield return state.Apply(new MOVE_STACK(state.Bytes[i].Offset - state.A.Value)); + } } } // It is always permissible to move to/from 16 bit mode if (state.LongA) { - actions.Add(new SHORT_M()); + yield return state.Apply(new SHORT_M()); // Add any possible 16-bit data manipulations if (state.S.IsScreenOffset) @@ -87,21 +155,21 @@ { var offset = (byte)(word.Low.Offset - addr); var data = (ushort)(word.Low.Data + (word.High.Data << 8)); - actions.Add(new STACK_REL_16_BIT_IMMEDIATE_STORE(data, offset)); - } - - // We can LDA #$XXXX / STA X,s for any values within 256 bytes of the current address - foreach (var datum in state.Bytes.Where(WithinRangeOf(addr, 256))) - { - var offset = (byte)(datum.Offset - addr); - actions.Add(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, offset)); + if (data == state.A.Value) + { + yield return state.Apply(new STACK_REL_16_BIT_STORE(data, offset)); + } + else + { + yield return state.Apply(new STACK_REL_16_BIT_IMMEDIATE_STORE(data, offset)); + } } } } else { - actions.Add(new LONG_M()); + yield return state.Apply(new LONG_M()); // Add any possible 8-bit manipulations if (state.S.IsScreenOffset) @@ -112,13 +180,21 @@ foreach (var datum in state.Bytes.Where(WithinRangeOf(addr, 256))) { var offset = datum.Offset - addr; - actions.Add(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, (byte)offset)); + yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, (byte)offset)); } } } - // Run through the actions to create a dictionary - return actions.Select(x => Tuple.Create(x, x.Apply(state))); + // If the accumulator and stack are both initialized, only propose moves to locations + // before and after the current 256 byte stack-relative window + if (state.A.IsScreenOffset && state.S.IsScreenOffset && state.LongA) + { + var addr = state.S.Value; + foreach (var datum in state.Bytes.Where(x => (x.Offset - addr) > 255 || (x.Offset - addr) < 0)) + { + yield return state.Apply(new MOVE_STACK(datum.Offset - state.A.Value)); + } + } } private Func WithinRangeOf(int addr, int range) diff --git a/SpriteCompiler/Program.cs b/SpriteCompiler/Program.cs index 4b46979..c4f0226 100644 --- a/SpriteCompiler/Program.cs +++ b/SpriteCompiler/Program.cs @@ -1,9 +1,63 @@ namespace SpriteCompiler { + using Fclp; + using SpriteCompiler.Problem; + using System; + using System.Collections.Generic; + using System.Linq; + + public class ApplicationArguments + { + public List Data { get; set; } + public List Mask { get; set; } + } + public class Program { + public static void WriteOutSolution(IEnumerable solution) + { + foreach (var step in solution.Skip(1)) + { + Console.WriteLine(step.Action.Emit()); + } + + Console.WriteLine(string.Format("; Total Cost = {0} cycles", (int)solution.Last().PathCost)); + } + static void Main(string[] args) { + byte[] data = null; + + Console.WriteLine(string.Join(", ", args.Select(s => "'" + s + "'"))); + data = args.Select(s => Convert.ToByte(s, 16)).ToArray(); + + /* + return; + + var p = new FluentCommandLineParser(); + + // specify which property the value will be assigned too. + p.Setup>(arg => arg.Data) + .As('d', "data") // define the short and long option name + .Required() // using the standard fluent Api to declare this Option as required. + .Callback(d => d.Select(s => Convert.ToByte(s, 16)).ToArray()); + + p.Setup>(arg => arg.Mask) + .As('m', "mask"); + + var result = p.Parse(args); + + if (!result.HasErrors) + { + */ + + var problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); + var search = SpriteGeneratorSearchProblem.Create(); + + var solution = search.Search(problem, new SpriteGeneratorState(data)); + + WriteOutSolution(solution); + //} } } } diff --git a/SpriteCompiler/SpriteCompiler.csproj b/SpriteCompiler/SpriteCompiler.csproj index f565285..1615ddd 100644 --- a/SpriteCompiler/SpriteCompiler.csproj +++ b/SpriteCompiler/SpriteCompiler.csproj @@ -33,6 +33,10 @@ 4 + + ..\packages\FluentCommandLineParser.1.4.3\lib\net35\FluentCommandLineParser.dll + True + ..\packages\OptimizedPriorityQueue.4.0.0\lib\net45\Priority Queue.dll True diff --git a/SpriteCompiler/packages.config b/SpriteCompiler/packages.config index 2fddea5..501c013 100644 --- a/SpriteCompiler/packages.config +++ b/SpriteCompiler/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file