Flesh out the sprite compiler state and successor functions. A bug exists that is causing no progress to be made resulting in an eventual OOM exception

This commit is contained in:
Lucas Scharenbroich 2016-11-29 00:20:29 -06:00
parent 943eb36acb
commit e00ae24ab0
10 changed files with 367 additions and 13 deletions

View File

@ -3,6 +3,7 @@ using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SpriteCompiler.Problem;
using SpriteCompiler.AI;
using System.Diagnostics;
namespace SpriteCompiler.Test
{
@ -22,5 +23,51 @@ namespace SpriteCompiler.Test
// Assert : The initial state IS the goal state
Assert.AreEqual(1, solution.Count());
}
[TestMethod]
public void TestSingleByteSprite()
{
// Arrange
var problem = SpriteGeneratorSearchProblem.CreateSearchProblem();
var search = SpriteGeneratorSearchProblem.Create();
// Act : solve the problem
var solution = search.Search(problem, new SpriteGeneratorState(new byte[] { 0xAA }));
// Assert
//
// The fastest way to draw a single byte at the current location should be
//
// TCS
// SHORT A
// LDA #$AA
// STA 0,s
// LONG A = 14 cycles
Assert.AreEqual(5, solution.Count());
Assert.AreEqual(14, solution.First().PathCost);
// Write out the solution
foreach (var step in solution)
{
Trace.WriteLine(step.Action.ToString());
}
}
/*
[TestMethod]
public void TestSingleWordSprite()
{
// Arrange
var problem = SpriteGeneratorSearchProblem.CreateSearchProblem();
var search = SpriteGeneratorSearchProblem.Create();
// Act : solve the problem
var solution = search.Search(problem, new SpriteGeneratorState());
// Assert : The initial state IS the goal state
Assert.AreEqual(1, solution.Count());
}
*/
}
}

View File

