using MultiversalDiplomacy.Adjudicate; using MultiversalDiplomacy.Adjudicate.Decision; using MultiversalDiplomacy.Model; using NUnit.Framework; namespace MultiversalDiplomacyTests; public class TimeTravelTest { [Test] public void MDATC_3_A_1_MoveIntoOwnPastForksTimeline() { TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); // Hold to move into the future, then move back into the past. setup[("a", 0)] .GetReference(out Season s0) ["Germany"] .Army("Mun").Holds().GetReference(out var mun0) .Execute() [("a", 1)] .GetReference(out Season s1) ["Germany"] .Army("Mun").MovesTo("Tyr", season: s0).GetReference(out var mun1); setup.ValidateOrders(); Assert.That(mun1, Is.Valid); setup.AdjudicateOrders(); Assert.That(mun1, Is.Victorious); World world = setup.UpdateWorld(); // Confirm that there are now four seasons: three in the main timeline and one in a fork. Assert.That( world.Seasons.Values.Where(s => s.Timeline == s0.Timeline).Count(), Is.EqualTo(3), "Failed to advance main timeline after last unit left"); Assert.That( world.Seasons.Values.Where(s => s.Timeline != s0.Timeline).Count(), Is.EqualTo(1), "Failed to fork timeline when unit moved in"); // Confirm that there is a unit in Tyr b1 originating from Mun a1 Season fork = world.Seasons["b1"]; Unit originalUnit = world.GetUnitAt("Mun", s0); Unit aMun0 = world.GetUnitAt("Mun", s1); Unit aTyr = world.GetUnitAt("Tyr", fork); Assert.That(aTyr.Past, Is.EqualTo(mun1.Order.Unit.Designation)); Assert.That(world.GetUnitByDesignation(aTyr.Past!).Past, Is.EqualTo(mun0.Order.Unit.Designation)); // Confirm that there is a unit in Mun b1 originating from Mun a0 Unit aMun1 = world.GetUnitAt("Mun", fork); Assert.That(aMun1.Past, Is.EqualTo(originalUnit.Designation)); } [Test] public void MDATC_3_A_2_SupportToRepelledPastMoveForksTimeline() { TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); // Fail to dislodge on the first turn, then support the move so it succeeds. setup[("a", 0)] .GetReference(out Season s0) ["Germany"] .Army("Mun").MovesTo("Tyr").GetReference(out var mun0) ["Austria"] .Army("Tyr").Holds().GetReference(out var tyr0); setup.ValidateOrders(); Assert.That(mun0, Is.Valid); Assert.That(tyr0, Is.Valid); setup.AdjudicateOrders(); Assert.That(mun0, Is.Repelled); Assert.That(tyr0, Is.NotDislodged); setup.UpdateWorld(); setup[("a", 1)] ["Germany"] .Army("Mun").Supports.Army("Mun", season: s0).MoveTo("Tyr").GetReference(out var mun1) ["Austria"] .Army("Tyr").Holds(); // Confirm that history is changed. setup.ValidateOrders(); Assert.That(mun1, Is.Valid); setup.AdjudicateOrders(); Assert.That(mun1, Is.NotCut); Assert.That(mun0, Is.Victorious); Assert.That(tyr0, Is.Dislodged); // Confirm that an alternate future is created. World world = setup.UpdateWorld(); Season fork = world.Seasons["b1"]; Unit tyr1 = world.GetUnitAt("Tyr", fork); Assert.That( tyr1.Past, Is.EqualTo(mun0.Order.Unit.Designation), "Expected A Mun a0 to advance to Tyr b1"); Assert.That( world.RetreatingUnits.Count, Is.EqualTo(1), "Expected A Tyr a0 to be in retreat"); Assert.That(world.RetreatingUnits.First().Unit, Is.EqualTo(tyr0.Order.Unit)); } [Test] public void MDATC_3_A_3_FailedMoveDoesNotForkTimeline() { TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); // Hold to create a future, then attempt to attack in the past. setup[("a", 0)] .GetReference(out Season s0) ["Germany"] .Army("Mun").Holds() ["Austria"] .Army("Tyr").Holds().GetReference(out var tyr0) .Execute() [("a", 1)] .GetReference(out Season s1) ["Germany"] .Army("Mun").MovesTo("Tyr", season: s0).GetReference(out var mun1) ["Austria"] .Army("Tyr").Holds(); setup.ValidateOrders(); Assert.That(mun1, Is.Valid); setup.AdjudicateOrders(); Assert.That(mun1, Is.Repelled); // The order to Tyr a0 should have been pulled into the adjudication set, so the reference // should not throw when accessing it. Assert.That(tyr0, Is.NotDislodged); // There should only be three seasons, all in the main timeline, since the move failed to // change the past and therefore did not create a new timeline. World world = setup.UpdateWorld(); Assert.That( world.GetFutures(s0).Count(), Is.EqualTo(1), "A failed move incorrectly forked the timeline"); Assert.That(world.GetFutures(s1).Count(), Is.EqualTo(1)); Season s2 = world.GetSeason(s1.Timeline, s1.Turn + 1); Assert.That(world.GetFutures(s2).Count(), Is.EqualTo(0)); } [Test] public void MDATC_3_A_4_SuperfluousSupportDoesNotForkTimeline() { TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); // Move, then support the past move even though it succeeded already. setup[("a", 0)] .GetReference(out Season s0) ["Germany"] .Army("Mun").MovesTo("Tyr").GetReference(out var mun0) .Army("Boh").Holds(); setup.ValidateOrders(); Assert.That(mun0, Is.Valid); setup.AdjudicateOrders(); Assert.That(mun0, Is.Victorious); setup.UpdateWorld(); setup[("a", 1)] .GetReference(out Season s1) ["Germany"] .Army("Tyr").Holds() .Army("Boh").Supports.Army("Mun", season: s0).MoveTo("Tyr").GetReference(out var boh1); // The support does work and the move does succeed, but... setup.ValidateOrders(); Assert.That(boh1, Is.Valid); setup.AdjudicateOrders(); Assert.That(boh1, Is.NotCut); Assert.That(mun0, Is.Victorious); // ...since it succeeded anyway, no fork is created. World world = setup.UpdateWorld(); Assert.That( world.GetFutures(s0).Count(), Is.EqualTo(1), "A superfluous support incorrectly forked the timeline"); Assert.That(world.GetFutures(s1).Count(), Is.EqualTo(1)); Season s2 = world.GetSeason(s1.Timeline, s1.Turn + 1); Assert.That(world.GetFutures(s2).Count(), Is.EqualTo(0)); } [Test] public void MDATC_3_A_5_CrossTimelineSupportDoesNotForkHead() { TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); // London creates two timelines by moving into the past. setup[("a", 0)] .GetReference(out var a0) ["England"].Army("Lon").Holds() ["Austria"].Army("Tyr").Holds() ["Germany"].Army("Mun").Holds() .Execute() [("a", 1)] ["England"].Army("Lon").MovesTo("Yor", a0) .Execute() // Head seasons: a2 b1 // Both contain copies of the armies in Mun and Tyr. // Now Germany dislodges Austria in Tyr by supporting the move across timelines. [("a", 2)] .GetReference(out var a2) ["Germany"].Army("Mun").MovesTo("Tyr").GetReference(out var mun_a2) ["Austria"].Army("Tyr").Holds().GetReference(out var tyr_a2) [("b", 1)] .GetReference(out var b1) ["Germany"].Army("Mun").Supports.Army("Mun", a2).MoveTo("Tyr").GetReference(out var mun_b1) ["Austria"].Army("Tyr").Holds(); // The attack against Tyr a2 succeeds. setup.ValidateOrders(); Assert.That(mun_a2, Is.Valid); Assert.That(tyr_a2, Is.Valid); Assert.That(mun_b1, Is.Valid); setup.AdjudicateOrders(); Assert.That(mun_a2, Is.Victorious); Assert.That(tyr_a2, Is.Dislodged); Assert.That(mun_b1, Is.NotCut); // Since both seasons were at the head of their timelines, there should be no forking. World world = setup.UpdateWorld(); Assert.That( world.GetFutures(a2).Count(), Is.EqualTo(1), "A cross-timeline support incorrectly forked the head of the timeline"); Assert.That( world.GetFutures(b1).Count(), Is.EqualTo(1), "A cross-timeline support incorrectly forked the head of the timeline"); Season a3 = world.GetSeason(a2.Timeline, a2.Turn + 1); Assert.That(world.GetFutures(a3).Count(), Is.EqualTo(0)); Season b2 = world.GetSeason(b1.Timeline, b1.Turn + 1); Assert.That(world.GetFutures(b2).Count(), Is.EqualTo(0)); } [Test] public void MDATC_3_A_6_CuttingCrossTimelineSupportDoesNotFork() { TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); // As above, only now London creates three timelines. setup[("a", 0)] .GetReference(out var a0) ["England"].Army("Lon").Holds() ["Austria"].Army("Boh").Holds() ["Germany"].Army("Mun").Holds() .Execute() [("a", 1)] ["England"].Army("Lon").MovesTo("Yor", a0) .Execute() // Head seasons: a2, b1 [("a", 2)] [("b", 1)] ["England"].Army("Yor").MovesTo("Edi", a0) .Execute() // Head seasons: a3, b2, c1 // All three of these contain copies of the armies in Mun and Tyr. // As above, Germany dislodges Austria in Tyr a3 by supporting the move from b2. [("a", 3)] .GetReference(out var a3) ["Germany"].Army("Mun").MovesTo("Tyr") ["Austria"].Army("Tyr").Holds() [("b", 2)] .GetReference(out Season b2) ["Germany"].Army("Mun").Supports.Army("Mun", a3).MoveTo("Tyr").GetReference(out var mun_b2) ["Austria"].Army("Tyr").Holds() [("c", 1)] ["Germany"].Army("Mun").Holds() ["Austria"].Army("Tyr").Holds() .Execute() // Head seasons: a4, b3, c2 // Now Austria cuts the support in b2 by attacking from c2. [("a", 4)] ["Germany"].Army("Tyr").Holds() [("b", 3)] ["Germany"].Army("Mun").Holds() ["Austria"].Army("Tyr").Holds() [("c", 2)] ["Germany"].Army("Mun").Holds() ["Austria"].Army("Tyr").MovesTo("Mun", b2).GetReference(out var tyr_c2); // The attack on Mun b2 is repelled, but the support is cut. setup.ValidateOrders(); Assert.That(tyr_c2, Is.Valid); setup.AdjudicateOrders(); Assert.That(tyr_c2, Is.Repelled); Assert.That(mun_b2, Is.NotDislodged); Assert.That(mun_b2, Is.Cut); // Though the support was cut, the timeline doesn't fork because the outcome of a battle // wasn't changed in this timeline. World world = setup.UpdateWorld(); Assert.That( world.GetFutures(a3).Count(), Is.EqualTo(1), "A cross-timeline support cut incorrectly forked the timeline"); Assert.That( world.GetFutures(b2).Count(), Is.EqualTo(1), "A cross-timeline support cut incorrectly forked the timeline"); } }