Eliminate sources of Unit reference equality

This commit is contained in:
Tim Van Baak 2024-08-16 20:13:15 +00:00
parent 9a64609605
commit eaafdeb5a9
10 changed files with 43 additions and 42 deletions

View File

@ -7,7 +7,7 @@ namespace MultiversalDiplomacy.Adjudicate.Decision;
public class MovementDecisions public class MovementDecisions
{ {
public Dictionary<Unit, IsDislodged> IsDislodged { get; } public Dictionary<string, IsDislodged> IsDislodged { get; }
public Dictionary<MoveOrder, HasPath> HasPath { get; } public Dictionary<MoveOrder, HasPath> HasPath { get; }
public Dictionary<SupportOrder, GivesSupport> GivesSupport { get; } public Dictionary<SupportOrder, GivesSupport> GivesSupport { get; }
public Dictionary<(string, string), HoldStrength> HoldStrength { get; } public Dictionary<(string, string), HoldStrength> HoldStrength { get; }
@ -113,7 +113,7 @@ public class MovementDecisions
&& SplitKey(other.Location).province == world.Map.GetLocation(me.Unit).Province.Name; && SplitKey(other.Location).province == world.Map.GetLocation(me.Unit).Province.Name;
bool IsSupportFor(SupportMoveOrder me, MoveOrder move) bool IsSupportFor(SupportMoveOrder me, MoveOrder move)
=> me.Target == move.Unit => me.Target.Key == move.Unit.Key
&& me.Season == move.Season && me.Season == move.Season
&& me.Location.Key == move.Location; && me.Location.Key == move.Location;
@ -136,7 +136,7 @@ public class MovementDecisions
.OfType<MoveOrder>() .OfType<MoveOrder>()
.Where(other => IsIncoming(order, other)) .Where(other => IsIncoming(order, other))
.ToList(); .ToList();
IsDislodged[order.Unit] = new(order, incoming); IsDislodged[order.Unit.Key] = new(order, incoming);
if (order is MoveOrder move) if (order is MoveOrder move)
{ {

View File

@ -163,7 +163,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// Support-hold orders are invalid if the unit supports itself. // Support-hold orders are invalid if the unit supports itself.
AdjudicatorHelpers.InvalidateIfNotMatching( AdjudicatorHelpers.InvalidateIfNotMatching(
order => order.Unit != order.Target, order => order.Unit.Key != order.Target.Key,
ValidationReason.NoSelfSupport, ValidationReason.NoSelfSupport,
ref supportHoldOrders, ref supportHoldOrders,
ref validationResults); ref validationResults);
@ -241,13 +241,13 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// were not addressed by 4.D.1-2 and will be handled according to 4.D.3, i.e. replaced with // were not addressed by 4.D.1-2 and will be handled according to 4.D.3, i.e. replaced with
// hold orders. Note that this happens last, after all other invalidations have been // hold orders. Note that this happens last, after all other invalidations have been
// applied in order to comply with what 4.D.3 specifies about illegal orders. // applied in order to comply with what 4.D.3 specifies about illegal orders.
List<Unit> duplicateOrderedUnits = unitOrders List<string> duplicateOrderedUnits = unitOrders
.GroupBy(o => o.Unit) .GroupBy(o => o.Unit.Key)
.Where(orderGroup => orderGroup.Count() > 1) .Where(orderGroup => orderGroup.Count() > 1)
.Select(orderGroup => orderGroup.Key) .Select(orderGroup => orderGroup.Key)
.ToList(); .ToList();
List<UnitOrder> duplicateOrders = unitOrders List<UnitOrder> duplicateOrders = unitOrders
.Where(o => duplicateOrderedUnits.Contains(o.Unit)) .Where(o => duplicateOrderedUnits.Contains(o.Unit.Key))
.ToList(); .ToList();
List<UnitOrder> validOrders = unitOrders.Except(duplicateOrders).ToList(); List<UnitOrder> validOrders = unitOrders.Except(duplicateOrders).ToList();
validationResults = validationResults validationResults = validationResults
@ -259,9 +259,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
List<Unit> allOrderableUnits = world.Units List<Unit> allOrderableUnits = world.Units
.Where(unit => !world.Timelines.GetFutures(unit.Season).Any()) .Where(unit => !world.Timelines.GetFutures(unit.Season).Any())
.ToList(); .ToList();
HashSet<Unit> orderedUnits = validOrders.Select(order => order.Unit).ToHashSet(); HashSet<string> orderedUnits = validOrders.Select(order => order.Unit.Key).ToHashSet();
List<Unit> unorderedUnits = allOrderableUnits List<Unit> unorderedUnits = allOrderableUnits
.Where(unit => !orderedUnits.Contains(unit)) .Where(unit => !orderedUnits.Contains(unit.Key))
.ToList(); .ToList();
List<HoldOrder> implicitHolds = unorderedUnits List<HoldOrder> implicitHolds = unorderedUnits
.Select(unit => new HoldOrder(unit.Power, unit)) .Select(unit => new HoldOrder(unit.Power, unit))
@ -308,9 +308,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
Dictionary<MoveOrder, DoesMove> moves = decisions Dictionary<MoveOrder, DoesMove> moves = decisions
.OfType<DoesMove>() .OfType<DoesMove>()
.ToDictionary(dm => dm.Order); .ToDictionary(dm => dm.Order);
Dictionary<Unit, IsDislodged> dislodges = decisions Dictionary<string, IsDislodged> dislodges = decisions
.OfType<IsDislodged>() .OfType<IsDislodged>()
.ToDictionary(dm => dm.Order.Unit); .ToDictionary(dm => dm.Order.Unit.Key);
// All moves to a particular season in a single phase result in the same future. Keep a // 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. // record of when a future season has been created.
@ -395,7 +395,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
OrderHistory history = newHistory[unitOrder.Unit.Season.Key]; OrderHistory history = newHistory[unitOrder.Unit.Season.Key];
// TODO does this add every order to every season?? // TODO does this add every order to every season??
history.Orders.Add(unitOrder); history.Orders.Add(unitOrder);
history.IsDislodgedOutcomes[unitOrder.Unit.Key] = dislodges[unitOrder.Unit].Outcome == true; history.IsDislodgedOutcomes[unitOrder.Unit.Key] = dislodges[unitOrder.Unit.Key].Outcome == true;
if (unitOrder is MoveOrder moveOrder) if (unitOrder is MoveOrder moveOrder)
{ {
history.DoesMoveOutcomes[moveOrder.Unit.Key] = moves[moveOrder].Outcome == true; history.DoesMoveOutcomes[moveOrder.Unit.Key] = moves[moveOrder].Outcome == true;
@ -520,7 +520,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
foreach (UnitOrder order in decision.Orders) foreach (UnitOrder order in decision.Orders)
{ {
// TODO these aren't timeline-specific // TODO these aren't timeline-specific
IsDislodged dislodged = decisions.IsDislodged[order.Unit]; IsDislodged dislodged = decisions.IsDislodged[order.Unit.Key];
progress |= ResolveDecision(dislodged, world, decisions, depth + 1); progress |= ResolveDecision(dislodged, world, decisions, depth + 1);
if (history.IsDislodgedOutcomes.TryGetValue(order.Unit.Key, out bool previous) if (history.IsDislodgedOutcomes.TryGetValue(order.Unit.Key, out bool previous)
&& dislodged.Resolved && dislodged.Resolved
@ -694,7 +694,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
} }
// Support is also cut if the unit is dislodged. // Support is also cut if the unit is dislodged.
IsDislodged dislodge = decisions.IsDislodged[decision.Order.Unit]; IsDislodged dislodge = decisions.IsDislodged[decision.Order.Unit.Key];
progress |= ResolveDecision(dislodge, world, decisions, depth + 1); progress |= ResolveDecision(dislodge, world, decisions, depth + 1);
if (dislodge.Outcome == true) if (dislodge.Outcome == true)
{ {

View File

@ -12,19 +12,23 @@ public static class PathFinder
/// Determines if a convoy path exists for a move in a convoy order. /// Determines if a convoy path exists for a move in a convoy order.
/// </summary> /// </summary>
public static bool ConvoyPathExists(World world, ConvoyOrder order) public static bool ConvoyPathExists(World world, ConvoyOrder order)
=> ConvoyPathExists(world, order.Target, order.Location, order.Season); => ConvoyPathExists(world, world.Map.GetLocation(order.Target), order.Location, order.Season);
/// <summary> /// <summary>
/// Determines if a convoy path exists for a move order. /// Determines if a convoy path exists for a move order.
/// </summary> /// </summary>
public static bool ConvoyPathExists(World world, MoveOrder order) public static bool ConvoyPathExists(World world, MoveOrder order)
=> ConvoyPathExists(world, order.Unit, world.Map.GetLocation(order.Location), order.Season); => ConvoyPathExists(
world,
world.Map.GetLocation(order.Unit),
world.Map.GetLocation(order.Location),
order.Season);
private static bool ConvoyPathExists( private static bool ConvoyPathExists(
World world, World world,
Unit movingUnit,
Location unitLocation, Location unitLocation,
Season unitSeason) Location destLocation,
Season destSeason)
{ {
// A convoy path exists between two locations if both are land locations in provinces that // A convoy path exists between two locations if both are land locations in provinces that
// also have coasts, and between those coasts there is a path of adjacent sea provinces // also have coasts, and between those coasts there is a path of adjacent sea provinces
@ -35,14 +39,14 @@ public static class PathFinder
.ToDictionary(unit => (unit.Location, unit.Season)); .ToDictionary(unit => (unit.Location, unit.Season));
// Verify that the origin is a coastal province. // Verify that the origin is a coastal province.
if (world.Map.GetLocation(movingUnit).Type != LocationType.Land) return false; if (unitLocation.Type != LocationType.Land) return false;
IEnumerable<Location> originCoasts = world.Map.GetLocation(movingUnit).Province.Locations IEnumerable<Location> originCoasts = unitLocation.Province.Locations
.Where(location => location.Type == LocationType.Water); .Where(location => location.Type == LocationType.Water);
if (!originCoasts.Any()) return false; if (!originCoasts.Any()) return false;
// Verify that the destination is a coastal province. // Verify that the destination is a coastal province.
if (unitLocation.Type != LocationType.Land) return false; if (destLocation.Type != LocationType.Land) return false;
IEnumerable<Location> destCoasts = unitLocation.Province.Locations IEnumerable<Location> destCoasts = destLocation.Province.Locations
.Where(location => location.Type == LocationType.Water); .Where(location => location.Type == LocationType.Water);
if (!destCoasts.Any()) return false; if (!destCoasts.Any()) return false;
@ -50,7 +54,7 @@ public static class PathFinder
// locations added to the to-visit set, but the logic will still work with these as // locations added to the to-visit set, but the logic will still work with these as
// starting points. // starting points.
Queue<(Location location, Season season)> toVisit = new( Queue<(Location location, Season season)> toVisit = new(
originCoasts.Select(location => (location, unitSeason))); originCoasts.Select(location => (location, destSeason)));
HashSet<(Location, Season)> visited = new(); HashSet<(Location, Season)> visited = new();
// Begin pathfinding. // Begin pathfinding.
@ -64,7 +68,7 @@ public static class PathFinder
foreach ((Location adjLocation, Season adjSeason) in adjacents) foreach ((Location adjLocation, Season adjSeason) in adjacents)
{ {
// If the destination is adjacent, then a path exists. // If the destination is adjacent, then a path exists.
if (destCoasts.Contains(adjLocation) && unitSeason == adjSeason) return true; if (destCoasts.Contains(adjLocation) && destSeason == adjSeason) return true;
// If not, add this location to the to-visit set if it isn't a coast, has a fleet, // If not, add this location to the to-visit set if it isn't a coast, has a fleet,
// and hasn't already been visited. // and hasn't already been visited.

View File

@ -37,6 +37,6 @@ public class ConvoyOrder : UnitOrder
public override string ToString() public override string ToString()
{ {
return $"{this.Unit} C {this.Target} -> {(this.Province, this.Season).ToShort()}"; return $"{this.Unit} con {this.Target} -> {(this.Province, this.Season).ToShort()}";
} }
} }

View File

@ -14,6 +14,6 @@ public class SupportHoldOrder : SupportOrder
public override string ToString() public override string ToString()
{ {
return $"{this.Unit} S {this.Target}"; return $"{this.Unit} sup {this.Target}";
} }
} }

View File

@ -36,6 +36,6 @@ public class SupportMoveOrder : SupportOrder
public override string ToString() public override string ToString()
{ {
return $"{this.Unit} S {this.Target} -> {(this.Province, this.Season).ToShort()}"; return $"{this.Unit} sup {this.Target} -> {(this.Province, this.Season).ToShort()}";
} }
} }

View File

@ -101,7 +101,7 @@ public class TimeTravelTest
world.RetreatingUnits.Count, world.RetreatingUnits.Count,
Is.EqualTo(1), Is.EqualTo(1),
"Expected A Tyr a0 to be in retreat"); "Expected A Tyr a0 to be in retreat");
Assert.That(world.RetreatingUnits.First().Unit, Is.EqualTo(tyr0.Order.Unit)); Assert.That(world.RetreatingUnits.First().Unit.Key, Is.EqualTo(tyr0.Order.Unit.Key));
} }
[Test] [Test]

View File

@ -206,7 +206,7 @@ public class MovementAdjudicatorTest
// Confirm the unit was created in the future // Confirm the unit was created in the future
Unit u2 = updated.GetUnitAt("Mun", s2); Unit u2 = updated.GetUnitAt("Mun", s2);
Assert.That(updated.Units.Count, Is.EqualTo(2)); Assert.That(updated.Units.Count, Is.EqualTo(2));
Assert.That(u2, Is.Not.EqualTo(mun1.Order.Unit)); Assert.That(u2.Key, Is.Not.EqualTo(mun1.Order.Unit.Key));
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit.Key)); Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit.Key));
Assert.That(u2.Season, Is.EqualTo(s2)); Assert.That(u2.Season, Is.EqualTo(s2));
@ -256,7 +256,7 @@ public class MovementAdjudicatorTest
// Confirm the unit was created in the future // Confirm the unit was created in the future
Unit u2 = updated.GetUnitAt("Tyr", s2); Unit u2 = updated.GetUnitAt("Tyr", s2);
Assert.That(updated.Units.Count, Is.EqualTo(2)); Assert.That(updated.Units.Count, Is.EqualTo(2));
Assert.That(u2, Is.Not.EqualTo(mun1.Order.Unit)); Assert.That(u2.Key, Is.Not.EqualTo(mun1.Order.Unit.Key));
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit.Key)); Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit.Key));
Assert.That(u2.Season, Is.EqualTo(s2)); Assert.That(u2.Season, Is.EqualTo(s2));

