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