Refactor season futures into World

This commit is contained in:
Tim Van Baak 2024-08-12 15:25:23 -07:00
parent 752a898123
commit 87685ec744
7 changed files with 56 additions and 37 deletions

View File

@ -50,7 +50,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// Invalidate any order given to a unit in the past. // Invalidate any order given to a unit in the past.
AdjudicatorHelpers.InvalidateIfNotMatching( AdjudicatorHelpers.InvalidateIfNotMatching(
order => !order.Unit.Season.Futures.Any(), order => !world.GetFutures(order.Unit.Season).Any(),
ValidationReason.IneligibleForOrder, ValidationReason.IneligibleForOrder,
ref unitOrders, ref unitOrders,
ref validationResults); ref validationResults);
@ -255,7 +255,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// Finally, add implicit hold orders for units without legal orders. // Finally, add implicit hold orders for units without legal orders.
List<Unit> allOrderableUnits = world.Units List<Unit> allOrderableUnits = world.Units
.Where(unit => !unit.Season.Futures.Any()) .Where(unit => !world.GetFutures(unit.Season).Any())
.ToList(); .ToList();
HashSet<Unit> orderedUnits = validOrders.Select(order => order.Unit).ToHashSet(); HashSet<Unit> orderedUnits = validOrders.Select(order => order.Unit).ToHashSet();
List<Unit> unorderedUnits = allOrderableUnits List<Unit> unorderedUnits = allOrderableUnits
@ -324,7 +324,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
if (advanceTimeline.Outcome == true) if (advanceTimeline.Outcome == true)
{ {
// A timeline that doesn't have a future yet simply continues. Otherwise, it forks. // 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.MakeNext()
: advanceTimeline.Season.MakeFork(); : advanceTimeline.Season.MakeFork();
} }
@ -486,7 +486,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
bool progress = false; bool progress = false;
// A season at the head of a timeline always advances. // 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"); progress |= LoggedUpdate(decision, true, depth, "A timeline head always advances");
return progress; return progress;

View File

