diff --git a/MultiversalDiplomacy/Adjudicate/Decision/AdjudicationDecision.cs b/MultiversalDiplomacy/Adjudicate/AdjudicationDecision.cs
similarity index 89%
rename from MultiversalDiplomacy/Adjudicate/Decision/AdjudicationDecision.cs
rename to MultiversalDiplomacy/Adjudicate/AdjudicationDecision.cs
index 2e365e1..15ea90d 100644
--- a/MultiversalDiplomacy/Adjudicate/Decision/AdjudicationDecision.cs
+++ b/MultiversalDiplomacy/Adjudicate/AdjudicationDecision.cs
@@ -1,4 +1,4 @@
-namespace MultiversalDiplomacy.Adjudicate.Decision;
+namespace MultiversalDiplomacy.Adjudicate;
///
/// Base class for adjudication decisions. The decision-based adjudication algorithm is based
diff --git a/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs b/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs
new file mode 100644
index 0000000..dc79957
--- /dev/null
+++ b/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs
@@ -0,0 +1,113 @@
+using MultiversalDiplomacy.Model;
+using MultiversalDiplomacy.Orders;
+
+namespace MultiversalDiplomacy.Adjudicate.Decision;
+
+public class MovementDecisions
+{
+ public Dictionary IsDislodged { get; }
+ public Dictionary HasPath { get; }
+ public Dictionary GivesSupport { get; }
+ public Dictionary HoldStrength { get; }
+ public Dictionary AttackStrength { get; }
+ public Dictionary DefendStrength { get; }
+ public Dictionary PreventStrength { get; }
+ public Dictionary DoesMove { get; }
+
+ public IEnumerable Values =>
+ this.IsDislodged.Values.Cast()
+ .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 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())
+ {
+ // Create a dislodge decision for this unit.
+ List incoming = orders
+ .OfType()
+ .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 supports = orders
+ .OfType()
+ .Where(support => support.IsSupportFor(move))
+ .ToList();
+
+ // Determine if this move is a head-to-head battle.
+ MoveOrder? opposingMove = orders
+ .OfType()
+ .FirstOrDefault(other => other != null && other.IsOpposing(move), null);
+
+ // Find competing moves.
+ List competing = orders
+ .OfType()
+ .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);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MultiversalDiplomacy/Adjudicate/IPhaseAdjudicator.cs b/MultiversalDiplomacy/Adjudicate/IPhaseAdjudicator.cs
index 5869f3d..5ab155a 100644
--- a/MultiversalDiplomacy/Adjudicate/IPhaseAdjudicator.cs
+++ b/MultiversalDiplomacy/Adjudicate/IPhaseAdjudicator.cs
@@ -23,11 +23,32 @@ public interface IPhaseAdjudicator
public List ValidateOrders(World world, List orders);
///
- /// Given a list of valid orders, adjudicate the success and failure of the orders. The world
- /// will be updated with new seasons and unit positions and returned alongside the adjudication
- /// results.
+ /// Given a list of valid orders, adjudicate the success and failure of the orders. The kinds
+ /// of adjudication decisions returned depends on the phase adjudicator.
///
- public (List results, World updated) AdjudicateOrders(
- World world,
- List orders);
+ /// The global game state.
+ ///
+ /// Orders to adjudicate. The order list should contain only valid orders, as validated by
+ /// , and should contain exactly one order for every unit able to
+ /// be ordered.
+ ///
+ ///
+ /// A list of adjudication decicions. The decision types will be specific to the phase
+ /// adjudicator and should be comprehensible to that adjudicator's method.
+ ///
+ public List AdjudicateOrders(World world, List orders);
+
+ ///
+ /// Given a list of adjudications, update the world according to the adjudication results.
+ ///
+ /// The global game state.
+ ///
+ /// The results of adjudication. Like , all objects to be updated
+ /// should have a relevant adjudication. The adjudication types will be specific to the phase
+ /// adjudicator.
+ ///
+ ///
+ /// A new copy of the world, updated according to the adjudication.
+ ///
+ public World UpdateWorld(World world, List decisions);
}
diff --git a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs
index 0799d0a..e8f43da 100644
--- a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs
+++ b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs
@@ -9,120 +9,6 @@ namespace MultiversalDiplomacy.Adjudicate;
///
public class MovementPhaseAdjudicator : IPhaseAdjudicator
{
- private class Decisions
- {
- public Dictionary IsDislodged { get; }
- public Dictionary HasPath { get; }
- public Dictionary GivesSupport { get; }
- public Dictionary HoldStrength { get; }
- public Dictionary AttackStrength { get; }
- public Dictionary DefendStrength { get; }
- public Dictionary PreventStrength { get; }
- public Dictionary DoesMove { get; }
-
- public List UnresolvedDecisions { get; }
-
- public Decisions(List 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())
- {
- // Create a dislodge decision for this unit.
- List incoming = orders
- .OfType()
- .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 supports = orders
- .OfType()
- .Where(support => support.IsSupportFor(move))
- .ToList();
-
- // Determine if this move is a head-to-head battle.
- MoveOrder? opposingMove = orders
- .OfType()
- .FirstOrDefault(other => other != null && other.IsOpposing(move), null);
-
- // Find competing moves.
- List competing = orders
- .OfType()
- .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()
- .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 List ValidateOrders(World world, List orders)
@@ -377,35 +263,38 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
return validationResults;
}
- public (List results, World updated) AdjudicateOrders(
- World world,
- List orders)
+ public List AdjudicateOrders(World world, List orders)
{
// Define all adjudication decisions to be made.
- Decisions decisions = new Decisions(orders);
+ MovementDecisions decisions = new(orders);
+
+ List unresolvedDecisions = decisions.Values.ToList();
// Adjudicate all decisions.
bool progress = false;
do
{
progress = false;
- foreach (AdjudicationDecision decision in decisions.UnresolvedDecisions.ToList())
+ foreach (AdjudicationDecision decision in unresolvedDecisions.ToList())
{
progress |= ResolveDecision(decision, world, decisions);
- if (decision.Resolved) decisions.UnresolvedDecisions.Remove(decision);
+ if (decision.Resolved) unresolvedDecisions.Remove(decision);
}
} while (progress);
- if (decisions.UnresolvedDecisions.Any())
+ if (unresolvedDecisions.Any())
{
throw new ApplicationException("Some orders not resolved!");
}
- List adjudications = new();
+ return decisions.Values.ToList();
+ }
- // All orders other than move orders are hold orders with extra steps.
- ILookup moveOrders = orders.ToLookup(order => order is MoveOrder);
- List nonMoveOrders = moveOrders[false].ToList();
+ public World UpdateWorld(World world, List decisions)
+ {
+ Dictionary moves = decisions
+ .OfType()
+ .ToDictionary(dm => dm.Order);
// 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.
@@ -413,34 +302,35 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
List createdUnits = new();
List retreats = new();
- // For each move order with a successful does-move decision, ensure the future exists and
- // progress the unit to the future.
- foreach (MoveOrder move in moveOrders[true].Cast())
+ // Successful move orders result in the unit moving to the destination and creating a new
+ // future, while unsuccessful move orders are processed the same way as non-move orders.
+ foreach (DoesMove doesMove in moves.Values)
{
- DoesMove doesMove = decisions.DoesMove[move];
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.
- future = move.Season.Futures.Any()
- ? move.Season.MakeNext()
- : move.Season.MakeFork();
- createdFutures[move.Season] = future;
+ // A timeline that doesn't have a future yet simply continues. Otherwise, it forks.
+ future = !doesMove.Order.Season.Futures.Any()
+ ? doesMove.Order.Season.MakeNext()
+ : doesMove.Order.Season.MakeFork();
+ 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())
+ // Process unsuccessful moves, all holds, and all supports.
+ foreach (IsDislodged isDislodged in decisions.OfType())
{
+ 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))
{
// 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.
- IsDislodged isDislodged = decisions.IsDislodged[order.Unit];
if (isDislodged.Outcome == false)
{
createdUnits.Add(order.Unit.Next(order.Unit.Location, future));
@@ -464,15 +353,6 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
RetreatingUnit retreat = new(order.Unit, validRetreats);
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
@@ -482,10 +362,13 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
.WithUnits(world.Units.Concat(createdUnits))
.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
{
IsDislodged d => ResolveIsUnitDislodged(d, world, decisions),
@@ -499,7 +382,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
_ => 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;
@@ -557,7 +443,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
return progress;
}
- private bool ResolveDoesMoveHavePath(HasPath decision, World world, Decisions decisions)
+ private bool ResolveDoesMoveHavePath(
+ HasPath decision,
+ World world,
+ MovementDecisions decisions)
{
bool progress= false;
@@ -583,7 +472,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
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;
@@ -632,7 +524,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
return progress;
}
- private bool ResolveHoldStrength(HoldStrength decision, World world, Decisions decisions)
+ private bool ResolveHoldStrength(
+ HoldStrength decision,
+ World world,
+ MovementDecisions decisions)
{
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;
@@ -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;
@@ -788,7 +689,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
return progress;
}
- private bool ResolvePreventStrength(PreventStrength decision, World world, Decisions decisions)
+ private bool ResolvePreventStrength(
+ PreventStrength decision,
+ World world,
+ MovementDecisions decisions)
{
bool progress = false;
@@ -837,7 +741,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
return progress;
}
- private bool ResolveDoesUnitMove(DoesMove decision, World world, Decisions decisions)
+ private bool ResolveDoesUnitMove(
+ DoesMove decision,
+ World world,
+ MovementDecisions decisions)
{
bool progress = false;
diff --git a/MultiversalDiplomacy/Adjudicate/OrderAdjudication.cs b/MultiversalDiplomacy/Adjudicate/OrderAdjudication.cs
deleted file mode 100644
index c6ee08d..0000000
--- a/MultiversalDiplomacy/Adjudicate/OrderAdjudication.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using MultiversalDiplomacy.Orders;
-
-namespace MultiversalDiplomacy.Adjudicate;
-
-///
-/// Represents the result of adjudicating an order.
-///
-public class OrderAdjudication
-{
- ///
- /// The order that was adjudicated.
- ///
- public Order Order { get; }
-
- ///
- /// Whether the order succeeded or failed.
- ///
- public bool Success { get; }
-
- // ///
- // /// The reason for the order's outcome.
- // ///
- // 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
-{
- ///
- /// Create an accepting this order.
- ///
- public static OrderAdjudication Succeed(this Order order)
- => new OrderAdjudication(order, true);
-}
\ No newline at end of file
diff --git a/MultiversalDiplomacyTests/DATC_A.cs b/MultiversalDiplomacyTests/DATC_A.cs
index b7809b6..55dd429 100644
--- a/MultiversalDiplomacyTests/DATC_A.cs
+++ b/MultiversalDiplomacyTests/DATC_A.cs
@@ -17,7 +17,7 @@ public class DATC_A
setup["England"]
.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));
}
@@ -57,7 +57,7 @@ public class DATC_A
setup["Germany"]
.Fleet("Kiel").MovesTo("Kiel").GetReference(out var order);
- setup.ValidateOrders(new MovementPhaseAdjudicator());
+ setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
Assert.That(order.Validation, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
}
@@ -75,7 +75,7 @@ public class DATC_A
.Fleet("London").MovesTo("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(orderYor.Validation, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
@@ -91,7 +91,7 @@ public class DATC_A
["Germany"]
.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));
}
@@ -105,7 +105,7 @@ public class DATC_A
.Fleet("London").MovesTo("Belgium")
.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));
}
@@ -121,7 +121,7 @@ public class DATC_A
["Austria"]
.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));
@@ -136,7 +136,7 @@ public class DATC_A
["Italy"]
.Fleet("Rome").MovesTo("Venice").GetReference(out var order);
- setup.ValidateOrders(new MovementPhaseAdjudicator());
+ setup.ValidateOrders(MovementPhaseAdjudicator.Instance);
Assert.That(order.Validation, Is.Invalid(ValidationReason.UnreachableDestination));
}
@@ -152,7 +152,7 @@ public class DATC_A
.Army("Apulia").MovesTo("Venice")
.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));
diff --git a/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs b/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs
index ec23950..11374e8 100644
--- a/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs
+++ b/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs
@@ -15,7 +15,7 @@ public class MovementAdjudicatorTest
setup["Germany"]
.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.Replacement, Is.Null, "Unexpected order replacement");
@@ -28,7 +28,7 @@ public class MovementAdjudicatorTest
setup["Germany"]
.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.Replacement, Is.Null, "Unexpected order replacement");
@@ -41,7 +41,7 @@ public class MovementAdjudicatorTest
setup["Germany"]
.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.Replacement, Is.Null, "Unexpected order replacement");
@@ -54,7 +54,7 @@ public class MovementAdjudicatorTest
setup["Germany"]
.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.Replacement, Is.Null, "Unexpected order replacement");
@@ -67,7 +67,7 @@ public class MovementAdjudicatorTest
setup["Germany"]
.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.Replacement, Is.Null, "Unexpected order replacement");
diff --git a/MultiversalDiplomacyTests/OrderReference.cs b/MultiversalDiplomacyTests/OrderReference.cs
index ca18d3c..5dea7a3 100644
--- a/MultiversalDiplomacyTests/OrderReference.cs
+++ b/MultiversalDiplomacyTests/OrderReference.cs
@@ -1,4 +1,6 @@
using MultiversalDiplomacy.Adjudicate;
+using MultiversalDiplomacy.Adjudicate.Decision;
+using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders;
using NUnit.Framework;
@@ -61,6 +63,45 @@ public class OrderReference where OrderType : Order
}
}
+ public List 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)
{
this.Builder = builder;
diff --git a/MultiversalDiplomacyTests/TestAdjudicator.cs b/MultiversalDiplomacyTests/TestAdjudicator.cs
index b5b5751..b85f8d1 100644
--- a/MultiversalDiplomacyTests/TestAdjudicator.cs
+++ b/MultiversalDiplomacyTests/TestAdjudicator.cs
@@ -1,4 +1,5 @@
using MultiversalDiplomacy.Adjudicate;
+using MultiversalDiplomacy.Adjudicate.Decision;
using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders;
@@ -6,24 +7,79 @@ namespace MultiversalDiplomacyTests;
public class TestAdjudicator : IPhaseAdjudicator
{
- public static Func, List> RubberStamp =
- (world, orders) => orders.Select(o => o.Validate(ValidationReason.Valid)).ToList();
+ public static List RubberStamp(World world, List orders)
+ {
+ return orders.Select(o => o.Validate(ValidationReason.Valid)).ToList();
+ }
+
+ public static List NoMoves(
+ World world,
+ List orders)
+ {
+ List results = new();
+ foreach (Order order in orders)
+ {
+ switch (order)
+ {
+ case MoveOrder move:
+ {
+ var doesMove = new DoesMove(move, null, new List());
+ doesMove.Update(false);
+ results.Add(doesMove);
+ var dislodged = new IsDislodged(move, new List());
+ 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());
+ dislodged.Update(false);
+ results.Add(dislodged);
+ break;
+ }
+ }
+ }
+ return results;
+ }
+
+ public static World Noop(World world, List decisions)
+ => world;
+
+ private static List NoValidate(World world, List orders)
+ => throw new NotImplementedException();
+
+ private static List NoAdjudicate(World world, List orders)
+ => throw new NotImplementedException();
+
+ private static World NoUpdate(World world, List decisions)
+ => throw new NotImplementedException();
private Func, List> ValidateOrdersCallback;
+ private Func, List> AdjudicateOrdersCallback;
+ private Func, World> UpdateWorldCallback;
public TestAdjudicator(
- Func, List> validateOrdersCallback)
+ Func, List>? validate = null,
+ Func, List>? adjudicate = null,
+ Func, World>? update = null)
{
- this.ValidateOrdersCallback = validateOrdersCallback;
+ this.ValidateOrdersCallback = validate ?? NoValidate;
+ this.AdjudicateOrdersCallback = adjudicate ?? NoAdjudicate;
+ this.UpdateWorldCallback = update ?? NoUpdate;
}
public List ValidateOrders(World world, List orders)
=> this.ValidateOrdersCallback.Invoke(world, orders);
- public (List results, World updated) AdjudicateOrders(
- World world,
- List orders)
- {
- throw new NotImplementedException();
- }
+ public List AdjudicateOrders(World world, List orders)
+ => this.AdjudicateOrdersCallback(world, orders);
+
+ public World UpdateWorld(World world, List decisions)
+ => this.UpdateWorldCallback(world, decisions);
}
\ No newline at end of file
diff --git a/MultiversalDiplomacyTests/TestCaseBuilder.cs b/MultiversalDiplomacyTests/TestCaseBuilder.cs
index 6767f7d..9812085 100644
--- a/MultiversalDiplomacyTests/TestCaseBuilder.cs
+++ b/MultiversalDiplomacyTests/TestCaseBuilder.cs
@@ -161,6 +161,7 @@ public class TestCaseBuilder
private List OrderList;
private Season Season;
public List? ValidationResults { get; private set; }
+ public List? AdjudicationResults { get; private set; }
///
/// Create a test case builder that will operate on a world.
@@ -172,6 +173,7 @@ public class TestCaseBuilder
this.Orders = new(this.OrderList);
this.Season = season ?? this.World.Seasons.First();
this.ValidationResults = null;
+ this.AdjudicationResults = null;
}
///
@@ -225,6 +227,32 @@ public class TestCaseBuilder
return this.ValidationResults;
}
+ public List AdjudicateOrders(IPhaseAdjudicator adjudicator)
+ {
+ if (this.ValidationResults == null)
+ {
+ throw new InvalidOperationException("Cannot adjudicate before validation");
+ }
+
+ List 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
{
public TestCaseBuilder Builder;
diff --git a/MultiversalDiplomacyTests/TestCaseBuilderTest.cs b/MultiversalDiplomacyTests/TestCaseBuilderTest.cs
index 267acbd..f116e0b 100644
--- a/MultiversalDiplomacyTests/TestCaseBuilderTest.cs
+++ b/MultiversalDiplomacyTests/TestCaseBuilderTest.cs
@@ -1,4 +1,5 @@
using MultiversalDiplomacy.Adjudicate;
+using MultiversalDiplomacy.Adjudicate.Decision;
using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders;
@@ -117,7 +118,7 @@ class TestCaseBuilderTest
[Test]
public void BuilderProvidesReferencesForValidation()
{
- IPhaseAdjudicator rubberStamp = new TestAdjudicator(TestAdjudicator.RubberStamp);
+ IPhaseAdjudicator rubberStamp = new TestAdjudicator(validate: TestAdjudicator.RubberStamp);
TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason());
setup["Germany"]
@@ -133,13 +134,13 @@ class TestCaseBuilderTest
Is.EqualTo(setup.World.GetLand("Mun")),
"Wrong unit");
- Assert.That(
- () => orderMun.Validation,
+ Assert.That(
+ code: () => _ = orderMun.Validation,
Throws.Exception,
"Validation property should be inaccessible before validation actually happens");
setup.ValidateOrders(rubberStamp);
- Assert.That(
- () => orderMun.Validation,
+ Assert.That(
+ code: () => _ = orderMun.Validation,
Throws.Nothing,
"Validation property should be accessible after validation");
@@ -156,4 +157,60 @@ class TestCaseBuilderTest
Is.EqualTo(ValidationReason.Valid),
"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(),
+ "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");
+ }
}