Refactor timelines and season creation logic into World

This commit is contained in:
Tim Van Baak 2024-08-13 15:24:40 -07:00
parent 58f877425a
commit 345d54f960
7 changed files with 41 additions and 78 deletions

View File

@ -312,9 +312,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// All moves to a particular season in a single phase result in the same future. Keep a
// record of when a future season has been created.
Dictionary<Season, Season> createdFutures = new();
List<Unit> createdUnits = new();
List<RetreatingUnit> retreats = new();
Dictionary<Season, Season> createdFutures = [];
List<Unit> createdUnits = [];
List<RetreatingUnit> retreats = [];
// Populate createdFutures with the timeline fork decisions
logger.Log(1, "Processing AdvanceTimeline decisions");
@ -324,9 +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] = !world.GetFutures(advanceTimeline.Season).Any()
? advanceTimeline.Season.MakeNext()
: advanceTimeline.Season.MakeFork();
createdFutures[advanceTimeline.Season] = world.ContinueOrFork(advanceTimeline.Season);
}
}

View File

@ -20,4 +20,10 @@ public static class ModelExtensions
{
return $"{coord.season.Timeline}-{coord.province.Abbreviations[0]}@{coord.season.Turn}";
}
}
public static World ContinueOrFork(this World world, Season season, out Season future)
{
future = world.ContinueOrFork(season);
return world.Update(seasons: world.Seasons.Append(future));
}
}

View File

@ -43,44 +43,12 @@ public class Season
[JsonIgnore]
public (string Timeline, int Turn) Coord => (this.Timeline, this.Turn);
/// <summary>
/// The shared timeline number generator.
/// </summary>
[JsonIgnore]
private TimelineFactory Timelines { get; }
private Season(string? past, int turn, string timeline, TimelineFactory factory)
public Season(string? past, int turn, string timeline)
{
this.Past = past;
this.Turn = turn;
this.Timeline = timeline;
this.Timelines = factory;
}
public override string ToString() => Designation;
/// <summary>
/// Create a root season at the beginning of time.
/// </summary>
public static Season MakeRoot()
{
TimelineFactory factory = new();
return new Season(
past: null,
turn: FIRST_TURN,
timeline: factory.NextTimeline(),
factory: factory);
}
/// <summary>
/// Create a season immediately after this one in the same timeline.
/// </summary>
public Season MakeNext()
=> new(this.Designation, Turn + 1, Timeline, Timelines);
/// <summary>
/// Create a season immediately after this one in a new timeline.
/// </summary>
public Season MakeFork()
=> new(this.Designation, Turn + 1, Timelines.NextTimeline(), Timelines);
}

View File

@ -3,7 +3,7 @@ namespace MultiversalDiplomacy.Model;
/// <summary>
/// A shared counter for handing out new timeline designations.
/// </summary>
internal class TimelineFactory
public class TimelineFactory
{
private static readonly char[] Letters = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',

View File

@ -63,6 +63,11 @@ public class World
/// </summary>
public ReadOnlyDictionary<string, OrderHistory> OrderHistory { get; }
/// <summary>
/// The shared timeline number generator.
/// </summary>
public TimelineFactory Timelines { get; }
/// <summary>
/// Immutable game options.
/// </summary>
@ -77,6 +82,7 @@ public class World
ReadOnlyCollection<Unit> units,
ReadOnlyCollection<RetreatingUnit> retreatingUnits,
ReadOnlyDictionary<string, OrderHistory> orderHistory,
TimelineFactory timelines,
Options options)
{
this.Map = map;
@ -84,6 +90,7 @@ public class World
this.Units = units;
this.RetreatingUnits = retreatingUnits;
this.OrderHistory = orderHistory;
this.Timelines = timelines;
this.Options = options;
this.SeasonLookup = new(Seasons.ToDictionary(season => $"{season.Timeline}{season.Turn}"));
@ -105,6 +112,7 @@ public class World
units ?? previous.Units,
retreatingUnits ?? previous.RetreatingUnits,
orderHistory ?? previous.OrderHistory,
previous.Timelines,
options ?? previous.Options)
{
}
@ -114,12 +122,14 @@ public class World
/// </summary>
public static World WithMap(Map map)
{
TimelineFactory timelines = new();
return new World(
map,
new([Season.MakeRoot()]),
new([new(past: null, Season.FIRST_TURN, timelines.NextTimeline())]),
new([]),
new([]),
new(new Dictionary<string, OrderHistory>()),
timelines,
new Options());
}
@ -209,21 +219,12 @@ public class World
}
/// <summary>
/// Create a season immediately after this one in the same timeline.
/// Create a continuation of this season if it has no futures, otherwise ceate a fork.
/// </summary>
public World ContinueSeason(string season)
=> Update(seasons: Seasons.Append(SeasonLookup[season].MakeNext()));
/// <summary>
/// Create a season immediately after this one in the same timeline.
/// </summary>
public World ContinueSeason(Season season) => ContinueSeason(season.ToString());
/// <summary>
/// Create a season immediately after this one in a new timeline.
/// </summary>
public World ForkSeason(string season)
=> Update(seasons: Seasons.Append(SeasonLookup[season].MakeFork()));
public Season ContinueOrFork(Season season)
=> GetFutures(season).Any()
? new(season.Designation, season.Turn + 1, Timelines.NextTimeline())
: new(season.Designation, season.Turn + 1, season.Timeline);
/// <summary>
/// A standard Diplomacy game setup.

View File

@ -9,30 +9,22 @@ public class SeasonTests
[Test]
public void TimelineForking()
{
World world = World
.WithMap(Map.Test)
.ContinueSeason("a0")
.ContinueSeason("a1")
.ContinueSeason("a2")
.ForkSeason("a1")
.ContinueSeason("b2")
.ForkSeason("a1")
.ForkSeason("a2");
World world = World.WithMap(Map.Test);
Season a0 = world.GetSeason("a0");
world = world
.ContinueOrFork(a0, out Season a1)
.ContinueOrFork(a1, out Season a2)
.ContinueOrFork(a2, out Season a3)
.ContinueOrFork(a1, out Season b2)
.ContinueOrFork(b2, out Season b3)
.ContinueOrFork(a1, out Season c2)
.ContinueOrFork(a2, out Season d3);
Assert.That(
world.Seasons.Select(season => season.ToString()),
Is.EquivalentTo(new List<string> { "a0", "a1", "a2", "a3", "b2", "b3", "c2", "d3" }),
"Unexpected seasons");
Season a0 = world.GetSeason("a0");
Season a1 = world.GetSeason("a1");
Season a2 = world.GetSeason("a2");
Season a3 = world.GetSeason("a3");
Season b2 = world.GetSeason("b2");
Season b3 = world.GetSeason("b3");
Season c2 = world.GetSeason("c2");
Season d3 = world.GetSeason("d3");
Assert.That(a0.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");
Assert.That(a1.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");
Assert.That(a2.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");

View File

@ -17,12 +17,10 @@ public class UnitTests
Season a0 = world.RootSeason;
Unit u1 = Unit.Build(Mun, a0, pw1, UnitType.Army);
world = world.ContinueSeason(a0);
Season a1 = world.GetSeason("a1");
world = world.ContinueOrFork(a0, out Season a1);
Unit u2 = u1.Next(Boh, a1);
world = world.ContinueSeason(a1);
Season a2 = world.GetSeason("a2");
_ = world.ContinueOrFork(a1, out Season a2);
Unit u3 = u2.Next(Tyr, a2);
Assert.That(u3.Past, Is.EqualTo(u2), "Missing unit past");