mirror of
https://github.com/lscharen/iigs-sprite-compiler.git
synced 2025-02-11 17:30:41 +00:00
Split out supporting classes into their own class files
This commit is contained in:
parent
de3754e91e
commit
9d7aeb355c
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
44
SpriteCompiler/Problem/IntegerPathCost.cs
Normal file
44
SpriteCompiler/Problem/IntegerPathCost.cs
Normal file
@ -0,0 +1,44 @@
|
||||
namespace SpriteCompiler.Problem
|
||||
{
|
||||
using SpriteCompiler.AI;
|
||||
|
||||
public sealed class IntegerPathCost : IPathCost<IntegerPathCost>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
56
SpriteCompiler/Problem/Register.cs
Normal file
56
SpriteCompiler/Problem/Register.cs
Normal file
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
14
SpriteCompiler/Problem/SpriteGeneratorGoalTest.cs
Normal file
14
SpriteCompiler/Problem/SpriteGeneratorGoalTest.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace SpriteCompiler.Problem
|
||||
{
|
||||
using SpriteCompiler.AI;
|
||||
|
||||
public sealed class SpriteGeneratorGoalTest : IGoalTest<SpriteGeneratorState>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
12
SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs
Normal file
12
SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace SpriteCompiler.Problem
|
||||
{
|
||||
using SpriteCompiler.AI;
|
||||
|
||||
public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction<SpriteGeneratorState, IntegerPathCost>
|
||||
{
|
||||
public IntegerPathCost Eval(SpriteGeneratorState state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
12
SpriteCompiler/Problem/SpriteGeneratorNodeExpander.cs
Normal file
12
SpriteCompiler/Problem/SpriteGeneratorNodeExpander.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace SpriteCompiler.Problem
|
||||
{
|
||||
using SpriteCompiler.AI;
|
||||
|
||||
public sealed class SpriteGeneratorNodeExpander : InformedNodeExpander<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>
|
||||
{
|
||||
public override SpriteGeneratorSearchNode CreateNode(SpriteGeneratorSearchNode parent, SpriteGeneratorState state)
|
||||
{
|
||||
return new SpriteGeneratorSearchNode(parent, state);
|
||||
}
|
||||
}
|
||||
}
|
17
SpriteCompiler/Problem/SpriteGeneratorSearchNode.cs
Normal file
17
SpriteCompiler/Problem/SpriteGeneratorSearchNode.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace SpriteCompiler.Problem
|
||||
{
|
||||
using SpriteCompiler.AI;
|
||||
|
||||
public sealed class SpriteGeneratorSearchNode : HeuristicSearchNode<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>
|
||||
{
|
||||
public SpriteGeneratorSearchNode(SpriteGeneratorSearchNode node, SpriteGeneratorState state)
|
||||
: base(node, state)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return (action == null) ? "NO ACTION" : action.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<IntegerPathCost>
|
||||
{
|
||||
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<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);
|
||||
|
||||
if (f != null)
|
||||
{
|
||||
f(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 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<CodeSequence, SpriteGeneratorState, IntegerPathCost>
|
||||
{
|
||||
public IntegerPathCost StepCost(SpriteGeneratorState fromState, CodeSequence action, SpriteGeneratorState toState)
|
||||
{
|
||||
return action.CycleCount;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpriteGeneratorGoalTest : IGoalTest<SpriteGeneratorState>
|
||||
{
|
||||
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<CodeSequence, SpriteGeneratorState>
|
||||
{
|
||||
public IDictionary<CodeSequence, SpriteGeneratorState> 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<CodeSequence>();
|
||||
|
||||
// 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<SpriteByte, bool> WithinRangeOf(int addr, int range)
|
||||
{
|
||||
return x => (x.Offset >= addr) && ((x.Offset - addr) < range);
|
||||
}
|
||||
}
|
||||
|
||||
public class SpriteGeneratorHeuristicFunction : IHeuristicFunction<SpriteGeneratorState, IntegerPathCost>
|
||||
{
|
||||
public IntegerPathCost Eval(SpriteGeneratorState state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpriteGeneratorSearchNode : HeuristicSearchNode<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>
|
||||
{
|
||||
public SpriteGeneratorSearchNode(SpriteGeneratorSearchNode node, SpriteGeneratorState state)
|
||||
: base(node, state)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return (action == null) ? "NO ACTION" : action.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpriteGeneratorNodeExpander : InformedNodeExpander<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>
|
||||
{
|
||||
public override SpriteGeneratorSearchNode CreateNode(SpriteGeneratorSearchNode parent, SpriteGeneratorState state)
|
||||
{
|
||||
return new SpriteGeneratorSearchNode(parent, state);
|
||||
}
|
||||
}
|
||||
|
||||
public class SpriteGeneratorSearchProblem
|
||||
public sealed class SpriteGeneratorSearchProblem
|
||||
{
|
||||
public static ISearchProblem<CodeSequence, SpriteGeneratorState, IntegerPathCost> CreateSearchProblem()
|
||||
{
|
||||
|
90
SpriteCompiler/Problem/SpriteGeneratorState.cs
Normal file
90
SpriteCompiler/Problem/SpriteGeneratorState.cs
Normal file
@ -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<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 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<SpriteGeneratorState> f = null)
|
||||
{
|
||||
var other = new SpriteGeneratorState(this);
|
||||
|
||||
if (f != null)
|
||||
{
|
||||
f(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; }
|
||||
}
|
||||
}
|
12
SpriteCompiler/Problem/SpriteGeneratorStepCost.cs
Normal file
12
SpriteCompiler/Problem/SpriteGeneratorStepCost.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace SpriteCompiler.Problem
|
||||
{
|
||||
using SpriteCompiler.AI;
|
||||
|
||||
public sealed class SpriteGeneratorStepCost : IStepCostFunction<CodeSequence, SpriteGeneratorState, IntegerPathCost>
|
||||
{
|
||||
public IntegerPathCost StepCost(SpriteGeneratorState fromState, CodeSequence action, SpriteGeneratorState toState)
|
||||
{
|
||||
return action.CycleCount;
|
||||
}
|
||||
}
|
||||
}
|
128
SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs
Normal file
128
SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs
Normal file
@ -0,0 +1,128 @@
|
||||
namespace SpriteCompiler.Problem
|
||||
{
|
||||
using SpriteCompiler.AI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
public sealed class SpriteGeneratorSuccessorFunction : ISuccessorFunction<CodeSequence, SpriteGeneratorState>
|
||||
{
|
||||
public IDictionary<CodeSequence, SpriteGeneratorState> 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<CodeSequence>();
|
||||
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<SpriteByte, bool> WithinRangeOf(int addr, int range)
|
||||
{
|
||||
return x => (x.Offset >= addr) && ((x.Offset - addr) < range);
|
||||
}
|
||||
}
|
||||
}
|
@ -69,8 +69,17 @@
|
||||
<Compile Include="AI\GraphSearch.cs" />
|
||||
<Compile Include="AI\TreeSearch.cs" />
|
||||
<Compile Include="Problem\CodeSequence.cs" />
|
||||
<Compile Include="Problem\IntegerPathCost.cs" />
|
||||
<Compile Include="Problem\Register.cs" />
|
||||
<Compile Include="Problem\SpriteByte.cs" />
|
||||
<Compile Include="Problem\SpriteGeneratorGoalTest.cs" />
|
||||
<Compile Include="Problem\SpriteGeneratorHeuristicFunction.cs" />
|
||||
<Compile Include="Problem\SpriteGeneratorNodeExpander.cs" />
|
||||
<Compile Include="Problem\SpriteGeneratorSearchNode.cs" />
|
||||
<Compile Include="Problem\SpriteGeneratorSearchProblem.cs" />
|
||||
<Compile Include="Problem\SpriteGeneratorState.cs" />
|
||||
<Compile Include="Problem\SpriteGeneratorStepCost.cs" />
|
||||
<Compile Include="Problem\SpriteGeneratorSuccessorFunction.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
Loading…
x
Reference in New Issue
Block a user