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 Dictionary<Unit, IsDislodged> IsDislodged { get; }
public Dictionary<string, IsDislodged> IsDislodged { get; }
public Dictionary<MoveOrder, HasPath> HasPath { get; }
public Dictionary<SupportOrder, GivesSupport> GivesSupport { 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;
bool IsSupportFor(SupportMoveOrder me, MoveOrder move)
=> me.Target == move.Unit
=> me.Target.Key == move.Unit.Key
&& me.Season == move.Season
&& me.Location.Key == move.Location;
@ -136,7 +136,7 @@ public class MovementDecisions
.OfType<MoveOrder>()
.Where(other => IsIncoming(order, other))
.ToList();
IsDislodged[order.Unit] = new(order, incoming);
IsDislodged[order.Unit.Key] = new(order, incoming);
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.
AdjudicatorHelpers.InvalidateIfNotMatching(
order => order.Unit != order.Target,
order => order.Unit.Key != order.Target.Key,
ValidationReason.NoSelfSupport,
ref supportHoldOrders,
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
// 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.
List<Unit> duplicateOrderedUnits = unitOrders
.GroupBy(o => o.Unit)
List<string> duplicateOrderedUnits = unitOrders
.GroupBy(o => o.Unit.Key)
.Where(orderGroup => orderGroup.Count() > 1)
.Select(orderGroup => orderGroup.Key)
.ToList();
List<UnitOrder> duplicateOrders = unitOrders
.Where(o => duplicateOrderedUnits.Contains(o.Unit))
.Where(o => duplicateOrderedUnits.Contains(o.Unit.Key))
.ToList();
List<UnitOrder> validOrders = unitOrders.Except(duplicateOrders).ToList();
validationResults = validationResults
@ -259,9 +259,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
List<Unit> allOrderableUnits = world.Units
.Where(unit => !world.Timelines.GetFutures(unit.Season).Any())
.ToList();
HashSet<Unit> orderedUnits = validOrders.Select(order => order.Unit).ToHashSet();
HashSet<string> orderedUnits = validOrders.Select(order => order.Unit.Key).ToHashSet();
List<Unit> unorderedUnits = allOrderableUnits
.Where(unit => !orderedUnits.Contains(unit))
.Where(unit => !orderedUnits.Contains(unit.Key))
.ToList();
List<HoldOrder> implicitHolds = unorderedUnits
.Select(unit => new HoldOrder(unit.Power, unit))
@ -308,9 +308,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
Dictionary<MoveOrder, DoesMove> moves = decisions
.OfType<DoesMove>()
.ToDictionary(dm => dm.Order);
Dictionary<Unit, IsDislodged> dislodges = decisions
Dictionary<string, IsDislodged> dislodges = decisions
.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
// record of when a future season has been created.
@ -395,7 +395,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
OrderHistory history = newHistory[unitOrder.Unit.Season.Key];
// TODO does this add every order to every season??
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)
{
history.DoesMoveOutcomes[moveOrder.Unit.Key] = moves[moveOrder].Outcome == true;
@ -520,7 +520,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
foreach (UnitOrder order in decision.Orders)
{
// 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);
if (history.IsDislodgedOutcomes.TryGetValue(order.Unit.Key, out bool previous)
&& dislodged.Resolved
@ -694,7 +694,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
}
// 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);
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.
/// </summary>
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>
/// Determines if a convoy path exists for a move order.
/// </summary>
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(
World world,
Unit movingUnit,
Location unitLocation,
Season unitSeason)
Location destLocation,
Season destSeason)
{
// 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
@ -35,14 +39,14 @@ public static class PathFinder
.ToDictionary(unit => (unit.Location, unit.Season));
// Verify that the origin is a coastal province.
if (world.Map.GetLocation(movingUnit).Type != LocationType.Land) return false;
IEnumerable<Location> originCoasts = world.Map.GetLocation(movingUnit).Province.Locations
if (unitLocation.Type != LocationType.Land) return false;
IEnumerable<Location> originCoasts = unitLocation.Province.Locations
.Where(location => location.Type == LocationType.Water);
if (!originCoasts.Any()) return false;
// Verify that the destination is a coastal province.
if (unitLocation.Type != LocationType.Land) return false;
IEnumerable<Location> destCoasts = unitLocation.Province.Locations
if (destLocation.Type != LocationType.Land) return false;
IEnumerable<Location> destCoasts = destLocation.Province.Locations
.Where(location => location.Type == LocationType.Water);
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
// starting points.
Queue<(Location location, Season season)> toVisit = new(
originCoasts.Select(location => (location, unitSeason)));
originCoasts.Select(location => (location, destSeason)));
HashSet<(Location, Season)> visited = new();
// Begin pathfinding.
@ -64,7 +68,7 @@ public static class PathFinder
foreach ((Location adjLocation, Season adjSeason) in adjacents)
{
// 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,
// and hasn't already been visited.

View File

@ -37,6 +37,6 @@ public class ConvoyOrder : UnitOrder
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()
{
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()
{
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,
Is.EqualTo(1),
"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]

View File

@ -206,7 +206,7 @@ public class MovementAdjudicatorTest
// Confirm the unit was created in the future
Unit u2 = updated.GetUnitAt("Mun", s2);
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.Season, Is.EqualTo(s2));
@ -256,7 +256,7 @@ public class MovementAdjudicatorTest
// Confirm the unit was created in the future
Unit u2 = updated.GetUnitAt("Tyr", s2);
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.Season, Is.EqualTo(s2));

View File

@ -58,7 +58,7 @@ public abstract class OrderReference
if (this.Order is UnitOrder unitOrder)
{
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())
{
return replacementOrder.Single();
@ -142,7 +142,7 @@ public abstract class OrderReference
if (this.Order is UnitOrder unitOrder)
{
var retreat = this.Builder.World.RetreatingUnits.Where(
ru => ru.Unit == unitOrder.Unit);
ru => ru.Unit.Key == unitOrder.Unit.Key);
if (retreat.Any())
{
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].IsDislodgedOutcomes.Count, Is.GreaterThan(0), "Missing dislodges");
// Assert.Ignore("Serialization doesn't fully work yet");
// Serialize and deserialize the world
string serialized = JsonSerializer.Serialize(setup.World, Options);
Console.WriteLine(serialized);
World reserialized = JsonSerializer.Deserialize<World>(serialized, Options)
?? throw new AssertionException("Failed to reserialize world");
@ -120,16 +117,16 @@ public class SerializationTest
setup.ValidateOrders();
Assert.That(mun1, Is.Valid);
var adjudications = setup.AdjudicateOrders();
foreach (var adj in adjudications)
{
Console.WriteLine($"{adj}");
}
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);
Assert.That(mun0move.Outcome, Is.True);
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.
World world = setup.UpdateWorld();
@ -143,6 +140,6 @@ public class SerializationTest
world.RetreatingUnits.Count,
Is.EqualTo(1),
"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));
}
}