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