View File

@ -58,7 +58,7 @@ public abstract class OrderReference
if (this.Order is UnitOrder unitOrder) if (this.Order is UnitOrder unitOrder)
{ {
var replacementOrder = this.Builder.ValidationResults.Where( var replacementOrder = this.Builder.ValidationResults.Where(
v => v.Order is UnitOrder uo && uo != unitOrder && uo.Unit == unitOrder.Unit); v => v.Order is UnitOrder uo && uo != unitOrder && uo.Unit.Key == unitOrder.Unit.Key);
if (replacementOrder.Any()) if (replacementOrder.Any())
{ {
return replacementOrder.Single(); return replacementOrder.Single();
@ -142,7 +142,7 @@ public abstract class OrderReference
if (this.Order is UnitOrder unitOrder) if (this.Order is UnitOrder unitOrder)
{ {
var retreat = this.Builder.World.RetreatingUnits.Where( var retreat = this.Builder.World.RetreatingUnits.Where(
ru => ru.Unit == unitOrder.Unit); ru => ru.Unit.Key == unitOrder.Unit.Key);
if (retreat.Any()) if (retreat.Any())
{ {
return retreat.Single(); return retreat.Single();

View File

@ -94,11 +94,8 @@ public class SerializationTest
Assert.That(setup.World.OrderHistory[s0.Key].DoesMoveOutcomes.Count, Is.GreaterThan(0), "Missing moves"); Assert.That(setup.World.OrderHistory[s0.Key].DoesMoveOutcomes.Count, Is.GreaterThan(0), "Missing moves");
Assert.That(setup.World.OrderHistory[s0.Key].IsDislodgedOutcomes.Count, Is.GreaterThan(0), "Missing dislodges"); Assert.That(setup.World.OrderHistory[s0.Key].IsDislodgedOutcomes.Count, Is.GreaterThan(0), "Missing dislodges");
// Assert.Ignore("Serialization doesn't fully work yet");
// Serialize and deserialize the world // Serialize and deserialize the world
string serialized = JsonSerializer.Serialize(setup.World, Options); string serialized = JsonSerializer.Serialize(setup.World, Options);
Console.WriteLine(serialized);
World reserialized = JsonSerializer.Deserialize<World>(serialized, Options) World reserialized = JsonSerializer.Deserialize<World>(serialized, Options)
?? throw new AssertionException("Failed to reserialize world"); ?? throw new AssertionException("Failed to reserialize world");
@ -120,16 +117,16 @@ public class SerializationTest
setup.ValidateOrders(); setup.ValidateOrders();
Assert.That(mun1, Is.Valid); Assert.That(mun1, Is.Valid);
var adjudications = setup.AdjudicateOrders(); var adjudications = setup.AdjudicateOrders();
foreach (var adj in adjudications)
{
Console.WriteLine($"{adj}");
}
Assert.That(mun1, Is.NotCut); Assert.That(mun1, Is.NotCut);
AttackStrength mun0attack = adjudications.OfType<AttackStrength>().Single();
Assert.That(mun0attack.Supports, Is.Not.Empty, "Support not tracked");
DoesMove mun0move = adjudications.OfType<DoesMove>().Single(move => move.Order.Unit.Key == mun0.Order.Unit.Key); DoesMove mun0move = adjudications.OfType<DoesMove>().Single(move => move.Order.Unit.Key == mun0.Order.Unit.Key);
Assert.That(mun0move.Outcome, Is.True); Assert.That(mun0move.Outcome, Is.True);
IsDislodged tyr0dislodge = adjudications.OfType<IsDislodged>().Single(dis => dis.Order.Unit.Key == tyr0.Order.Unit.Key); IsDislodged tyr0dislodge = adjudications.OfType<IsDislodged>().Single(dis => dis.Order.Unit.Key == tyr0.Order.Unit.Key);
Assert.That(tyr0dislodge, Is.True); Assert.That(tyr0dislodge.Outcome, Is.True);
// Confirm that an alternate future is created. // Confirm that an alternate future is created.
World world = setup.UpdateWorld(); World world = setup.UpdateWorld();
@ -143,6 +140,6 @@ public class SerializationTest
world.RetreatingUnits.Count, world.RetreatingUnits.Count,
Is.EqualTo(1), Is.EqualTo(1),
"Expected A Tyr a0 to be in retreat"); "Expected A Tyr a0 to be in retreat");
Assert.That(world.RetreatingUnits.First().Unit, Is.EqualTo(tyr0.Order.Unit)); Assert.That(world.RetreatingUnits.First().Unit.Key, Is.EqualTo(tyr0.Order.Unit.Key));
} }
} }