Compare commits

..

No commits in common. "lookup" and "develop" have entirely different histories.

15 changed files with 60 additions and 36 deletions

View File

@ -94,7 +94,7 @@ public class MovementDecisions
.ToList();
(string province, string season) UnitPoint(Unit unit)
=> (unit.GetProvince(world).Name, unit.Season.Key);
=> (world.Map.GetLocation(unit.Location).Province.Name, unit.Season.Key);
(string province, string season) MovePoint(MoveOrder move)
=> (SplitKey(move.Location).province, move.Season.Key);
@ -102,7 +102,7 @@ public class MovementDecisions
foreach (UnitOrder order in relevantOrders)
{
HoldStrength[UnitPoint(order.Unit)] = new(
order.Unit.GetProvince(world),
world.Map.GetLocation(order.Unit.Location).Province,
order.Unit.Season,
order);
}
@ -110,7 +110,7 @@ public class MovementDecisions
bool IsIncoming(UnitOrder me, MoveOrder other)
=> me != other
&& other.Season == me.Unit.Season
&& SplitKey(other.Location).province == me.Unit.GetProvince(world).Name;
&& SplitKey(other.Location).province == world.Map.GetLocation(me.Unit).Province.Name;
bool IsSupportFor(SupportMoveOrder me, MoveOrder move)
=> me.Target.Key == move.Unit.Key
@ -120,8 +120,8 @@ public class MovementDecisions
bool AreOpposing(MoveOrder one, MoveOrder two)
=> one.Season == two.Unit.Season
&& two.Season == one.Unit.Season
&& SplitKey(one.Location).province == two.Unit.GetProvince(world).Name
&& SplitKey(two.Location).province == one.Unit.GetProvince(world).Name;
&& SplitKey(one.Location).province == world.Map.GetLocation(two.Unit).Province.Name
&& SplitKey(two.Location).province == world.Map.GetLocation(one.Unit).Province.Name;
bool AreCompeting(MoveOrder one, MoveOrder two)
=> one != two
@ -174,7 +174,7 @@ public class MovementDecisions
// Ensure a hold strength decision exists for the target's province.
HoldStrength.Ensure(UnitPoint(support.Target), () => new(
support.Target.GetProvince(world),
world.Map.GetLocation(support.Target.Location).Province,
support.Target.Season));
if (support is SupportHoldOrder supportHold)

View File