@ -115,7 +115,7 @@ public static class PathFinder
// The immediate past and all immediate futures are adjacent. // The immediate past and all immediate futures are adjacent.
if (season.Past != null) adjacents.Add(world.GetSeason(season.Past)); 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 // 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 // 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)) current = world.GetSeason(current.Past))
{ {
adjacentTimelineRoots.AddRange( 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 // 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. // the first timeline by definition cannot have co-branches.
if (current?.Past != null && world.GetSeason(current.Past) is Season past) if (current?.Past != null && world.GetSeason(current.Past) is Season past)
{ {
IEnumerable<Season> cobranchRoots = past.Futures IEnumerable<Season> cobranchRoots = world
.GetFutures(past)
.Where(s => s.Timeline != current.Timeline && s.Timeline != past.Timeline); .Where(s => s.Timeline != current.Timeline && s.Timeline != past.Timeline);
adjacentTimelineRoots.AddRange(cobranchRoots); adjacentTimelineRoots.AddRange(cobranchRoots);
} }
@ -147,7 +148,7 @@ public static class PathFinder
{ {
for (Season? branchSeason = timelineRoot; for (Season? branchSeason = timelineRoot;
branchSeason != null && branchSeason.Turn <= season.Turn + 1; branchSeason != null && branchSeason.Turn <= season.Turn + 1;
branchSeason = branchSeason.Futures branchSeason = world.GetFutures(branchSeason)
.FirstOrDefault(s => s!.Timeline == branchSeason.Timeline, null)) .FirstOrDefault(s => s!.Timeline == branchSeason.Timeline, null))
{ {
if (branchSeason.Turn >= season.Turn - 1) adjacents.Add(branchSeason); if (branchSeason.Turn >= season.Turn - 1) adjacents.Add(branchSeason);

View File

@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace MultiversalDiplomacy.Model; namespace MultiversalDiplomacy.Model;
/// <summary> /// <summary>
@ -30,36 +32,32 @@ public class Season
public string Timeline { get; } public string Timeline { get; }
/// <summary> /// <summary>
/// The season's spatial location as a timeline-turn tuple. /// The multiversal designation of this season.
/// </summary> /// </summary>
[JsonIgnore]
public string Designation => $"{this.Timeline}{this.Turn}";
/// <summary>
/// The season's multiversal location as a timeline-turn tuple for convenience.
/// </summary>
[JsonIgnore]
public (string Timeline, int Turn) Coord => (this.Timeline, this.Turn); public (string Timeline, int Turn) Coord => (this.Timeline, this.Turn);
/// <summary> /// <summary>
/// The shared timeline number generator. /// The shared timeline number generator.
/// </summary> /// </summary>
[JsonIgnore]
private TimelineFactory Timelines { get; } private TimelineFactory Timelines { get; }
/// <summary>
/// Future seasons created directly from this season.
/// </summary>
public IEnumerable<Season> Futures => this.FutureList;
private List<Season> FutureList { get; }
private Season(Season? past, int turn, string timeline, TimelineFactory factory) private Season(Season? past, int turn, string timeline, TimelineFactory factory)
{ {
this.Past = past?.ToString(); this.Past = past?.ToString();
this.Turn = turn; this.Turn = turn;
this.Timeline = timeline; this.Timeline = timeline;
this.Timelines = factory; this.Timelines = factory;
this.FutureList = [];
past?.FutureList.Add(this);
} }
public override string ToString() public override string ToString() => Designation;
{
return $"{this.Timeline}{this.Turn}";
}
/// <summary> /// <summary>
/// Create a root season at the beginning of time. /// Create a root season at the beginning of time.

View File

@ -232,6 +232,21 @@ public class World
public Season GetSeason(string designation) public Season GetSeason(string designation)
=> SeasonLookup[designation]; => SeasonLookup[designation];
/// <summary>
/// Get all seasons that are immediate futures of a season.
/// </summary>
/// <param name="present">A season designation.</param>
/// <returns>The immediate futures of the designated season.</returns>
public IEnumerable<Season> GetFutures(string present)
=> Seasons.Where(future => future.Past == present);
/// <summary>
/// Get all seasons that are immediate futures of a season.
/// </summary>
/// <param name="present">A season.</param>
/// <returns>The immediate futures of the season.</returns>
public IEnumerable<Season> GetFutures(Season present) => GetFutures(present.Designation);
/// <summary> /// <summary>
/// Returns the first season in this season's timeline. The first season is the /// 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 /// root of the first timeline. The earliest season in each alternate timeline is

View File

@ -136,12 +136,12 @@ public class TimeTravelTest
// change the past and therefore did not create a new timeline. // change the past and therefore did not create a new timeline.
World world = setup.UpdateWorld(); World world = setup.UpdateWorld();
Assert.That( Assert.That(
s0.Futures.Count(), world.GetFutures(s0).Count(),
Is.EqualTo(1), Is.EqualTo(1),
"A failed move incorrectly forked the timeline"); "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); 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] [Test]
@ -178,12 +178,12 @@ public class TimeTravelTest
// ...since it succeeded anyway, no fork is created. // ...since it succeeded anyway, no fork is created.
World world = setup.UpdateWorld(); World world = setup.UpdateWorld();
Assert.That( Assert.That(
s0.Futures.Count(), world.GetFutures(s0).Count(),
Is.EqualTo(1), Is.EqualTo(1),
"A superfluous support incorrectly forked the timeline"); "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); 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] [Test]
@ -226,17 +226,17 @@ public class TimeTravelTest
// Since both seasons were at the head of their timelines, there should be no forking. // Since both seasons were at the head of their timelines, there should be no forking.
World world = setup.UpdateWorld(); World world = setup.UpdateWorld();
Assert.That( Assert.That(
a2.Futures.Count(), world.GetFutures(a2).Count(),
Is.EqualTo(1), Is.EqualTo(1),
"A cross-timeline support incorrectly forked the head of the timeline"); "A cross-timeline support incorrectly forked the head of the timeline");
Assert.That( Assert.That(
b1.Futures.Count(), world.GetFutures(b1).Count(),
Is.EqualTo(1), Is.EqualTo(1),
"A cross-timeline support incorrectly forked the head of the timeline"); "A cross-timeline support incorrectly forked the head of the timeline");
Season a3 = world.GetSeason(a2.Timeline, a2.Turn + 1); 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); 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] [Test]
@ -297,11 +297,11 @@ public class TimeTravelTest
// wasn't changed in this timeline. // wasn't changed in this timeline.
World world = setup.UpdateWorld(); World world = setup.UpdateWorld();
Assert.That( Assert.That(
a3.Futures.Count(), world.GetFutures(a3).Count(),
Is.EqualTo(1), Is.EqualTo(1),
"A cross-timeline support cut incorrectly forked the timeline"); "A cross-timeline support cut incorrectly forked the timeline");
Assert.That( Assert.That(
b2.Futures.Count(), world.GetFutures(b2).Count(),
Is.EqualTo(1), Is.EqualTo(1),
"A cross-timeline support cut incorrectly forked the timeline"); "A cross-timeline support cut incorrectly forked the timeline");
} }

View File

@ -172,7 +172,7 @@ public class MovementAdjudicatorTest
Assert.That(updated.Seasons.Count, Is.EqualTo(2)); Assert.That(updated.Seasons.Count, Is.EqualTo(2));
Season future = updated.Seasons.Single(s => s != updated.RootSeason); Season future = updated.Seasons.Single(s => s != updated.RootSeason);
Assert.That(future.Past, Is.EqualTo(updated.RootSeason.ToString())); 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.Timeline, Is.EqualTo(updated.RootSeason.Timeline));
Assert.That(future.Turn, Is.EqualTo(Season.FIRST_TURN + 1)); Assert.That(future.Turn, Is.EqualTo(Season.FIRST_TURN + 1));
@ -201,7 +201,7 @@ public class MovementAdjudicatorTest
// Confirm the future was created // Confirm the future was created
Season s2 = updated.GetSeason("a1"); Season s2 = updated.GetSeason("a1");
Assert.That(s2.Past, Is.EqualTo(s1.ToString())); 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.Timeline, Is.EqualTo(s1.Timeline));
Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1)); Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1));
@ -251,7 +251,7 @@ public class MovementAdjudicatorTest
// Confirm the future was created // Confirm the future was created
Season s2 = updated.GetSeason(s1.Timeline, s1.Turn + 1); Season s2 = updated.GetSeason(s1.Timeline, s1.Turn + 1);
Assert.That(s2.Past, Is.EqualTo(s1.ToString())); 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.Timeline, Is.EqualTo(s1.Timeline));
Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1)); Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1));

View File

@ -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, 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, 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.InAdjacentTimeline(b3, d3), Is.False, "Expected alts from different origins not to be adjacent");
Assert.That(world.GetFutures(a0), Is.EquivalentTo(new List<Season> { a1 }), "Unexpected futures");
Assert.That(world.GetFutures(a1), Is.EquivalentTo(new List<Season> { a2, b2, c2 }), "Unexpected futures");
Assert.That(world.GetFutures(a2), Is.EquivalentTo(new List<Season> { a3, d3 }), "Unexpected futures");
Assert.That(world.GetFutures(b2), Is.EquivalentTo(new List<Season> { b3 }), "Unexpected futures");
} }
} }