From d4e68844c643bd95c7d50f288db3a38c608f633a Mon Sep 17 00:00:00 2001 From: Jaculabilis Date: Mon, 28 Mar 2022 22:34:57 -0700 Subject: [PATCH] Simplify world updates and expose root season --- .../Adjudicate/MovementPhaseAdjudicator.cs | 8 +- MultiversalDiplomacy/Model/World.cs | 102 +++++++++--------- MultiversalDiplomacyTests/DATC_A.cs | 2 +- .../MovementAdjudicatorTest.cs | 26 ++--- MultiversalDiplomacyTests/TestCaseBuilder.cs | 2 +- .../TestCaseBuilderTest.cs | 8 +- MultiversalDiplomacyTests/UnitTests.cs | 2 +- 7 files changed, 78 insertions(+), 72 deletions(-) diff --git a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs index 837d035..b79f8f3 100644 --- a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs +++ b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs @@ -357,10 +357,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator // TODO provide more structured information about order outcomes - World updated = world - .WithSeasons(world.Seasons.Concat(createdFutures.Values)) - .WithUnits(world.Units.Concat(createdUnits)) - .WithRetreats(retreats); + World updated = world.Update( + seasons: world.Seasons.Concat(createdFutures.Values), + units: world.Units.Concat(createdUnits), + retreats: retreats); return updated; } diff --git a/MultiversalDiplomacy/Model/World.cs b/MultiversalDiplomacy/Model/World.cs index 88b1f3c..69cb500 100644 --- a/MultiversalDiplomacy/Model/World.cs +++ b/MultiversalDiplomacy/Model/World.cs @@ -22,6 +22,11 @@ public class World /// public ReadOnlyCollection Seasons { get; } + /// + /// The first season of the game. + /// + public Season RootSeason { get; } + /// /// All units in the multiverse. /// @@ -37,10 +42,14 @@ public class World /// public Options Options { get; } + /// + /// Create a new World, providing all state data. + /// private World( ReadOnlyCollection provinces, ReadOnlyCollection powers, ReadOnlyCollection seasons, + Season rootSeason, ReadOnlyCollection units, ReadOnlyCollection retreatingUnits, Options options) @@ -48,22 +57,49 @@ public class World this.Provinces = provinces; this.Powers = powers; this.Seasons = seasons; + this.RootSeason = rootSeason; this.Units = units; this.RetreatingUnits = retreatingUnits; this.Options = options; } /// - /// Create a new world with specified provinces and powers. + /// Create a new World from a previous one, replacing some state data. + /// + private World( + World previous, + ReadOnlyCollection? provinces = null, + ReadOnlyCollection? powers = null, + ReadOnlyCollection? seasons = null, + ReadOnlyCollection? units = null, + ReadOnlyCollection? retreatingUnits = null, + Options? options = null) + : this( + provinces ?? previous.Provinces, + powers ?? previous.Powers, + seasons ?? previous.Seasons, + previous.RootSeason, // Can't change the root season + units ?? previous.Units, + retreatingUnits ?? previous.RetreatingUnits, + options ?? previous.Options) + { + } + + /// + /// Create a new world with specified provinces and powers and an initial season. /// public static World WithMap(IEnumerable provinces, IEnumerable powers) - => new World( + { + Season root = Season.MakeRoot(); + return new World( new(provinces.ToList()), new(powers.ToList()), - new(new List()), + new(new List { root }), + root, new(new List()), new(new List()), new Options()); + } /// /// Create a new world with the standard Diplomacy provinces and powers. @@ -71,42 +107,22 @@ public class World public static World WithStandardMap() => WithMap(StandardProvinces, StandardPowers); - /// - /// Create a new world with new seasons. - /// - public World WithSeasons(IEnumerable seasons) + public World Update( + IEnumerable? seasons = null, + IEnumerable? units = null, + IEnumerable? retreats = null) => new World( - this.Provinces, - this.Powers, - new(seasons.ToList()), - this.Units, - this.RetreatingUnits, - this.Options); - - /// - /// Create a new world with an initial season. - /// - public World WithInitialSeason() - => WithSeasons(new List { Season.MakeRoot() }); - - /// - /// Create a new world with new units. - /// - public World WithUnits(IEnumerable units) - => new World( - this.Provinces, - this.Powers, - this.Seasons, - new(units.ToList()), - this.RetreatingUnits, - this.Options); + previous: this, + seasons: seasons == null ? this.Seasons : new(seasons.ToList()), + units: units == null ? this.Units : new(units.ToList()), + retreatingUnits: retreats == null ? this.RetreatingUnits : new(retreats.ToList())); /// /// Create a new world with new units created from unit specs. Units specs are in the format /// " []". If the province or coast name has a space in it, the - /// abbreviation should be used. + /// abbreviation should be used. Unit specs always describe units in the root season. /// - public World WithUnits(params string[] unitSpecs) + public World AddUnits(params string[] unitSpecs) { IEnumerable units = unitSpecs.Select(spec => { @@ -123,18 +139,18 @@ public class World : splits.Length == 3 ? this.GetWater(splits[2]) : this.GetWater(splits[2], splits[3]); - Unit unit = Unit.Build(location, this.Seasons.First(), power, type); + Unit unit = Unit.Build(location, this.RootSeason, power, type); return unit; }); - return this.WithUnits(units); + return this.Update(units: units); } /// /// Create a new world with standard Diplomacy initial unit placements. /// - public World WithStandardUnits() + public World AddStandardUnits() { - return this.WithUnits( + return this.AddUnits( "Austria A Bud", "Austria A Vir", "Austria F Tri", @@ -160,22 +176,12 @@ public class World ); } - public World WithRetreats(IEnumerable retreatingUnits) - => new World( - this.Provinces, - this.Powers, - this.Seasons, - this.Units, - new(retreatingUnits.ToList()), - this.Options); - /// /// A standard Diplomacy game setup. /// public static World Standard => World .WithStandardMap() - .WithInitialSeason() - .WithStandardUnits(); + .AddStandardUnits(); /// /// Get a province by name. Throws if the province is not found. diff --git a/MultiversalDiplomacyTests/DATC_A.cs b/MultiversalDiplomacyTests/DATC_A.cs index e296d12..f55de6c 100644 --- a/MultiversalDiplomacyTests/DATC_A.cs +++ b/MultiversalDiplomacyTests/DATC_A.cs @@ -9,7 +9,7 @@ namespace MultiversalDiplomacyTests; public class DATC_A { - private World StandardEmpty { get; } = World.WithStandardMap().WithInitialSeason(); + private World StandardEmpty { get; } = World.WithStandardMap(); [Test] public void DATC_6_A_1_MoveToAnAreaThatIsNotANeighbor() diff --git a/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs b/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs index 9e9d616..a54d2eb 100644 --- a/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs +++ b/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs @@ -11,72 +11,72 @@ public class MovementAdjudicatorTest [Test] public void Validation_ValidHold() { - TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap()); setup["Germany"] .Army("Mun").Holds().GetReference(out var order); setup.ValidateOrders(MovementPhaseAdjudicator.Instance); - Assert.That(order.Validation, Is.Valid, "Unexpected validation result"); + Assert.That(order, Is.Valid, "Unexpected validation result"); Assert.That(order.Replacement, Is.Null, "Unexpected order replacement"); } [Test] public void Validation_ValidMove() { - TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap()); setup["Germany"] .Army("Mun").MovesTo("Tyr").GetReference(out var order); setup.ValidateOrders(MovementPhaseAdjudicator.Instance); - Assert.That(order.Validation, Is.Valid, "Unexpected validation result"); + Assert.That(order, Is.Valid, "Unexpected validation result"); Assert.That(order.Replacement, Is.Null, "Unexpected order replacement"); } [Test] public void Validation_ValidConvoy() { - TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap()); setup["Germany"] .Fleet("Nth").Convoys.Army("Hol").To("Lon").GetReference(out var order); setup.ValidateOrders(MovementPhaseAdjudicator.Instance); - Assert.That(order.Validation, Is.Valid, "Unexpected validation result"); + Assert.That(order, Is.Valid, "Unexpected validation result"); Assert.That(order.Replacement, Is.Null, "Unexpected order replacement"); } [Test] public void Validation_ValidSupportHold() { - TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap()); setup["Germany"] .Army("Mun").Supports.Army("Kie").Hold().GetReference(out var order); setup.ValidateOrders(MovementPhaseAdjudicator.Instance); - Assert.That(order.Validation, Is.Valid, "Unexpected validation result"); + Assert.That(order, Is.Valid, "Unexpected validation result"); Assert.That(order.Replacement, Is.Null, "Unexpected order replacement"); } [Test] public void Validation_ValidSupportMove() { - TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap()); setup["Germany"] .Army("Mun").Supports.Army("Kie").MoveTo("Ber").GetReference(out var order); setup.ValidateOrders(MovementPhaseAdjudicator.Instance); - Assert.That(order.Validation, Is.Valid, "Unexpected validation result"); + Assert.That(order, Is.Valid, "Unexpected validation result"); Assert.That(order.Replacement, Is.Null, "Unexpected order replacement"); } [Test] public void Adjudication_Hold() { - TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap()); setup["Germany"] .Army("Mun").Holds().GetReference(out var order); @@ -96,7 +96,7 @@ public class MovementAdjudicatorTest [Test] public void Adjudication_Move() { - TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap()); setup["Germany"] .Army("Mun").MovesTo("Tyr").GetReference(out var order); @@ -122,7 +122,7 @@ public class MovementAdjudicatorTest [Test] public void Adjudication_Support() { - TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap()); setup["Germany"] .Army("Mun").MovesTo("Tyr").GetReference(out var move) .Army("Boh").Supports.Army("Mun").MoveTo("Tyr").GetReference(out var support); diff --git a/MultiversalDiplomacyTests/TestCaseBuilder.cs b/MultiversalDiplomacyTests/TestCaseBuilder.cs index 9812085..75aa36c 100644 --- a/MultiversalDiplomacyTests/TestCaseBuilder.cs +++ b/MultiversalDiplomacyTests/TestCaseBuilder.cs @@ -217,7 +217,7 @@ public class TestCaseBuilder // Not found Unit newUnit = Unit.Build(location, season, power, type); - this.World = this.World.WithUnits(this.World.Units.Append(newUnit)); + this.World = this.World.Update(units: this.World.Units.Append(newUnit)); return newUnit; } diff --git a/MultiversalDiplomacyTests/TestCaseBuilderTest.cs b/MultiversalDiplomacyTests/TestCaseBuilderTest.cs index f116e0b..539b141 100644 --- a/MultiversalDiplomacyTests/TestCaseBuilderTest.cs +++ b/MultiversalDiplomacyTests/TestCaseBuilderTest.cs @@ -12,7 +12,7 @@ class TestCaseBuilderTest [Test] public void BuilderCreatesUnits() { - TestCaseBuilder setup = new(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new(World.WithStandardMap()); Assert.That(setup.World.Powers.Count(), Is.EqualTo(7), "Unexpected power count"); Assert.That(setup.World.Units, Is.Empty, "Expected no units to be created yet"); @@ -50,7 +50,7 @@ class TestCaseBuilderTest [Test] public void BuilderCreatesOrders() { - TestCaseBuilder setup = new(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new(World.WithStandardMap()); Assert.That(setup.World.Powers.Count(), Is.EqualTo(7), "Unexpected power count"); Assert.That(setup.World.Units, Is.Empty, "Expected no units to be created yet"); @@ -120,7 +120,7 @@ class TestCaseBuilderTest { IPhaseAdjudicator rubberStamp = new TestAdjudicator(validate: TestAdjudicator.RubberStamp); - TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap()); setup["Germany"] .Army("Mun").Holds().GetReference(out var orderMun); @@ -164,7 +164,7 @@ class TestCaseBuilderTest validate: TestAdjudicator.RubberStamp, adjudicate: TestAdjudicator.NoMoves); - TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap()); setup["Germany"] .Army("Mun").Holds().GetReference(out var orderMun); diff --git a/MultiversalDiplomacyTests/UnitTests.cs b/MultiversalDiplomacyTests/UnitTests.cs index cbea6f8..09a427f 100644 --- a/MultiversalDiplomacyTests/UnitTests.cs +++ b/MultiversalDiplomacyTests/UnitTests.cs @@ -9,7 +9,7 @@ public class UnitTests [Test] public void MovementTest() { - World world = World.WithStandardMap().WithInitialSeason(); + World world = World.WithStandardMap(); Location Mun = world.GetLand("Mun"), Boh = world.GetLand("Boh"), Tyr = world.GetLand("Tyr");