@ -92,7 +92,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
ILookup<bool, MoveOrder> moveOrdersByAdjacency = moveOrders
.ToLookup(order =>
// Map adjacency
order.Unit.GetLocation(world).Adjacents.Select(loc => loc.Key).Contains(order.Location)
world.Map.GetLocation(order.Unit).Adjacents.Select(loc => loc.Key).Contains(order.Location)
// Turn adjacency
&& Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1
// Timeline adjacency
@ -177,8 +177,8 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
AdjudicatorHelpers.InvalidateIfNotMatching(
order =>
// Map adjacency with respect to province
order.Unit.GetLocation(world).Adjacents.Any(
adjLocation => adjLocation.Province == order.Target.GetLocation(world).Province)
world.Map.GetLocation(order.Unit).Adjacents.Any(
adjLocation => adjLocation.Province == world.Map.GetLocation(order.Target).Province)
// Turn adjacency
&& Math.Abs(order.Unit.Season.Turn - order.Target.Season.Turn) <= 1
// Timeline adjacency
@ -197,7 +197,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 => order.Unit.GetProvince(world) != order.Province,
order => world.Map.GetLocation(order.Unit).Province != order.Province,
ValidationReason.NoSupportMoveAgainstSelf,
ref supportMoveOrders,
ref validationResults);
@ -209,7 +209,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
AdjudicatorHelpers.InvalidateIfNotMatching(
order =>
// Map adjacency with respect to province
order.Unit.GetLocation(world).Adjacents.Any(
world.Map.GetLocation(order.Unit).Adjacents.Any(
adjLocation => adjLocation.Province == order.Province)
// Turn adjacency
&& Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1
@ -370,7 +370,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
if (isDislodged.Outcome == false)
{
// Non-dislodged units continue into the future.
Unit next = order.Unit.Next(order.Unit.GetLocation(world).Key, future);
Unit next = order.Unit.Next(world.Map.GetLocation(order.Unit).Key, future);
logger.Log(3, "Advancing unit to {0}", next);
createdUnits.Add(next);
}
@ -379,7 +379,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 = order.Unit.GetLocation(world).Adjacents
var validRetreats = world.Map.GetLocation(order.Unit).Adjacents
.Select(loc => (future, loc))
.ToList();
RetreatingUnit retreat = new(order.Unit, validRetreats);
@ -640,7 +640,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// If the origin and destination are adjacent, then there is a path.
if (// Map adjacency
decision.Order.Unit.GetLocation(world).Adjacents.Select(loc => loc.Key).Contains(decision.Order.Location)
world.Map.GetLocation(decision.Order.Unit).Adjacents.Select(loc => loc.Key).Contains(decision.Order.Location)
// Turn adjacency
&& Math.Abs(decision.Order.Unit.Season.Turn - decision.Order.Season.Turn) <= 1
// Timeline adjacency

View File

@ -12,7 +12,7 @@ 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.GetLocation(world), 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.
@ -20,7 +20,7 @@ public static class PathFinder
public static bool ConvoyPathExists(World world, MoveOrder order)
=> ConvoyPathExists(
world,
order.Unit.GetLocation(world),
world.Map.GetLocation(order.Unit),
world.Map.GetLocation(order.Location),
order.Season);

View File

@ -72,6 +72,9 @@ public class Map
public Location GetLocation(string designation)
=> LocationLookup[designation];
public Location GetLocation(Unit unit)
=> GetLocation(unit.Location);
/// <summary>
/// Get the sole land location of a province.
/// </summary>

View File

@ -259,7 +259,7 @@ public class OrderParser(World world)
// and the location is ignored. This also satisfies DATC 4.B.5, which requires that a wrong coast for the
// subject be ignored.
subject = world.Units.FirstOrDefault(unit
=> unit!.GetProvince(world).Name == province.Name
=> world.Map.GetLocation(unit!.Location).ProvinceName == province.Name
&& unit!.Season.Timeline == timeline
&& unit!.Season.Turn == turn,
null);
@ -338,7 +338,7 @@ public class OrderParser(World world)
// If the order location didn't disambiguate the coasts, either because it's missing or it's nonsense, the
// order can be disambiguated by there being one accessible coast from the order source.
if (destLocationKey is null) {
Location source = subject.GetLocation(world);
Location source = world.Map.GetLocation(subject.Location);
var accessibleLocations = destProvince.Locations.Where(loc => loc.Adjacents.Contains(source));
if (accessibleLocations.Count() == 1) destLocationKey ??= accessibleLocations.Single().Key;
}
@ -434,7 +434,7 @@ public class OrderParser(World world)
// If the order location didn't disambiguate the coasts, either because it's missing or it's nonsense, the
// order can be disambiguated by there being one accessible coast from the order source.
if (destLocationKey is null) {
Location source = target.GetLocation(world);
Location source = world.Map.GetLocation(target.Location);
var accessibleLocations = destProvince.Locations.Where(loc => loc.Adjacents.Contains(source));
if (accessibleLocations.Count() == 1) destLocationKey ??= accessibleLocations.Single().Key;
}

View File

@ -7,6 +7,11 @@ namespace MultiversalDiplomacy.Model;
/// </summary>
public class Unit
{
/// <summary>
/// The previous iteration of a unit. This is null if the unit was just built.
/// </summary>
public string? Past { get; }
/// <summary>
/// The location on the map where the unit is.
/// </summary>
@ -33,8 +38,9 @@ public class Unit
[JsonIgnore]
public string Key => $"{Type.ToShort()} {Season.Timeline}-{Location}@{Season.Turn}";
public Unit(string location, Season season, string power, UnitType type)
public Unit(string? past, string location, Season season, string power, UnitType type)
{
this.Past = past;
this.Location = location;
this.Season = season;
this.Power = power;
@ -49,15 +55,11 @@ public class Unit
/// method after accepting a build order.
/// </summary>
public static Unit Build(string location, Season season, string power, UnitType type)
=> new(location, season, power, 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(location, season, this.Power, this.Type);
public Location GetLocation(World world) => world.Map.GetLocation(Location);
public Province GetProvince(World world) => GetLocation(world).Province;
=> new(past: this.Key, location, season, this.Power, this.Type);
}

View File

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

View File

@ -100,7 +100,7 @@ public class AdjudicationQueryScriptHandler(
=> val.Valid
&& val.Order is HoldOrder hold
&& hold.Unit.Season == season
&& hold.Unit.GetProvince(World).Name == province.Name);
&& World.Map.GetLocation(hold.Unit.Location).ProvinceName == province.Name);
if (!matchingHolds.Any()) return ScriptResult.Fail("No matching holds");
return ScriptResult.Succeed(this);
@ -129,7 +129,7 @@ public class AdjudicationQueryScriptHandler(
var matching = Validations.Where(val
=> val.Order is UnitOrder order
&& order.Unit.Season == season
&& order.Unit.GetProvince(World).Name == province.Name);
&& World.Map.GetLocation(order.Unit.Location).ProvinceName == province.Name);
if (!matching.Any()) return ScriptResult.Fail("No matching validations");
if (args[0] == "order-valid" && !matching.First().Valid) {
@ -181,7 +181,7 @@ public class AdjudicationQueryScriptHandler(
var matchingDislodges = Adjudications.Where(adj
=> adj is IsDislodged dislodge
&& dislodge.Order.Unit.Season == season
&& dislodge.Order.Unit.GetProvince(World).Name == province.Name);
&& World.Map.GetLocation(dislodge.Order.Unit.Location).ProvinceName == province.Name);
if (!matchingDislodges.Any()) return ScriptResult.Fail("No matching dislodge decisions");
var isDislodged = matchingDislodges.Cast<IsDislodged>().First();
@ -219,7 +219,7 @@ public class AdjudicationQueryScriptHandler(
var matchingMoves = Adjudications.Where(adj
=> adj is DoesMove moves
&& moves.Order.Unit.Season == season
&& moves.Order.Unit.GetProvince(World).Name == province.Name);
&& World.Map.GetLocation(moves.Order.Unit.Location).ProvinceName == province.Name);
if (!matchingMoves.Any()) return ScriptResult.Fail("No matching movement decisions");
var doesMove = matchingMoves.Cast<DoesMove>().First();
@ -257,7 +257,7 @@ public class AdjudicationQueryScriptHandler(
var matchingSupports = Adjudications.Where(adj
=> adj is GivesSupport sup
&& sup.Order.Unit.Season == season
&& sup.Order.Unit.GetProvince(World).Name == province.Name);
&& World.Map.GetLocation(sup.Order.Unit.Location).ProvinceName == province.Name);
if (!matchingSupports.Any()) return ScriptResult.Fail("No matching support decisions");
var supports = matchingSupports.Cast<GivesSupport>().First();

View File

@ -47,9 +47,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.Key));
Assert.That(world.GetUnitByKey(aTyr.Past!).Past, Is.EqualTo(mun0.Order.Unit.Key));
// 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.Key));
}
[Test]
@ -91,6 +94,10 @@ public class TimeTravelTest
World world = setup.UpdateWorld();
Season fork = new("b1");
Unit tyr1 = world.GetUnitAt("Tyr", fork);
Assert.That(
tyr1.Past,
Is.EqualTo(mun0.Order.Unit.Key),
"Expected A Mun a0 to advance to Tyr b1");
Assert.That(
world.RetreatingUnits.Count,
Is.EqualTo(1),

View File

@ -171,7 +171,7 @@ public class MovementAdjudicatorTest
// Confirm the unit was created
Assert.That(updated.Units.Count, Is.EqualTo(2));
Unit second = updated.Units.OrderBy(u => -u.Season.Turn).First();
Unit second = updated.Units.Single(u => u.Past != null);
Assert.That(second.Location, Is.EqualTo(mun.Order.Unit.Location));
Assert.That(second.Season.Timeline, Is.EqualTo(mun.Order.Unit.Season.Timeline));
@ -208,6 +208,7 @@ public class MovementAdjudicatorTest
Unit u2 = updated.GetUnitAt("Mun", s2);
Assert.That(updated.Units.Count, Is.EqualTo(2));
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));
setup[("a", 1)]
@ -227,6 +228,7 @@ public class MovementAdjudicatorTest
updated = setup.UpdateWorld();
Season s3 = new(s2.Timeline, s2.Turn + 1);
Unit u3 = updated.GetUnitAt("Mun", s3);
Assert.That(u3.Past, Is.EqualTo(mun2.Order.Unit.Key));
}
[Test]
@ -256,6 +258,7 @@ public class MovementAdjudicatorTest
Unit u2 = updated.GetUnitAt("Tyr", s2);
Assert.That(updated.Units.Count, Is.EqualTo(2));
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));
setup[("a", 1)]
@ -275,5 +278,6 @@ public class MovementAdjudicatorTest
updated = setup.UpdateWorld();
Season s3 = new(s2.Timeline, s2.Turn + 1);
Unit u3 = updated.GetUnitAt("Mun", s3);
Assert.That(u3.Past, Is.EqualTo(u2.Key));
}
}

