using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Orders; using static MultiversalDiplomacy.Model.Location; namespace MultiversalDiplomacy.Adjudicate.Decision; public class MovementDecisions { public Dictionary IsDislodged { get; } public Dictionary HasPath { get; } public Dictionary GivesSupport { get; } public Dictionary<(string, string), HoldStrength> HoldStrength { get; } public Dictionary AttackStrength { get; } public Dictionary DefendStrength { get; } public Dictionary PreventStrength { get; } public Dictionary DoesMove { get; } public Dictionary AdvanceTimeline { get; } public IEnumerable Values => IsDislodged.Values.Cast() .Concat(HasPath.Values) .Concat(GivesSupport.Values) .Concat(HoldStrength.Values) .Concat(AttackStrength.Values) .Concat(DefendStrength.Values) .Concat(PreventStrength.Values) .Concat(DoesMove.Values) .Concat(AdvanceTimeline.Values); public MovementDecisions(World world, List orders) { IsDislodged = new(); HasPath = new(); GivesSupport = new(); HoldStrength = new(); AttackStrength = new(); DefendStrength = new(); PreventStrength = new(); DoesMove = new(); AdvanceTimeline = new(); // The orders argument only contains the submitted orders. The adjudicator will need to adjudicate not only // presently submitted orders, but also previously submitted orders if present orders affect the past. This // necessitates doing some lookups to find all affected seasons. // At a minimum, the submitted orders imply a dislodge decision for each unit, which affects every season those // orders were given to. var submittedOrdersBySeason = orders.Cast().ToLookup(order => order.Unit.Season); foreach (var group in submittedOrdersBySeason) { AdvanceTimeline[group.Key.Key] = new(group.Key, group); } // Create timeline decisions for each season potentially affected by the submitted orders. // Since adjudication is deterministic and pure, if none of the affecting orders succeed, // the adjudication decisions for the extra seasons will resolve the same way and the // advance decision for the timeline will resolve false. foreach (Order order in orders) { switch (order) { case MoveOrder move: AdvanceTimeline.Ensure( move.Season.Key, () => new(move.Season, world.OrderHistory[move.Season.Key].Orders)); AdvanceTimeline[move.Season.Key].Orders.Add(move); break; case SupportHoldOrder supportHold: AdvanceTimeline.Ensure( supportHold.Target.Season.Key, () => new(supportHold.Target.Season, world.OrderHistory[supportHold.Target.Season.Key].Orders)); AdvanceTimeline[supportHold.Target.Season.Key].Orders.Add(supportHold); break; case SupportMoveOrder supportMove: AdvanceTimeline.Ensure( supportMove.Target.Season.Key, () => new(supportMove.Target.Season, world.OrderHistory[supportMove.Target.Season.Key].Orders)); AdvanceTimeline[supportMove.Target.Season.Key].Orders.Add(supportMove); AdvanceTimeline.Ensure( supportMove.Season.Key, () => new(supportMove.Season, world.OrderHistory[supportMove.Season.Key].Orders)); AdvanceTimeline[supportMove.Season.Key].Orders.Add(supportMove); break; } } // Get the orders in the affected timelines. List relevantOrders = AdvanceTimeline.Values .SelectMany(at => at.Orders) .Distinct() .ToList(); (string province, string season) UnitPoint(Unit unit) => (world.Map.GetLocation(unit.Location).Province.Name, unit.Season.Key); (string province, string season) MovePoint(MoveOrder move) => (SplitKey(move.Location).province, move.Season.Key); // Create a hold strength decision with an associated order for every province with a unit. foreach (UnitOrder order in relevantOrders) { HoldStrength[UnitPoint(order.Unit)] = new( world.Map.GetLocation(order.Unit.Location).Province, order.Unit.Season, order); } bool IsIncoming(UnitOrder me, MoveOrder other) => me != other && other.Season == me.Unit.Season && SplitKey(other.Location).province == world.Map.GetLocation(me.Unit).Province.Name; bool IsSupportFor(SupportMoveOrder me, MoveOrder move) => me.Target.Key == move.Unit.Key && me.Season == move.Season && me.Location.Key == move.Location; bool AreOpposing(MoveOrder one, MoveOrder two) => one.Season == two.Unit.Season && two.Season == one.Unit.Season && SplitKey(one.Location).province == world.Map.GetLocation(two.Unit).Province.Name && SplitKey(two.Location).province == world.Map.GetLocation(one.Unit).Province.Name; bool AreCompeting(MoveOrder one, MoveOrder two) => one != two && one.Season == two.Season && SplitKey(one.Location).province == SplitKey(two.Location).province; // Create all other relevant decisions for each order in the affected timelines. foreach (UnitOrder order in relevantOrders) { // Create a dislodge decision for this unit. List incoming = relevantOrders .OfType() .Where(other => IsIncoming(order, other)) .ToList(); IsDislodged[order.Unit.Key] = new(order, incoming); if (order is MoveOrder move) { // Find supports corresponding to this move. List supports = relevantOrders .OfType() .Where(support => IsSupportFor(support, move)) .ToList(); // Determine if this move is a head-to-head battle. MoveOrder? opposingMove = relevantOrders .OfType() .FirstOrDefault(other => AreOpposing(move, other!), null); // Find competing moves. List competing = relevantOrders .OfType() .Where(other => AreCompeting(move, other)) .ToList(); // Create the move-related decisions. HasPath[move] = new(move); AttackStrength[move] = new(move, supports, opposingMove); DefendStrength[move] = new(move, supports); PreventStrength[move] = new(move, supports, opposingMove); DoesMove[move] = new(move, opposingMove, competing); // Ensure a hold strength decision exists for the destination. HoldStrength.Ensure(MovePoint(move), () => new(world.Map.GetLocation(move.Location).Province, move.Season)); } else if (order is SupportOrder support) { // Create the support decision. GivesSupport[support] = new(support, incoming); // Ensure a hold strength decision exists for the target's province. HoldStrength.Ensure(UnitPoint(support.Target), () => new( world.Map.GetLocation(support.Target.Location).Province, support.Target.Season)); if (support is SupportHoldOrder supportHold) { HoldStrength[UnitPoint(support.Target)].Supports.Add(supportHold); } else if (support is SupportMoveOrder supportMove) { // Ensure a hold strength decision exists for the target's destination. HoldStrength.Ensure( (supportMove.Province.Name, supportMove.Season.Key), () => new(supportMove.Province, supportMove.Season)); } } } } }