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.
|
||||
List<MoveOrder> incoming = orders
|
||||
.OfType<MoveOrder>()
|
||||
.Where(move => move.Province == order.Unit.Province)
|
||||
.Where(order.IsIncoming)
|
||||
.ToList();
|
||||
this.IsDislodged[order.Unit] = new(order, incoming);
|
||||
|
||||
|
@ -108,9 +108,7 @@ public class MovementDecisions
|
|||
// Find competing moves.
|
||||
List<MoveOrder> competing = orders
|
||||
.OfType<MoveOrder>()
|
||||
.Where(other
|
||||
=> other != move
|
||||
&& other.Province == move.Province)
|
||||
.Where(move.IsCompeting)
|
||||
.ToList();
|
||||
|
||||
// Create the move-related decisions.
|
||||
|
|
|
@ -34,9 +34,20 @@ public class MoveOrder : UnitOrder
|
|||
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)
|
||||
=> 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>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <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
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a constraint that checks for a positive order validation.
|
||||
/// </summary>
|
||||
public static OrderValidationConstraint Valid
|
||||
=> new(true, ValidationReason.Valid);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a constraint that checks for a negative order validation.
|
||||
/// </summary>
|
||||
public static OrderValidationConstraint Invalid(ValidationReason expected)
|
||||
=> new(false, expected);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a constraint that checks for a positive <see cref="IsDislodged"/> decision.
|
||||
/// </summary>
|
||||
public static OrderBinaryAdjudicationConstraint<IsDislodged> Dislodged
|
||||
=> new(true);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a constraint that checks for a negative <see cref="IsDislodged"/> decision.
|
||||
/// </summary>
|
||||
public static OrderBinaryAdjudicationConstraint<IsDislodged> NotDislodged
|
||||
=> new(false);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a constraint that checks for a positive <see cref="DoesMove"/> decision.
|
||||
/// </summary>
|
||||
public static OrderBinaryAdjudicationConstraint<DoesMove> Victorious
|
||||
=> new(true);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a constraint that checks for a negative <see cref="DoesMove"/> decision.
|
||||
/// </summary>
|
||||
public static OrderBinaryAdjudicationConstraint<DoesMove> Repelled
|
||||
=> 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>
|
||||
/// Make the support order target an army.
|
||||
/// </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>
|
||||
/// Make the support order target a fleet.
|
||||
|
@ -157,7 +163,14 @@ public class TestCaseBuilder
|
|||
/// <summary>
|
||||
/// Give the unit an order to support the target's move order.
|
||||
/// </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>
|
||||
|
@ -506,14 +519,18 @@ public class TestCaseBuilder
|
|||
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
|
||||
? this.PowerContext.Power
|
||||
: this.Builder.World.GetPower(powerName);
|
||||
Location location = this.Builder.World.GetLand(provinceName);
|
||||
Season destSeason = season ?? this.SeasonContext.Season;
|
||||
Unit unit = this.Builder.GetOrBuildUnit(
|
||||
power, location, this.SeasonContext.Season, UnitType.Army);
|
||||
power, location, destSeason, UnitType.Army);
|
||||
return new SupportTypeContext(this, unit);
|
||||
}
|
||||
|
||||
|
@ -559,16 +576,20 @@ public class TestCaseBuilder
|
|||
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
|
||||
? this.Builder.World.GetLand(provinceName)
|
||||
: this.Builder.World.GetWater(provinceName, coast);
|
||||
Season targetDestSeason = season ?? this.Target.Season;
|
||||
SupportMoveOrder order = new SupportMoveOrder(
|
||||
this.PowerContext.Power,
|
||||
this.UnitContext.Unit,
|
||||
this.Target,
|
||||
this.SeasonContext.Season,
|
||||
targetDestSeason,
|
||||
destination);
|
||||
this.Builder.OrderList.Add(order);
|
||||
return new OrderDefinedContext<SupportMoveOrder>(this.UnitContext, order);
|
||||
|
|
|
@ -50,4 +50,54 @@ public class TimeTravelTest
|
|||
Unit aMun1 = world.GetUnitAt("Mun", fork.Coord);
|
||||
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