using System;
using System.Collections.Generic;
using Luneraria.Core;
namespace Luneraria.View
{
///
/// Fingerprint of a match (U2C): FNV-1a over every combat step (step, time, and per unit id,
/// alive, hit points, position, target) and every settlement (gains, lost buildings, winner).
/// Two runs are the same match exactly when their fingerprints are equal; the Unity match and
/// the plain core run are compared this way at any frame rate.
///
public sealed class LuneTrace
{
const ulong Offset = 14695981039346656037UL;
const ulong Prime = 1099511628211UL;
ulong _hash = Offset;
public int Steps;
public readonly List RoundHashes = new List();
public readonly List Rounds = new List();
public ulong Hash { get { return _hash; } }
void Mix(ulong value)
{
for (int i = 0; i < 8; i++)
{
_hash ^= (value >> (i * 8)) & 0xFF;
_hash *= Prime;
}
}
void Mix(double value) { Mix((ulong)BitConverter.DoubleToInt64Bits(value)); }
void Mix(float value) { Mix((ulong)(uint)BitConverter.ToInt32(BitConverter.GetBytes(value), 0)); }
public void RecordStep(LuneCombat combat)
{
Steps++;
Mix((ulong)combat.Steps);
Mix(combat.Elapsed);
foreach (LuneUnit unit in combat.Units)
{
Mix((ulong)unit.Id);
Mix(unit.Alive ? 1UL : 0UL);
Mix(unit.Hp);
Mix(unit.Position.X);
Mix(unit.Position.Y);
Mix((ulong)(unit.Target != null ? unit.Target.Id : 0));
}
}
public void RecordSettlement(LuneMatch match)
{
Settlement settlement = match.LastSettlement;
foreach (int gain in settlement.Gains)
Mix((ulong)(uint)gain);
foreach (string building in settlement.BuildingsLost)
foreach (char c in building)
Mix((ulong)c);
Mix((ulong)(uint)match.Winner);
RoundHashes.Add(_hash);
Rounds.Add(string.Format("round {0}: {1} steps, lost [{2}], winner {3}", settlement.Round, match.Combat != null ? match.Combat.Steps : 0,
string.Join(",", settlement.BuildingsLost), match.Winner));
}
/// The same match without Unity: plan, begin, step to the end, round after round.
public static LuneTrace RunCore(MatchOptions options, int startRound, int extraGold, string profile, int maxRound = 60)
{
var match = LuneMatchRunner.NewMatch(options, startRound, extraGold);
var trace = new LuneTrace();
while (match.Phase != Phase.Over && match.RoundNumber <= maxRound)
{
for (int slot = 0; slot < 6; slot++)
LuneBot.Plan(match, slot, profile);
match.BeginCombat();
while (match.Phase == Phase.Combat)
{
match.StepCombat();
trace.RecordStep(match.Combat);
}
trace.RecordSettlement(match);
}
return trace;
}
}
}