Refactor season futures into World

This commit is contained in:
Tim Van Baak 2024-08-12 15:25:23 -07:00
parent 3242186208
commit b17ce9485a
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.
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<Unit> allOrderableUnits = world.Units
.Where(unit => !unit.Season.Futures.Any())
.Where(unit => !world.GetFutures(unit.Season).Any())
.ToList();
HashSet<Unit> orderedUnits = validOrders.Select(order => order.Unit).ToHashSet();
List<Unit> 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;

View File

@ -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<Season> cobranchRoots = past.Futures
IEnumerable<Season> 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);

View File

@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace MultiversalDiplomacy.Model;
/// <summary>
@ -30,36 +32,32 @@ public class Season
public string Timeline { get; }
/// <summary>
/// The season's spatial location as a timeline-turn tuple.
/// The multiversal designation of this season.
/// </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);
/// <summary>
/// The shared timeline number generator.
/// </summary>
[JsonIgnore]
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)
{
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;
/// <summary>
/// Create a root season at the beginning of time.

View File

@ -232,6 +232,21 @@ public class World
public Season GetSeason(string 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>
/// 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

View File

@ -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");
}

View File

@ -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));

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, 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<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");
}
}