View File

@ -108,7 +108,7 @@ 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 == unitOrder.Unit.GetProvince(Builder.World),
&& hold.Province == Builder.World.Map.GetLocation(unitOrder.Unit).Province,
_ => false,
}).ToList();
return adjudications;

View File

@ -133,6 +133,10 @@ public class SerializationTest
World world = setup.UpdateWorld();
Season fork = new("b1");
Unit tyr1 = world.GetUnitAt("Tyr", fork);
Assert.That(
tyr1.Past,
Is.EqualTo(mun0.Order.Unit.Key),
"Expected A Mun a0 to advance to Tyr b1");
Assert.That(
world.RetreatingUnits.Count,
Is.EqualTo(1),

View File

@ -263,7 +263,7 @@ public class TestCaseBuilder
foreach (Unit unit in this.World.Units)
{
if (unit.Power == power
&& unit.GetProvince(World) == location.Province
&& World.Map.GetLocation(unit).Province == location.Province
&& unit.Season == season)
{
return unit;

View File

@ -68,7 +68,7 @@ class TestCaseBuilderTest
List<UnitOrder> orders = setup.Orders.OfType<UnitOrder>().ToList();
Func<UnitOrder, bool> OrderForProvince(string name)
=> order => order.Unit.GetProvince(setup.World).Name == name;
=> order => setup.World.Map.GetLocation(order.Unit).Province.Name == name;
UnitOrder orderBer = orders.Single(OrderForProvince("Berlin"));
Assert.That(orderBer, Is.InstanceOf<MoveOrder>(), "Unexpected order type");

View File

@ -22,6 +22,10 @@ public class UnitTests
_ = world.WithNewSeason(a1, out Season a2);
Unit u3 = u2.Next(Tyr.Key, a2);
Assert.That(u3.Past, Is.EqualTo(u2.Key), "Missing unit past");
Assert.That(u2.Past, Is.EqualTo(u1.Key), "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");