namespace HSchool.Simulation.Tests; public class GameClockTests { private static readonly DateTime Start = new(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc); private const double OneTwentiethOfASecond = 1d / 20d; private const double GameMinutesPerRealSecond = 1d; [Fact] public void NewClock_StartsRunningAtTheStartDate() { var clock = new GameClock(Start); Assert.Equal(Start, clock.Time); Assert.True(clock.IsRunning); Assert.Equal(ClockSpeed.DefaultIndex, clock.SpeedIndex); Assert.Equal(1d, clock.Multiplier); } [Fact] public void PausedClock_DoesNotMove() { var clock = new GameClock(Start) { IsRunning = false }; for (var i = 0; i < 100; i++) { clock.Advance(OneTwentiethOfASecond, GameMinutesPerRealSecond); } Assert.Equal(Start, clock.Time); } [Fact] public void OneRealSecond_AdvancesOneGameMinute() { var clock = new GameClock(Start); // One real second at 20 Hz. for (var i = 0; i < 20; i++) { clock.Advance(OneTwentiethOfASecond, GameMinutesPerRealSecond); } Assert.Equal(Start.AddMinutes(1), clock.Time); } [Fact] public void TwentyStepsAtX1_AdvanceOneGameMinuteNotFive() { var clock = new GameClock(Start) { SpeedIndex = 1 }; for (var i = 0; i < 20; i++) { clock.Advance(OneTwentiethOfASecond, GameMinutesPerRealSecond); } Assert.Equal(Start.AddMinutes(1), clock.Time); } [Theory] [InlineData(0, 0.5)] [InlineData(1, 1)] [InlineData(2, 2)] [InlineData(3, 5)] [InlineData(4, 10)] public void SpeedIndex_ScalesTheGameMinutesPerSecond(int speedIndex, double expectedMinutes) { var clock = new GameClock(Start) { SpeedIndex = speedIndex }; for (var i = 0; i < 20; i++) { clock.Advance(OneTwentiethOfASecond, GameMinutesPerRealSecond); } Assert.Equal(Start.AddMinutes(expectedMinutes), clock.Time); } [Theory] [InlineData(-1)] [InlineData(5)] [InlineData(200)] public void InvalidSpeedIndex_IsIgnored(int speedIndex) { var clock = new GameClock(Start) { SpeedIndex = 2 }; clock.SpeedIndex = speedIndex; Assert.Equal(2, clock.SpeedIndex); } [Fact] public void Pausing_FreezesTimeWhereItStopped() { var clock = new GameClock(Start); for (var i = 0; i < 20; i++) { clock.Advance(OneTwentiethOfASecond, GameMinutesPerRealSecond); } var paused = clock.Time; clock.IsRunning = false; for (var i = 0; i < 100; i++) { clock.Advance(OneTwentiethOfASecond, GameMinutesPerRealSecond); } Assert.Equal(paused, clock.Time); } [Fact] public void SameTickCount_AlwaysProducesTheSameDate() { Assert.Equal(RunOneHourOfTicks(), RunOneHourOfTicks()); static DateTime RunOneHourOfTicks() { var clock = new GameClock(Start) { SpeedIndex = 0 }; for (var i = 0; i < 20 * 60 * 60; i++) { clock.Advance(OneTwentiethOfASecond, GameMinutesPerRealSecond); } return clock.Time; } } [Fact] public void Time_IsUtcSoSerializationIsUnambiguous() { var clock = new GameClock(new DateTime(2012, 4, 3, 6, 0, 0, DateTimeKind.Unspecified)); Assert.Equal(DateTimeKind.Utc, clock.Time.Kind); } [Fact] public void JumpTo_MovesTheCalendarWithoutTicking() { var clock = new GameClock(Start) { IsRunning = false }; var target = new DateTime(2012, 4, 9, 6, 0, 0, DateTimeKind.Utc); clock.JumpTo(target); Assert.Equal(target, clock.Time); Assert.Equal(DateTimeKind.Utc, clock.Time.Kind); Assert.False(clock.IsRunning); } [Fact] public void StartDateOutsideTheSupportedRange_Throws() { Assert.Throws(() => new GameClock(new DateTime(1800, 1, 1, 0, 0, 0, DateTimeKind.Utc))); } }