diff --git a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs index bf5d9be..2d4de6b 100644 --- a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs +++ b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs @@ -50,7 +50,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator // Invalidate any order given to a unit in the past. AdjudicatorHelpers.InvalidateIfNotMatching( - order => !order.Unit.Season.Futures.Any(), + order => !world.GetFutures(order.Unit.Season).Any(), ValidationReason.IneligibleForOrder, ref unitOrders, ref validationResults); @@ -255,7 +255,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator // Finally, add implicit hold orders for units without legal orders. List allOrderableUnits = world.Units - .Where(unit => !unit.Season.Futures.Any()) + .Where(unit => !world.GetFutures(unit.Season).Any()) .ToList(); HashSet orderedUnits = validOrders.Select(order => order.Unit).ToHashSet(); List unorderedUnits = allOrderableUnits @@ -324,7 +324,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator if (advanceTimeline.Outcome == true) { // A timeline that doesn't have a future yet simply continues. Otherwise, it forks. - createdFutures[advanceTimeline.Season] = !advanceTimeline.Season.Futures.Any() + createdFutures[advanceTimeline.Season] = !world.GetFutures(advanceTimeline.Season).Any() ? advanceTimeline.Season.MakeNext() : advanceTimeline.Season.MakeFork(); } @@ -486,7 +486,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator bool progress = false; // A season at the head of a timeline always advances. - if (!decision.Season.Futures.Any()) + if (!world.GetFutures(decision.Season).Any()) { progress |= LoggedUpdate(decision, true, depth, "A timeline head always advances"); return progress; diff --git a/MultiversalDiplomacy/Adjudicate/PathFinder.cs b/MultiversalDiplomacy/Adjudicate/PathFinder.cs index 47e85f1..cc5d217 100644 --- a/MultiversalDiplomacy/Adjudicate/PathFinder.cs +++ b/MultiversalDiplomacy/Adjudicate/PathFinder.cs @@ -115,7 +115,7 @@ public static class PathFinder // The immediate past and all immediate futures are adjacent. if (season.Past != null) adjacents.Add(world.GetSeason(season.Past)); - adjacents.AddRange(season.Futures); + adjacents.AddRange(world.GetFutures(season)); // Find all adjacent timelines by finding all timelines that branched off of this season's // timeline, i.e. all futures of this season's past that have different timelines. Also @@ -127,7 +127,7 @@ public static class PathFinder current = world.GetSeason(current.Past)) { adjacentTimelineRoots.AddRange( - current.Futures.Where(s => s.Timeline != current.Timeline)); + world.GetFutures(current).Where(s => s.Timeline != current.Timeline)); } // At the end of the for loop, if this season is part of the first timeline, then current @@ -137,7 +137,8 @@ public static class PathFinder // the first timeline by definition cannot have co-branches. if (current?.Past != null && world.GetSeason(current.Past) is Season past) { - IEnumerable cobranchRoots = past.Futures + IEnumerable cobranchRoots = world + .GetFutures(past) .Where(s => s.Timeline != current.Timeline && s.Timeline != past.Timeline); adjacentTimelineRoots.AddRange(cobranchRoots); } @@ -147,7 +148,7 @@ public static class PathFinder { for (Season? branchSeason = timelineRoot; branchSeason != null && branchSeason.Turn <= season.Turn + 1; - branchSeason = branchSeason.Futures + branchSeason = world.GetFutures(branchSeason) .FirstOrDefault(s => s!.Timeline == branchSeason.Timeline, null)) { if (branchSeason.Turn >= season.Turn - 1) adjacents.Add(branchSeason); diff --git a/MultiversalDiplomacy/Model/Season.cs b/MultiversalDiplomacy/Model/Season.cs index e70e9dd..1866504 100644 --- a/MultiversalDiplomacy/Model/Season.cs +++ b/MultiversalDiplomacy/Model/Season.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace MultiversalDiplomacy.Model; /// @@ -30,36 +32,32 @@ public class Season public string Timeline { get; } /// - /// The season's spatial location as a timeline-turn tuple. + /// The multiversal designation of this season. /// + [JsonIgnore] + public string Designation => $"{this.Timeline}{this.Turn}"; + + /// + /// The season's multiversal location as a timeline-turn tuple for convenience. + /// + [JsonIgnore] public (string Timeline, int Turn) Coord => (this.Timeline, this.Turn); /// /// The shared timeline number generator. /// + [JsonIgnore] private TimelineFactory Timelines { get; } - /// - /// Future seasons created directly from this season. - /// - public IEnumerable Futures => this.FutureList; - private List FutureList { get; } - private Season(Season? past, int turn, string timeline, TimelineFactory factory) { this.Past = past?.ToString(); this.Turn = turn; this.Timeline = timeline; this.Timelines = factory; - this.FutureList = []; - - past?.FutureList.Add(this); } - public override string ToString() - { - return $"{this.Timeline}{this.Turn}"; - } + public override string ToString() => Designation; /// /// Create a root season at the beginning of time. diff --git a/MultiversalDiplomacy/Model/World.cs b/MultiversalDiplomacy/Model/World.cs index 53f4c82..2d701d5 100644 --- a/MultiversalDiplomacy/Model/World.cs +++ b/MultiversalDiplomacy/Model/World.cs @@ -232,6 +232,21 @@ public class World public Season GetSeason(string designation) => SeasonLookup[designation]; + /// + /// Get all seasons that are immediate futures of a season. + /// + /// A season designation. + /// The immediate futures of the designated season. + public IEnumerable GetFutures(string present) + => Seasons.Where(future => future.Past == present); + + /// + /// Get all seasons that are immediate futures of a season. + /// + /// A season. + /// The immediate futures of the season. + public IEnumerable GetFutures(Season present) => GetFutures(present.Designation); + /// /// Returns the first season in this season's timeline. The first season is the /// root of the first timeline. The earliest season in each alternate timeline is diff --git a/MultiversalDiplomacyTests/MDATC_A.cs b/MultiversalDiplomacyTests/MDATC_A.cs index 8b1b70b..b2b7961 100644 --- a/MultiversalDiplomacyTests/MDATC_A.cs +++ b/MultiversalDiplomacyTests/MDATC_A.cs @@ -136,12 +136,12 @@ public class TimeTravelTest // change the past and therefore did not create a new timeline. World world = setup.UpdateWorld(); Assert.That( - s0.Futures.Count(), + world.GetFutures(s0).Count(), Is.EqualTo(1), "A failed move incorrectly forked the timeline"); - Assert.That(s1.Futures.Count(), Is.EqualTo(1)); + Assert.That(world.GetFutures(s1).Count(), Is.EqualTo(1)); Season s2 = world.GetSeason(s1.Timeline, s1.Turn + 1); - Assert.That(s2.Futures.Count(), Is.EqualTo(0)); + Assert.That(world.GetFutures(s2).Count(), Is.EqualTo(0)); } [Test] @@ -178,12 +178,12 @@ public class TimeTravelTest // ...since it succeeded anyway, no fork is created. World world = setup.UpdateWorld(); Assert.That( - s0.Futures.Count(), + world.GetFutures(s0).Count(), Is.EqualTo(1), "A superfluous support incorrectly forked the timeline"); - Assert.That(s1.Futures.Count(), Is.EqualTo(1)); + Assert.That(world.GetFutures(s1).Count(), Is.EqualTo(1)); Season s2 = world.GetSeason(s1.Timeline, s1.Turn + 1); - Assert.That(s2.Futures.Count(), Is.EqualTo(0)); + Assert.That(world.GetFutures(s2).Count(), Is.EqualTo(0)); } [Test] @@ -226,17 +226,17 @@ public class TimeTravelTest // Since both seasons were at the head of their timelines, there should be no forking. World world = setup.UpdateWorld(); Assert.That( - a2.Futures.Count(), + world.GetFutures(a2).Count(), Is.EqualTo(1), "A cross-timeline support incorrectly forked the head of the timeline"); Assert.That( - b1.Futures.Count(), + 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(a3.Futures.Count(), Is.EqualTo(0)); + Assert.That(world.GetFutures(a3).Count(), Is.EqualTo(0)); Season b2 = world.GetSeason(b1.Timeline, b1.Turn + 1); - Assert.That(b2.Futures.Count(), Is.EqualTo(0)); + Assert.That(world.GetFutures(b2).Count(), Is.EqualTo(0)); } [Test] @@ -297,11 +297,11 @@ public class TimeTravelTest // wasn't changed in this timeline. World world = setup.UpdateWorld(); Assert.That( - a3.Futures.Count(), + world.GetFutures(a3).Count(), Is.EqualTo(1), "A cross-timeline support cut incorrectly forked the timeline"); Assert.That( - b2.Futures.Count(), + world.GetFutures(b2).Count(), Is.EqualTo(1), "A cross-timeline support cut incorrectly forked the timeline"); } diff --git a/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs b/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs index 2c35dd1..0ff1ea7 100644 --- a/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs +++ b/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs @@ -172,7 +172,7 @@ public class MovementAdjudicatorTest Assert.That(updated.Seasons.Count, Is.EqualTo(2)); Season future = updated.Seasons.Single(s => s != updated.RootSeason); Assert.That(future.Past, Is.EqualTo(updated.RootSeason.ToString())); - Assert.That(future.Futures, Is.Empty); + Assert.That(updated.GetFutures(future), Is.Empty); Assert.That(future.Timeline, Is.EqualTo(updated.RootSeason.Timeline)); Assert.That(future.Turn, Is.EqualTo(Season.FIRST_TURN + 1)); @@ -201,7 +201,7 @@ public class MovementAdjudicatorTest // Confirm the future was created Season s2 = updated.GetSeason("a1"); Assert.That(s2.Past, Is.EqualTo(s1.ToString())); - Assert.That(s2.Futures, Is.Empty); + Assert.That(updated.GetFutures(s2), Is.Empty); Assert.That(s2.Timeline, Is.EqualTo(s1.Timeline)); Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1)); @@ -251,7 +251,7 @@ public class MovementAdjudicatorTest // Confirm the future was created Season s2 = updated.GetSeason(s1.Timeline, s1.Turn + 1); Assert.That(s2.Past, Is.EqualTo(s1.ToString())); - Assert.That(s2.Futures, Is.Empty); + Assert.That(updated.GetFutures(s2), Is.Empty); Assert.That(s2.Timeline, Is.EqualTo(s1.Timeline)); Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1)); diff --git a/MultiversalDiplomacyTests/SeasonTests.cs b/MultiversalDiplomacyTests/SeasonTests.cs index e4e401c..a1ae68e 100644 --- a/MultiversalDiplomacyTests/SeasonTests.cs +++ b/MultiversalDiplomacyTests/SeasonTests.cs @@ -61,5 +61,10 @@ public class SeasonTests Assert.That(world.InAdjacentTimeline(b3, a3), Is.True, "Expected alts to be adjacent to origin"); Assert.That(world.InAdjacentTimeline(b3, c2), Is.True, "Expected alts with common origin to be adjacent"); Assert.That(world.InAdjacentTimeline(b3, d3), Is.False, "Expected alts from different origins not to be adjacent"); + + Assert.That(world.GetFutures(a0), Is.EquivalentTo(new List { a1 }), "Unexpected futures"); + Assert.That(world.GetFutures(a1), Is.EquivalentTo(new List { a2, b2, c2 }), "Unexpected futures"); + Assert.That(world.GetFutures(a2), Is.EquivalentTo(new List { a3, d3 }), "Unexpected futures"); + Assert.That(world.GetFutures(b2), Is.EquivalentTo(new List { b3 }), "Unexpected futures"); } }