diff --git a/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs b/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs index d63d65a..b5e56b5 100644 --- a/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs +++ b/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs @@ -1,6 +1,8 @@ using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Orders; +using static MultiversalDiplomacy.Model.Location; + namespace MultiversalDiplomacy.Adjudicate.Decision; public class MovementDecisions @@ -94,7 +96,7 @@ public class MovementDecisions (string province, string season) UnitPoint(Unit unit) => (world.Map.GetLocation(unit.Location).Province.Name, unit.Season.Key); (string province, string season) MovePoint(MoveOrder move) - => (move.Location.Province.Name, move.Season.Key); + => (SplitKey(move.Location).province, move.Season.Key); // Create a hold strength decision with an associated order for every province with a unit. foreach (UnitOrder order in relevantOrders) @@ -108,13 +110,23 @@ public class MovementDecisions bool IsIncoming(UnitOrder me, MoveOrder other) => me != other && other.Season == me.Unit.Season - && other.Location.Province.Name == world.Map.GetLocation(me.Unit).Province.Name; + && SplitKey(other.Location).province == world.Map.GetLocation(me.Unit).Province.Name; + + bool IsSupportFor(SupportMoveOrder me, MoveOrder move) + => me.Target == move.Unit + && me.Season == move.Season + && me.Location.Key == move.Location; bool AreOpposing(MoveOrder one, MoveOrder two) => one.Season == two.Unit.Season && two.Season == one.Unit.Season - && one.Location.Province.Name == world.Map.GetLocation(two.Unit).Province.Name - && two.Location.Province.Name == world.Map.GetLocation(one.Unit).Province.Name; + && SplitKey(one.Location).province == world.Map.GetLocation(two.Unit).Province.Name + && SplitKey(two.Location).province == world.Map.GetLocation(one.Unit).Province.Name; + + bool AreCompeting(MoveOrder one, MoveOrder two) + => one != two + && one.Season == two.Season + && SplitKey(one.Location).province == SplitKey(two.Location).province; // Create all other relevant decisions for each order in the affected timelines. foreach (UnitOrder order in relevantOrders) @@ -131,7 +143,7 @@ public class MovementDecisions // Find supports corresponding to this move. List supports = relevantOrders .OfType() - .Where(support => support.IsSupportFor(move)) + .Where(support => IsSupportFor(support, move)) .ToList(); // Determine if this move is a head-to-head battle. @@ -142,7 +154,7 @@ public class MovementDecisions // Find competing moves. List competing = relevantOrders .OfType() - .Where(move.IsCompeting) + .Where(other => AreCompeting(move, other)) .ToList(); // Create the move-related decisions. @@ -153,7 +165,7 @@ public class MovementDecisions DoesMove[move] = new(move, opposingMove, competing); // Ensure a hold strength decision exists for the destination. - HoldStrength.Ensure(MovePoint(move), () => new(move.Location.Province, move.Season)); + HoldStrength.Ensure(MovePoint(move), () => new(world.Map.GetLocation(move.Location).Province, move.Season)); } else if (order is SupportOrder support) { diff --git a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs index 74ac569..f4b89c9 100644 --- a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs +++ b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs @@ -3,6 +3,8 @@ using MultiversalDiplomacy.Adjudicate.Logging; using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Orders; +using static MultiversalDiplomacy.Model.Location; + namespace MultiversalDiplomacy.Adjudicate; /// @@ -69,15 +71,15 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator // Trivial check: armies cannot move to water and fleets cannot move to land. AdjudicatorHelpers.InvalidateIfNotMatching( - order => (order.Unit.Type == UnitType.Army && order.Location.Type == LocationType.Land) - || (order.Unit.Type == UnitType.Fleet && order.Location.Type == LocationType.Water), + order => (order.Unit.Type == UnitType.Army && world.Map.GetLocation(order.Location).Type == LocationType.Land) + || (order.Unit.Type == UnitType.Fleet && world.Map.GetLocation(order.Location).Type == LocationType.Water), ValidationReason.IllegalDestinationType, ref moveOrders, ref validationResults); // Trivial check: a unit cannot move to where it already is. AdjudicatorHelpers.InvalidateIfNotMatching( - order => !(order.Location.Key == order.Unit.Location && order.Season == order.Unit.Season), + order => !(order.Location == order.Unit.Location && order.Season == order.Unit.Season), ValidationReason.DestinationMatchesOrigin, ref moveOrders, ref validationResults); @@ -90,7 +92,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator ILookup moveOrdersByAdjacency = moveOrders .ToLookup(order => // Map adjacency - world.Map.GetLocation(order.Unit).Adjacents.Contains(order.Location) + world.Map.GetLocation(order.Unit).Adjacents.Select(loc => loc.Key).Contains(order.Location) // Turn adjacency && Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1 // Timeline adjacency @@ -339,7 +341,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator Season moveSeason = doesMove.Order.Season; if (doesMove.Outcome == true && createdFutures.TryGetValue(moveSeason, out Season future)) { - Unit next = doesMove.Order.Unit.Next(doesMove.Order.Location.Key, future); + Unit next = doesMove.Order.Unit.Next(doesMove.Order.Location, future); logger.Log(3, "Advancing unit to {0}", next); createdUnits.Add(next); } @@ -635,7 +637,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator // If the origin and destination are adjacent, then there is a path. if (// Map adjacency - world.Map.GetLocation(decision.Order.Unit).Adjacents.Contains(decision.Order.Location) + world.Map.GetLocation(decision.Order.Unit).Adjacents.Select(loc => loc.Key).Contains(decision.Order.Location) // Turn adjacency && Math.Abs(decision.Order.Unit.Season.Turn - decision.Order.Season.Turn) <= 1 // Timeline adjacency @@ -776,7 +778,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator // If there is a head to head battle, a unit at the destination that isn't moving away, or // a unit at the destination that will fail to move away, then the attacking unit will have // to dislodge it. - UnitOrder? destOrder = decisions.HoldStrength[(decision.Order.Location.Province.Name, decision.Order.Season.Key)].Order; + UnitOrder? destOrder = decisions.HoldStrength[(SplitKey(decision.Order.Location).province, decision.Order.Season.Key)].Order; DoesMove? destMoveAway = destOrder is MoveOrder moveAway ? decisions.DoesMove[moveAway] : null; @@ -955,7 +957,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator // strength. NumericAdjudicationDecision defense = decision.OpposingMove != null ? decisions.DefendStrength[decision.OpposingMove] - : decisions.HoldStrength[(decision.Order.Location.Province.Name, decision.Order.Season.Key)]; + : decisions.HoldStrength[(SplitKey(decision.Order.Location).province, decision.Order.Season.Key)]; progress |= ResolveDecision(defense, world, decisions, depth + 1); // If the attack doesn't beat the defense, resolve the move to false. diff --git a/MultiversalDiplomacy/Adjudicate/PathFinder.cs b/MultiversalDiplomacy/Adjudicate/PathFinder.cs index e4e7512..34d7a10 100644 --- a/MultiversalDiplomacy/Adjudicate/PathFinder.cs +++ b/MultiversalDiplomacy/Adjudicate/PathFinder.cs @@ -18,7 +18,7 @@ public static class PathFinder /// Determines if a convoy path exists for a move order. /// public static bool ConvoyPathExists(World world, MoveOrder order) - => ConvoyPathExists(world, order.Unit, order.Location, order.Season); + => ConvoyPathExists(world, order.Unit, world.Map.GetLocation(order.Location), order.Season); private static bool ConvoyPathExists( World world, diff --git a/MultiversalDiplomacy/Model/Location.cs b/MultiversalDiplomacy/Model/Location.cs index 0e652e8..c25079f 100644 --- a/MultiversalDiplomacy/Model/Location.cs +++ b/MultiversalDiplomacy/Model/Location.cs @@ -53,6 +53,12 @@ public class Location this.AdjacentList = new List(); } + public static (string province, string location) SplitKey(string locationKey) + { + var split = locationKey.Split(['/'], 2); + return (split[0], split[1]); + } + public override string ToString() { return this.Name == "land" || this.Name == "water" diff --git a/MultiversalDiplomacy/Orders/MoveOrder.cs b/MultiversalDiplomacy/Orders/MoveOrder.cs index ebc75cf..a15d366 100644 --- a/MultiversalDiplomacy/Orders/MoveOrder.cs +++ b/MultiversalDiplomacy/Orders/MoveOrder.cs @@ -1,5 +1,7 @@ using MultiversalDiplomacy.Model; +using static MultiversalDiplomacy.Model.Location; + namespace MultiversalDiplomacy.Orders; /// @@ -15,9 +17,9 @@ public class MoveOrder : UnitOrder /// /// The destination location to which the unit should move. /// - public Location Location { get; } + public string Location { get; } - public MoveOrder(string power, Unit unit, Season season, Location location) + public MoveOrder(string power, Unit unit, Season season, string location) : base (power, unit) { this.Season = season; @@ -26,14 +28,6 @@ public class MoveOrder : UnitOrder public override string ToString() { - return $"{this.Unit} -> {Season.Timeline}-{Location.Province}@{Season.Turn}"; + return $"{this.Unit} -> {Season.Timeline}-{SplitKey(Location).province}@{Season.Turn}"; } - - /// - /// Returns whether another move order has the same destination as this order. - /// - public bool IsCompeting(MoveOrder other) - => this != other - && this.Season == other.Season - && this.Location.Province == other.Location.Province; } diff --git a/MultiversalDiplomacy/Orders/SupportMoveOrder.cs b/MultiversalDiplomacy/Orders/SupportMoveOrder.cs index e66148a..fdf1ad9 100644 --- a/MultiversalDiplomacy/Orders/SupportMoveOrder.cs +++ b/MultiversalDiplomacy/Orders/SupportMoveOrder.cs @@ -38,9 +38,4 @@ public class SupportMoveOrder : SupportOrder { return $"{this.Unit} S {this.Target} -> {(this.Province, this.Season).ToShort()}"; } - - public bool IsSupportFor(MoveOrder move) - => this.Target == move.Unit - && this.Season == move.Season - && this.Location == move.Location; } \ No newline at end of file diff --git a/MultiversalDiplomacyTests/TestCaseBuilder.cs b/MultiversalDiplomacyTests/TestCaseBuilder.cs index 3e047fb..c6148d2 100644 --- a/MultiversalDiplomacyTests/TestCaseBuilder.cs +++ b/MultiversalDiplomacyTests/TestCaseBuilder.cs @@ -423,7 +423,7 @@ public class TestCaseBuilder this.PowerContext.Power, this.Unit, destSeason, - destination); + destination.Key); this.Builder.OrderList.Add(moveOrder); return new OrderDefinedContext(this, moveOrder); } diff --git a/MultiversalDiplomacyTests/TestCaseBuilderTest.cs b/MultiversalDiplomacyTests/TestCaseBuilderTest.cs index 496e07c..d653cf1 100644 --- a/MultiversalDiplomacyTests/TestCaseBuilderTest.cs +++ b/MultiversalDiplomacyTests/TestCaseBuilderTest.cs @@ -74,7 +74,7 @@ class TestCaseBuilderTest Assert.That(orderBer, Is.InstanceOf(), "Unexpected order type"); Assert.That( (orderBer as MoveOrder)?.Location, - Is.EqualTo(setup.World.Map.GetLand("Kiel")), + Is.EqualTo(setup.World.Map.GetLand("Kiel").Key), "Unexpected move order destination"); UnitOrder orderPru = orders.Single(OrderForProvince("Prussia"));