Fix missing temporal dimension to dislodge checks
This commit is contained in:
parent
9f5ecaa16a
commit
d491ea9f64
|
@ -84,7 +84,7 @@ public class MovementDecisions
|
||||||
// Create a dislodge decision for this unit.
|
// Create a dislodge decision for this unit.
|
||||||
List<MoveOrder> incoming = orders
|
List<MoveOrder> incoming = orders
|
||||||
.OfType<MoveOrder>()
|
.OfType<MoveOrder>()
|
||||||
.Where(move => move.Province == order.Unit.Province)
|
.Where(order.IsIncoming)
|
||||||
.ToList();
|
.ToList();
|
||||||
this.IsDislodged[order.Unit] = new(order, incoming);
|
this.IsDislodged[order.Unit] = new(order, incoming);
|
||||||
|
|
||||||
|
@ -108,9 +108,7 @@ public class MovementDecisions
|
||||||
// Find competing moves.
|
// Find competing moves.
|
||||||
List<MoveOrder> competing = orders
|
List<MoveOrder> competing = orders
|
||||||
.OfType<MoveOrder>()
|
.OfType<MoveOrder>()
|
||||||
.Where(other
|
.Where(move.IsCompeting)
|
||||||
=> other != move
|
|
||||||
&& other.Province == move.Province)
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Create the move-related decisions.
|
// Create the move-related decisions.
|
||||||
|
|
|
@ -34,9 +34,20 @@ public class MoveOrder : UnitOrder
|
||||||
this.Location = location;
|
this.Location = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether another move order is in a head-to-head battle with this order.
|
||||||
|
/// </summary>
|
||||||
public bool IsOpposing(MoveOrder other)
|
public bool IsOpposing(MoveOrder other)
|
||||||
=> this.Season == other.Unit.Season
|
=> this.Season == other.Unit.Season
|
||||||
&& other.Season == this.Unit.Season
|
&& other.Season == this.Unit.Season
|
||||||
&& this.Province == other.Unit.Province
|
&& this.Province == other.Unit.Province
|
||||||
&& other.Province == this.Unit.Province;
|
&& other.Province == this.Unit.Province;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether another move order has the same destination as this order.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCompeting(MoveOrder other)
|
||||||
|
=> this != other
|
||||||
|
&& this.Season == other.Season
|
||||||
|
&& this.Province == other.Province;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,4 +16,12 @@ public abstract class UnitOrder : Order
|
||||||
{
|
{
|
||||||
this.Unit = unit;
|
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;
|
||||||
}
|
}
|
|
@ -3,23 +3,57 @@ using MultiversalDiplomacy.Adjudicate.Decision;
|
||||||
|
|
||||||
namespace MultiversalDiplomacyTests;
|
namespace MultiversalDiplomacyTests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiversal Diplomacy assertion constraint extension provider. "NotX" constraints are provided
|
||||||
|
/// because properties can't be added to Is.Not via extension.
|
||||||
|
/// </summary>
|
||||||
public class Is : NUnit.Framework.Is
|
public class Is : NUnit.Framework.Is
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a constraint that checks for a positive order validation.
|
||||||
|
/// </summary>
|
||||||
public static OrderValidationConstraint Valid
|
public static OrderValidationConstraint Valid
|
||||||
=> new(true, ValidationReason.Valid);
|
=> new(true, ValidationReason.Valid);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a constraint that checks for a negative order validation.
|
||||||
|
/// </summary>
|
||||||
public static OrderValidationConstraint Invalid(ValidationReason expected)
|
public static OrderValidationConstraint Invalid(ValidationReason expected)
|
||||||
=> new(false, expected);
|
=> new(false, expected);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a constraint that checks for a positive <see cref="IsDislodged"/> decision.
|
||||||
|
/// </summary>
|
||||||
public static OrderBinaryAdjudicationConstraint<IsDislodged> Dislodged
|
public static OrderBinaryAdjudicationConstraint<IsDislodged> Dislodged
|
||||||
=> new(true);
|
=> new(true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a constraint that checks for a negative <see cref="IsDislodged"/> decision.
|
||||||
|
/// </summary>
|
||||||
public static OrderBinaryAdjudicationConstraint<IsDislodged> NotDislodged
|
public static OrderBinaryAdjudicationConstraint<IsDislodged> NotDislodged
|
||||||
=> new(false);
|
=> new(false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a constraint that checks for a positive <see cref="DoesMove"/> decision.
|
||||||
|
/// </summary>
|
||||||
public static OrderBinaryAdjudicationConstraint<DoesMove> Victorious
|
public static OrderBinaryAdjudicationConstraint<DoesMove> Victorious
|
||||||
=> new(true);
|
=> new(true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a constraint that checks for a negative <see cref="DoesMove"/> decision.
|
||||||
|
/// </summary>
|
||||||
public static OrderBinaryAdjudicationConstraint<DoesMove> Repelled
|
public static OrderBinaryAdjudicationConstraint<DoesMove> Repelled
|
||||||
=> new(false);
|
=> new(false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a constraint that checks for a positive <see cref="GivesSupport"/> decision.
|
||||||
|
/// </summary>
|
||||||
|
public static OrderBinaryAdjudicationConstraint<GivesSupport> NotCut
|
||||||
|
=> new(true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a constraint that checks for a negative <see cref="GivesSupport"/> decision.
|
||||||
|
/// </summary>
|
||||||
|
public static OrderBinaryAdjudicationConstraint<GivesSupport> Cut
|
||||||
|
=> new(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,13 @@ public class TestCaseBuilder
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Make the support order target an army.
|
/// Make the support order target an army.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ISupportTypeContext Army(string provinceName, string? powerName = null);
|
/// <param name="season">
|
||||||
|
/// The unit season. If not specified, defaults to the same season as the ordered unit.
|
||||||
|
/// </param>
|
||||||
|
public ISupportTypeContext Army(
|
||||||
|
string provinceName,
|
||||||
|
Season? season = null,
|
||||||
|
string? powerName = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Make the support order target a fleet.
|
/// Make the support order target a fleet.
|
||||||
|
@ -157,7 +163,14 @@ public class TestCaseBuilder
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Give the unit an order to support the target's move order.
|
/// Give the unit an order to support the target's move order.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IOrderDefinedContext<SupportMoveOrder> MoveTo(string provinceName, string? coast = null);
|
/// <param name="season">
|
||||||
|
/// The target's destination season. If not specified, defaults to the same season as the
|
||||||
|
/// target (not the ordered unit).
|
||||||
|
/// </param>
|
||||||
|
public IOrderDefinedContext<SupportMoveOrder> MoveTo(
|
||||||
|
string provinceName,
|
||||||
|
Season? season = null,
|
||||||
|
string? coast = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -506,14 +519,18 @@ public class TestCaseBuilder
|
||||||
this.UnitContext = unitContext;
|
this.UnitContext = unitContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISupportTypeContext Army(string provinceName, string? powerName = null)
|
public ISupportTypeContext Army(
|
||||||
|
string provinceName,
|
||||||
|
Season? season = null,
|
||||||
|
string? powerName = null)
|
||||||
{
|
{
|
||||||
Power power = powerName == null
|
Power power = powerName == null
|
||||||
? this.PowerContext.Power
|
? this.PowerContext.Power
|
||||||
: this.Builder.World.GetPower(powerName);
|
: this.Builder.World.GetPower(powerName);
|
||||||
Location location = this.Builder.World.GetLand(provinceName);
|
Location location = this.Builder.World.GetLand(provinceName);
|
||||||
|
Season destSeason = season ?? this.SeasonContext.Season;
|
||||||
Unit unit = this.Builder.GetOrBuildUnit(
|
Unit unit = this.Builder.GetOrBuildUnit(
|
||||||
power, location, this.SeasonContext.Season, UnitType.Army);
|
power, location, destSeason, UnitType.Army);
|
||||||
return new SupportTypeContext(this, unit);
|
return new SupportTypeContext(this, unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,16 +576,20 @@ public class TestCaseBuilder
|
||||||
return new OrderDefinedContext<SupportHoldOrder>(this.UnitContext, order);
|
return new OrderDefinedContext<SupportHoldOrder>(this.UnitContext, order);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOrderDefinedContext<SupportMoveOrder> MoveTo(string provinceName, string? coast = null)
|
public IOrderDefinedContext<SupportMoveOrder> MoveTo(
|
||||||
|
string provinceName,
|
||||||
|
Season? season = null,
|
||||||
|
string? coast = null)
|
||||||
{
|
{
|
||||||
Location destination = this.Target.Type == UnitType.Army
|
Location destination = this.Target.Type == UnitType.Army
|
||||||
? this.Builder.World.GetLand(provinceName)
|
? this.Builder.World.GetLand(provinceName)
|
||||||
: this.Builder.World.GetWater(provinceName, coast);
|
: this.Builder.World.GetWater(provinceName, coast);
|
||||||
|
Season targetDestSeason = season ?? this.Target.Season;
|
||||||
SupportMoveOrder order = new SupportMoveOrder(
|
SupportMoveOrder order = new SupportMoveOrder(
|
||||||
this.PowerContext.Power,
|
this.PowerContext.Power,
|
||||||
this.UnitContext.Unit,
|
this.UnitContext.Unit,
|
||||||
this.Target,
|
this.Target,
|
||||||
this.SeasonContext.Season,
|
targetDestSeason,
|
||||||
destination);
|
destination);
|
||||||
this.Builder.OrderList.Add(order);
|
this.Builder.OrderList.Add(order);
|
||||||
return new OrderDefinedContext<SupportMoveOrder>(this.UnitContext, order);
|
return new OrderDefinedContext<SupportMoveOrder>(this.UnitContext, order);
|
||||||
|
|
|
@ -50,4 +50,54 @@ public class TimeTravelTest
|
||||||
Unit aMun1 = world.GetUnitAt("Mun", fork.Coord);
|
Unit aMun1 = world.GetUnitAt("Mun", fork.Coord);
|
||||||
Assert.That(aMun1.Past, Is.EqualTo(originalUnit));
|
Assert.That(aMun1.Past, Is.EqualTo(originalUnit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SupportToRepelledPastMoveForksTimeline()
|
||||||
|
{
|
||||||
|
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
|
// Fail to dislodge on the first turn, then support the move so it succeeds.
|
||||||
|
setup[(0, 0)]
|
||||||
|
.GetReference(out Season s0)
|
||||||
|
["Germany"]
|
||||||
|
.Army("Mun").MovesTo("Tyr").GetReference(out var mun0)
|
||||||
|
["Austria"]
|
||||||
|
.Army("Tyr").Holds().GetReference(out var tyr0);
|
||||||
|
|
||||||
|
setup.ValidateOrders();
|
||||||
|
Assert.That(mun0, Is.Valid);
|
||||||
|
Assert.That(tyr0, Is.Valid);
|
||||||
|
setup.AdjudicateOrders();
|
||||||
|
Assert.That(mun0, Is.Repelled);
|
||||||
|
Assert.That(tyr0, Is.NotDislodged);
|
||||||
|
setup.UpdateWorld();
|
||||||
|
|
||||||
|
setup[(1, 0)]
|
||||||
|
["Germany"]
|
||||||
|
.Army("Mun").Supports.Army("Mun", season: s0).MoveTo("Tyr").GetReference(out var mun1)
|
||||||
|
["Austria"]
|
||||||
|
.Army("Tyr").Holds();
|
||||||
|
|
||||||
|
// Confirm that history is changed.
|
||||||
|
setup.ValidateOrders();
|
||||||
|
Assert.That(mun1, Is.Valid);
|
||||||
|
setup.AdjudicateOrders();
|
||||||
|
Assert.That(mun1, Is.NotCut);
|
||||||
|
Assert.That(mun0, Is.Victorious);
|
||||||
|
Assert.That(tyr0, Is.Dislodged);
|
||||||
|
|
||||||
|
// Confirm that an alternate future is created.
|
||||||
|
World world = setup.UpdateWorld();
|
||||||
|
Season fork = world.GetSeason(1, 1);
|
||||||
|
Unit tyr1 = world.GetUnitAt("Tyr", fork.Coord);
|
||||||
|
Assert.That(
|
||||||
|
tyr1.Past,
|
||||||
|
Is.EqualTo(mun0.Order.Unit),
|
||||||
|
"Expected A Mun 0:0 to advance to Tyr 1:1");
|
||||||
|
Assert.That(
|
||||||
|
world.RetreatingUnits.Count,
|
||||||
|
Is.EqualTo(1),
|
||||||
|
"Expected A Tyr 0:0 to be in retreat");
|
||||||
|
Assert.That(world.RetreatingUnits.First().Unit, Is.EqualTo(tyr0.Order.Unit));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue