From 345d54f96082a1d8a6c0dc43f491f206f4f7ae10 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Tue, 13 Aug 2024 15:24:40 -0700 Subject: [PATCH] Refactor timelines and season creation logic into World --- .../Adjudicate/MovementPhaseAdjudicator.cs | 10 +++--- MultiversalDiplomacy/Model/ModelExtensions.cs | 8 ++++- MultiversalDiplomacy/Model/Season.cs | 34 +------------------ MultiversalDiplomacy/Model/TimelineFactory.cs | 2 +- MultiversalDiplomacy/Model/World.cs | 31 +++++++++-------- MultiversalDiplomacyTests/SeasonTests.cs | 28 ++++++--------- MultiversalDiplomacyTests/UnitTests.cs | 6 ++-- 7 files changed, 41 insertions(+), 78 deletions(-) diff --git a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs index 51174a3..c097d0d 100644 --- a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs +++ b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs @@ -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 createdFutures = new(); - List createdUnits = new(); - List retreats = new(); + Dictionary createdFutures = []; + List createdUnits = []; + List 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); } } diff --git a/MultiversalDiplomacy/Model/ModelExtensions.cs b/MultiversalDiplomacy/Model/ModelExtensions.cs index 7d53187..dbd8991 100644 --- a/MultiversalDiplomacy/Model/ModelExtensions.cs +++ b/MultiversalDiplomacy/Model/ModelExtensions.cs @@ -20,4 +20,10 @@ public static class ModelExtensions { return $"{coord.season.Timeline}-{coord.province.Abbreviations[0]}@{coord.season.Turn}"; } -} \ No newline at end of file + + public static World ContinueOrFork(this World world, Season season, out Season future) + { + future = world.ContinueOrFork(season); + return world.Update(seasons: world.Seasons.Append(future)); + } +} diff --git a/MultiversalDiplomacy/Model/Season.cs b/MultiversalDiplomacy/Model/Season.cs index d67f22c..84a3597 100644 --- a/MultiversalDiplomacy/Model/Season.cs +++ b/MultiversalDiplomacy/Model/Season.cs @@ -43,44 +43,12 @@ public class Season [JsonIgnore] public (string Timeline, int Turn) Coord => (this.Timeline, this.Turn); - /// - /// The shared timeline number generator. - /// - [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; - - /// - /// Create a root season at the beginning of time. - /// - public static Season MakeRoot() - { - TimelineFactory factory = new(); - return new Season( - past: null, - turn: FIRST_TURN, - timeline: factory.NextTimeline(), - factory: factory); - } - - /// - /// Create a season immediately after this one in the same timeline. - /// - public Season MakeNext() - => new(this.Designation, Turn + 1, Timeline, Timelines); - - /// - /// Create a season immediately after this one in a new timeline. - /// - public Season MakeFork() - => new(this.Designation, Turn + 1, Timelines.NextTimeline(), Timelines); } diff --git a/MultiversalDiplomacy/Model/TimelineFactory.cs b/MultiversalDiplomacy/Model/TimelineFactory.cs index 1921842..1c33f0f 100644 --- a/MultiversalDiplomacy/Model/TimelineFactory.cs +++ b/MultiversalDiplomacy/Model/TimelineFactory.cs @@ -3,7 +3,7 @@ namespace MultiversalDiplomacy.Model; /// /// A shared counter for handing out new timeline designations. /// -internal class TimelineFactory +public class TimelineFactory { private static readonly char[] Letters = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', diff --git a/MultiversalDiplomacy/Model/World.cs b/MultiversalDiplomacy/Model/World.cs index bce3d82..7013a59 100644 --- a/MultiversalDiplomacy/Model/World.cs +++ b/MultiversalDiplomacy/Model/World.cs @@ -63,6 +63,11 @@ public class World /// public ReadOnlyDictionary OrderHistory { get; } + /// + /// The shared timeline number generator. + /// + public TimelineFactory Timelines { get; } + /// /// Immutable game options. /// @@ -77,6 +82,7 @@ public class World ReadOnlyCollection units, ReadOnlyCollection retreatingUnits, ReadOnlyDictionary 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 /// 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()), + timelines, new Options()); } @@ -209,21 +219,12 @@ public class World } /// - /// 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. /// - public World ContinueSeason(string season) - => Update(seasons: Seasons.Append(SeasonLookup[season].MakeNext())); - - /// - /// Create a season immediately after this one in the same timeline. - /// - public World ContinueSeason(Season season) => ContinueSeason(season.ToString()); - - /// - /// Create a season immediately after this one in a new timeline. - /// - 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); /// /// A standard Diplomacy game setup. diff --git a/MultiversalDiplomacyTests/SeasonTests.cs b/MultiversalDiplomacyTests/SeasonTests.cs index a1ae68e..3769e07 100644 --- a/MultiversalDiplomacyTests/SeasonTests.cs +++ b/MultiversalDiplomacyTests/SeasonTests.cs @@ -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 { "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"); diff --git a/MultiversalDiplomacyTests/UnitTests.cs b/MultiversalDiplomacyTests/UnitTests.cs index faf3028..46b869c 100644 --- a/MultiversalDiplomacyTests/UnitTests.cs +++ b/MultiversalDiplomacyTests/UnitTests.cs @@ -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");