mirror of
https://github.com/lscharen/iigs-sprite-compiler.git
synced 2024-12-21 16:30:15 +00:00
Simplify success function and improve heuristic accuracy
This commit is contained in:
parent
df2318db29
commit
53561fbcc8
47
SpriteCompiler/AI/InstrumentedNodeExpander.cs
Normal file
47
SpriteCompiler/AI/InstrumentedNodeExpander.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpriteCompiler.AI
|
||||||
|
{
|
||||||
|
public static class IntrumentedParameters
|
||||||
|
{
|
||||||
|
public const string NODES_EXPANDED = "nodesExpanded";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InstrumentedNodeExpander<A, S, T, C> : NodeExpanderDelegator<A, S, T, C>
|
||||||
|
where T : ISearchNode<A, S, T, C>
|
||||||
|
where C : ICost<C>
|
||||||
|
{
|
||||||
|
private readonly IDictionary<string, int> metrics = new Dictionary<string, int>();
|
||||||
|
private readonly INodeExpander<A, S, T, C> expander;
|
||||||
|
|
||||||
|
public InstrumentedNodeExpander(INodeExpander<A, S, T, C> expander)
|
||||||
|
: base(expander)
|
||||||
|
{
|
||||||
|
ClearMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<T> Expand(ISearchProblem<A, S, C> problem, T node)
|
||||||
|
{
|
||||||
|
metrics[IntrumentedParameters.NODES_EXPANDED] += 1;
|
||||||
|
return base.Expand(problem, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int this[string key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return metrics[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearMetrics()
|
||||||
|
{
|
||||||
|
metrics[IntrumentedParameters.NODES_EXPANDED] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
85
SpriteCompiler/Helpers/EnumerableExtensions.cs
Normal file
85
SpriteCompiler/Helpers/EnumerableExtensions.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpriteCompiler.Helpers
|
||||||
|
{
|
||||||
|
public static class EnumerableExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<Tuple<T, T>> Pairs<T>(this IEnumerable<T> source)
|
||||||
|
{
|
||||||
|
return source.Zip(source.Skip(1), (x, y) => Tuple.Create(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IGrouping<T, T>> GroupAdjacent<T>(this IEnumerable<T> source, Func<T, T, bool> comparer)
|
||||||
|
{
|
||||||
|
return GroupAdjacent(source, x => x, comparer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
|
||||||
|
{
|
||||||
|
return GroupAdjacent(source, keySelector, (a, b) => a.Equals(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, TKey, bool> comparer)
|
||||||
|
{
|
||||||
|
TKey last = default(TKey);
|
||||||
|
bool haveLast = false;
|
||||||
|
List<TSource> list = new List<TSource>();
|
||||||
|
foreach (TSource s in source)
|
||||||
|
{
|
||||||
|
TKey k = keySelector(s);
|
||||||
|
if (haveLast)
|
||||||
|
{
|
||||||
|
if (!comparer(last, k))
|
||||||
|
{
|
||||||
|
yield return new GroupOfAdjacent<TSource, TKey>(list, last);
|
||||||
|
list = new List<TSource>();
|
||||||
|
list.Add(s);
|
||||||
|
last = k;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.Add(s);
|
||||||
|
last = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.Add(s);
|
||||||
|
last = k;
|
||||||
|
haveLast = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (haveLast)
|
||||||
|
{
|
||||||
|
yield return new GroupOfAdjacent<TSource, TKey>(list, last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GroupOfAdjacent<TSource, TKey> : IEnumerable<TSource>, IGrouping<TKey, TSource>
|
||||||
|
{
|
||||||
|
public TKey Key { get; set; }
|
||||||
|
private List<TSource> GroupList { get; set; }
|
||||||
|
|
||||||
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return ((System.Collections.Generic.IEnumerable<TSource>)this).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Collections.Generic.IEnumerator<TSource> System.Collections.Generic.IEnumerable<TSource>.GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach (var s in GroupList)
|
||||||
|
yield return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupOfAdjacent(List<TSource> source, TKey key)
|
||||||
|
{
|
||||||
|
GroupList = source;
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -102,7 +102,11 @@ namespace SpriteCompiler.Problem
|
|||||||
|
|
||||||
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
|
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
|
||||||
{
|
{
|
||||||
return state.Clone(_ => _.P &= 0xEF);
|
return state.Clone(_ =>
|
||||||
|
{
|
||||||
|
_.P &= 0xEF;
|
||||||
|
_.AllowModeChange = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@ -122,7 +126,11 @@ namespace SpriteCompiler.Problem
|
|||||||
|
|
||||||
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
|
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
|
||||||
{
|
{
|
||||||
return state.Clone(_ => _.P |= 0x10);
|
return state.Clone(_ =>
|
||||||
|
{
|
||||||
|
_.P |= 0x10;
|
||||||
|
_.AllowModeChange = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@ -224,6 +232,40 @@ namespace SpriteCompiler.Problem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class STACK_REL_8_BIT_READ_MODIFY_PUSH : CodeSequence
|
||||||
|
{
|
||||||
|
private readonly byte value;
|
||||||
|
private readonly byte mask;
|
||||||
|
|
||||||
|
public STACK_REL_8_BIT_READ_MODIFY_PUSH(byte value, byte mask) : base(11) { this.value = value; this.mask = mask; }
|
||||||
|
|
||||||
|
public override SpriteGeneratorState Apply(SpriteGeneratorState state)
|
||||||
|
{
|
||||||
|
return state.Clone(_ =>
|
||||||
|
{
|
||||||
|
_.A = Register.UNINITIALIZED;
|
||||||
|
_.RemoveByte((ushort)_.S.Value);
|
||||||
|
_.S.Add(-1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "LDA 0,s / AND #$" + mask.ToString("X2") + " / ORA #$" + value.ToString("X2") + " / PHA";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Emit()
|
||||||
|
{
|
||||||
|
return String.Join("\n",
|
||||||
|
FormatLine("", "LDA", "0,s", "4 cycles"),
|
||||||
|
FormatLine("", "AND", "#$" + mask.ToString("X2"), "2 cycles"),
|
||||||
|
FormatLine("", "ORA", "#$" + value.ToString("X2"), "2 cycles"),
|
||||||
|
FormatLine("", "PHA", "", "3 cycles")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public sealed class STACK_REL_16_BIT_STORE : CodeSequence
|
public sealed class STACK_REL_16_BIT_STORE : CodeSequence
|
||||||
{
|
{
|
||||||
private readonly byte offset;
|
private readonly byte offset;
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
namespace SpriteCompiler.Problem
|
namespace SpriteCompiler.Problem
|
||||||
{
|
{
|
||||||
using SpriteCompiler.AI;
|
using SpriteCompiler.AI;
|
||||||
|
using SpriteCompiler.Helpers;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction<SpriteGeneratorState, IntegerCost>
|
public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction<SpriteGeneratorState, IntegerCost>
|
||||||
{
|
{
|
||||||
@ -39,6 +41,29 @@
|
|||||||
return gapCost + spanCost;
|
return gapCost + spanCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const int MASKED_DATA = 0;
|
||||||
|
private const int SOLID_DATA = 1;
|
||||||
|
|
||||||
|
private int ClassifyChunk(IEnumerable<SpriteByte> chunk)
|
||||||
|
{
|
||||||
|
return (chunk.First().Mask == 0x00) ? SOLID_DATA : MASKED_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AreEquivalent(SpriteByte left, SpriteByte right)
|
||||||
|
{
|
||||||
|
// Return true if the two bytes are in an equivalence class
|
||||||
|
return
|
||||||
|
// The have to be adjacent
|
||||||
|
((right.Offset - left.Offset) == 1) &&
|
||||||
|
(
|
||||||
|
// First case: Both bytes are solid
|
||||||
|
((left.Mask == 0x00) && (right.Mask == 0x00)) ||
|
||||||
|
|
||||||
|
// Second case: Both bytes are masked
|
||||||
|
((left.Mask != 0x00) && (right.Mask != 0x00))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public IntegerCost Eval(SpriteGeneratorState state)
|
public IntegerCost Eval(SpriteGeneratorState state)
|
||||||
{
|
{
|
||||||
// An admissible heuistic that 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
|
||||||
@ -46,40 +71,62 @@
|
|||||||
// An even-length run can be done, at best in 4 cycles/word
|
// An even-length run can be done, at best in 4 cycles/word
|
||||||
// An odd-length run is even + 3 cycles/byte
|
// An odd-length run is even + 3 cycles/byte
|
||||||
|
|
||||||
if (state.IsEmpty) return 0;
|
// Easy case -- no data means no code
|
||||||
|
if (state.IsEmpty)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
var offsets = state.RemainingBytes();
|
// Get a list of all the bytes that have not been emitted
|
||||||
var start = offsets[0];
|
var remaining = state.RemainingBytes();
|
||||||
var stack = state.S.Value;
|
|
||||||
var curr = start;
|
// Count the number of
|
||||||
|
//
|
||||||
|
// solid words
|
||||||
|
// solid bytes
|
||||||
|
// masked words
|
||||||
|
// masked bytes
|
||||||
|
//
|
||||||
|
// By grouping the remaining bytes into solid / masked runs
|
||||||
|
var chunks = remaining.GroupAdjacent((x, y) => AreEquivalent(x, y));
|
||||||
|
|
||||||
|
// Figure out the full range of offsets that need to be written
|
||||||
|
var range = remaining.Last().Offset - remaining.First().Offset;
|
||||||
|
|
||||||
|
// At best, we can write 257 bytes before needing to move the stack (0,s to 16-bit save to ff,s).
|
||||||
|
var minStackMoves = range / 257;
|
||||||
|
|
||||||
|
// Calculate a heuristic
|
||||||
var cost = 0;
|
var cost = 0;
|
||||||
|
|
||||||
for (int i = 1; i < offsets.Count; i++)
|
// If the stack is undefined, then there is at least a 2 cycle
|
||||||
|
// cost to initialize it
|
||||||
|
if (!state.S.IsScreenOffset)
|
||||||
{
|
{
|
||||||
var prev = curr;
|
cost += 2;
|
||||||
curr = offsets[i];
|
|
||||||
|
|
||||||
if (prev == (curr - 1))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the estimate cost
|
cost += minStackMoves * 5;
|
||||||
if (state.S.IsScreenOffset)
|
|
||||||
{
|
|
||||||
cost += SpanAndGapCost(stack, start, prev, curr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cost += SpanAndGapCost(start, prev, curr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a new sppan
|
// Iterate through each chunk, determine which equivalence class the chunk is
|
||||||
start = curr;
|
// in and then assign a minimum score
|
||||||
}
|
foreach (var chunk in chunks)
|
||||||
|
{
|
||||||
|
var len = chunk.Count();
|
||||||
|
|
||||||
// End with the span
|
switch (ClassifyChunk(chunk))
|
||||||
cost += SpanAndGapCost(start, curr, curr);
|
{
|
||||||
|
// Solid data costs at least 4 cycles / word + 3 cycles for a byte
|
||||||
|
case SOLID_DATA:
|
||||||
|
cost += 4 * (len / 2) + 3 * (len % 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Masked data costs at least 16 cycles / word + 11 cycles for a byte
|
||||||
|
case MASKED_DATA:
|
||||||
|
cost += 16 * (len / 2) + 11 * (len % 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cost;
|
return cost;
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,31 @@
|
|||||||
public static List<SpriteByte> DATASET = null;
|
public static List<SpriteByte> DATASET = null;
|
||||||
public static IDictionary<int, SpriteByte> DATASET_BY_OFFSET = null;
|
public static IDictionary<int, SpriteByte> DATASET_BY_OFFSET = null;
|
||||||
|
|
||||||
|
// Histogram of the byte data
|
||||||
|
public static IDictionary<byte, int> DATASET_SOLID_BYTES = null;
|
||||||
|
|
||||||
|
// Histogram of all possible words -- includes overlaps, e.g. $11 $11 $11 $11 = ($1111, 3)
|
||||||
|
public static IDictionary<ushort, int> DATASET_SOLID_WORDS = null;
|
||||||
|
|
||||||
public static SpriteGeneratorState Init(IEnumerable<SpriteByte> bytes)
|
public static SpriteGeneratorState Init(IEnumerable<SpriteByte> bytes)
|
||||||
{
|
{
|
||||||
DATASET = bytes.ToList();
|
DATASET = bytes.OrderBy(x => x.Offset).ToList();
|
||||||
DATASET_BY_OFFSET = DATASET.ToDictionary(x => (int)x.Offset, x => x);
|
DATASET_BY_OFFSET = DATASET.ToDictionary(x => (int)x.Offset, x => x);
|
||||||
|
|
||||||
|
DATASET_SOLID_BYTES = DATASET
|
||||||
|
.Where(x => x.Mask == 0x00)
|
||||||
|
.GroupBy(x => x.Data)
|
||||||
|
.ToDictionary(x => x.Key, x => x.Count())
|
||||||
|
;
|
||||||
|
|
||||||
|
DATASET_SOLID_WORDS = DATASET
|
||||||
|
.Zip(DATASET.Skip(1), (x, y) => new { Left = x, Right = y })
|
||||||
|
.Where(x => (x.Left.Offset == (x.Right.Offset - 1)) && x.Left.Mask == 0x00 && x.Right.Mask == 0x00)
|
||||||
|
.Select(x => (x.Right.Data << 8) | x.Left.Data)
|
||||||
|
.GroupBy(x => x)
|
||||||
|
.ToDictionary(x => (ushort)x.Key, x => x.Count())
|
||||||
|
;
|
||||||
|
|
||||||
return new SpriteGeneratorState();
|
return new SpriteGeneratorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +64,8 @@
|
|||||||
S = Register.UNINITIALIZED;
|
S = Register.UNINITIALIZED;
|
||||||
|
|
||||||
P = 0x30; // Start in native mode (16 bit A/X/Y) with the carry clear
|
P = 0x30; // Start in native mode (16 bit A/X/Y) with the carry clear
|
||||||
|
|
||||||
|
AllowModeChange = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpriteGeneratorState(SpriteGeneratorState other)
|
private SpriteGeneratorState(SpriteGeneratorState other)
|
||||||
@ -55,6 +77,7 @@
|
|||||||
D = other.D;
|
D = other.D;
|
||||||
S = other.S;
|
S = other.S;
|
||||||
P = other.P;
|
P = other.P;
|
||||||
|
AllowModeChange = other.AllowModeChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
@ -66,18 +89,19 @@
|
|||||||
{
|
{
|
||||||
Closed.Add(offset);
|
Closed.Add(offset);
|
||||||
Closed.Add((ushort)(offset + 1));
|
Closed.Add((ushort)(offset + 1));
|
||||||
|
AllowModeChange = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveByte(ushort offset)
|
public void RemoveByte(ushort offset)
|
||||||
{
|
{
|
||||||
Closed.Add(offset);
|
Closed.Add(offset);
|
||||||
|
AllowModeChange = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ushort> RemainingBytes()
|
public List<SpriteByte> RemainingBytes()
|
||||||
{
|
{
|
||||||
return DATASET
|
return DATASET
|
||||||
.Select(x => x.Offset)
|
.Where(x => !Closed.Contains(x.Offset))
|
||||||
.Where(x => !Closed.Contains(x))
|
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +136,12 @@
|
|||||||
|
|
||||||
public byte P { get; set; }
|
public byte P { get; set; }
|
||||||
|
|
||||||
|
// Flag that is cleared whenever there is a switch from
|
||||||
|
// 8/16-bit mode. It is reset once a PHA or STA occurs.
|
||||||
|
// A PEA instruction has no effect. This gates allowable
|
||||||
|
// state transition to prevent long REP/SEP seqences.
|
||||||
|
public bool AllowModeChange { get; set; }
|
||||||
|
|
||||||
public const byte LONG_A = 0x10;
|
public const byte LONG_A = 0x10;
|
||||||
public const byte LONG_I = 0x20;
|
public const byte LONG_I = 0x20;
|
||||||
|
|
||||||
@ -129,7 +159,8 @@
|
|||||||
Y.Equals(other.Y) &&
|
Y.Equals(other.Y) &&
|
||||||
D.Equals(other.D) &&
|
D.Equals(other.D) &&
|
||||||
S.Equals(other.S) &&
|
S.Equals(other.S) &&
|
||||||
P.Equals(other.P)
|
P.Equals(other.P) &&
|
||||||
|
AllowModeChange == other.AllowModeChange
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +172,8 @@
|
|||||||
Y.GetHashCode() +
|
Y.GetHashCode() +
|
||||||
D.GetHashCode() +
|
D.GetHashCode() +
|
||||||
S.GetHashCode() +
|
S.GetHashCode() +
|
||||||
P.GetHashCode()
|
P.GetHashCode() +
|
||||||
|
AllowModeChange.GetHashCode()
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
namespace SpriteCompiler.Problem
|
namespace SpriteCompiler.Problem
|
||||||
{
|
{
|
||||||
using SpriteCompiler.AI;
|
using SpriteCompiler.AI;
|
||||||
|
using SpriteCompiler.Helpers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -77,168 +78,120 @@
|
|||||||
{
|
{
|
||||||
public IEnumerable<Tuple<CodeSequence, SpriteGeneratorState>> Successors(SpriteGeneratorState state)
|
public IEnumerable<Tuple<CodeSequence, SpriteGeneratorState>> Successors(SpriteGeneratorState state)
|
||||||
{
|
{
|
||||||
// This is the work-horse of the compiler. For a given state we need to enumerate all of the
|
// Get the list of remaining bytes by removing the closed list from the global sprite dataset
|
||||||
// potential next operations.
|
var open = state.RemainingBytes();
|
||||||
//
|
|
||||||
// 1. If there are 16-bits of data at then current offset, we can
|
// If the open list is empty, there can be only one reaons -- we're in an 8-bit
|
||||||
// a. Use one of the cached valued in A/X/Y/D if they match (4 cycles)
|
// mode.
|
||||||
// b. Use a PEA to push immediate values (5 cycles)
|
if (!open.Any())
|
||||||
// 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)
|
yield return state.Apply(new LONG_M());
|
||||||
//
|
yield break;
|
||||||
// 2. Move the stack
|
}
|
||||||
// a. Add a value directly (7 cycles, A = unknown)
|
|
||||||
// b. Skip 1 byte (6 cycles, A = unknown TSC/DEC/TSC)
|
// Get the first byte -- if we are expanding a state we can safely assume there
|
||||||
// c. Multiple skips (LDA X,s/AND/ORA/STA = 16/byte, ADC #/TCS/LDX #/PHX = 10/byte
|
// is still data to expand.
|
||||||
//
|
var firstByte = open.First();
|
||||||
// 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)
|
// Identify the first run of solid bytes
|
||||||
// b. If any reg is already 8-bit, LDA #imm/PHA (6 cycles)
|
var firstSolid = FirstSolidRun(open);
|
||||||
//
|
|
||||||
// We always try to return actions that write data since that moves us toward the goal state
|
// In an initial state, the stack has not been set, but the accumulator contains a screen
|
||||||
|
// offset address. There are three possible options
|
||||||
|
//
|
||||||
|
// 1. Set the stack to the accumulator value. This is optimal when there are very few
|
||||||
|
// data bytes to set and the overhead of moving the stack negates any benefit from
|
||||||
|
// using stack push instructions
|
||||||
|
//
|
||||||
|
// 2. Set the stack to the first offset of the open set. This can be optimal if there are a few
|
||||||
|
// bytes and moving the stack forward a bit can allow the code to reach them without needing
|
||||||
|
// a second stack adjustment.
|
||||||
|
//
|
||||||
|
// 3. Set the stack to the first, right-most offset that end a sequence of solid bytes
|
||||||
|
if (!state.S.IsScreenOffset && state.A.IsScreenOffset)
|
||||||
|
{
|
||||||
|
// If the first byte is within 255 bytes of the accumulator, propose setting
|
||||||
|
// the stack to the accumulator value
|
||||||
|
var delta = firstByte.Offset - state.A.Value;
|
||||||
|
if (delta >= 0 && delta < 256)
|
||||||
|
{
|
||||||
|
yield return state.Apply(new MOVE_STACK(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the first byte offset is not equal to the accumulator value, propose that
|
||||||
|
if (delta > 0)
|
||||||
|
{
|
||||||
|
yield return state.Apply(new MOVE_STACK(delta));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first edge of a solid run....TODO
|
||||||
|
if (firstSolid != null && firstSolid.Count >= 2)
|
||||||
|
{
|
||||||
|
yield return state.Apply(new MOVE_STACK(firstSolid.Last.Offset - state.A.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the first byte is 256 bytes or more ahead of the current stack location,
|
||||||
|
// then we need to advance
|
||||||
|
var firstByteDistance = firstByte.Offset - state.S.Value;
|
||||||
|
if (state.S.IsScreenOffset && firstByteDistance >= 256)
|
||||||
|
{
|
||||||
|
// Go to the next byte, or the first solid edge
|
||||||
|
yield return state.Apply(new MOVE_STACK(firstByteDistance));
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the list of remaining bytes by removing the closed list from the global sprite
|
|
||||||
var open = SpriteGeneratorState.DATASET.Where(x => !state.Closed.Contains(x.Offset)).ToList();
|
|
||||||
var bytes = open.ToDictionary(x => x.Offset, x => x);
|
var bytes = open.ToDictionary(x => x.Offset, x => x);
|
||||||
|
|
||||||
// Get the current byte and current word that exist at the current stack location
|
// Get the current byte and current word that exist at the current stack location
|
||||||
var topByte = state.TryGetStackByte(bytes);
|
var topByte = state.TryGetStackByte(bytes);
|
||||||
var topWord = state.TryGetStackWord(bytes);
|
var topWord = state.TryGetStackWord(bytes);
|
||||||
var nextWord = state.TryGetStackWord(bytes, -2); // Also get the next value below the current word
|
|
||||||
|
|
||||||
// If there is some data at the top of the stack, see what we can do
|
// If the top of the stack is a solid work, we can always emit a PEA regardless of 8/16-bit mode
|
||||||
if (topWord.HasValue)
|
if (topWord.HasValue && topWord.Value.Mask == 0x000)
|
||||||
{
|
|
||||||
// First, the simple case -- the data has no mask
|
|
||||||
if (topWord.Value.Mask == 0x000)
|
|
||||||
{
|
|
||||||
// If any of the registers has the exact value we need, then it is always fastest to just push the value
|
|
||||||
if (state.LongA)
|
|
||||||
{
|
|
||||||
if (state.A.IsLiteral && state.A.Value == topWord.Value.Data)
|
|
||||||
{
|
|
||||||
yield return state.Apply(new PHA_16());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return state.Apply(new PEA(topWord.Value.Data));
|
|
||||||
yield return state.Apply(new LOAD_16_BIT_IMMEDIATE_AND_PUSH(topWord.Value.Data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Otherwise, the only alternative is a PEA instruction
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
yield return state.Apply(new PEA(topWord.Value.Data));
|
yield return state.Apply(new PEA(topWord.Value.Data));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a valid byte, then we can look for an 8-bit push, or an immediate mode LDA #XX/STA 0,s
|
// First set of operations for when the accumulator is in 8-bit mode. We are basically limited
|
||||||
if (topByte.HasValue)
|
// to either
|
||||||
{
|
//
|
||||||
if (topByte.Value.Mask == 0x00)
|
// 1. Switching to 16-bit mode
|
||||||
{
|
// 3. Using a PHA to push an 8-bit solid or masked value on the stack
|
||||||
|
// 4. Using a STA 0,s to store an 8-bit solid or masked value on the stack
|
||||||
|
|
||||||
if (!state.LongA)
|
if (!state.LongA)
|
||||||
{
|
{
|
||||||
if (state.A.IsLiteral && ((state.A.Value & 0xFF) == topByte.Value.Data))
|
// The byte to be stored at the top of the stack has some special methods available
|
||||||
|
if (topByte.HasValue)
|
||||||
|
{
|
||||||
|
var datum = topByte.Value;
|
||||||
|
|
||||||
|
// Solid byte
|
||||||
|
if (datum.Mask == 0x00)
|
||||||
|
{
|
||||||
|
if (state.A.IsLiteral && ((state.A.Value & 0xFF) == datum.Data))
|
||||||
{
|
{
|
||||||
yield return state.Apply(new PHA_8());
|
yield return state.Apply(new PHA_8());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
yield return state.Apply(new LOAD_8_BIT_IMMEDIATE_AND_PUSH(topByte.Value.Data));
|
yield return state.Apply(new LOAD_8_BIT_IMMEDIATE_AND_PUSH(datum.Data));
|
||||||
}
|
yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, 0));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the accumulator holds an offset then we could move to any byte position, but it is only beneficial to
|
// Masked byte
|
||||||
// either
|
|
||||||
//
|
|
||||||
// 1. Set the stack to the current accumulator value
|
|
||||||
// 2. Set the stack to the start of a contiguous segment
|
|
||||||
// 3. Set the stack to the end of a contiguous segment
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
// If any of the open bytes are within 255 bytes of the accumulator, consider just
|
|
||||||
// setting the stack to the accumulator value
|
|
||||||
if (open.Any(x => (x.Offset - state.A.Value) >= 0 && (x.Offset - state.A.Value) < 256))
|
|
||||||
{
|
|
||||||
yield return state.Apply(new MOVE_STACK(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < open.Count; i++)
|
|
||||||
{
|
|
||||||
if (i == 0)
|
|
||||||
{
|
|
||||||
yield return state.Apply(new MOVE_STACK(open[i].Offset - state.A.Value));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == open.Count - 1)
|
|
||||||
{
|
|
||||||
yield return state.Apply(new MOVE_STACK(open[i].Offset - state.A.Value));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((open[i].Offset - open[i - 1].Offset) > 1)
|
|
||||||
{
|
|
||||||
yield return state.Apply(new MOVE_STACK(open[i].Offset - state.A.Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is always permissible to move to/from 16 bit mode
|
|
||||||
if (state.LongA)
|
|
||||||
{
|
|
||||||
yield return state.Apply(new SHORT_M());
|
|
||||||
|
|
||||||
// Add any possible 16-bit data manipulations
|
|
||||||
if (state.S.IsScreenOffset)
|
|
||||||
{
|
|
||||||
var addr = state.S.Value;
|
|
||||||
|
|
||||||
// Look for consecutive bytes. The second byte can come from the DATASET
|
|
||||||
var local = open.Where(WithinRangeOf(addr, 256)).ToList();
|
|
||||||
var words = local
|
|
||||||
.Where(x => SpriteGeneratorState.DATASET_BY_OFFSET.ContainsKey(x.Offset + 1))
|
|
||||||
.Select(x => new { Low = x, High = SpriteGeneratorState.DATASET_BY_OFFSET[x.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));
|
|
||||||
var mask = (ushort)(word.Low.Mask + (word.High.Mask << 8));
|
|
||||||
|
|
||||||
// Easy case when mask is empty
|
|
||||||
if (mask == 0x0000)
|
|
||||||
{
|
|
||||||
if (data == state.A.Value)
|
|
||||||
{
|
|
||||||
yield return state.Apply(new STACK_REL_16_BIT_STORE(offset));
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
yield return state.Apply(new STACK_REL_16_BIT_IMMEDIATE_STORE(data, offset));
|
yield return state.Apply(new STACK_REL_8_BIT_READ_MODIFY_PUSH(datum.Data, datum.Mask));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise there is really only one choice LDA / AND / ORA / STA sequence
|
// Otherwise, just store the next byte closest to the stack
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return state.Apply(new STACK_REL_16_BIT_READ_MODIFY_WRITE(data, mask, offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return state.Apply(new LONG_M());
|
|
||||||
|
|
||||||
// Add any possible 8-bit manipulations
|
|
||||||
if (state.S.IsScreenOffset)
|
if (state.S.IsScreenOffset)
|
||||||
{
|
{
|
||||||
var addr = state.S.Value;
|
var addr = state.S.Value;
|
||||||
@ -268,28 +221,141 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.AllowModeChange)
|
||||||
|
{
|
||||||
|
yield return state.Apply(new LONG_M());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the accumulator and stack are both initialized, only propose moves to locations
|
// Now consider what can be done when the accumulator is 16-bit. All of the stack
|
||||||
// before and after the current 256 byte stack-relative window
|
// manipulation happens here, too.
|
||||||
if (state.S.IsScreenOffset && state.LongA)
|
if (state.LongA)
|
||||||
{
|
{
|
||||||
// If the accumulator already has a screen offset value, just calculate it
|
// Handle the special case of a value sitting right on top of the stack
|
||||||
if (state.A.IsScreenOffset)
|
if (topWord.HasValue)
|
||||||
{
|
{
|
||||||
var addr = state.S.Value;
|
var datum = topWord.Value;
|
||||||
foreach (var datum in open.Where(x => (x.Offset - addr) > 255 || (x.Offset - addr) < 0))
|
|
||||||
|
// First, the simple case -- the data has no mask
|
||||||
|
if (datum.Mask == 0x000)
|
||||||
{
|
{
|
||||||
yield return state.Apply(new MOVE_STACK(datum.Offset - state.A.Value));
|
if (state.A.IsLiteral && state.A.Value == datum.Data)
|
||||||
|
{
|
||||||
|
yield return state.Apply(new PHA_16());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Otherwise, put the stack in the accumulator
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
yield return state.Apply(new TSC());
|
// Only consider this if the value appear in the sprite more than once
|
||||||
|
if (SpriteGeneratorState.DATASET_SOLID_WORDS[topWord.Value.Data] > 1)
|
||||||
|
{
|
||||||
|
yield return state.Apply(new LOAD_16_BIT_IMMEDIATE_AND_PUSH(topWord.Value.Data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the stack is set, find the next word to store (just one to reduce branching factor)
|
||||||
|
if (state.S.IsScreenOffset)
|
||||||
|
{
|
||||||
|
var addr = state.S.Value;
|
||||||
|
|
||||||
|
var local = open.Where(WithinRangeOf(addr, 256)).ToList();
|
||||||
|
var words = local
|
||||||
|
.Where(x => SpriteGeneratorState.DATASET_BY_OFFSET.ContainsKey(x.Offset + 1))
|
||||||
|
.Select(x => new { Low = x, High = SpriteGeneratorState.DATASET_BY_OFFSET[x.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));
|
||||||
|
var mask = (ushort)(word.Low.Mask + (word.High.Mask << 8));
|
||||||
|
// Easy case when mask is empty
|
||||||
|
if (mask == 0x0000)
|
||||||
|
{
|
||||||
|
if (data == state.A.Value)
|
||||||
|
{
|
||||||
|
yield return state.Apply(new STACK_REL_16_BIT_STORE(offset));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yield return state.Apply(new STACK_REL_16_BIT_IMMEDIATE_STORE(data, offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise there is really only one choice LDA / AND / ORA / STA sequence
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yield return state.Apply(new STACK_REL_16_BIT_READ_MODIFY_WRITE(data, mask, offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.AllowModeChange)
|
||||||
|
{
|
||||||
|
yield return state.Apply(new SHORT_M());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Done:
|
||||||
|
var z = 0; z += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSolidPair(Tuple<SpriteByte, SpriteByte> pair)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
(pair.Item1.Offset == (pair.Item2.Offset - 1)) &&
|
||||||
|
pair.Item1.Mask == 0x00 &&
|
||||||
|
pair.Item2.Mask == 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SolidRun
|
||||||
|
{
|
||||||
|
private readonly SpriteByte first;
|
||||||
|
private readonly SpriteByte last;
|
||||||
|
|
||||||
|
public SolidRun(SpriteByte first, SpriteByte last)
|
||||||
|
{
|
||||||
|
this.first = first;
|
||||||
|
this.last = last;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpriteByte First { get { return first; } }
|
||||||
|
public SpriteByte Last { get { return last; } }
|
||||||
|
public int Count { get { return last.Offset - first.Offset + 1; } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private SolidRun FirstSolidRun(IEnumerable<SpriteByte> open)
|
||||||
|
{
|
||||||
|
bool trigger = false;
|
||||||
|
SpriteByte first = default(SpriteByte);
|
||||||
|
SpriteByte last = default(SpriteByte);
|
||||||
|
|
||||||
|
foreach (var item in open)
|
||||||
|
{
|
||||||
|
if (item.Mask == 0x00 && !trigger)
|
||||||
|
{
|
||||||
|
first = item;
|
||||||
|
trigger = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Mask != 0x00 && trigger)
|
||||||
|
{
|
||||||
|
return new SolidRun(first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
last = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get to the end and are still sold, great
|
||||||
|
if (last.Mask == 0x00 && trigger)
|
||||||
|
{
|
||||||
|
return new SolidRun(first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private Func<SpriteByte, bool> WithinRangeOf(int addr, int range)
|
private Func<SpriteByte, bool> WithinRangeOf(int addr, int range)
|
||||||
{
|
{
|
||||||
|
@ -68,6 +68,7 @@
|
|||||||
<Compile Include="AI\IHeuristicFunction.cs" />
|
<Compile Include="AI\IHeuristicFunction.cs" />
|
||||||
<Compile Include="AI\InformedNodeExpander.cs" />
|
<Compile Include="AI\InformedNodeExpander.cs" />
|
||||||
<Compile Include="AI\INodeLimiter.cs" />
|
<Compile Include="AI\INodeLimiter.cs" />
|
||||||
|
<Compile Include="AI\InstrumentedNodeExpander.cs" />
|
||||||
<Compile Include="AI\ISearchStepInfo.cs" />
|
<Compile Include="AI\ISearchStepInfo.cs" />
|
||||||
<Compile Include="AI\ISearchStrategy.cs" />
|
<Compile Include="AI\ISearchStrategy.cs" />
|
||||||
<Compile Include="AI\ISuccessorFunction.cs" />
|
<Compile Include="AI\ISuccessorFunction.cs" />
|
||||||
@ -87,6 +88,7 @@
|
|||||||
<Compile Include="AI\SearchProblem.cs" />
|
<Compile Include="AI\SearchProblem.cs" />
|
||||||
<Compile Include="AI\GraphSearch.cs" />
|
<Compile Include="AI\GraphSearch.cs" />
|
||||||
<Compile Include="AI\TreeSearch.cs" />
|
<Compile Include="AI\TreeSearch.cs" />
|
||||||
|
<Compile Include="Helpers\EnumerableExtensions.cs" />
|
||||||
<Compile Include="Problem\CodeSequence.cs" />
|
<Compile Include="Problem\CodeSequence.cs" />
|
||||||
<Compile Include="AI\IntegerPathCost.cs" />
|
<Compile Include="AI\IntegerPathCost.cs" />
|
||||||
<Compile Include="Problem\Register.cs" />
|
<Compile Include="Problem\Register.cs" />
|
||||||
|
Loading…
Reference in New Issue
Block a user