mirror of
https://github.com/lscharen/iigs-sprite-compiler.git
synced 2024-06-09 16:29:29 +00:00
Work toward improved heuristic functions ad add core unit tests
This commit is contained in:
parent
9d7aeb355c
commit
b7a3af9437
48
SpriteCompiler.Test/RegisterTests.cs
Normal file
48
SpriteCompiler.Test/RegisterTests.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,6 +50,7 @@
|
||||||
</Otherwise>
|
</Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="RegisterTests.cs" />
|
||||||
<Compile Include="Tests.cs" />
|
<Compile Include="Tests.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -47,11 +47,11 @@ namespace SpriteCompiler.Test
|
||||||
// STA 0,s
|
// STA 0,s
|
||||||
// LONG A = 14 cycles
|
// LONG A = 14 cycles
|
||||||
|
|
||||||
Assert.AreEqual(5, solution.Count());
|
|
||||||
Assert.AreEqual(14, (int)solution.Last().PathCost);
|
|
||||||
|
|
||||||
// Write out the solution
|
// Write out the solution
|
||||||
WriteOutSolution(solution);
|
WriteOutSolution(solution);
|
||||||
|
|
||||||
|
Assert.AreEqual(5, solution.Count());
|
||||||
|
Assert.AreEqual(14, (int)solution.Last().PathCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -72,11 +72,39 @@ namespace SpriteCompiler.Test
|
||||||
// LDA #$55AA
|
// LDA #$55AA
|
||||||
// STA 0,s = 10 cycles
|
// STA 0,s = 10 cycles
|
||||||
|
|
||||||
|
// Write out the solution
|
||||||
|
WriteOutSolution(solution);
|
||||||
|
|
||||||
Assert.AreEqual(3, solution.Count());
|
Assert.AreEqual(3, solution.Count());
|
||||||
Assert.AreEqual(10, (int)solution.Last().PathCost);
|
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
|
// Write out the solution
|
||||||
WriteOutSolution(solution);
|
WriteOutSolution(solution);
|
||||||
|
|
||||||
|
//Assert.AreEqual(3, solution.Count());
|
||||||
|
//Assert.AreEqual(10, (int)solution.Last().PathCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteOutSolution(IEnumerable<SpriteGeneratorSearchNode> solution)
|
private void WriteOutSolution(IEnumerable<SpriteGeneratorSearchNode> solution)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{
|
{
|
||||||
using System;
|
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 UNINITIALIZED = new Register(0, DataType.UNINITIALIZED);
|
||||||
public static readonly Register INITIAL_OFFSET = new Register(0, DataType.SCREEN_OFFSET);
|
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"));
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,64 @@
|
||||||
namespace SpriteCompiler.Problem
|
namespace SpriteCompiler.Problem
|
||||||
{
|
{
|
||||||
using SpriteCompiler.AI;
|
using SpriteCompiler.AI;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
public sealed class SpriteGeneratorHeuristicFunction : IHeuristicFunction<SpriteGeneratorState, IntegerPathCost>
|
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)
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
public static ISearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost> Create()
|
public static ISearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost> Create()
|
||||||
{
|
{
|
||||||
//var problem = CreateSearchProblem();
|
var expander = new SpriteGeneratorNodeExpander();
|
||||||
var strategy = new TreeSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(new SpriteGeneratorNodeExpander());
|
var strategy = new TreeSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(expander);
|
||||||
|
|
||||||
return new AStarSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(strategy);
|
return new AStarSearch<CodeSequence, SpriteGeneratorState, SpriteGeneratorSearchNode, IntegerPathCost>(strategy);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
public class SpriteGeneratorState
|
public class SpriteGeneratorState : IEquatable<SpriteGeneratorState>
|
||||||
{
|
{
|
||||||
public SpriteGeneratorState()
|
public SpriteGeneratorState()
|
||||||
: this(new SpriteByte[0])
|
: this(new SpriteByte[0])
|
||||||
|
@ -86,5 +86,39 @@
|
||||||
public Register S { get; set; } // S is always an offset, not a literal number
|
public Register S { get; set; } // S is always an offset, not a literal number
|
||||||
|
|
||||||
public byte P { get; set; }
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user