Compare commits
No commits in common. "7749d8df4e0e5189aa36ad808f9ed395830cc67d" and "b2ff8896b2ff16071afe724f19d0b32a5ddd7964" have entirely different histories.
7749d8df4e
...
b2ff8896b2
|
@ -91,33 +91,19 @@ public class MovementDecisions
|
|||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
(Province province, Season season) Point(Unit unit)
|
||||
=> (world.Map.GetLocation(unit.Location).Province, unit.Season);
|
||||
|
||||
// Create a hold strength decision with an associated order for every province with a unit.
|
||||
foreach (UnitOrder order in relevantOrders)
|
||||
{
|
||||
HoldStrength[Point(order.Unit)] = new(Point(order.Unit), order);
|
||||
HoldStrength[order.Unit.Point] = new(order.Unit.Point, order);
|
||||
}
|
||||
|
||||
bool IsIncoming(UnitOrder me, MoveOrder other)
|
||||
=> me != other
|
||||
&& other.Season == me.Unit.Season
|
||||
&& other.Province == world.Map.GetLocation(me.Unit).Province;
|
||||
|
||||
bool AreOpposing(MoveOrder one, MoveOrder two)
|
||||
=> one.Season == two.Unit.Season
|
||||
&& two.Season == one.Unit.Season
|
||||
&& one.Province == world.Map.GetLocation(two.Unit).Province
|
||||
&& two.Province == world.Map.GetLocation(one.Unit).Province;
|
||||
|
||||
// Create all other relevant decisions for each order in the affected timelines.
|
||||
foreach (UnitOrder order in relevantOrders)
|
||||
{
|
||||
// Create a dislodge decision for this unit.
|
||||
List<MoveOrder> incoming = relevantOrders
|
||||
.OfType<MoveOrder>()
|
||||
.Where(other => IsIncoming(order, other))
|
||||
.Where(order.IsIncoming)
|
||||
.ToList();
|
||||
IsDislodged[order.Unit] = new(order, incoming);
|
||||
|
||||
|
@ -132,7 +118,7 @@ public class MovementDecisions
|
|||
// Determine if this move is a head-to-head battle.
|
||||
MoveOrder? opposingMove = relevantOrders
|
||||
.OfType<MoveOrder>()
|
||||
.FirstOrDefault(other => AreOpposing(move, other!), null);
|
||||
.FirstOrDefault(other => other!.IsOpposing(move), null);
|
||||
|
||||
// Find competing moves.
|
||||
List<MoveOrder> competing = relevantOrders
|
||||
|
@ -156,11 +142,11 @@ public class MovementDecisions
|
|||
GivesSupport[support] = new(support, incoming);
|
||||
|
||||
// Ensure a hold strength decision exists for the target's province.
|
||||
HoldStrength.Ensure(Point(support.Target), () => new(Point(support.Target)));
|
||||
HoldStrength.Ensure(support.Target.Point, () => new(support.Target.Point));
|
||||
|
||||
if (support is SupportHoldOrder supportHold)
|
||||
{
|
||||
HoldStrength[Point(support.Target)].Supports.Add(supportHold);
|
||||
HoldStrength[support.Target.Point].Supports.Add(supportHold);
|
||||
}
|
||||
else if (support is SupportMoveOrder supportMove)
|
||||
{
|
||||
|
|
|
@ -77,7 +77,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
|
||||
// Trivial check: a unit cannot move to where it already is.
|
||||
AdjudicatorHelpers.InvalidateIfNotMatching(
|
||||
order => !(order.Location.Designation == 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 +90,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
ILookup<bool, MoveOrder> moveOrdersByAdjacency = moveOrders
|
||||
.ToLookup(order =>
|
||||
// Map adjacency
|
||||
world.Map.GetLocation(order.Unit).Adjacents.Contains(order.Location)
|
||||
order.Unit.Location.Adjacents.Contains(order.Location)
|
||||
// Turn adjacency
|
||||
&& Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1
|
||||
// Timeline adjacency
|
||||
|
@ -138,7 +138,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
// Trivial check: cannot convoy a unit to its own location
|
||||
AdjudicatorHelpers.InvalidateIfNotMatching(
|
||||
order => !(
|
||||
order.Location.Designation == order.Target.Location
|
||||
order.Location == order.Target.Location
|
||||
&& order.Season == order.Target.Season),
|
||||
ValidationReason.DestinationMatchesOrigin,
|
||||
ref convoyOrders,
|
||||
|
@ -175,8 +175,8 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
AdjudicatorHelpers.InvalidateIfNotMatching(
|
||||
order =>
|
||||
// Map adjacency with respect to province
|
||||
world.Map.GetLocation(order.Unit).Adjacents.Any(
|
||||
adjLocation => adjLocation.Province == world.Map.GetLocation(order.Target).Province)
|
||||
order.Unit.Location.Adjacents.Any(
|
||||
adjLocation => adjLocation.Province == order.Target.Province)
|
||||
// Turn adjacency
|
||||
&& Math.Abs(order.Unit.Season.Turn - order.Target.Season.Turn) <= 1
|
||||
// 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
|
||||
// province.
|
||||
AdjudicatorHelpers.InvalidateIfNotMatching(
|
||||
order => world.Map.GetLocation(order.Unit).Province != order.Province,
|
||||
order => order.Unit.Province != order.Province,
|
||||
ValidationReason.NoSupportMoveAgainstSelf,
|
||||
ref supportMoveOrders,
|
||||
ref validationResults);
|
||||
|
@ -207,7 +207,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
AdjudicatorHelpers.InvalidateIfNotMatching(
|
||||
order =>
|
||||
// Map adjacency with respect to province
|
||||
world.Map.GetLocation(order.Unit).Adjacents.Any(
|
||||
order.Unit.Location.Adjacents.Any(
|
||||
adjLocation => adjLocation.Province == order.Province)
|
||||
// Turn adjacency
|
||||
&& Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1
|
||||
|
@ -337,7 +337,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
Season moveSeason = doesMove.Order.Season;
|
||||
if (doesMove.Outcome == true && createdFutures.ContainsKey(moveSeason))
|
||||
{
|
||||
Unit next = doesMove.Order.Unit.Next(doesMove.Order.Location.Designation, createdFutures[moveSeason]);
|
||||
Unit next = doesMove.Order.Unit.Next(doesMove.Order.Location, createdFutures[moveSeason]);
|
||||
logger.Log(3, "Advancing unit to {0}", next);
|
||||
createdUnits.Add(next);
|
||||
}
|
||||
|
@ -366,7 +366,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
if (isDislodged.Outcome == false)
|
||||
{
|
||||
// Non-dislodged units continue into the future.
|
||||
Unit next = order.Unit.Next(world.Map.GetLocation(order.Unit).Designation, future);
|
||||
Unit next = order.Unit.Next(order.Unit.Location, future);
|
||||
logger.Log(3, "Advancing unit to {0}", next);
|
||||
createdUnits.Add(next);
|
||||
}
|
||||
|
@ -375,7 +375,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
// Create a retreat for each dislodged unit.
|
||||
// TODO check valid retreats and disbands
|
||||
logger.Log(3, "Creating retreat for {0}", order.Unit);
|
||||
var validRetreats = world.Map.GetLocation(order.Unit).Adjacents
|
||||
var validRetreats = order.Unit.Location.Adjacents
|
||||
.Select(loc => (future, loc))
|
||||
.ToList();
|
||||
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 (// Map adjacency
|
||||
world.Map.GetLocation(decision.Order.Unit).Adjacents.Contains(decision.Order.Location)
|
||||
decision.Order.Unit.Location.Adjacents.Contains(decision.Order.Location)
|
||||
// Turn adjacency
|
||||
&& Math.Abs(decision.Order.Unit.Season.Turn - decision.Order.Season.Turn) <= 1
|
||||
// Timeline adjacency
|
||||
|
|
|
@ -30,13 +30,13 @@ public static class PathFinder
|
|||
// 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
|
||||
// belong to another power or were not given convoy orders; it will simply fail.
|
||||
IDictionary<(string location, Season season), Unit> fleets = world.Units
|
||||
IDictionary<(Location location, Season season), Unit> fleets = world.Units
|
||||
.Where(unit => unit.Type == UnitType.Fleet)
|
||||
.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 (movingUnit.Location.Type != LocationType.Land) return false;
|
||||
IEnumerable<Location> originCoasts = movingUnit.Province.Locations
|
||||
.Where(location => location.Type == LocationType.Water);
|
||||
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,
|
||||
// and hasn't already been visited.
|
||||
if (!adjLocation.Province.Locations.Any(l => l.Type == LocationType.Land)
|
||||
&& fleets.ContainsKey((adjLocation.Designation, adjSeason))
|
||||
&& fleets.ContainsKey((adjLocation, adjSeason))
|
||||
&& !visited.Contains((adjLocation, adjSeason)))
|
||||
{
|
||||
toVisit.Enqueue((adjLocation, adjSeason));
|
||||
|
|
|
@ -20,12 +20,12 @@ public class Location
|
|||
/// <summary>
|
||||
/// The location's full human-readable name.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
public string? Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The location's shorthand abbreviation.
|
||||
/// </summary>
|
||||
public string Abbreviation { get; }
|
||||
public string? Abbreviation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The location's type.
|
||||
|
@ -39,12 +39,7 @@ public class Location
|
|||
public IEnumerable<Location> Adjacents => this.AdjacentList;
|
||||
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.Name = name;
|
||||
|
@ -55,7 +50,7 @@ public class Location
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.Name == "land" || this.Name == "water"
|
||||
return this.Name == null
|
||||
? $"{this.Province.Name} ({this.Type})"
|
||||
: $"{this.Province.Name} ({this.Type}:{this.Name}]";
|
||||
}
|
||||
|
|
|
@ -17,8 +17,6 @@ public class Map
|
|||
|
||||
private List<Province> _Provinces { get; }
|
||||
|
||||
private Dictionary<string, Location> LocationLookup { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The game powers.
|
||||
/// </summary>
|
||||
|
@ -31,10 +29,6 @@ public class Map
|
|||
Type = type;
|
||||
_Provinces = provinces.ToList();
|
||||
_Powers = powers.ToList();
|
||||
|
||||
LocationLookup = Provinces
|
||||
.SelectMany(province => province.Locations)
|
||||
.ToDictionary(location => location.Designation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -47,27 +41,29 @@ public class Map
|
|||
/// Get a province by name. Throws if the province is not found.
|
||||
/// </summary>
|
||||
private static Province GetProvince(string provinceName, IEnumerable<Province> provinces)
|
||||
=> provinces.SingleOrDefault(
|
||||
p => p!.Name.Equals(provinceName, StringComparison.InvariantCultureIgnoreCase)
|
||||
|| p.Abbreviations.Any(
|
||||
a => a.Equals(provinceName, StringComparison.InvariantCultureIgnoreCase)),
|
||||
null)
|
||||
?? throw new KeyNotFoundException($"Province {provinceName} not found");
|
||||
{
|
||||
string provinceNameUpper = provinceName.ToUpperInvariant();
|
||||
Province? foundProvince = provinces.SingleOrDefault(
|
||||
p => p!.Name.ToUpperInvariant() == provinceNameUpper
|
||||
|| p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper),
|
||||
null);
|
||||
if (foundProvince == null) throw new KeyNotFoundException(
|
||||
$"Province {provinceName} not found");
|
||||
return foundProvince;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the location in a province matching a predicate. Throws if there is not exactly one
|
||||
/// such location.
|
||||
/// </summary>
|
||||
private Location GetLocation(string provinceName, Func<Location, bool> predicate)
|
||||
=> GetProvince(provinceName).Locations.SingleOrDefault(
|
||||
l => l != null && predicate(l), null)
|
||||
?? throw new KeyNotFoundException($"No such location in {provinceName}");
|
||||
|
||||
public Location GetLocation(string designation)
|
||||
=> LocationLookup[designation];
|
||||
|
||||
public Location GetLocation(Unit unit)
|
||||
=> GetLocation(unit.Location);
|
||||
{
|
||||
Location? foundLocation = GetProvince(provinceName).Locations.SingleOrDefault(
|
||||
l => l != null && predicate(l), null);
|
||||
if (foundLocation == null) throw new KeyNotFoundException(
|
||||
$"No such location in {provinceName}");
|
||||
return foundLocation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the sole land location of a province.
|
||||
|
@ -125,10 +121,10 @@ public class Map
|
|||
#region Provinces
|
||||
Province.Empty("North Africa", "NAF")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Tunis", "TUN")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Bohemia", "BOH")
|
||||
.AddLandLocation(),
|
||||
Province.Supply("Budapest", "BUD")
|
||||
|
@ -137,71 +133,71 @@ public class Map
|
|||
.AddLandLocation(),
|
||||
Province.Supply("Trieste", "TRI")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Tyrolia", "TYR")
|
||||
.AddLandLocation(),
|
||||
Province.Time("Vienna", "VIE")
|
||||
.AddLandLocation(),
|
||||
Province.Empty("Albania", "ALB")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Bulgaria", "BUL")
|
||||
.AddLandLocation()
|
||||
.AddCoastLocation("east coast", "ec")
|
||||
.AddCoastLocation("south coast", "sc"),
|
||||
Province.Supply("Greece", "GRE")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Rumania", "RUM", "RMA")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Serbia", "SER")
|
||||
.AddLandLocation(),
|
||||
Province.Empty("Clyde", "CLY")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Edinburgh", "EDI")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Liverpool", "LVP", "LPL")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Time("London", "LON")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Wales", "WAL")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Yorkshire", "YOR")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Brest", "BRE")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Burgundy", "BUR")
|
||||
.AddLandLocation(),
|
||||
Province.Empty("Gascony", "GAS")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Marseilles", "MAR")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Time("Paris", "PAR")
|
||||
.AddLandLocation(),
|
||||
Province.Empty("Picardy", "PIC")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Time("Berlin", "BER")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Kiel", "KIE")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Munich", "MUN")
|
||||
.AddLandLocation(),
|
||||
Province.Empty("Prussia", "PRU")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Ruhr", "RUH", "RHR")
|
||||
.AddLandLocation(),
|
||||
Province.Empty("Silesia", "SIL")
|
||||
|
@ -212,43 +208,43 @@ public class Map
|
|||
.AddCoastLocation("south coast", "sc"),
|
||||
Province.Supply("Portugal", "POR")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Apulia", "APU")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Naples", "NAP")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Piedmont", "PIE")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Time("Rome", "ROM", "RME")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Tuscany", "TUS")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Venice", "VEN")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Belgium", "BEL")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Holland", "HOL")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Finland", "FIN")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Livonia", "LVN", "LVA")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Time("Moscow", "MOS")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Sevastopol", "SEV")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Saint Petersburg", "STP")
|
||||
.AddLandLocation()
|
||||
.AddCoastLocation("north coast", "nc")
|
||||
|
@ -259,28 +255,28 @@ public class Map
|
|||
.AddLandLocation(),
|
||||
Province.Supply("Denmark", "DEN")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Norway", "NWY")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Sweden", "SWE")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Ankara", "ANK")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Armenia", "ARM")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Time("Constantinople", "CON")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Supply("Smyrna", "SMY")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Syria", "SYR")
|
||||
.AddLandLocation()
|
||||
.AddOceanLocation(),
|
||||
.AddCoastLocation(),
|
||||
Province.Empty("Barents Sea", "BAR")
|
||||
.AddOceanLocation(),
|
||||
Province.Empty("English Channel", "ENC", "ECH")
|
||||
|
|
|
@ -37,7 +37,7 @@ public class Province
|
|||
this.Abbreviations = abbreviations;
|
||||
this.IsSupplyCenter = isSupply;
|
||||
this.IsTimeCenter = isTime;
|
||||
this.LocationList = [];
|
||||
this.LocationList = new List<Location>();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
@ -49,26 +49,26 @@ public class Province
|
|||
/// Create a new province with no supply center.
|
||||
/// </summary>
|
||||
public static Province Empty(string name, params string[] abbreviations)
|
||||
=> new(name, abbreviations, isSupply: false, isTime: false);
|
||||
=> new Province(name, abbreviations, isSupply: false, isTime: false);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new province with a supply center.
|
||||
/// </summary>
|
||||
public static Province Supply(string name, params string[] abbreviations)
|
||||
=> new(name, abbreviations, isSupply: true, isTime: false);
|
||||
=> new Province(name, abbreviations, isSupply: true, isTime: false);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new province with a time center.
|
||||
/// </summary>
|
||||
public static Province Time(string name, params string[] abbreviations)
|
||||
=> new(name, abbreviations, isSupply: true, isTime: true);
|
||||
=> new Province(name, abbreviations, isSupply: true, isTime: true);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new land location in this province.
|
||||
/// </summary>
|
||||
public Province AddLandLocation()
|
||||
{
|
||||
Location location = new(this, "land", "l", LocationType.Land);
|
||||
Location location = new Location(this, name: null, abbreviation: null, LocationType.Land);
|
||||
this.LocationList.Add(location);
|
||||
return this;
|
||||
}
|
||||
|
@ -78,7 +78,19 @@ public class Province
|
|||
/// </summary>
|
||||
public Province AddOceanLocation()
|
||||
{
|
||||
Location location = new(this, "water", "w", LocationType.Water);
|
||||
Location location = new Location(this, name: null, abbreviation: null, LocationType.Water);
|
||||
this.LocationList.Add(location);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new coastal location. Coastal locations must have names to disambiguate them
|
||||
/// from the single land location in coastal provinces.
|
||||
/// </summary>
|
||||
public Province AddCoastLocation()
|
||||
{
|
||||
// Use a default name for provinces with only one coastal location
|
||||
Location location = new Location(this, "coast", "c", LocationType.Water);
|
||||
this.LocationList.Add(location);
|
||||
return this;
|
||||
}
|
||||
|
@ -89,7 +101,7 @@ public class Province
|
|||
/// </summary>
|
||||
public Province AddCoastLocation(string name, string abbreviation)
|
||||
{
|
||||
Location location = new(this, name, abbreviation, LocationType.Water);
|
||||
Location location = new Location(this, name, abbreviation, LocationType.Water);
|
||||
this.LocationList.Add(location);
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MultiversalDiplomacy.Model;
|
||||
|
||||
/// <summary>
|
||||
|
@ -8,12 +10,18 @@ public class Unit
|
|||
/// <summary>
|
||||
/// The previous iteration of a unit. This is null if the unit was just built.
|
||||
/// </summary>
|
||||
public string? Past { get; }
|
||||
public Unit? Past { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The location on the map where the unit is.
|
||||
/// </summary>
|
||||
public string Location { get; }
|
||||
public Location Location { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The province where the unit is.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Province Province => this.Location.Province;
|
||||
|
||||
/// <summary>
|
||||
/// The season in time when the unit is.
|
||||
|
@ -31,11 +39,11 @@ public class Unit
|
|||
public UnitType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A unique designation for this unit.
|
||||
/// The unit's spatiotemporal location as a province-season tuple.
|
||||
/// </summary>
|
||||
public string Designation => $"{Type.ToShort()} {Season.Timeline}-{Location}@{Season.Turn}";
|
||||
public (Province province, Season season) Point => (this.Province, this.Season);
|
||||
|
||||
private Unit(string? past, string location, Season season, Power power, UnitType type)
|
||||
private Unit(Unit? past, Location location, Season season, Power power, UnitType type)
|
||||
{
|
||||
this.Past = past;
|
||||
this.Location = location;
|
||||
|
@ -45,18 +53,20 @@ public class Unit
|
|||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Power.Name[0]} {Type.ToShort()} {Season.Timeline}-{Location}@{Season.Turn}";
|
||||
{
|
||||
return $"{this.Power.Name[0]} {this.Type.ToShort()} {(this.Province, this.Season).ToShort()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new unit. No validation is performed; the adjudicator should only call this
|
||||
/// method after accepting a build order.
|
||||
/// </summary>
|
||||
public static Unit Build(string location, Season season, Power power, UnitType type)
|
||||
public static Unit Build(Location location, Season season, Power power, UnitType type)
|
||||
=> new(past: null, location, season, power, type);
|
||||
|
||||
/// <summary>
|
||||
/// Advance this unit's timeline to a new location and season.
|
||||
/// </summary>
|
||||
public Unit Next(string location, Season season)
|
||||
=> new(past: this.Designation, location, season, this.Power, this.Type);
|
||||
public Unit Next(Location location, Season season)
|
||||
=> new(past: this, location, season, this.Power, this.Type);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MultiversalDiplomacy.Model;
|
||||
|
@ -204,7 +205,7 @@ public class World
|
|||
: splits.Length == 3
|
||||
? Map.GetWater(splits[2])
|
||||
: Map.GetWater(splits[2], splits[3]);
|
||||
Unit unit = Unit.Build(location.Designation, this.RootSeason, power, type);
|
||||
Unit unit = Unit.Build(location, this.RootSeason, power, type);
|
||||
return unit;
|
||||
});
|
||||
return this.Update(units: units);
|
||||
|
@ -327,13 +328,9 @@ public class World
|
|||
Province province = Map.GetProvince(provinceName);
|
||||
season ??= RootSeason;
|
||||
Unit? foundUnit = this.Units.SingleOrDefault(
|
||||
u => Map.GetLocation(u!).Province == province && u!.Season == season,
|
||||
u => u!.Province == province && u.Season == season,
|
||||
null)
|
||||
?? throw new KeyNotFoundException($"Unit at {province} at {season} not found");
|
||||
return foundUnit;
|
||||
}
|
||||
|
||||
public Unit GetUnitByDesignation(string designation)
|
||||
=> Units.SingleOrDefault(u => u!.Designation == designation, null)
|
||||
?? throw new KeyNotFoundException($"Unit {designation} not found");
|
||||
}
|
||||
|
|
|
@ -39,6 +39,15 @@ public class MoveOrder : UnitOrder
|
|||
return $"{this.Unit} -> {(this.Province, this.Season).ToShort()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether another move order is in a head-to-head battle with this order.
|
||||
/// </summary>
|
||||
public bool IsOpposing(MoveOrder other)
|
||||
=> this.Season == other.Unit.Season
|
||||
&& other.Season == this.Unit.Season
|
||||
&& this.Province == other.Unit.Province
|
||||
&& other.Province == this.Unit.Province;
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether another move order has the same destination as this order.
|
||||
/// </summary>
|
||||
|
|
|
@ -16,4 +16,12 @@ public abstract class UnitOrder : Order
|
|||
{
|
||||
this.Unit = unit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a move order is moving into this order's unit's province.
|
||||
/// </summary>
|
||||
public bool IsIncoming(MoveOrder other)
|
||||
=> this != other
|
||||
&& other.Season == this.Unit.Season
|
||||
&& other.Province == this.Unit.Province;
|
||||
}
|
|
@ -46,12 +46,12 @@ public class TimeTravelTest
|
|||
Unit originalUnit = world.GetUnitAt("Mun", s0);
|
||||
Unit aMun0 = world.GetUnitAt("Mun", s1);
|
||||
Unit aTyr = world.GetUnitAt("Tyr", fork);
|
||||
Assert.That(aTyr.Past, Is.EqualTo(mun1.Order.Unit.Designation));
|
||||
Assert.That(world.GetUnitByDesignation(aTyr.Past!).Past, Is.EqualTo(mun0.Order.Unit.Designation));
|
||||
Assert.That(aTyr.Past, Is.EqualTo(mun1.Order.Unit));
|
||||
Assert.That(aTyr.Past?.Past, Is.EqualTo(mun0.Order.Unit));
|
||||
|
||||
// Confirm that there is a unit in Mun b1 originating from Mun a0
|
||||
Unit aMun1 = world.GetUnitAt("Mun", fork);
|
||||
Assert.That(aMun1.Past, Is.EqualTo(originalUnit.Designation));
|
||||
Assert.That(aMun1.Past, Is.EqualTo(originalUnit));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -95,7 +95,7 @@ public class TimeTravelTest
|
|||
Unit tyr1 = world.GetUnitAt("Tyr", fork);
|
||||
Assert.That(
|
||||
tyr1.Past,
|
||||
Is.EqualTo(mun0.Order.Unit.Designation),
|
||||
Is.EqualTo(mun0.Order.Unit),
|
||||
"Expected A Mun a0 to advance to Tyr b1");
|
||||
Assert.That(
|
||||
world.RetreatingUnits.Count,
|
||||
|
|
|
@ -209,7 +209,7 @@ public class MovementAdjudicatorTest
|
|||
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.Past, Is.EqualTo(mun1.Order.Unit.Designation));
|
||||
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit));
|
||||
Assert.That(u2.Season, Is.EqualTo(s2));
|
||||
|
||||
setup[("a", 1)]
|
||||
|
@ -229,7 +229,7 @@ public class MovementAdjudicatorTest
|
|||
updated = setup.UpdateWorld();
|
||||
Season s3 = updated.GetSeason(s2.Timeline, s2.Turn + 1);
|
||||
Unit u3 = updated.GetUnitAt("Mun", s3);
|
||||
Assert.That(u3.Past, Is.EqualTo(mun2.Order.Unit.Designation));
|
||||
Assert.That(u3.Past, Is.EqualTo(mun2.Order.Unit));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -259,7 +259,7 @@ public class MovementAdjudicatorTest
|
|||
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.Past, Is.EqualTo(mun1.Order.Unit.Designation));
|
||||
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit));
|
||||
Assert.That(u2.Season, Is.EqualTo(s2));
|
||||
|
||||
setup[("a", 1)]
|
||||
|
@ -279,6 +279,6 @@ public class MovementAdjudicatorTest
|
|||
updated = setup.UpdateWorld();
|
||||
Season s3 = updated.GetSeason(s2.Timeline, s2.Turn + 1);
|
||||
Unit u3 = updated.GetUnitAt("Mun", s3);
|
||||
Assert.That(u3.Past, Is.EqualTo(u2.Designation));
|
||||
Assert.That(u3.Past, Is.EqualTo(u2));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,8 @@ public abstract class OrderReference
|
|||
DefendStrength defend => defend.Order == this.Order,
|
||||
PreventStrength prevent => prevent.Order == this.Order,
|
||||
HoldStrength hold => this.Order is UnitOrder unitOrder
|
||||
&& hold.Province == Builder.World.Map.GetLocation(unitOrder.Unit).Province,
|
||||
? hold.Province == unitOrder.Unit.Province
|
||||
: false,
|
||||
_ => false,
|
||||
}).ToList();
|
||||
return adjudications;
|
||||
|
|
|
@ -94,7 +94,7 @@ public class SerializationTest
|
|||
Unit tyr1 = world.GetUnitAt("Tyr", fork);
|
||||
Assert.That(
|
||||
tyr1.Past,
|
||||
Is.EqualTo(mun0.Order.Unit.Designation),
|
||||
Is.EqualTo(mun0.Order.Unit),
|
||||
"Expected A Mun a0 to advance to Tyr b1");
|
||||
Assert.That(
|
||||
world.RetreatingUnits.Count,
|
||||
|
|
|
@ -262,7 +262,7 @@ public class TestCaseBuilder
|
|||
foreach (Unit unit in this.World.Units)
|
||||
{
|
||||
if (unit.Power == power
|
||||
&& World.Map.GetLocation(unit).Province == location.Province
|
||||
&& unit.Province == location.Province
|
||||
&& unit.Season == season)
|
||||
{
|
||||
return unit;
|
||||
|
@ -270,7 +270,7 @@ public class TestCaseBuilder
|
|||
}
|
||||
|
||||
// Not found
|
||||
Unit newUnit = Unit.Build(location.Designation, season, power, type);
|
||||
Unit newUnit = Unit.Build(location, season, power, type);
|
||||
this.World = this.World.Update(units: this.World.Units.Append(newUnit));
|
||||
return newUnit;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class TestCaseBuilderTest
|
|||
Assert.That(fleetSTP.Type, Is.EqualTo(UnitType.Fleet), "Unit created with wrong type");
|
||||
Assert.That(
|
||||
fleetSTP.Location,
|
||||
Is.EqualTo(setup.World.Map.GetWater("STP", "wc").Designation),
|
||||
Is.EqualTo(setup.World.Map.GetWater("STP", "wc")),
|
||||
"Unit created on wrong coast");
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ class TestCaseBuilderTest
|
|||
List<UnitOrder> orders = setup.Orders.OfType<UnitOrder>().ToList();
|
||||
|
||||
Func<UnitOrder, bool> OrderForProvince(string name)
|
||||
=> order => setup.World.Map.GetLocation(order.Unit).Province.Name == name;
|
||||
=> order => order.Unit.Province.Name == name;
|
||||
|
||||
UnitOrder orderBer = orders.Single(OrderForProvince("Berlin"));
|
||||
Assert.That(orderBer, Is.InstanceOf<MoveOrder>(), "Unexpected order type");
|
||||
|
@ -128,7 +128,7 @@ class TestCaseBuilderTest
|
|||
"Wrong power");
|
||||
Assert.That(
|
||||
orderMun.Order.Unit.Location,
|
||||
Is.EqualTo(setup.World.Map.GetLand("Mun").Designation),
|
||||
Is.EqualTo(setup.World.Map.GetLand("Mun")),
|
||||
"Wrong unit");
|
||||
|
||||
Assert.That(
|
||||
|
|
|
@ -15,24 +15,24 @@ public class UnitTests
|
|||
Tyr = world.Map.GetLand("Tyr");
|
||||
Power pw1 = world.Map.GetPower("Austria");
|
||||
Season a0 = world.RootSeason;
|
||||
Unit u1 = Unit.Build(Mun.Designation, a0, pw1, UnitType.Army);
|
||||
Unit u1 = Unit.Build(Mun, a0, pw1, UnitType.Army);
|
||||
|
||||
world = world.ContinueOrFork(a0, out Season a1);
|
||||
Unit u2 = u1.Next(Boh.Designation, a1);
|
||||
Unit u2 = u1.Next(Boh, a1);
|
||||
|
||||
_ = world.ContinueOrFork(a1, out Season a2);
|
||||
Unit u3 = u2.Next(Tyr.Designation, a2);
|
||||
Unit u3 = u2.Next(Tyr, a2);
|
||||
|
||||
Assert.That(u3.Past, Is.EqualTo(u2.Designation), "Missing unit past");
|
||||
Assert.That(u2.Past, Is.EqualTo(u1.Designation), "Missing unit past");
|
||||
Assert.That(u3.Past, Is.EqualTo(u2), "Missing unit past");
|
||||
Assert.That(u2.Past, Is.EqualTo(u1), "Missing unit past");
|
||||
Assert.That(u1.Past, Is.Null, "Unexpected unit past");
|
||||
|
||||
Assert.That(u1.Season, Is.EqualTo(a0), "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(u1.Location, Is.EqualTo(Mun.Designation), "Unexpected unit location");
|
||||
Assert.That(u2.Location, Is.EqualTo(Boh.Designation), "Unexpected unit location");
|
||||
Assert.That(u3.Location, Is.EqualTo(Tyr.Designation), "Unexpected unit location");
|
||||
Assert.That(u1.Location, Is.EqualTo(Mun), "Unexpected unit location");
|
||||
Assert.That(u2.Location, Is.EqualTo(Boh), "Unexpected unit location");
|
||||
Assert.That(u3.Location, Is.EqualTo(Tyr), "Unexpected unit location");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue