From 9d7aeb355c84629d3e62c242b86e3f4e2fc06c11 Mon Sep 17 00:00:00 2001 From: Lucas Scharenbroich Date: Wed, 30 Nov 2016 08:17:05 -0600 Subject: [PATCH] Split out supporting classes into their own class files --- SpriteCompiler/Problem/CodeSequence.cs | 37 +- SpriteCompiler/Problem/IntegerPathCost.cs | 44 +++ SpriteCompiler/Problem/Register.cs | 56 +++ .../Problem/SpriteGeneratorGoalTest.cs | 14 + .../SpriteGeneratorHeuristicFunction.cs | 12 + .../Problem/SpriteGeneratorNodeExpander.cs | 12 + .../Problem/SpriteGeneratorSearchNode.cs | 17 + .../Problem/SpriteGeneratorSearchProblem.cs | 332 +----------------- .../Problem/SpriteGeneratorState.cs | 90 +++++ .../Problem/SpriteGeneratorStepCost.cs | 12 + .../SpriteGeneratorSuccessorFunction.cs | 128 +++++++ SpriteCompiler/SpriteCompiler.csproj | 9 + 12 files changed, 426 insertions(+), 337 deletions(-) create mode 100644 SpriteCompiler/Problem/IntegerPathCost.cs create mode 100644 SpriteCompiler/Problem/Register.cs create mode 100644 SpriteCompiler/Problem/SpriteGeneratorGoalTest.cs create mode 100644 SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs create mode 100644 SpriteCompiler/Problem/SpriteGeneratorNodeExpander.cs create mode 100644 SpriteCompiler/Problem/SpriteGeneratorSearchNode.cs create mode 100644 SpriteCompiler/Problem/SpriteGeneratorState.cs create mode 100644 SpriteCompiler/Problem/SpriteGeneratorStepCost.cs create mode 100644 SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs diff --git a/SpriteCompiler/Problem/CodeSequence.cs b/SpriteCompiler/Problem/CodeSequence.cs index 95691e1..176116b 100644 --- a/SpriteCompiler/Problem/CodeSequence.cs +++ b/SpriteCompiler/Problem/CodeSequence.cs @@ -113,9 +113,9 @@ namespace SpriteCompiler.Problem public override string ToString() { return String.Join("\n", - FormatLine("", "LDA", "#$" + value.ToString("X2"), "2 cycles"), - FormatLine("", "STA", offset.ToString("X2") + ",s", "4 cycles") - ); + FormatLine("", "LDA", "#$" + value.ToString("X2"), "2 cycles"), + FormatLine("", "STA", offset.ToString("X2") + ",s", "4 cycles") + ); } } @@ -131,18 +131,39 @@ namespace SpriteCompiler.Problem return state.Clone(_ => { _.A = _.A.LoadConstant(value); - _.RemoveByte((ushort)(offset + _.S.Value)); - _.RemoveByte((ushort)(offset + _.S.Value + 1)); + _.RemoveWord((ushort)(offset + _.S.Value)); }); } public override string ToString() { return String.Join("\n", - FormatLine("", "LDA", "#$" + value.ToString("X4"), "3 cycles"), - FormatLine("", "STA", offset.ToString("X2") + ",s", "5 cycles") - ); + FormatLine("", "LDA", "#$" + value.ToString("X4"), "3 cycles"), + FormatLine("", "STA", offset.ToString("X2") + ",s", "5 cycles") + ); } } + public sealed class PEA : CodeSequence + { + private readonly ushort value; + + public PEA(ushort value) : base(5) { this.value = value; } + + public override SpriteGeneratorState Apply(SpriteGeneratorState state) + { + return state.Clone(_ => + { + _.S.Add(-2); + _.RemoveWord((ushort)(_.S.Value - 1)); + }); + } + + public override string ToString() + { + return FormatLine("", "PEA", "$" + value.ToString("X4"), "5 cycles"); + } + } + + } diff --git a/SpriteCompiler/Problem/IntegerPathCost.cs b/SpriteCompiler/Problem/IntegerPathCost.cs new file mode 100644 index 0000000..42b1370 --- /dev/null +++ b/SpriteCompiler/Problem/IntegerPathCost.cs @@ -0,0 +1,44 @@ +namespace SpriteCompiler.Problem +{ + using SpriteCompiler.AI; + + public sealed class IntegerPathCost : IPathCost + { + private readonly int value; + + public IntegerPathCost() + : this(0) + { + } + + private IntegerPathCost(int value) + { + this.value = value; + } + + public static implicit operator int(IntegerPathCost obj) + { + return obj.value; + } + + public static implicit operator IntegerPathCost(int value) + { + return new IntegerPathCost(value); + } + + public IntegerPathCost Add(IntegerPathCost other) + { + return value + other.value; + } + + public int CompareTo(IntegerPathCost other) + { + return value.CompareTo(other.value); + } + + public override string ToString() + { + return value.ToString(); + } + } +} diff --git a/SpriteCompiler/Problem/Register.cs b/SpriteCompiler/Problem/Register.cs new file mode 100644 index 0000000..5ee62b5 --- /dev/null +++ b/SpriteCompiler/Problem/Register.cs @@ -0,0 +1,56 @@ +namespace SpriteCompiler.Problem +{ + using System; + + public sealed 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 value 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")); + } + } +} diff --git a/SpriteCompiler/Problem/SpriteGeneratorGoalTest.cs b/SpriteCompiler/Problem/SpriteGeneratorGoalTest.cs new file mode 100644 index 0000000..10decaf --- /dev/null +++ b/SpriteCompiler/Problem/SpriteGeneratorGoalTest.cs @@ -0,0 +1,14 @@ +namespace SpriteCompiler.Problem +{ + using SpriteCompiler.AI; + + public sealed class SpriteGeneratorGoalTest : IGoalTest + { + public bool IsGoal(SpriteGeneratorState state) + { + // We have reached our goal when there is no data left to display and we are back in + // 16-bit mode + return state.IsEmpty && state.LongA && state.LongI; + } + } +} diff --git a/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs b/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs new file mode 100644 index 0000000..ccb316c --- /dev/null +++ b/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs @@ -0,0 +1,12 @@ +namespace SpriteCompiler.Problem +{ + using SpriteCompiler.AI; + + public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction + { + public IntegerPathCost Eval(SpriteGeneratorState state) + { + return 0; + } + } +} diff --git a/SpriteCompiler/Problem/SpriteGeneratorNodeExpander.cs b/SpriteCompiler/Problem/SpriteGeneratorNodeExpander.cs new file mode 100644 index 0000000..f17ca9c --- /dev/null +++ b/SpriteCompiler/Problem/SpriteGeneratorNodeExpander.cs @@ -0,0 +1,12 @@ +namespace SpriteCompiler.Problem +{ + using SpriteCompiler.AI; + + public sealed class SpriteGeneratorNodeExpander : InformedNodeExpander + { + public override SpriteGeneratorSearchNode CreateNode(SpriteGeneratorSearchNode parent, SpriteGeneratorState state) + { + return new SpriteGeneratorSearchNode(parent, state); + } + } +} diff --git a/SpriteCompiler/Problem/SpriteGeneratorSearchNode.cs b/SpriteCompiler/Problem/SpriteGeneratorSearchNode.cs new file mode 100644 index 0000000..55d0924 --- /dev/null +++ b/SpriteCompiler/Problem/SpriteGeneratorSearchNode.cs @@ -0,0 +1,17 @@ +namespace SpriteCompiler.Problem +{ + using SpriteCompiler.AI; + + public sealed class SpriteGeneratorSearchNode : HeuristicSearchNode + { + public SpriteGeneratorSearchNode(SpriteGeneratorSearchNode node, SpriteGeneratorState state) + : base(node, state) + { + } + + public override string ToString() + { + return (action == null) ? "NO ACTION" : action.ToString(); + } + } +} diff --git a/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs b/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs index 815e933..88b403d 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs @@ -1,334 +1,8 @@ -using SpriteCompiler.AI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpriteCompiler.Problem +namespace SpriteCompiler.Problem { - public sealed class IntegerPathCost : IPathCost - { - private readonly int value; + using SpriteCompiler.AI; - public IntegerPathCost() - : this(0) - { - } - - private IntegerPathCost(int value) - { - this.value = value; - } - - public static implicit operator int(IntegerPathCost obj) - { - return obj.value; - } - - public static implicit operator IntegerPathCost(int value) - { - return new IntegerPathCost(value); - } - - public IntegerPathCost Add(IntegerPathCost other) - { - return value + other.value; - } - - public int CompareTo(IntegerPathCost other) - { - return value.CompareTo(other.value); - } - - public override string ToString() - { - return value.ToString(); - } - } - - public class SpriteGeneratorState - { - public SpriteGeneratorState() - : this(new SpriteByte[0]) - { - } - - public SpriteGeneratorState(byte[] data) - : this(data.Select((x, i) => new SpriteByte(x, (ushort)i))) - { - } - - public SpriteGeneratorState(IEnumerable 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(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 f = null) - { - var other = new SpriteGeneratorState(this); - - if (f != null) - { - f(other); - } - - return other; - } - - public List 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 value 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 - { - public IntegerPathCost StepCost(SpriteGeneratorState fromState, CodeSequence action, SpriteGeneratorState toState) - { - return action.CycleCount; - } - } - - public class SpriteGeneratorGoalTest : IGoalTest - { - public bool IsGoal(SpriteGeneratorState state) - { - // We have reached our goal when there is no data left to display and we are back in - // 16-bit mode - return state.IsEmpty && state.LongA && state.LongI; - } - } - - public class SpriteGeneratorSuccessorFunction : ISuccessorFunction - { - public IDictionary Successors(SpriteGeneratorState state) - { - // This is the work-horse of the compiler. For a given state we need to enumerate all of the - // potential next operations. - // - // 1. If there are 16-bits of data at then current offset, we can - // a. Use one of the cached valued in A/X/Y/D if they match (4 cycles) - // b. Use a PEA to push immediate values (5 cycles) - // c. Load a value into A/X/Y and then push (7 cycles, only feasible if the value appears elsewhere in the sprite) - // d. Load the value into D and then push (9 cycles, and leaves A = D) - // - // 2. Move the stack - // a. Add a value directly (7 cycles, A = unknown) - // b. Skip 1 byte (6 cycles, A = unknown TSC/DEC/TSC) - // c. Multiple skips (LDA X,s/AND/ORA/STA = 16/byte, ADC #/TCS/LDX #/PHX = 10/byte - // - // 3. Single-byte at the end of a solid run - // 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) - - var actions = new List(); - - // If the accumulator holds an offset then we could move to any byte position. - 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 stack-relative 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 - if (state.S.IsScreenOffset) - { - var addr = state.S.Value; - - // Look for consecutive bytes - var local = state.Bytes.Where(WithinRangeOf(addr, 257)).ToList(); // 16-bit value can extend to the 256th byte - var words = local - .Skip(1) - .Select((x, i) => new { High = x, Low = local[i] }) - .Where(p => p.Low.Offset == (p.High.Offset - 1)) - .ToList(); - - foreach (var word in words) - { - 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)); - } - } - - } - 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 current address - 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)); - } - } - } - - // Run through the actions to create a dictionary - return actions.ToDictionary(x => x, x => x.Apply(state)); - } - - private Func WithinRangeOf(int addr, int range) - { - return x => (x.Offset >= addr) && ((x.Offset - addr) < range); - } - } - - public class SpriteGeneratorHeuristicFunction : IHeuristicFunction - { - public IntegerPathCost Eval(SpriteGeneratorState state) - { - return 0; - } - } - - public class SpriteGeneratorSearchNode : HeuristicSearchNode - { - public SpriteGeneratorSearchNode(SpriteGeneratorSearchNode node, SpriteGeneratorState state) - : base(node, state) - { - } - - public override string ToString() - { - return (action == null) ? "NO ACTION" : action.ToString(); - } - } - - public class SpriteGeneratorNodeExpander : InformedNodeExpander - { - public override SpriteGeneratorSearchNode CreateNode(SpriteGeneratorSearchNode parent, SpriteGeneratorState state) - { - return new SpriteGeneratorSearchNode(parent, state); - } - } - - public class SpriteGeneratorSearchProblem + public sealed class SpriteGeneratorSearchProblem { public static ISearchProblem CreateSearchProblem() { diff --git a/SpriteCompiler/Problem/SpriteGeneratorState.cs b/SpriteCompiler/Problem/SpriteGeneratorState.cs new file mode 100644 index 0000000..9cba586 --- /dev/null +++ b/SpriteCompiler/Problem/SpriteGeneratorState.cs @@ -0,0 +1,90 @@ +namespace SpriteCompiler.Problem +{ + using System; + using System.Collections.Generic; + using System.Linq; + + public class SpriteGeneratorState + { + public SpriteGeneratorState() + : this(new SpriteByte[0]) + { + } + + public SpriteGeneratorState(byte[] data) + : this(data.Select((x, i) => new SpriteByte(x, (ushort)i))) + { + } + + public SpriteGeneratorState(IEnumerable 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(other.Bytes); + A = other.A; + X = other.X; + Y = other.Y; + D = other.D; + S = other.S; + P = other.P; + } + + public void RemoveWord(ushort offset) + { + var total = Bytes.RemoveAll(x => x.Offset == offset || x.Offset == (offset + 1)); + if (total != 2) + { + throw new ArgumentException(string.Format("Cannot remove word at {0}", offset)); + } + } + + public void RemoveByte(ushort offset) + { + var total = Bytes.RemoveAll(x => x.Offset == offset); + if (total != 1) + { + throw new ArgumentException(string.Format("Cannot remove byte at {0}", offset)); + } + } + + public SpriteGeneratorState Clone(Action f = null) + { + var other = new SpriteGeneratorState(this); + + if (f != null) + { + f(other); + } + + return other; + } + + public List 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; } + } +} diff --git a/SpriteCompiler/Problem/SpriteGeneratorStepCost.cs b/SpriteCompiler/Problem/SpriteGeneratorStepCost.cs new file mode 100644 index 0000000..e6c6c00 --- /dev/null +++ b/SpriteCompiler/Problem/SpriteGeneratorStepCost.cs @@ -0,0 +1,12 @@ +namespace SpriteCompiler.Problem +{ + using SpriteCompiler.AI; + + public sealed class SpriteGeneratorStepCost : IStepCostFunction + { + public IntegerPathCost StepCost(SpriteGeneratorState fromState, CodeSequence action, SpriteGeneratorState toState) + { + return action.CycleCount; + } + } +} diff --git a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs new file mode 100644 index 0000000..0d37414 --- /dev/null +++ b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs @@ -0,0 +1,128 @@ +namespace SpriteCompiler.Problem +{ + using SpriteCompiler.AI; + using System; + using System.Collections.Generic; + using System.Linq; + + public sealed class SpriteGeneratorSuccessorFunction : ISuccessorFunction + { + public IDictionary Successors(SpriteGeneratorState state) + { + // This is the work-horse of the compiler. For a given state we need to enumerate all of the + // potential next operations. + // + // 1. If there are 16-bits of data at then current offset, we can + // a. Use one of the cached valued in A/X/Y/D if they match (4 cycles) + // b. Use a PEA to push immediate values (5 cycles) + // c. Load a value into A/X/Y and then push (7 cycles, only feasible if the value appears elsewhere in the sprite) + // d. Load the value into D and then push (9 cycles, and leaves A = D) + // + // 2. Move the stack + // a. Add a value directly (7 cycles, A = unknown) + // b. Skip 1 byte (6 cycles, A = unknown TSC/DEC/TSC) + // c. Multiple skips (LDA X,s/AND/ORA/STA = 16/byte, ADC #/TCS/LDX #/PHX = 10/byte + // + // 3. Single-byte at the end of a solid run + // 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) + + var actions = new List(); + 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) + { + 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 stack-relative 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)); + } + } + + // 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) + { + var addr = state.S.Value; + if (bytes.ContainsKey((ushort)addr) && bytes.ContainsKey((ushort)(addr - 1))) + { + var high = bytes[(ushort)addr].Data; + var low = bytes[(ushort)(addr - 1)].Data; + + var word = (ushort)(low + (high << 8)); + actions.Add(new PEA(word)); + } + } + + // 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 + if (state.S.IsScreenOffset) + { + var addr = state.S.Value; + + // Look for consecutive bytes + var local = state.Bytes.Where(WithinRangeOf(addr, 257)).ToList(); // 16-bit value can extend to the 256th byte + var words = local + .Skip(1) + .Select((x, i) => new { High = x, Low = local[i] }) + .Where(p => p.Low.Offset == (p.High.Offset - 1)) + .ToList(); + + foreach (var word in words) + { + 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)); + } + } + } + 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 current address + 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)); + } + } + } + + // Run through the actions to create a dictionary + return actions.ToDictionary(x => x, x => x.Apply(state)); + } + + private Func WithinRangeOf(int addr, int range) + { + return x => (x.Offset >= addr) && ((x.Offset - addr) < range); + } + } +} diff --git a/SpriteCompiler/SpriteCompiler.csproj b/SpriteCompiler/SpriteCompiler.csproj index 05c05c3..f565285 100644 --- a/SpriteCompiler/SpriteCompiler.csproj +++ b/SpriteCompiler/SpriteCompiler.csproj @@ -69,8 +69,17 @@ + + + + + + + + +