using System; using System.Collections.Generic; using System.Text.Json; using BinxForge.CoinRoom; class Program { static readonly List checks = new List(); static void Check(string name, bool passed) { checks.Add(new { name, passed }); if (!passed) throw new Exception(name); } static bool Near(double a, double b) { return Math.Abs(a - b) < 0.000001; } static void Main() { var rules = new RoundRules(); Check("fresh round has three coins and sixty seconds", rules.Score == 0 && rules.Active && rules.Remaining == 60); Check("unknown coin IDs rejected", !rules.Collect(-1) && !rules.Collect(3) && rules.Score == 0); Check("first coin scores once", rules.Collect(0) && !rules.Collect(0) && rules.Score == 1); rules.Pause(true); Check("pause rejects new pickups", !rules.Active && !rules.Collect(1)); rules.Tick(1); rules.HitPatrol(); Check("pause freezes timer and patrol loss", rules.Remaining == 60 && rules.State == RoundState.Playing); rules.Pause(false); Check("resume restores active state", rules.Active); Check("all three unique coins win", rules.Collect(2) && rules.Collect(1) && rules.State == RoundState.Won && rules.Score == 3); rules.Tick(0.1); rules.HitPatrol(); rules.Pause(true); Check("win is terminal", rules.State == RoundState.Won && rules.Remaining == 60 && !rules.Collect(0)); rules.Restart(); Check("restart resets coin identities and clock", rules.Active && rules.Score == 0 && rules.Remaining == 60 && rules.Collect(0)); rules.HitPatrol(); Check("active patrol contact loses", rules.State == RoundState.Lost); Check("loss rejects pickups", !rules.Collect(1) && rules.Score == 1); rules.Restart(); rules.Tick(double.NaN); rules.Tick(double.PositiveInfinity); rules.Tick(-1); rules.Tick(0); Check("invalid elapsed time ignored", rules.Remaining == 60); rules.Tick(5); Check("stall time bounded to a tenth of a second", Near(rules.Remaining, 59.9)); rules.Restart(); for (int i = 0; i < 3001; i++) rules.Tick(0.02); Check("active clock expires without becoming negative", rules.Remaining == 0 && rules.State == RoundState.Lost); rules.Tick(0.1); Check("timeout remains terminal", rules.Remaining == 0 && !rules.Active); RoundRules.Movement(1, 0, out double x, out double y); Check("cardinal movement uses four units per second", Near(x, 4) && y == 0); RoundRules.Movement(1, 1, out x, out y); Check("diagonal movement has the same speed", Near(Math.Sqrt(x * x + y * y), 4)); RoundRules.Movement(0, 0, out x, out y); Check("released input stops movement", x == 0 && y == 0); RoundRules.Movement(double.NaN, 1, out x, out y); Check("invalid input produces no movement", x == 0 && y == 0); var second = new RoundRules(); rules.Restart(); rules.Collect(0); Check("independent rounds share no score", second.Score == 0 && second.Remaining == 60); foreach (int rate in new[] { 15, 30, 60, 120 }) { var round = new RoundRules(); for (int i = 0; i < rate; i++) round.Tick(1.0 / rate); Check("one active second at " + rate + " updates", Near(round.Remaining, 59)); } Console.WriteLine(JsonSerializer.Serialize(new { result = "passed", passed = checks.Count, failed = 0, scope = "actual engine-independent C# source; not Unity compilation, input, physics, rendering or export", checks })); } }