From b7a3af9437e59cc3ba3f74be4882dbf1f30dc377 Mon Sep 17 00:00:00 2001 From: Lucas Scharenbroich Date: Wed, 30 Nov 2016 16:07:22 -0600 Subject: [PATCH] Work toward improved heuristic functions ad add core unit tests --- SpriteCompiler.Test/RegisterTests.cs | 48 +++++++++++++++++ .../SpriteCompiler.Test.csproj | 1 + SpriteCompiler.Test/Tests.cs | 34 ++++++++++-- SpriteCompiler/Problem/Register.cs | 32 ++++++++++- .../SpriteGeneratorHeuristicFunction.cs | 54 ++++++++++++++++++- .../Problem/SpriteGeneratorSearchProblem.cs | 4 +- .../Problem/SpriteGeneratorState.cs | 36 ++++++++++++- 7 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 SpriteCompiler.Test/RegisterTests.cs diff --git a/SpriteCompiler.Test/RegisterTests.cs b/SpriteCompiler.Test/RegisterTests.cs new file mode 100644 index 0000000..7140d86 --- /dev/null +++ b/SpriteCompiler.Test/RegisterTests.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SpriteCompiler.Problem; +using SpriteCompiler.AI; +using System.Diagnostics; +using System.Collections.Generic; + +namespace SpriteCompiler.Test +{ + [TestClass] + public class RegisterTests + { + [TestMethod] + public void TestRegisterEquality() + { + var uninitialized1 = Register.UNINITIALIZED; + var uninitialized2 = Register.UNINITIALIZED; + + Assert.AreEqual(uninitialized1, uninitialized2); + + var addr1 = Register.INITIAL_OFFSET; + var addr2 = Register.INITIAL_OFFSET; + var addr3 = Register.INITIAL_OFFSET.Add(1); + var addr4 = addr3.Add(-1); + + Assert.AreEqual(addr1, addr2); + Assert.AreNotEqual(addr2, addr3); + Assert.AreEqual(addr1, addr4); + + var literal1 = Register.UNINITIALIZED.LoadConstant(0); + var literal2 = Register.UNINITIALIZED.LoadConstant(1); + var literal3 = Register.UNINITIALIZED.LoadConstant(1); + + Assert.AreNotEqual(literal1, literal2); + Assert.AreNotEqual(literal1, addr1); + Assert.AreNotEqual(literal1, uninitialized1); + Assert.AreEqual(literal2, literal3); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TestUninitializedAdd() + { + Register.UNINITIALIZED.Add(1); + } + } +} diff --git a/SpriteCompiler.Test/SpriteCompiler.Test.csproj b/SpriteCompiler.Test/SpriteCompiler.Test.csproj index 425e7e6..92e471a 100644 --- a/SpriteCompiler.Test/SpriteCompiler.Test.csproj +++ b/SpriteCompiler.Test/SpriteCompiler.Test.csproj @@ -50,6 +50,7 @@ + diff --git a/SpriteCompiler.Test/Tests.cs b/SpriteCompiler.Test/Tests.cs index 777a41b..33ab430 100644 --- a/SpriteCompiler.Test/Tests.cs +++ b/SpriteCompiler.Test/Tests.cs @@ -47,11 +47,11 @@ namespace SpriteCompiler.Test // STA 0,s // LONG A = 14 cycles - Assert.AreEqual(5, solution.Count()); - Assert.AreEqual(14, (int)solution.Last().PathCost); - // Write out the solution WriteOutSolution(solution); + + Assert.AreEqual(5, solution.Count()); + Assert.AreEqual(14, (int)solution.Last().PathCost); } [TestMethod] @@ -72,11 +72,39 @@ namespace SpriteCompiler.Test // LDA #$55AA // STA 0,s = 10 cycles + // Write out the solution + WriteOutSolution(solution); + Assert.AreEqual(3, solution.Count()); Assert.AreEqual(10, (int)solution.Last().PathCost); + } + + [TestMethod] + public void TestConsecutiveWordSprite() + { + // Arrange + var problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); + var search = SpriteGeneratorSearchProblem.Create(); + + // Act : solve the problem + var solution = search.Search(problem, new SpriteGeneratorState(new byte[] { 0xAA, 0x55, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 })); + + // Assert + // + // The fastest way to draw a consecutive workds hould be + // + // ADC #7 + // TCS + // PEA $6655 + // PEA $4433 + // PEA $2211 + // PEA $55AA = 25 cycles // Write out the solution WriteOutSolution(solution); + + //Assert.AreEqual(3, solution.Count()); + //Assert.AreEqual(10, (int)solution.Last().PathCost); } private void WriteOutSolution(IEnumerable solution) diff --git a/SpriteCompiler/Problem/Register.cs b/SpriteCompiler/Problem/Register.cs index 5ee62b5..b37724f 100644 --- a/SpriteCompiler/Problem/Register.cs +++ b/SpriteCompiler/Problem/Register.cs @@ -2,7 +2,7 @@ { using System; - public sealed class Register + public sealed class Register : IEquatable { public static readonly Register UNINITIALIZED = new Register(0, DataType.UNINITIALIZED); public static readonly Register INITIAL_OFFSET = new Register(0, DataType.SCREEN_OFFSET); @@ -52,5 +52,35 @@ { return string.Format("{0} ({1})", Tag, Value.ToString("X4")); } + + public override bool Equals(object obj) + { + return Equals(obj as Register); + } + + public bool Equals(Register other) + { + if (other == null) + return false; + + // Unititialized is equal to unititialized. Otherwise, compare the value, too. + return Tag.Equals(other.Tag) && (Tag.Equals(DataType.UNINITIALIZED) || Value == other.Value); + } + + public static bool operator ==(Register reg1, Register reg2) + { + if (((object)reg1) == null || ((object)reg2) == null) + return Object.Equals(reg1, reg2); + + return reg1.Equals(reg2); + } + + public static bool operator !=(Register reg1, Register reg2) + { + if (((object)reg1) == null || ((object)reg2) == null) + return ! Object.Equals(reg1, reg2); + + return ! (reg1.Equals(reg2)); + } } } diff --git a/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs b/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs index ccb316c..1e7469c 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorHeuristicFunction.cs @@ -1,12 +1,64 @@ namespace SpriteCompiler.Problem { using SpriteCompiler.AI; + using System.Linq; public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction { + private static int SpanAndGapCost(int start, int end, int next) + { + // [start, end] is the span + // (end, next) is the gap + // + // start <= end <= next + var len = end - start + 1; + + // No gap, no penalty + var gapCost = (end == next) ? 0 : 5; + var spanCost = 4 * (len / 2) + 3 * (len % 2); + + return gapCost + spanCost; + } + public IntegerPathCost Eval(SpriteGeneratorState state) { - return 0; + // An admissible heuistic calculates a cost based on the gaps and runs in a sprite + // + // An even-length run can be done, at best in 4 cycles/word + // An odd-length run is even + 3 cycles/byte + // + // Each gap needs at least 5 cycles to cover (ADC # / TCS) + + var count = state.Bytes.Count; + + if (count == 0) return 0; + + var offsets = state.Bytes.Select(x => x.Offset).OrderBy(x => x).ToList(); + var start = offsets[0]; + var curr = start; + var cost = 0; + + for (int i = 1; i < count; i++) + { + var prev = curr; + curr = offsets[i]; + + if (prev == (curr - 1)) + { + continue; + } + + // Calculate the estimate cost + cost += SpanAndGapCost(start, prev, curr); + + // Start a new sppan + start = curr; + } + + // End with the span + cost += SpanAndGapCost(start, curr, curr); + + return cost; } } } diff --git a/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs b/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs index 88b403d..387ab78 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs @@ -16,8 +16,8 @@ public static ISearch Create() { - //var problem = CreateSearchProblem(); - var strategy = new TreeSearch(new SpriteGeneratorNodeExpander()); + var expander = new SpriteGeneratorNodeExpander(); + var strategy = new TreeSearch(expander); return new AStarSearch(strategy); } diff --git a/SpriteCompiler/Problem/SpriteGeneratorState.cs b/SpriteCompiler/Problem/SpriteGeneratorState.cs index 9cba586..c356467 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorState.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorState.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Linq; - public class SpriteGeneratorState + public class SpriteGeneratorState : IEquatable { public SpriteGeneratorState() : this(new SpriteByte[0]) @@ -86,5 +86,39 @@ public Register S { get; set; } // S is always an offset, not a literal number public byte P { get; set; } + + public override bool Equals(object obj) + { + return Equals(obj as SpriteGeneratorState); + } + + public bool Equals(SpriteGeneratorState other) + { + // Two states are equal if the bytes are the same and all registers are the same + return Bytes.SequenceEqual(other.Bytes) && + A.Equals(other.A) && + X.Equals(other.X) && + Y.Equals(other.Y) && + D.Equals(other.D) && + S.Equals(other.S) && + P.Equals(other.P) + ; + } + + public static bool operator ==(SpriteGeneratorState state1, SpriteGeneratorState state2) + { + if (((object)state1) == null || ((object)state2) == null) + return Object.Equals(state1, state2); + + return state1.Equals(state2); + } + + public static bool operator !=(SpriteGeneratorState state1, SpriteGeneratorState state2) + { + if (((object)state1) == null || ((object)state2) == null) + return !Object.Equals(state1, state2); + + return !(state1.Equals(state2)); + } } }