Refactor adjudication into separate adjudication and update steps
This makes it easier to unit test adjudication decisions directly.
This commit is contained in:
parent
36ea621782
commit
6b1b9dce10
|
@ -1,4 +1,4 @@
|
||||||
namespace MultiversalDiplomacy.Adjudicate.Decision;
|
namespace MultiversalDiplomacy.Adjudicate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for adjudication decisions. The decision-based adjudication algorithm is based
|
/// Base class for adjudication decisions. The decision-based adjudication algorithm is based
|
|
@ -0,0 +1,113 @@
|
||||||
|
using MultiversalDiplomacy.Model;
|
||||||
|
using MultiversalDiplomacy.Orders;
|
||||||
|
|
||||||
|
namespace MultiversalDiplomacy.Adjudicate.Decision;
|
||||||
|
|
||||||
|
public class MovementDecisions
|
||||||
|
{
|
||||||
|
public Dictionary<Unit, IsDislodged> IsDislodged { get; }
|
||||||
|
public Dictionary<MoveOrder, HasPath> HasPath { get; }
|
||||||
|
public Dictionary<SupportOrder, GivesSupport> GivesSupport { get; }
|
||||||
|
public Dictionary<Province, HoldStrength> HoldStrength { get; }
|
||||||
|
public Dictionary<MoveOrder, AttackStrength> AttackStrength { get; }
|
||||||
|
public Dictionary<MoveOrder, DefendStrength> DefendStrength { get; }
|
||||||
|
public Dictionary<MoveOrder, PreventStrength> PreventStrength { get; }
|
||||||
|
public Dictionary<MoveOrder, DoesMove> DoesMove { get; }
|
||||||
|
|
||||||
|
public IEnumerable<AdjudicationDecision> Values =>
|
||||||
|
this.IsDislodged.Values.Cast<AdjudicationDecision>()
|
||||||
|
.Concat(this.HasPath.Values)
|
||||||
|
.Concat(this.GivesSupport.Values)
|
||||||
|
.Concat(this.HoldStrength.Values)
|
||||||
|
.Concat(this.AttackStrength.Values)
|
||||||
|
.Concat(this.DefendStrength.Values)
|
||||||
|
.Concat(this.PreventStrength.Values)
|
||||||
|
.Concat(this.DoesMove.Values);
|
||||||
|
|
||||||
|
public MovementDecisions(List<Order> orders)
|
||||||
|
{
|
||||||
|
this.IsDislodged = new();
|
||||||
|
this.HasPath = new();
|
||||||
|
this.GivesSupport = new();
|
||||||
|
this.HoldStrength = new();
|
||||||
|
this.AttackStrength = new();
|
||||||
|
this.DefendStrength = new();
|
||||||
|
this.PreventStrength = new();
|
||||||
|
this.DoesMove = new();
|
||||||
|
|
||||||
|
foreach (UnitOrder order in orders.Cast<UnitOrder>())
|
||||||
|
{
|
||||||
|
// Create a dislodge decision for this unit.
|
||||||
|
List<MoveOrder> incoming = orders
|
||||||
|
.OfType<MoveOrder>()
|
||||||
|
.Where(move => move.Location.Province == order.Unit.Location.Province)
|
||||||
|
.ToList();
|
||||||
|
this.IsDislodged[order.Unit] = new(order, incoming);
|
||||||
|
|
||||||
|
// Ensure a hold strength decision exists. Overwrite any previous once, since it may
|
||||||
|
// have been created without an order by a previous move or support.
|
||||||
|
Province province = order.Unit.Location.Province;
|
||||||
|
this.HoldStrength[province] = new(province, order);
|
||||||
|
|
||||||
|
if (order is MoveOrder move)
|
||||||
|
{
|
||||||
|
// Find supports corresponding to this move.
|
||||||
|
List<SupportMoveOrder> supports = orders
|
||||||
|
.OfType<SupportMoveOrder>()
|
||||||
|
.Where(support => support.IsSupportFor(move))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Determine if this move is a head-to-head battle.
|
||||||
|
MoveOrder? opposingMove = orders
|
||||||
|
.OfType<MoveOrder>()
|
||||||
|
.FirstOrDefault(other => other != null && other.IsOpposing(move), null);
|
||||||
|
|
||||||
|
// Find competing moves.
|
||||||
|
List<MoveOrder> competing = orders
|
||||||
|
.OfType<MoveOrder>()
|
||||||
|
.Where(other => other.Location.Province == move.Location.Province)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Create the move-related decisions.
|
||||||
|
this.HasPath[move] = new(move);
|
||||||
|
this.AttackStrength[move] = new(move, supports, opposingMove);
|
||||||
|
this.DefendStrength[move] = new(move, supports);
|
||||||
|
this.PreventStrength[move] = new(move, supports, opposingMove);
|
||||||
|
this.DoesMove[move] = new(move, opposingMove, competing);
|
||||||
|
|
||||||
|
// Ensure a hold strength decision exists for the destination.
|
||||||
|
Province dest = move.Location.Province;
|
||||||
|
if (!this.HoldStrength.ContainsKey(dest))
|
||||||
|
{
|
||||||
|
this.HoldStrength[dest] = new(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (order is SupportOrder support)
|
||||||
|
{
|
||||||
|
// Create the support decision.
|
||||||
|
this.GivesSupport[support] = new(support, incoming);
|
||||||
|
|
||||||
|
// Ensure a hold strength decision exists for the target's province.
|
||||||
|
Province target = support.Target.Location.Province;
|
||||||
|
if (!this.HoldStrength.ContainsKey(target))
|
||||||
|
{
|
||||||
|
this.HoldStrength[target] = new(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (support is SupportHoldOrder supportHold)
|
||||||
|
{
|
||||||
|
this.HoldStrength[target].Supports.Add(supportHold);
|
||||||
|
}
|
||||||
|
else if (support is SupportMoveOrder supportMove)
|
||||||
|
{
|
||||||
|
// Ensure a hold strength decision exists for the target's destination.
|
||||||
|
Province dest = supportMove.Location.Province;
|
||||||
|
if (!this.HoldStrength.ContainsKey(dest))
|
||||||
|
{
|
||||||
|
this.HoldStrength[dest] = new(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,11 +23,32 @@ public interface IPhaseAdjudicator
|
||||||
public List<OrderValidation> ValidateOrders(World world, List<Order> orders);
|
public List<OrderValidation> ValidateOrders(World world, List<Order> orders);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a list of valid orders, adjudicate the success and failure of the orders. The world
|
/// Given a list of valid orders, adjudicate the success and failure of the orders. The kinds
|
||||||
/// will be updated with new seasons and unit positions and returned alongside the adjudication
|
/// of adjudication decisions returned depends on the phase adjudicator.
|
||||||
/// results.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public (List<OrderAdjudication> results, World updated) AdjudicateOrders(
|
/// <param name="world">The global game state.</param>
|
||||||
World world,
|
/// <param name="orders">
|
||||||
List<Order> orders);
|
/// Orders to adjudicate. The order list should contain only valid orders, as validated by
|
||||||
|
/// <see cref="ValidateOrders"/>, and should contain exactly one order for every unit able to
|
||||||
|
/// be ordered.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A list of adjudication decicions. The decision types will be specific to the phase
|
||||||
|
/// adjudicator and should be comprehensible to that adjudicator's <see cref="Update"/> method.
|
||||||
|
/// </returns>
|
||||||
|
public List<AdjudicationDecision> AdjudicateOrders(World world, List<Order> orders);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a list of adjudications, update the world according to the adjudication results.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="world">The global game state.</param>
|
||||||
|
/// <param name="decisions">
|
||||||
|
/// The results of adjudication. Like <see cref="AdjudicateOrders"/>, all objects to be updated
|
||||||
|
/// should have a relevant adjudication. The adjudication types will be specific to the phase
|
||||||
|
/// adjudicator.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A new copy of the world, updated according to the adjudication.
|
||||||
|
/// </returns>
|
||||||
|
public World UpdateWorld(World world, List<AdjudicationDecision> decisions);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,120 +9,6 @@ namespace MultiversalDiplomacy.Adjudicate;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
{
|
{
|
||||||
private class Decisions
|
|
||||||
{
|
|
||||||
public Dictionary<Unit, IsDislodged> IsDislodged { get; }
|
|
||||||
public Dictionary<MoveOrder, HasPath> HasPath { get; }
|
|
||||||
public Dictionary<SupportOrder, GivesSupport> GivesSupport { get; }
|
|
||||||
public Dictionary<Province, HoldStrength> HoldStrength { get; }
|
|
||||||
public Dictionary<MoveOrder, AttackStrength> AttackStrength { get; }
|
|
||||||
public Dictionary<MoveOrder, DefendStrength> DefendStrength { get; }
|
|
||||||
public Dictionary<MoveOrder, PreventStrength> PreventStrength { get; }
|
|
||||||
public Dictionary<MoveOrder, DoesMove> DoesMove { get; }
|
|
||||||
|
|
||||||
public List<AdjudicationDecision> UnresolvedDecisions { get; }
|
|
||||||
|
|
||||||
public Decisions(List<Order> orders)
|
|
||||||
{
|
|
||||||
this.IsDislodged = new();
|
|
||||||
this.HasPath = new();
|
|
||||||
this.GivesSupport = new();
|
|
||||||
this.HoldStrength = new();
|
|
||||||
this.AttackStrength = new();
|
|
||||||
this.DefendStrength = new();
|
|
||||||
this.PreventStrength = new();
|
|
||||||
this.DoesMove = new();
|
|
||||||
|
|
||||||
foreach (UnitOrder order in orders.Cast<UnitOrder>())
|
|
||||||
{
|
|
||||||
// Create a dislodge decision for this unit.
|
|
||||||
List<MoveOrder> incoming = orders
|
|
||||||
.OfType<MoveOrder>()
|
|
||||||
.Where(move => move.Location.Province == order.Unit.Location.Province)
|
|
||||||
.ToList();
|
|
||||||
this.IsDislodged[order.Unit] = new(order, incoming);
|
|
||||||
|
|
||||||
// Ensure a hold strength decision exists.
|
|
||||||
Province province = order.Unit.Location.Province;
|
|
||||||
if (!this.HoldStrength.ContainsKey(province))
|
|
||||||
{
|
|
||||||
this.HoldStrength[province] = new(province, order);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order is MoveOrder move)
|
|
||||||
{
|
|
||||||
// Find supports corresponding to this move.
|
|
||||||
List<SupportMoveOrder> supports = orders
|
|
||||||
.OfType<SupportMoveOrder>()
|
|
||||||
.Where(support => support.IsSupportFor(move))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Determine if this move is a head-to-head battle.
|
|
||||||
MoveOrder? opposingMove = orders
|
|
||||||
.OfType<MoveOrder>()
|
|
||||||
.FirstOrDefault(other => other != null && other.IsOpposing(move), null);
|
|
||||||
|
|
||||||
// Find competing moves.
|
|
||||||
List<MoveOrder> competing = orders
|
|
||||||
.OfType<MoveOrder>()
|
|
||||||
.Where(other => other.Location.Province == move.Location.Province)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Create the move-related decisions.
|
|
||||||
this.HasPath[move] = new(move);
|
|
||||||
this.AttackStrength[move] = new(move, supports, opposingMove);
|
|
||||||
this.DefendStrength[move] = new(move, supports);
|
|
||||||
this.PreventStrength[move] = new(move, supports, opposingMove);
|
|
||||||
this.DoesMove[move] = new(move, opposingMove, competing);
|
|
||||||
|
|
||||||
// Ensure a hold strength decision exists for the destination.
|
|
||||||
Province dest = move.Location.Province;
|
|
||||||
if (!this.HoldStrength.ContainsKey(dest))
|
|
||||||
{
|
|
||||||
this.HoldStrength[dest] = new(dest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (order is SupportOrder support)
|
|
||||||
{
|
|
||||||
// Create the support decision.
|
|
||||||
this.GivesSupport[support] = new(support, incoming);
|
|
||||||
|
|
||||||
// Ensure a hold strength decision exists for the target's province.
|
|
||||||
Province target = support.Target.Location.Province;
|
|
||||||
if (!this.HoldStrength.ContainsKey(target))
|
|
||||||
{
|
|
||||||
this.HoldStrength[target] = new(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (support is SupportHoldOrder supportHold)
|
|
||||||
{
|
|
||||||
this.HoldStrength[target].Supports.Add(supportHold);
|
|
||||||
}
|
|
||||||
else if (support is SupportMoveOrder supportMove)
|
|
||||||
{
|
|
||||||
// Ensure a hold strength decision exists for the target's destination.
|
|
||||||
Province dest = supportMove.Location.Province;
|
|
||||||
if (!this.HoldStrength.ContainsKey(dest))
|
|
||||||
{
|
|
||||||
this.HoldStrength[dest] = new(dest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.UnresolvedDecisions = new List<AdjudicationDecision>()
|
|
||||||
.Concat(this.IsDislodged.Values)
|
|
||||||
.Concat(this.HasPath.Values)
|
|
||||||
.Concat(this.GivesSupport.Values)
|
|
||||||
.Concat(this.HoldStrength.Values)
|
|
||||||
.Concat(this.AttackStrength.Values)
|
|
||||||
.Concat(this.DefendStrength.Values)
|
|
||||||
.Concat(this.PreventStrength.Values)
|
|
||||||
.Concat(this.DoesMove.Values)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IPhaseAdjudicator Instance { get; } = new MovementPhaseAdjudicator();
|
public static IPhaseAdjudicator Instance { get; } = new MovementPhaseAdjudicator();
|
||||||
|
|
||||||
public List<OrderValidation> ValidateOrders(World world, List<Order> orders)
|
public List<OrderValidation> ValidateOrders(World world, List<Order> orders)
|
||||||
|
@ -377,35 +263,38 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
return validationResults;
|
return validationResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
public (List<OrderAdjudication> results, World updated) AdjudicateOrders(
|
public List<AdjudicationDecision> AdjudicateOrders(World world, List<Order> orders)
|
||||||
World world,
|
|
||||||
List<Order> orders)
|
|
||||||
{
|
{
|
||||||
// Define all adjudication decisions to be made.
|
// Define all adjudication decisions to be made.
|
||||||
Decisions decisions = new Decisions(orders);
|
MovementDecisions decisions = new(orders);
|
||||||
|
|
||||||
|
List<AdjudicationDecision> unresolvedDecisions = decisions.Values.ToList();
|
||||||
|
|
||||||
// Adjudicate all decisions.
|
// Adjudicate all decisions.
|
||||||
bool progress = false;
|
bool progress = false;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
progress = false;
|
progress = false;
|
||||||
foreach (AdjudicationDecision decision in decisions.UnresolvedDecisions.ToList())
|
foreach (AdjudicationDecision decision in unresolvedDecisions.ToList())
|
||||||
{
|
{
|
||||||
progress |= ResolveDecision(decision, world, decisions);
|
progress |= ResolveDecision(decision, world, decisions);
|
||||||
if (decision.Resolved) decisions.UnresolvedDecisions.Remove(decision);
|
if (decision.Resolved) unresolvedDecisions.Remove(decision);
|
||||||
}
|
}
|
||||||
} while (progress);
|
} while (progress);
|
||||||
|
|
||||||
if (decisions.UnresolvedDecisions.Any())
|
if (unresolvedDecisions.Any())
|
||||||
{
|
{
|
||||||
throw new ApplicationException("Some orders not resolved!");
|
throw new ApplicationException("Some orders not resolved!");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<OrderAdjudication> adjudications = new();
|
return decisions.Values.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
// All orders other than move orders are hold orders with extra steps.
|
public World UpdateWorld(World world, List<AdjudicationDecision> decisions)
|
||||||
ILookup<bool, Order> moveOrders = orders.ToLookup(order => order is MoveOrder);
|
{
|
||||||
List<Order> nonMoveOrders = moveOrders[false].ToList();
|
Dictionary<MoveOrder, DoesMove> moves = decisions
|
||||||
|
.OfType<DoesMove>()
|
||||||
|
.ToDictionary(dm => dm.Order);
|
||||||
|
|
||||||
// All moves to a particular season in a single phase result in the same future. Keep a
|
// All moves to a particular season in a single phase result in the same future. Keep a
|
||||||
// record of when a future season has been created.
|
// record of when a future season has been created.
|
||||||
|
@ -413,34 +302,35 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
List<Unit> createdUnits = new();
|
List<Unit> createdUnits = new();
|
||||||
List<RetreatingUnit> retreats = new();
|
List<RetreatingUnit> retreats = new();
|
||||||
|
|
||||||
// For each move order with a successful does-move decision, ensure the future exists and
|
// Successful move orders result in the unit moving to the destination and creating a new
|
||||||
// progress the unit to the future.
|
// future, while unsuccessful move orders are processed the same way as non-move orders.
|
||||||
foreach (MoveOrder move in moveOrders[true].Cast<MoveOrder>())
|
foreach (DoesMove doesMove in moves.Values)
|
||||||
{
|
{
|
||||||
DoesMove doesMove = decisions.DoesMove[move];
|
|
||||||
if (doesMove.Outcome == true)
|
if (doesMove.Outcome == true)
|
||||||
{
|
{
|
||||||
if (!createdFutures.TryGetValue(move.Season, out Season? future))
|
if (!createdFutures.TryGetValue(doesMove.Order.Season, out Season? future))
|
||||||
{
|
{
|
||||||
// A timeline doesn't fork unless it already has a continuation.
|
// A timeline that doesn't have a future yet simply continues. Otherwise, it forks.
|
||||||
future = move.Season.Futures.Any()
|
future = !doesMove.Order.Season.Futures.Any()
|
||||||
? move.Season.MakeNext()
|
? doesMove.Order.Season.MakeNext()
|
||||||
: move.Season.MakeFork();
|
: doesMove.Order.Season.MakeFork();
|
||||||
createdFutures[move.Season] = future;
|
createdFutures[doesMove.Order.Season] = future;
|
||||||
}
|
}
|
||||||
createdUnits.Add(move.Unit.Next(move.Location, future));
|
createdUnits.Add(doesMove.Order.Unit.Next(doesMove.Order.Location, future));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the move order failed, the moving unit will stay put, which puts it in the
|
|
||||||
// same bucket as the hold orders.
|
|
||||||
nonMoveOrders.Add(move);
|
|
||||||
}
|
|
||||||
adjudications.Add(new(move, doesMove.Outcome == true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (UnitOrder order in nonMoveOrders.Cast<UnitOrder>())
|
// Process unsuccessful moves, all holds, and all supports.
|
||||||
|
foreach (IsDislodged isDislodged in decisions.OfType<IsDislodged>())
|
||||||
{
|
{
|
||||||
|
UnitOrder order = isDislodged.Order;
|
||||||
|
|
||||||
|
// Skip the move orders that were processed above.
|
||||||
|
if (order is MoveOrder move && moves[move].Outcome == true)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!createdFutures.TryGetValue(order.Unit.Season, out Season? future))
|
if (!createdFutures.TryGetValue(order.Unit.Season, out Season? future))
|
||||||
{
|
{
|
||||||
// Any unit given an order is, by definition, at the front of a timeline.
|
// Any unit given an order is, by definition, at the front of a timeline.
|
||||||
|
@ -449,7 +339,6 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each stationary unit that wasn't dislodged, continue it into the future.
|
// For each stationary unit that wasn't dislodged, continue it into the future.
|
||||||
IsDislodged isDislodged = decisions.IsDislodged[order.Unit];
|
|
||||||
if (isDislodged.Outcome == false)
|
if (isDislodged.Outcome == false)
|
||||||
{
|
{
|
||||||
createdUnits.Add(order.Unit.Next(order.Unit.Location, future));
|
createdUnits.Add(order.Unit.Next(order.Unit.Location, future));
|
||||||
|
@ -464,15 +353,6 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
RetreatingUnit retreat = new(order.Unit, validRetreats);
|
RetreatingUnit retreat = new(order.Unit, validRetreats);
|
||||||
retreats.Add(retreat);
|
retreats.Add(retreat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (order is SupportOrder support)
|
|
||||||
{
|
|
||||||
adjudications.Add(new(support, decisions.GivesSupport[support].Outcome == true));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
adjudications.Add(new(order, isDislodged.Outcome == false));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO provide more structured information about order outcomes
|
// TODO provide more structured information about order outcomes
|
||||||
|
@ -482,10 +362,13 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
.WithUnits(world.Units.Concat(createdUnits))
|
.WithUnits(world.Units.Concat(createdUnits))
|
||||||
.WithRetreats(retreats);
|
.WithRetreats(retreats);
|
||||||
|
|
||||||
return (adjudications, updated);
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ResolveDecision(AdjudicationDecision decision, World world, Decisions decisions)
|
private bool ResolveDecision(
|
||||||
|
AdjudicationDecision decision,
|
||||||
|
World world,
|
||||||
|
MovementDecisions decisions)
|
||||||
=> decision.Resolved ? false : decision switch
|
=> decision.Resolved ? false : decision switch
|
||||||
{
|
{
|
||||||
IsDislodged d => ResolveIsUnitDislodged(d, world, decisions),
|
IsDislodged d => ResolveIsUnitDislodged(d, world, decisions),
|
||||||
|
@ -499,7 +382,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
_ => throw new NotSupportedException($"Unknown decision type: {decision.GetType()}")
|
_ => throw new NotSupportedException($"Unknown decision type: {decision.GetType()}")
|
||||||
};
|
};
|
||||||
|
|
||||||
private bool ResolveIsUnitDislodged(IsDislodged decision, World world, Decisions decisions)
|
private bool ResolveIsUnitDislodged(
|
||||||
|
IsDislodged decision,
|
||||||
|
World world,
|
||||||
|
MovementDecisions decisions)
|
||||||
{
|
{
|
||||||
bool progress = false;
|
bool progress = false;
|
||||||
|
|
||||||
|
@ -557,7 +443,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ResolveDoesMoveHavePath(HasPath decision, World world, Decisions decisions)
|
private bool ResolveDoesMoveHavePath(
|
||||||
|
HasPath decision,
|
||||||
|
World world,
|
||||||
|
MovementDecisions decisions)
|
||||||
{
|
{
|
||||||
bool progress= false;
|
bool progress= false;
|
||||||
|
|
||||||
|
@ -583,7 +472,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
throw new NotImplementedException(); // TODO
|
throw new NotImplementedException(); // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ResolveIsSupportGiven(GivesSupport decision, World world, Decisions decisions)
|
private bool ResolveIsSupportGiven(
|
||||||
|
GivesSupport decision,
|
||||||
|
World world,
|
||||||
|
MovementDecisions decisions)
|
||||||
{
|
{
|
||||||
bool progress = false;
|
bool progress = false;
|
||||||
|
|
||||||
|
@ -632,7 +524,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ResolveHoldStrength(HoldStrength decision, World world, Decisions decisions)
|
private bool ResolveHoldStrength(
|
||||||
|
HoldStrength decision,
|
||||||
|
World world,
|
||||||
|
MovementDecisions decisions)
|
||||||
{
|
{
|
||||||
bool progress = false;
|
bool progress = false;
|
||||||
|
|
||||||
|
@ -670,7 +565,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ResolveAttackStrength(AttackStrength decision, World world, Decisions decisions)
|
private bool ResolveAttackStrength(
|
||||||
|
AttackStrength decision,
|
||||||
|
World world,
|
||||||
|
MovementDecisions decisions)
|
||||||
{
|
{
|
||||||
bool progress = false;
|
bool progress = false;
|
||||||
|
|
||||||
|
@ -768,7 +666,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ResolveDefendStrength(DefendStrength decision, World world, Decisions decisions)
|
private bool ResolveDefendStrength(
|
||||||
|
DefendStrength decision,
|
||||||
|
World world,
|
||||||
|
MovementDecisions decisions)
|
||||||
{
|
{
|
||||||
bool progress = false;
|
bool progress = false;
|
||||||
|
|
||||||
|
@ -788,7 +689,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ResolvePreventStrength(PreventStrength decision, World world, Decisions decisions)
|
private bool ResolvePreventStrength(
|
||||||
|
PreventStrength decision,
|
||||||
|
World world,
|
||||||
|
MovementDecisions decisions)
|
||||||
{
|
{
|
||||||
bool progress = false;
|
bool progress = false;
|
||||||
|
|
||||||
|
@ -837,7 +741,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ResolveDoesUnitMove(DoesMove decision, World world, Decisions decisions)
|
private bool ResolveDoesUnitMove(
|
||||||
|
DoesMove decision,
|
||||||
|
World world,
|
||||||
|
MovementDecisions decisions)
|
||||||
{
|
{
|
||||||
bool progress = false;
|
bool progress = false;
|
||||||
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
using MultiversalDiplomacy.Orders;
|
|
||||||
|
|
||||||
namespace MultiversalDiplomacy.Adjudicate;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the result of adjudicating an order.
|
|
||||||
/// </summary>
|
|
||||||
public class OrderAdjudication
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The order that was adjudicated.
|
|
||||||
/// </summary>
|
|
||||||
public Order Order { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the order succeeded or failed.
|
|
||||||
/// </summary>
|
|
||||||
public bool Success { get; }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// The reason for the order's outcome.
|
|
||||||
// /// </summary>
|
|
||||||
// public string Reason { get; }
|
|
||||||
|
|
||||||
public OrderAdjudication(Order order, bool success/*, string reason*/)
|
|
||||||
{
|
|
||||||
this.Order = order;
|
|
||||||
this.Success = success;
|
|
||||||
// this.Reason = reason;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class OrderAdjudicationExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Create an <see cref="OrderAdjudication"/> accepting this order.
|
|
||||||
/// </summary>
|
|
||||||
public static OrderAdjudication Succeed(this Order order)
|
|
||||||
=> new OrderAdjudication(order, true);
|
|
||||||
}
|
|
|
@ -17,7 +17,7 @@ public class DATC_A
|
||||||
setup["England"]
|
setup["England"]
|
||||||
.Fleet("North Sea").MovesTo("Picardy").GetReference(out var order);
|
.Fleet("North Sea").MovesTo("Picardy").GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Invalid(ValidationReason.UnreachableDestination));
|
Assert.That(order.Validation, Is.Invalid(ValidationReason.UnreachableDestination));
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ public class DATC_A
|
||||||
setup["Germany"]
|
setup["Germany"]
|
||||||
.Fleet("Kiel").MovesTo("Kiel").GetReference(out var order);
|
.Fleet("Kiel").MovesTo("Kiel").GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
|
Assert.That(order.Validation, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ public class DATC_A
|
||||||
.Fleet("London").MovesTo("Yorkshire")
|
.Fleet("London").MovesTo("Yorkshire")
|
||||||
.Army("Wales").Supports.Fleet("London").MoveTo("Yorkshire");
|
.Army("Wales").Supports.Fleet("London").MoveTo("Yorkshire");
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(orderNth.Validation, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
|
Assert.That(orderNth.Validation, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
|
||||||
Assert.That(orderYor.Validation, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
|
Assert.That(orderYor.Validation, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
|
||||||
|
@ -91,7 +91,7 @@ public class DATC_A
|
||||||
["Germany"]
|
["Germany"]
|
||||||
.Fleet("London", powerName: "England").MovesTo("North Sea").GetReference(out var order);
|
.Fleet("London", powerName: "England").MovesTo("North Sea").GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Invalid(ValidationReason.InvalidUnitForPower));
|
Assert.That(order.Validation, Is.Invalid(ValidationReason.InvalidUnitForPower));
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ public class DATC_A
|
||||||
.Fleet("London").MovesTo("Belgium")
|
.Fleet("London").MovesTo("Belgium")
|
||||||
.Fleet("North Sea").Convoys.Army("London").To("Belgium").GetReference(out var order);
|
.Fleet("North Sea").Convoys.Army("London").To("Belgium").GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Invalid(ValidationReason.InvalidOrderTypeForUnit));
|
Assert.That(order.Validation, Is.Invalid(ValidationReason.InvalidOrderTypeForUnit));
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ public class DATC_A
|
||||||
["Austria"]
|
["Austria"]
|
||||||
.Fleet("Trieste").Supports.Fleet("Trieste").Hold().GetReference(out var order);
|
.Fleet("Trieste").Supports.Fleet("Trieste").Hold().GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Invalid(ValidationReason.NoSelfSupport));
|
Assert.That(order.Validation, Is.Invalid(ValidationReason.NoSelfSupport));
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ public class DATC_A
|
||||||
["Italy"]
|
["Italy"]
|
||||||
.Fleet("Rome").MovesTo("Venice").GetReference(out var order);
|
.Fleet("Rome").MovesTo("Venice").GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Invalid(ValidationReason.UnreachableDestination));
|
Assert.That(order.Validation, Is.Invalid(ValidationReason.UnreachableDestination));
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ public class DATC_A
|
||||||
.Army("Apulia").MovesTo("Venice")
|
.Army("Apulia").MovesTo("Venice")
|
||||||
.Fleet("Rome").Supports.Army("Apulia").MoveTo("Venice").GetReference(out var order);
|
.Fleet("Rome").Supports.Army("Apulia").MoveTo("Venice").GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Invalid(ValidationReason.UnreachableSupport));
|
Assert.That(order.Validation, Is.Invalid(ValidationReason.UnreachableSupport));
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ public class MovementAdjudicatorTest
|
||||||
setup["Germany"]
|
setup["Germany"]
|
||||||
.Army("Mun").Holds().GetReference(out var order);
|
.Army("Mun").Holds().GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Valid, "Unexpected validation result");
|
Assert.That(order.Validation, Is.Valid, "Unexpected validation result");
|
||||||
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
|
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
|
||||||
|
@ -28,7 +28,7 @@ public class MovementAdjudicatorTest
|
||||||
setup["Germany"]
|
setup["Germany"]
|
||||||
.Army("Mun").MovesTo("Tyr").GetReference(out var order);
|
.Army("Mun").MovesTo("Tyr").GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Valid, "Unexpected validation result");
|
Assert.That(order.Validation, Is.Valid, "Unexpected validation result");
|
||||||
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
|
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
|
||||||
|
@ -41,7 +41,7 @@ public class MovementAdjudicatorTest
|
||||||
setup["Germany"]
|
setup["Germany"]
|
||||||
.Fleet("Nth").Convoys.Army("Hol").To("Lon").GetReference(out var order);
|
.Fleet("Nth").Convoys.Army("Hol").To("Lon").GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Valid, "Unexpected validation result");
|
Assert.That(order.Validation, Is.Valid, "Unexpected validation result");
|
||||||
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
|
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
|
||||||
|
@ -54,7 +54,7 @@ public class MovementAdjudicatorTest
|
||||||
setup["Germany"]
|
setup["Germany"]
|
||||||
.Army("Mun").Supports.Army("Kie").Hold().GetReference(out var order);
|
.Army("Mun").Supports.Army("Kie").Hold().GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Valid, "Unexpected validation result");
|
Assert.That(order.Validation, Is.Valid, "Unexpected validation result");
|
||||||
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
|
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
|
||||||
|
@ -67,7 +67,7 @@ public class MovementAdjudicatorTest
|
||||||
setup["Germany"]
|
setup["Germany"]
|
||||||
.Army("Mun").Supports.Army("Kie").MoveTo("Ber").GetReference(out var order);
|
.Army("Mun").Supports.Army("Kie").MoveTo("Ber").GetReference(out var order);
|
||||||
|
|
||||||
setup.ValidateOrders(new MovementPhaseAdjudicator());
|
setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
|
||||||
|
|
||||||
Assert.That(order.Validation, Is.Valid, "Unexpected validation result");
|
Assert.That(order.Validation, Is.Valid, "Unexpected validation result");
|
||||||
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
|
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using MultiversalDiplomacy.Adjudicate;
|
using MultiversalDiplomacy.Adjudicate;
|
||||||
|
using MultiversalDiplomacy.Adjudicate.Decision;
|
||||||
|
using MultiversalDiplomacy.Model;
|
||||||
using MultiversalDiplomacy.Orders;
|
using MultiversalDiplomacy.Orders;
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
@ -61,6 +63,45 @@ public class OrderReference<OrderType> where OrderType : Order
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<AdjudicationDecision> Adjudications
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (this.Builder.AdjudicationResults == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Adjudication has not been done yet");
|
||||||
|
}
|
||||||
|
var adjudications = this.Builder.AdjudicationResults.Where(ad => ad switch
|
||||||
|
{
|
||||||
|
IsDislodged dislodged => dislodged.Order == this.Order,
|
||||||
|
DoesMove moves => moves.Order == this.Order,
|
||||||
|
_ => false,
|
||||||
|
}).ToList();
|
||||||
|
return adjudications;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RetreatingUnit? Retreat
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (this.Builder.AdjudicationResults == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Adjudication has not been done yet");
|
||||||
|
}
|
||||||
|
if (this.Order is UnitOrder unitOrder)
|
||||||
|
{
|
||||||
|
var retreat = this.Builder.World.RetreatingUnits.Where(
|
||||||
|
ru => ru.Unit == unitOrder.Unit);
|
||||||
|
if (retreat.Any())
|
||||||
|
{
|
||||||
|
return retreat.Single();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public OrderReference(TestCaseBuilder builder, OrderType order)
|
public OrderReference(TestCaseBuilder builder, OrderType order)
|
||||||
{
|
{
|
||||||
this.Builder = builder;
|
this.Builder = builder;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using MultiversalDiplomacy.Adjudicate;
|
using MultiversalDiplomacy.Adjudicate;
|
||||||
|
using MultiversalDiplomacy.Adjudicate.Decision;
|
||||||
using MultiversalDiplomacy.Model;
|
using MultiversalDiplomacy.Model;
|
||||||
using MultiversalDiplomacy.Orders;
|
using MultiversalDiplomacy.Orders;
|
||||||
|
|
||||||
|
@ -6,24 +7,79 @@ namespace MultiversalDiplomacyTests;
|
||||||
|
|
||||||
public class TestAdjudicator : IPhaseAdjudicator
|
public class TestAdjudicator : IPhaseAdjudicator
|
||||||
{
|
{
|
||||||
public static Func<World, List<Order>, List<OrderValidation>> RubberStamp =
|
public static List<OrderValidation> RubberStamp(World world, List<Order> orders)
|
||||||
(world, orders) => orders.Select(o => o.Validate(ValidationReason.Valid)).ToList();
|
{
|
||||||
|
return orders.Select(o => o.Validate(ValidationReason.Valid)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<AdjudicationDecision> NoMoves(
|
||||||
|
World world,
|
||||||
|
List<Order> orders)
|
||||||
|
{
|
||||||
|
List<AdjudicationDecision> results = new();
|
||||||
|
foreach (Order order in orders)
|
||||||
|
{
|
||||||
|
switch (order)
|
||||||
|
{
|
||||||
|
case MoveOrder move:
|
||||||
|
{
|
||||||
|
var doesMove = new DoesMove(move, null, new List<MoveOrder>());
|
||||||
|
doesMove.Update(false);
|
||||||
|
results.Add(doesMove);
|
||||||
|
var dislodged = new IsDislodged(move, new List<MoveOrder>());
|
||||||
|
dislodged.Update(false);
|
||||||
|
results.Add(dislodged);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
if (order is not UnitOrder unitOrder)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(order.GetType().Name);
|
||||||
|
}
|
||||||
|
var dislodged = new IsDislodged(unitOrder, new List<MoveOrder>());
|
||||||
|
dislodged.Update(false);
|
||||||
|
results.Add(dislodged);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static World Noop(World world, List<AdjudicationDecision> decisions)
|
||||||
|
=> world;
|
||||||
|
|
||||||
|
private static List<OrderValidation> NoValidate(World world, List<Order> orders)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
|
||||||
|
private static List<AdjudicationDecision> NoAdjudicate(World world, List<Order> orders)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
|
||||||
|
private static World NoUpdate(World world, List<AdjudicationDecision> decisions)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
|
||||||
private Func<World, List<Order>, List<OrderValidation>> ValidateOrdersCallback;
|
private Func<World, List<Order>, List<OrderValidation>> ValidateOrdersCallback;
|
||||||
|
private Func<World, List<Order>, List<AdjudicationDecision>> AdjudicateOrdersCallback;
|
||||||
|
private Func<World, List<AdjudicationDecision>, World> UpdateWorldCallback;
|
||||||
|
|
||||||
public TestAdjudicator(
|
public TestAdjudicator(
|
||||||
Func<World, List<Order>, List<OrderValidation>> validateOrdersCallback)
|
Func<World, List<Order>, List<OrderValidation>>? validate = null,
|
||||||
|
Func<World, List<Order>, List<AdjudicationDecision>>? adjudicate = null,
|
||||||
|
Func<World, List<AdjudicationDecision>, World>? update = null)
|
||||||
{
|
{
|
||||||
this.ValidateOrdersCallback = validateOrdersCallback;
|
this.ValidateOrdersCallback = validate ?? NoValidate;
|
||||||
|
this.AdjudicateOrdersCallback = adjudicate ?? NoAdjudicate;
|
||||||
|
this.UpdateWorldCallback = update ?? NoUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OrderValidation> ValidateOrders(World world, List<Order> orders)
|
public List<OrderValidation> ValidateOrders(World world, List<Order> orders)
|
||||||
=> this.ValidateOrdersCallback.Invoke(world, orders);
|
=> this.ValidateOrdersCallback.Invoke(world, orders);
|
||||||
|
|
||||||
public (List<OrderAdjudication> results, World updated) AdjudicateOrders(
|
public List<AdjudicationDecision> AdjudicateOrders(World world, List<Order> orders)
|
||||||
World world,
|
=> this.AdjudicateOrdersCallback(world, orders);
|
||||||
List<Order> orders)
|
|
||||||
{
|
public World UpdateWorld(World world, List<AdjudicationDecision> decisions)
|
||||||
throw new NotImplementedException();
|
=> this.UpdateWorldCallback(world, decisions);
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -161,6 +161,7 @@ public class TestCaseBuilder
|
||||||
private List<Order> OrderList;
|
private List<Order> OrderList;
|
||||||
private Season Season;
|
private Season Season;
|
||||||
public List<OrderValidation>? ValidationResults { get; private set; }
|
public List<OrderValidation>? ValidationResults { get; private set; }
|
||||||
|
public List<AdjudicationDecision>? AdjudicationResults { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a test case builder that will operate on a world.
|
/// Create a test case builder that will operate on a world.
|
||||||
|
@ -172,6 +173,7 @@ public class TestCaseBuilder
|
||||||
this.Orders = new(this.OrderList);
|
this.Orders = new(this.OrderList);
|
||||||
this.Season = season ?? this.World.Seasons.First();
|
this.Season = season ?? this.World.Seasons.First();
|
||||||
this.ValidationResults = null;
|
this.ValidationResults = null;
|
||||||
|
this.AdjudicationResults = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -225,6 +227,32 @@ public class TestCaseBuilder
|
||||||
return this.ValidationResults;
|
return this.ValidationResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<AdjudicationDecision> AdjudicateOrders(IPhaseAdjudicator adjudicator)
|
||||||
|
{
|
||||||
|
if (this.ValidationResults == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot adjudicate before validation");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Order> orders = this.ValidationResults
|
||||||
|
.Where(validation => validation.Valid)
|
||||||
|
.Select(validation => validation.Order)
|
||||||
|
.ToList();
|
||||||
|
this.AdjudicationResults = adjudicator.AdjudicateOrders(this.World, orders);
|
||||||
|
return this.AdjudicationResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public World UpdateWorld(IPhaseAdjudicator adjudicator)
|
||||||
|
{
|
||||||
|
if (this.AdjudicationResults == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot update before adjudication");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.World = adjudicator.UpdateWorld(this.World, this.AdjudicationResults);
|
||||||
|
return this.World;
|
||||||
|
}
|
||||||
|
|
||||||
private class PowerContext : IPowerContext
|
private class PowerContext : IPowerContext
|
||||||
{
|
{
|
||||||
public TestCaseBuilder Builder;
|
public TestCaseBuilder Builder;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using MultiversalDiplomacy.Adjudicate;
|
using MultiversalDiplomacy.Adjudicate;
|
||||||
|
using MultiversalDiplomacy.Adjudicate.Decision;
|
||||||
using MultiversalDiplomacy.Model;
|
using MultiversalDiplomacy.Model;
|
||||||
using MultiversalDiplomacy.Orders;
|
using MultiversalDiplomacy.Orders;
|
||||||
|
|
||||||
|
@ -117,7 +118,7 @@ class TestCaseBuilderTest
|
||||||
[Test]
|
[Test]
|
||||||
public void BuilderProvidesReferencesForValidation()
|
public void BuilderProvidesReferencesForValidation()
|
||||||
{
|
{
|
||||||
IPhaseAdjudicator rubberStamp = new TestAdjudicator(TestAdjudicator.RubberStamp);
|
IPhaseAdjudicator rubberStamp = new TestAdjudicator(validate: TestAdjudicator.RubberStamp);
|
||||||
|
|
||||||
TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason());
|
TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason());
|
||||||
setup["Germany"]
|
setup["Germany"]
|
||||||
|
@ -133,13 +134,13 @@ class TestCaseBuilderTest
|
||||||
Is.EqualTo(setup.World.GetLand("Mun")),
|
Is.EqualTo(setup.World.GetLand("Mun")),
|
||||||
"Wrong unit");
|
"Wrong unit");
|
||||||
|
|
||||||
Assert.That<OrderValidation>(
|
Assert.That(
|
||||||
() => orderMun.Validation,
|
code: () => _ = orderMun.Validation,
|
||||||
Throws.Exception,
|
Throws.Exception,
|
||||||
"Validation property should be inaccessible before validation actually happens");
|
"Validation property should be inaccessible before validation actually happens");
|
||||||
setup.ValidateOrders(rubberStamp);
|
setup.ValidateOrders(rubberStamp);
|
||||||
Assert.That<OrderValidation>(
|
Assert.That(
|
||||||
() => orderMun.Validation,
|
code: () => _ = orderMun.Validation,
|
||||||
Throws.Nothing,
|
Throws.Nothing,
|
||||||
"Validation property should be accessible after validation");
|
"Validation property should be accessible after validation");
|
||||||
|
|
||||||
|
@ -156,4 +157,60 @@ class TestCaseBuilderTest
|
||||||
Is.EqualTo(ValidationReason.Valid),
|
Is.EqualTo(ValidationReason.Valid),
|
||||||
"Unexpected validation reason");
|
"Unexpected validation reason");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void BuilderProvidesReferencesForAdjudication()
|
||||||
|
{
|
||||||
|
IPhaseAdjudicator rubberStamp = new TestAdjudicator(
|
||||||
|
validate: TestAdjudicator.RubberStamp,
|
||||||
|
adjudicate: TestAdjudicator.NoMoves);
|
||||||
|
|
||||||
|
TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason());
|
||||||
|
setup["Germany"]
|
||||||
|
.Army("Mun").Holds().GetReference(out var orderMun);
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
code: () => _ = orderMun.Adjudications,
|
||||||
|
Throws.Exception,
|
||||||
|
"Adjudication property should be inaccessible before validation");
|
||||||
|
Assert.That(
|
||||||
|
code: () => _ = orderMun.Retreat,
|
||||||
|
Throws.Exception,
|
||||||
|
"Retreat property should be inaccessible before validation");
|
||||||
|
|
||||||
|
setup.ValidateOrders(rubberStamp);
|
||||||
|
Assert.That(
|
||||||
|
code: () => _ = orderMun.Adjudications,
|
||||||
|
Throws.Exception,
|
||||||
|
"Adjudication property should be inaccessible before adjudication");
|
||||||
|
Assert.That(
|
||||||
|
code: () => _ = orderMun.Retreat,
|
||||||
|
Throws.Exception,
|
||||||
|
"Retreat property should be inaccessible before adjudication");
|
||||||
|
|
||||||
|
var decisions = setup.AdjudicateOrders(rubberStamp);
|
||||||
|
Assert.That(
|
||||||
|
code: () => _ = orderMun.Adjudications,
|
||||||
|
Throws.Nothing,
|
||||||
|
"Adjudication property should be accessible after adjudication");
|
||||||
|
Assert.That(
|
||||||
|
code: () => _ = orderMun.Retreat,
|
||||||
|
Throws.Nothing,
|
||||||
|
"Retreat property should be accessible after validation");
|
||||||
|
|
||||||
|
Assert.That(orderMun.Retreat, Is.Null, "Noop adjudicator shouldn't cause retreats");
|
||||||
|
Assert.That(
|
||||||
|
orderMun.Adjudications.Count,
|
||||||
|
Is.EqualTo(1),
|
||||||
|
"Unexpected number of adjudications");
|
||||||
|
AdjudicationDecision decision = orderMun.Adjudications.First();
|
||||||
|
Assert.That(decision.Resolved, Is.True, "Unexpected unresolved decision");
|
||||||
|
Assert.That(
|
||||||
|
decision,
|
||||||
|
Is.AssignableTo<IsDislodged>(),
|
||||||
|
"Noop adjudicator should provide a dislodge decision for a hold");
|
||||||
|
CollectionAssert.Contains(
|
||||||
|
decisions,
|
||||||
|
decision,
|
||||||
|
"Expected the adjudicated decision to be provided by the order reference");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue