5dplomacy/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs

194 lines
8.7 KiB
C#

using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders;
using static MultiversalDiplomacy.Model.Location;
namespace MultiversalDiplomacy.Adjudicate.Decision;
public class MovementDecisions
{
public Dictionary<string, IsDislodged> IsDislodged { get; }
public Dictionary<MoveOrder, HasPath> HasPath { get; }
public Dictionary<SupportOrder, GivesSupport> GivesSupport { get; }
public Dictionary<(string, string), 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 Dictionary<string, AdvanceTimeline> AdvanceTimeline { get; }
public IEnumerable<AdjudicationDecision> Values =>
IsDislodged.Values.Cast<AdjudicationDecision>()
.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<Order> 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<UnitOrder>().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<UnitOrder> 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<MoveOrder> incoming = relevantOrders
.OfType<MoveOrder>()
.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<SupportMoveOrder> supports = relevantOrders
.OfType<SupportMoveOrder>()
.Where(support => IsSupportFor(support, move))
.ToList();
// Determine if this move is a head-to-head battle.
MoveOrder? opposingMove = relevantOrders
.OfType<MoveOrder>()
.FirstOrDefault(other => AreOpposing(move, other!), null);
// Find competing moves.
List<MoveOrder> competing = relevantOrders
.OfType<MoveOrder>()
.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));
}
}
}
}
}