mirror of
https://github.com/lscharen/iigs-sprite-compiler.git
synced 2024-08-31 18:29:06 +00:00
Fix bugs in heuristic function and code sequences
This commit is contained in:
parent
5fc5a254ab
commit
5fe12243ad
37
SpriteCompiler.Test/HeuristicTests.cs
Normal file
37
SpriteCompiler.Test/HeuristicTests.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -35,7 +35,17 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FluentAssertions, Version=4.17.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.17.0\lib\net45\FluentAssertions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentAssertions.Core, Version=4.17.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.17.0\lib\net45\FluentAssertions.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
</ItemGroup>
|
||||
<Choose>
|
||||
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
|
||||
@ -50,6 +60,7 @@
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<Compile Include="HeuristicTests.cs" />
|
||||
<Compile Include="RegisterTests.cs" />
|
||||
<Compile Include="Tests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
@ -60,6 +71,9 @@
|
||||
<Name>SpriteCompiler</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Choose>
|
||||
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
|
||||
<ItemGroup>
|
||||
|
4
SpriteCompiler.Test/packages.config
Normal file
4
SpriteCompiler.Test/packages.config
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentAssertions" version="4.17.0" targetFramework="net452" />
|
||||
</packages>
|
@ -2,6 +2,7 @@
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
public abstract class AbstractAISearch<A, S, T, C>
|
||||
@ -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))
|
||||
{
|
||||
|
@ -43,5 +43,7 @@
|
||||
pathCost = HasParent ? parent.PathCost.Add(value) : value;
|
||||
}
|
||||
}
|
||||
|
||||
virtual public C EstCost { get { return PathCost; } }
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
public interface ISearchNode<A, S, T, C> : ISearchNode<C> where C : IPathCost<C>
|
||||
{
|
||||
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>
|
||||
{
|
||||
C PathCost { get; }
|
||||
C EstCost { get; }
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
|
||||
namespace SpriteCompiler.AI
|
||||
{
|
||||
using System.Linq;
|
||||
|
||||
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()
|
||||
@ -11,7 +13,13 @@ namespace SpriteCompiler.AI
|
||||
|
||||
public IEnumerable<T> Expand(ISearchProblem<A, S, C> 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;
|
||||
@ -21,6 +29,8 @@ namespace SpriteCompiler.AI
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SpriteCompiler.AI
|
||||
{
|
||||
|
@ -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()
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -2,9 +2,28 @@
|
||||
{
|
||||
using SpriteCompiler.AI;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction<SpriteGeneratorState, IntegerPathCost>
|
||||
{
|
||||
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
|
||||
if (state.S.IsScreenOffset)
|
||||
{
|
||||
cost += SpanAndGapCost(stack, start, prev, curr);
|
||||
}
|
||||
else
|
||||
{
|
||||
cost += SpanAndGapCost(start, prev, curr);
|
||||
}
|
||||
|
||||
// Start a new sppan
|
||||
start = curr;
|
||||
|
@ -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<SpriteByte> 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);
|
||||
|
@ -5,6 +5,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
public static class StateHelpers
|
||||
{
|
||||
public static byte? TryGetStackByte(this SpriteGeneratorState state, IDictionary<ushort, SpriteByte> 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<ushort, SpriteByte> data)
|
||||
{
|
||||
return TryGetStackWord(state, data, 0);
|
||||
}
|
||||
|
||||
public static ushort? TryGetStackWord(this SpriteGeneratorState state, IDictionary<ushort, SpriteByte> 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<CodeSequence, SpriteGeneratorState> Apply(this SpriteGeneratorState state, CodeSequence code)
|
||||
{
|
||||
return Tuple.Create(code, code.Apply(state));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SpriteGeneratorSuccessorFunction : ISuccessorFunction<CodeSequence, SpriteGeneratorState>
|
||||
{
|
||||
public IEnumerable<Tuple<CodeSequence, SpriteGeneratorState>> 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<CodeSequence>();
|
||||
// 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)))
|
||||
if (data == state.A.Value)
|
||||
{
|
||||
var offset = (byte)(datum.Offset - addr);
|
||||
actions.Add(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, offset));
|
||||
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<SpriteByte, bool> WithinRangeOf(int addr, int range)
|
||||
|
@ -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<string> Data { get; set; }
|
||||
public List<string> Mask { get; set; }
|
||||
}
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static void WriteOutSolution(IEnumerable<SpriteGeneratorSearchNode> 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<ApplicationArguments>();
|
||||
|
||||
// specify which property the value will be assigned too.
|
||||
p.Setup<List<string>>(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<List<string>>(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);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,10 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FluentCommandLineParser, Version=1.4.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentCommandLineParser.1.4.3\lib\net35\FluentCommandLineParser.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Priority Queue, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\OptimizedPriorityQueue.4.0.0\lib\net45\Priority Queue.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentCommandLineParser" version="1.4.3" targetFramework="net452" />
|
||||
<package id="OptimizedPriorityQueue" version="4.0.0" targetFramework="net452" />
|
||||
</packages>
|
Loading…
Reference in New Issue
Block a user