Shift usage of Unit.Location to Unit.LocationId

This is in preparation for removing province and location references from Unit
This commit is contained in:
Tim Van Baak 2024-08-14 07:39:49 -07:00
parent 442015b942
commit abaa7f7a92
9 changed files with 52 additions and 41 deletions

View File

@ -77,7 +77,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// Trivial check: a unit cannot move to where it already is. // Trivial check: a unit cannot move to where it already is.
AdjudicatorHelpers.InvalidateIfNotMatching( AdjudicatorHelpers.InvalidateIfNotMatching(
order => !(order.Location == order.Unit.Location && order.Season == order.Unit.Season), order => !(order.Location.Designation == order.Unit.LocationId && order.Season == order.Unit.Season),
ValidationReason.DestinationMatchesOrigin, ValidationReason.DestinationMatchesOrigin,
ref moveOrders, ref moveOrders,
ref validationResults); ref validationResults);
@ -90,7 +90,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
ILookup<bool, MoveOrder> moveOrdersByAdjacency = moveOrders ILookup<bool, MoveOrder> moveOrdersByAdjacency = moveOrders
.ToLookup(order => .ToLookup(order =>
// Map adjacency // Map adjacency
order.Unit.Location.Adjacents.Contains(order.Location) world.Map.GetLocation(order.Unit).Adjacents.Contains(order.Location)
// Turn adjacency // Turn adjacency
&& Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1 && Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1
// Timeline adjacency // Timeline adjacency
@ -138,7 +138,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// Trivial check: cannot convoy a unit to its own location // Trivial check: cannot convoy a unit to its own location
AdjudicatorHelpers.InvalidateIfNotMatching( AdjudicatorHelpers.InvalidateIfNotMatching(
order => !( order => !(
order.Location == order.Target.Location order.Location.Designation == order.Target.LocationId
&& order.Season == order.Target.Season), && order.Season == order.Target.Season),
ValidationReason.DestinationMatchesOrigin, ValidationReason.DestinationMatchesOrigin,
ref convoyOrders, ref convoyOrders,
@ -175,8 +175,8 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
AdjudicatorHelpers.InvalidateIfNotMatching( AdjudicatorHelpers.InvalidateIfNotMatching(
order => order =>
// Map adjacency with respect to province // Map adjacency with respect to province
order.Unit.Location.Adjacents.Any( world.Map.GetLocation(order.Unit).Adjacents.Any(
adjLocation => adjLocation.Province == order.Target.Province) adjLocation => adjLocation.Province == world.Map.GetLocation(order.Target).Province)
// Turn adjacency // Turn adjacency
&& Math.Abs(order.Unit.Season.Turn - order.Target.Season.Turn) <= 1 && Math.Abs(order.Unit.Season.Turn - order.Target.Season.Turn) <= 1
// Timeline adjacency // Timeline adjacency
@ -195,7 +195,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// Support-move orders are invalid if the unit supports a move to any location in its own // Support-move orders are invalid if the unit supports a move to any location in its own
// province. // province.
AdjudicatorHelpers.InvalidateIfNotMatching( AdjudicatorHelpers.InvalidateIfNotMatching(
order => order.Unit.Province != order.Province, order => world.Map.GetLocation(order.Unit).Province != order.Province,
ValidationReason.NoSupportMoveAgainstSelf, ValidationReason.NoSupportMoveAgainstSelf,
ref supportMoveOrders, ref supportMoveOrders,
ref validationResults); ref validationResults);
@ -207,7 +207,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
AdjudicatorHelpers.InvalidateIfNotMatching( AdjudicatorHelpers.InvalidateIfNotMatching(
order => order =>
// Map adjacency with respect to province // Map adjacency with respect to province
order.Unit.Location.Adjacents.Any( world.Map.GetLocation(order.Unit).Adjacents.Any(
adjLocation => adjLocation.Province == order.Province) adjLocation => adjLocation.Province == order.Province)
// Turn adjacency // Turn adjacency
&& Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1 && Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1
@ -366,7 +366,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
if (isDislodged.Outcome == false) if (isDislodged.Outcome == false)
{ {
// Non-dislodged units continue into the future. // Non-dislodged units continue into the future.
Unit next = order.Unit.Next(order.Unit.Location, future); Unit next = order.Unit.Next(world.Map.GetLocation(order.Unit), future);
logger.Log(3, "Advancing unit to {0}", next); logger.Log(3, "Advancing unit to {0}", next);
createdUnits.Add(next); createdUnits.Add(next);
} }
@ -375,7 +375,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// Create a retreat for each dislodged unit. // Create a retreat for each dislodged unit.
// TODO check valid retreats and disbands // TODO check valid retreats and disbands
logger.Log(3, "Creating retreat for {0}", order.Unit); logger.Log(3, "Creating retreat for {0}", order.Unit);
var validRetreats = order.Unit.Location.Adjacents var validRetreats = world.Map.GetLocation(order.Unit).Adjacents
.Select(loc => (future, loc)) .Select(loc => (future, loc))
.ToList(); .ToList();
RetreatingUnit retreat = new(order.Unit, validRetreats); RetreatingUnit retreat = new(order.Unit, validRetreats);
@ -633,7 +633,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// If the origin and destination are adjacent, then there is a path. // If the origin and destination are adjacent, then there is a path.
if (// Map adjacency if (// Map adjacency
decision.Order.Unit.Location.Adjacents.Contains(decision.Order.Location) world.Map.GetLocation(decision.Order.Unit).Adjacents.Contains(decision.Order.Location)
// Turn adjacency // Turn adjacency
&& Math.Abs(decision.Order.Unit.Season.Turn - decision.Order.Season.Turn) <= 1 && Math.Abs(decision.Order.Unit.Season.Turn - decision.Order.Season.Turn) <= 1
// Timeline adjacency // Timeline adjacency

View File

@ -30,13 +30,13 @@ public static class PathFinder
// 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
// (not coastal) that are occupied by fleets. The move order is valid even if the fleets // (not coastal) that are occupied by fleets. The move order is valid even if the fleets
// belong to another power or were not given convoy orders; it will simply fail. // belong to another power or were not given convoy orders; it will simply fail.
IDictionary<(Location location, Season season), Unit> fleets = world.Units IDictionary<(string location, Season season), Unit> fleets = world.Units
.Where(unit => unit.Type == UnitType.Fleet) .Where(unit => unit.Type == UnitType.Fleet)
.ToDictionary(unit => (unit.Location, unit.Season)); .ToDictionary(unit => (unit.LocationId, unit.Season));
// Verify that the origin is a coastal province. // Verify that the origin is a coastal province.
if (movingUnit.Location.Type != LocationType.Land) return false; if (world.Map.GetLocation(movingUnit).Type != LocationType.Land) return false;
IEnumerable<Location> originCoasts = movingUnit.Province.Locations IEnumerable<Location> originCoasts = world.Map.GetLocation(movingUnit).Province.Locations
.Where(location => location.Type == LocationType.Water); .Where(location => location.Type == LocationType.Water);
if (!originCoasts.Any()) return false; if (!originCoasts.Any()) return false;
@ -69,7 +69,7 @@ public static class PathFinder
// 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.
if (!adjLocation.Province.Locations.Any(l => l.Type == LocationType.Land) if (!adjLocation.Province.Locations.Any(l => l.Type == LocationType.Land)
&& fleets.ContainsKey((adjLocation, adjSeason)) && fleets.ContainsKey((adjLocation.Designation, adjSeason))
&& !visited.Contains((adjLocation, adjSeason))) && !visited.Contains((adjLocation, adjSeason)))
{ {
toVisit.Enqueue((adjLocation, adjSeason)); toVisit.Enqueue((adjLocation, adjSeason));

View File

@ -39,6 +39,11 @@ public class Location
public IEnumerable<Location> Adjacents => this.AdjacentList; public IEnumerable<Location> Adjacents => this.AdjacentList;
private List<Location> AdjacentList { get; set; } private List<Location> AdjacentList { get; set; }
/// <summary>
/// The unique name of this location in the map.
/// </summary>
public string Designation => $"{this.ProvinceName}/{this.Abbreviation}";
public Location(Province province, string name, string abbreviation, LocationType type) public Location(Province province, string name, string abbreviation, LocationType type)
{ {
this.Province = province; this.Province = province;

View File

@ -17,6 +17,8 @@ public class Map
private List<Province> _Provinces { get; } private List<Province> _Provinces { get; }
private Dictionary<string, Location> LocationLookup { get; }
/// <summary> /// <summary>
/// The game powers. /// The game powers.
/// </summary> /// </summary>
@ -29,6 +31,10 @@ public class Map
Type = type; Type = type;
_Provinces = provinces.ToList(); _Provinces = provinces.ToList();
_Powers = powers.ToList(); _Powers = powers.ToList();
LocationLookup = Provinces
.SelectMany(province => province.Locations)
.ToDictionary(location => location.Designation);
} }
/// <summary> /// <summary>
@ -41,29 +47,27 @@ public class Map
/// Get a province by name. Throws if the province is not found. /// Get a province by name. Throws if the province is not found.
/// </summary> /// </summary>
private static Province GetProvince(string provinceName, IEnumerable<Province> provinces) private static Province GetProvince(string provinceName, IEnumerable<Province> provinces)
{ => provinces.SingleOrDefault(
string provinceNameUpper = provinceName.ToUpperInvariant(); p => p!.Name.Equals(provinceName, StringComparison.InvariantCultureIgnoreCase)
Province? foundProvince = provinces.SingleOrDefault( || p.Abbreviations.Any(
p => p!.Name.ToUpperInvariant() == provinceNameUpper a => a.Equals(provinceName, StringComparison.InvariantCultureIgnoreCase)),
|| p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper), null)
null); ?? throw new KeyNotFoundException($"Province {provinceName} not found");
if (foundProvince == null) throw new KeyNotFoundException(
$"Province {provinceName} not found");
return foundProvince;
}
/// <summary> /// <summary>
/// Get the location in a province matching a predicate. Throws if there is not exactly one /// Get the location in a province matching a predicate. Throws if there is not exactly one
/// such location. /// such location.
/// </summary> /// </summary>
private Location GetLocation(string provinceName, Func<Location, bool> predicate) private Location GetLocation(string provinceName, Func<Location, bool> predicate)
{ => GetProvince(provinceName).Locations.SingleOrDefault(
Location? foundLocation = GetProvince(provinceName).Locations.SingleOrDefault( l => l != null && predicate(l), null)
l => l != null && predicate(l), null); ?? throw new KeyNotFoundException($"No such location in {provinceName}");
if (foundLocation == null) throw new KeyNotFoundException(
$"No such location in {provinceName}"); public Location GetLocation(string designation)
return foundLocation; => LocationLookup[designation];
}
public Location GetLocation(Unit unit)
=> GetLocation(unit.LocationId);
/// <summary> /// <summary>
/// Get the sole land location of a province. /// Get the sole land location of a province.

View File

@ -17,6 +17,8 @@ public class Unit
/// </summary> /// </summary>
public Location Location { get; } public Location Location { get; }
public string LocationId => Location.Designation;
/// <summary> /// <summary>
/// The province where the unit is. /// The province where the unit is.
/// </summary> /// </summary>

View File

@ -328,7 +328,7 @@ public class World
Province province = Map.GetProvince(provinceName); Province province = Map.GetProvince(provinceName);
season ??= RootSeason; season ??= RootSeason;
Unit? foundUnit = this.Units.SingleOrDefault( Unit? foundUnit = this.Units.SingleOrDefault(
u => u!.Province == province && u.Season == season, u => Map.GetLocation(u!).Province == province && u!.Season == season,
null) null)
?? throw new KeyNotFoundException($"Unit at {province} at {season} not found"); ?? throw new KeyNotFoundException($"Unit at {province} at {season} not found");
return foundUnit; return foundUnit;

View File

@ -179,7 +179,7 @@ public class MovementAdjudicatorTest
// Confirm the unit was created // Confirm the unit was created
Assert.That(updated.Units.Count, Is.EqualTo(2)); Assert.That(updated.Units.Count, Is.EqualTo(2));
Unit second = updated.Units.Single(u => u.Past != null); Unit second = updated.Units.Single(u => u.Past != null);
Assert.That(second.Location, Is.EqualTo(mun.Order.Unit.Location)); Assert.That(second.LocationId, Is.EqualTo(mun.Order.Unit.LocationId));
} }
[Test] [Test]

View File

@ -39,8 +39,8 @@ class TestCaseBuilderTest
Assert.That(fleetSTP.Power.Name, Is.EqualTo("Russia"), "Unit created with wrong power"); Assert.That(fleetSTP.Power.Name, Is.EqualTo("Russia"), "Unit created with wrong power");
Assert.That(fleetSTP.Type, Is.EqualTo(UnitType.Fleet), "Unit created with wrong type"); Assert.That(fleetSTP.Type, Is.EqualTo(UnitType.Fleet), "Unit created with wrong type");
Assert.That( Assert.That(
fleetSTP.Location, fleetSTP.LocationId,
Is.EqualTo(setup.World.Map.GetWater("STP", "wc")), Is.EqualTo(setup.World.Map.GetWater("STP", "wc").Designation),
"Unit created on wrong coast"); "Unit created on wrong coast");
} }
@ -127,8 +127,8 @@ class TestCaseBuilderTest
Is.EqualTo(setup.World.Map.GetPower("Germany")), Is.EqualTo(setup.World.Map.GetPower("Germany")),
"Wrong power"); "Wrong power");
Assert.That( Assert.That(
orderMun.Order.Unit.Location, orderMun.Order.Unit.LocationId,
Is.EqualTo(setup.World.Map.GetLand("Mun")), Is.EqualTo(setup.World.Map.GetLand("Mun").Designation),
"Wrong unit"); "Wrong unit");
Assert.That( Assert.That(

View File

@ -31,8 +31,8 @@ public class UnitTests
Assert.That(u2.Season, Is.EqualTo(a1), "Unexpected unit season"); Assert.That(u2.Season, Is.EqualTo(a1), "Unexpected unit season");
Assert.That(u3.Season, Is.EqualTo(a2), "Unexpected unit season"); Assert.That(u3.Season, Is.EqualTo(a2), "Unexpected unit season");
Assert.That(u1.Location, Is.EqualTo(Mun), "Unexpected unit location"); Assert.That(u1.LocationId, Is.EqualTo(Mun.Designation), "Unexpected unit location");
Assert.That(u2.Location, Is.EqualTo(Boh), "Unexpected unit location"); Assert.That(u2.LocationId, Is.EqualTo(Boh.Designation), "Unexpected unit location");
Assert.That(u3.Location, Is.EqualTo(Tyr), "Unexpected unit location"); Assert.That(u3.LocationId, Is.EqualTo(Tyr.Designation), "Unexpected unit location");
} }
} }