Eliminate sources of Unit reference equality
This commit is contained in:
parent
9a64609605
commit
eaafdeb5a9
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,6 @@ public class SupportHoldOrder : SupportOrder
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Unit} S {this.Target}";
|
||||
return $"{this.Unit} sup {this.Target}";
|
||||
}
|
||||
}
|
|
@ -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()}";
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue