Work toward improved heuristic functions ad add core unit tests

This commit is contained in:
Lucas Scharenbroich 2016-11-30 16:07:22 -06:00
parent 9d7aeb355c
commit b7a3af9437
7 changed files with 201 additions and 8 deletions

View File

@ -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);
}
}
}

View File

@ -50,6 +50,7 @@
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="RegisterTests.cs" />
<Compile Include="Tests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View File

@ -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<SpriteGeneratorSearchNode> solution)

View File

@ -2,7 +2,7 @@
{
using System;
public sealed class Register
public sealed class Register : IEquatable<Register>
{
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));
}
}
}

View File

@ -1,12 +1,64 @@
namespace SpriteCompiler.Problem
{
using SpriteCompiler.AI;
using System.Linq;
public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction<SpriteGeneratorState, IntegerPathCost>
{
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;
}
}
}

View File

@ -16,8 +16,8 @@
public static ISearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost> Create()
{
//var problem = CreateSearchProblem();
var strategy = new TreeSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(new SpriteGeneratorNodeExpander());
var expander = new SpriteGeneratorNodeExpander();
var strategy = new TreeSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(expander);
return new AStarSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(strategy);
}

View File

@ -4,7 +4,7 @@
using System.Collections.Generic;
using System.Linq;
public class SpriteGeneratorState
public class SpriteGeneratorState : IEquatable<SpriteGeneratorState>
{
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));
}
}
}