@ -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>
where C : IPathCost<C>, new()
{
public int Compare(T x, T y)
{

View File

@ -2,7 +2,7 @@
{
public class AStarSearch<A, S, T, C> : BestFirstSearch<A, S, T, C>
where T : HeuristicSearchNode<A, S, T, C>
where C : IPathCost<C>
where C : IPathCost<C>, new()
{
public AStarSearch(AbstractAISearch<A, S, T, C> search)
: base(search) // , new AStarComparator<A, S, T, C>())

View File

@ -5,14 +5,14 @@
public abstract class AbstractSearchNode<A, S, T, C> : ISearchNode<A, S, T, C>
where T : ISearchNode<A, S, T, C>
where C : IPathCost<C>
where C : IPathCost<C>, new()
{
protected readonly S state;
protected readonly T parent;
protected A action = default(A);
protected C pathCost = default(C);
protected C stepCost = default(C);
protected C pathCost = new C();
protected C stepCost = new C();
protected readonly int depth;
public AbstractSearchNode(T node, S state)

View File

@ -4,7 +4,7 @@
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>
where C : IPathCost<C>, new()
{
public HeuristicSearchNode(T node, S state)
: base(node, state)

View File

@ -5,7 +5,7 @@ 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>
where C : IPathCost<C>, new()
{
public abstract T CreateNode(T parent, S state);

View File

@ -9,9 +9,113 @@ namespace SpriteCompiler.Problem
/// <summary>
/// A set of code sequences that can be used to generate sprites
/// </summary>
public class CodeSequence
public abstract class CodeSequence
{
protected CodeSequence(int cycles)
{
CycleCount = cycles;
}
// Number of cycles that this code snippets takes to execute
public int CycleCount { get; }
public int CycleCount { get; private set; }
// Function to generate a new state based on the code's operation
public abstract SpriteGeneratorState Apply(SpriteGeneratorState state);
// Helper function for ToString implementations
protected string FormatLine(string label, string opcode, string operand, string comment)
{
return String.Format("{0}\t{1}\t{2}\t; {3}");
}
}
public sealed class MOVE_STACK : CodeSequence
{
private readonly int offset;
public MOVE_STACK(int offset)
: base(offset == 0 ? 2 : 5)
{
this.offset = offset;
}
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
{
return state.Clone(_ =>
{
_.A = _.A.Add(offset);
_.S = _.A;
});
}
public override string ToString()
{
if (offset == 0)
{
return FormatLine("", "TCS", "", "2 cycles");
}
else
{
return String.Join("\n",
FormatLine("", "ADC", "#" + offset.ToString(), "3 cycles"),
FormatLine("", "TCS", "", "2 cycles")
);
}
}
}
public sealed class SHORT_M : CodeSequence
{
public SHORT_M() : base(3) { }
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
{
return state.Clone(_ => _.P |= 0x10);
}
public override string ToString()
{
return FormatLine("", "SEP", "#$10", "3 cycles");
}
}
public sealed class LONG_M : CodeSequence
{
public LONG_M() : base(3) { }
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
{
return state.Clone(_ => _.P &= 0xEF);
}
public override string ToString()
{
return FormatLine("", "REP", "#$10", "3 cycles");
}
}
public sealed class STACK_REL_8_BIT_IMMEDIATE_STORE : CodeSequence
{
private readonly byte value;
private readonly byte offset;
public STACK_REL_8_BIT_IMMEDIATE_STORE(byte value, byte offset) : base(6) { this.value = value; this.offset = offset; }
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
{
return state.Clone(_ =>
{
_.A = _.A.LoadConstant((_.A.Value & 0xFF00) | value);
_.RemoveByte((ushort)(offset + _.S.Value));
});
}
public override string ToString()
{
return String.Join("\n",
FormatLine("", "LDA", "#$" + value.ToString("X2"), "2 cycles"),
FormatLine("", "STA", offset.ToString("X2") + ",s", "4 cycles")
);
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpriteCompiler.Problem
{
public struct SpriteByte
{
private static byte DataToMask(byte data)
{
return (byte)((((data & 0xF0) == 0x00) ? 0xF0 : 0x00) | (((data & 0x0F) == 0x00) ? 0x0F : 0x00));
}
public SpriteByte(byte data, ushort offset)
: this(data, DataToMask(data), offset)
{
}
public SpriteByte(byte data, byte mask, ushort offset)
{
Data = data;
Mask = mask;
Offset = offset;
}
public byte Data { get; private set; }
public byte Mask { get; private set; }
public ushort Offset { get; private set; }
}
}

View File

@ -11,6 +11,11 @@ namespace SpriteCompiler.Problem
{
private readonly int value;
public IntegerPathCost()
: this(0)
{
}
private IntegerPathCost(int value)
{
this.value = value;
@ -40,14 +45,125 @@ namespace SpriteCompiler.Problem
public class SpriteGeneratorState
{
public SpriteGeneratorState()
: this(new SpriteByte[0])
{
}
public SpriteGeneratorState(byte[][] data, byte[][] mask)
public SpriteGeneratorState(byte[] data)
: this(data.Select((x, i) => new SpriteByte(x, (ushort)i)))
{
}
public bool IsEmpty { get { return true; } }
public SpriteGeneratorState(IEnumerable<SpriteByte> bytes)
{
Bytes = bytes.ToList();
// Initialize the CPU state
A = Register.INITIAL_OFFSET; // the address to draw the sprite is passed in, this is a run-time value
X = Register.UNINITIALIZED; // the other registered are also undefined
Y = Register.UNINITIALIZED;
D = Register.UNINITIALIZED;
S = Register.UNINITIALIZED;
P = 0x30; // Start in native mode (16 bit A/X/Y) with the carry clear
}
private SpriteGeneratorState(SpriteGeneratorState other)
{
Bytes = new List<SpriteByte>(other.Bytes);
A = other.A;
X = other.X;
Y = other.Y;
D = other.D;
S = other.S;
P = other.P;
}
public void RemoveByte(ushort offset)
{
var total = Bytes.RemoveAll(x => x.Offset == offset);
if (total != 1)
{
throw new ArgumentException(string.Format("Cannot remove: {0}", total));
}
}
public SpriteGeneratorState Clone(Action<SpriteGeneratorState> f = null)
{
var other = new SpriteGeneratorState(this);
f?.Invoke(other);
return other;
}
public List<SpriteByte> Bytes { get; private set; }
public bool IsEmpty { get { return Bytes.Count == 0; } }
public bool LongA { get { return (P & 0x10) == 0x10; } }
public bool LongI { get { return (P & 0x20) == 0x20; } }
// Maintain the state of the execution
public Register A { get; set; } // Nullable because unknown values can be passed through the accumulator
public Register X { get; set; }
public Register Y { get; set; }
public Register D { get; set; }
public Register S { get; set; } // S is always an offset, not a literal number
public byte P { get; set; }
}
public class Register
{
public static readonly Register UNINITIALIZED = new Register(0, DataType.UNINITIALIZED);
public static readonly Register INITIAL_OFFSET = new Register(0, DataType.SCREEN_OFFSET);
public enum DataType
{
UNINITIALIZED,
SCREEN_OFFSET,
LITERAL
}
private Register(int value, DataType tag)
{
Value = value;
Tag = tag;
}
public Register Clone()
{
return new Register(Value, Tag);
}
public Register Add(int offset)
{
if (IsUninitialized)
{
throw new ArgumentException("Cannot add valued to uninitialized registers");
}
// Adding a value does not change the tag
return new Register(Value + offset, Tag);
}
public Register LoadConstant(int value)
{
return new Register(value, DataType.LITERAL);
}
public bool IsUninitialized { get { return DataType.UNINITIALIZED.Equals(Tag); } }
public bool IsScreenOffset { get { return DataType.SCREEN_OFFSET.Equals(Tag); } }
public bool IsLiteral { get { return DataType.LITERAL.Equals(Tag); } }
public DataType Tag { get; private set; }
public int Value { get; private set; }
public override string ToString()
{
return string.Format("{0} ({1})", Tag, Value.ToString("X4"));
}
}
public class SpriteGeneratorStepCost : IStepCostFunction<CodeSequence, SpriteGeneratorState, IntegerPathCost>
@ -71,7 +187,56 @@ namespace SpriteCompiler.Problem
{
public IDictionary<CodeSequence, SpriteGeneratorState> Successors(SpriteGeneratorState state)
{
return new Dictionary<CodeSequence, SpriteGeneratorState>();
var actions = new List<CodeSequence>();
// If the accumulator holds an offset then we could move to any byte position. We do
// not allow two consecutive MOVE_STACK operations
if (state.A.IsScreenOffset && !state.S.IsScreenOffset)
{
foreach (var datum in state.Bytes)
{
actions.Add(new MOVE_STACK(datum.Offset - state.A.Value));
}
}
// If the accumulator and stack are both initialized, only propose moves to locations
// before and after the current 256 byte window
if (state.A.IsScreenOffset && state.S.IsScreenOffset)
{
var addr = state.S.Value;
foreach (var datum in state.Bytes.Where(x => (x.Offset - addr) > 255 || (x.Offset - addr) < 0))
{
actions.Add(new MOVE_STACK(datum.Offset - state.A.Value));
}
}
// It is always permissible to move to/from 16 bit mode
if (state.LongA)
{
actions.Add(new SHORT_M());
// Add any possible 16-bit data manipulations
}
else
{
actions.Add(new LONG_M());
// Add any possible 8-bit manipulations
if (state.S.IsScreenOffset)
{
var addr = state.S.Value;
// We can LDA #$XX / STA X,s for any values within 256 bytes of the curren address
foreach (var datum in state.Bytes.Where(x => (x.Offset - addr) < 255))
{
var offset = datum.Offset - addr;
actions.Add(new STACK_REL_8_BIT_IMMEDIATE_STORE((byte)offset, datum.Data));
}
}
}
// Run through the actions to create a dictionary
return actions.ToDictionary(x => x, x => x.Apply(state));
}
}
@ -89,6 +254,11 @@ namespace SpriteCompiler.Problem
: base(node, state)
{
}
public override string ToString()
{
return (action == null) ? "NO ACTION" : action.ToString();
}
}
public class SpriteGeneratorNodeExpander : InformedNodeExpander<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>
@ -113,7 +283,7 @@ namespace SpriteCompiler.Problem
public static ISearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost> Create()
{
var problem = CreateSearchProblem();
//var problem = CreateSearchProblem();
var strategy = new TreeSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(new SpriteGeneratorNodeExpander());
return new AStarSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(strategy);

View File

@ -69,6 +69,7 @@
<Compile Include="AI\GraphSearch.cs" />
<Compile Include="AI\TreeSearch.cs" />
<Compile Include="Problem\CodeSequence.cs" />
<Compile Include="Problem\SpriteByte.cs" />
<Compile Include="Problem\SpriteGeneratorSearchProblem.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />