using System.Collections.Generic; using System.Globalization; using System.Text; namespace BadDay.Core; public enum Goal { HoldHand, Withdraw } public enum Outcome { /// Resolve reached zero before the box went off. WillBroke, /// The box was disabled while the will still held. BoxDisabled, /// MaxSeconds passed with the box on and the will intact. Endured, } public enum EntryKind { Sample, Flinch, WillBroke, Withdrew, BoxDisabled, Endured } public readonly record struct TimelineEntry( long TimeMs, EntryKind Kind, double Stimulus, double Pain, double Strain, double Resolve, Goal Goal); public sealed record PainBoxResult( ulong Seed, Outcome Outcome, /// Time the run ended, ms. long EndMs, /// Time resolve hit zero, ms; null unless is WillBroke. long? TimeToBreakMs, int Flinches, IReadOnlyList Timeline) { /// /// Deterministic text form (invariant culture, fixed precision). Two runs /// with the same seed and parameters render byte-identical. /// public string Render() { var sb = new StringBuilder(); sb.Append("time_ms kind stimulus pain strain resolve goal\n"); foreach (var e in Timeline) { sb.Append(e.TimeMs.ToString(CultureInfo.InvariantCulture)).Append(' ') .Append(e.Kind).Append(' ') .Append(e.Stimulus.ToString("F3", CultureInfo.InvariantCulture)).Append(' ') .Append(e.Pain.ToString("F3", CultureInfo.InvariantCulture)).Append(' ') .Append(e.Strain.ToString("F3", CultureInfo.InvariantCulture)).Append(' ') .Append(e.Resolve.ToString("F3", CultureInfo.InvariantCulture)).Append(' ') .Append(e.Goal).Append('\n'); } return sb.ToString(); } public string Summary() => Outcome switch { Outcome.WillBroke => $"seed {Seed}: will broke at {TimeToBreakMs} ms, hand out at {EndMs} ms, {Flinches} flinches", Outcome.BoxDisabled => $"seed {Seed}: box disabled at {EndMs} ms, will held, {Flinches} flinches", _ => $"seed {Seed}: endured to {EndMs} ms, will held, {Flinches} flinches", }; }