Implement AdvanceTimeline resolution
This commit is contained in:
parent
7471a035f0
commit
39c3aabe45
|
@ -0,0 +1,21 @@
|
|||
namespace System.Collections.Generic;
|
||||
|
||||
public static class AdjudicationDictionaryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Create and add a value to a dictionary only if the key is not already present.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The dictionary to check for the key.</param>
|
||||
/// <param name="key">The key to check and use if it isn't already present.</param>
|
||||
/// <param name="valueFunc">A function that returns the value to insert if the key is not present.</param>
|
||||
public static void Ensure<TKey, TValue>(
|
||||
this IDictionary<TKey, TValue> dictionary,
|
||||
TKey key,
|
||||
Func<TValue> valueFunc)
|
||||
{
|
||||
if (!dictionary.ContainsKey(key))
|
||||
{
|
||||
dictionary[key] = valueFunc();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,9 @@ public class AdvanceTimeline : BinaryAdjudicationDecision
|
|||
public Season Season { get; }
|
||||
public List<UnitOrder> Orders { get; }
|
||||
|
||||
public override string ToString()
|
||||
=> $"AdvanceTimeline({Season})";
|
||||
|
||||
public AdvanceTimeline(Season season, IEnumerable<UnitOrder> orders)
|
||||
{
|
||||
this.Season = season;
|
||||
|
|
|
@ -16,139 +16,139 @@ public class MovementDecisions
|
|||
public Dictionary<Season, AdvanceTimeline> AdvanceTimeline { 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)
|
||||
.Concat(this.AdvanceTimeline.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)
|
||||
{
|
||||
this.IsDislodged = new();
|
||||
this.HasPath = new();
|
||||
this.GivesSupport = new();
|
||||
this.HoldStrength = new();
|
||||
this.AttackStrength = new();
|
||||
this.DefendStrength = new();
|
||||
this.PreventStrength = new();
|
||||
this.DoesMove = new();
|
||||
this.AdvanceTimeline = new();
|
||||
IsDislodged = new();
|
||||
HasPath = new();
|
||||
GivesSupport = new();
|
||||
HoldStrength = new();
|
||||
AttackStrength = new();
|
||||
DefendStrength = new();
|
||||
PreventStrength = new();
|
||||
DoesMove = new();
|
||||
AdvanceTimeline = new();
|
||||
|
||||
// Record which seasons are referenced by the order set.
|
||||
HashSet<Season> orderedSeasons = new();
|
||||
foreach (UnitOrder order in orders.Cast<UnitOrder>())
|
||||
// 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)
|
||||
{
|
||||
_ = orderedSeasons.Add(order.Unit.Season);
|
||||
AdvanceTimeline[group.Key] = new(group.Key, group);
|
||||
}
|
||||
|
||||
// Expand the order list to include any other seasons that are potentially affected.
|
||||
// In the event that those seasons don't end up affected (all moves to it fail, all
|
||||
// supports to it are cut), it is still safe to re-adjudicate everything because
|
||||
// adjudication is deterministic and doesn't produce side effects.
|
||||
HashSet<Season> affectedSeasons = new();
|
||||
// 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:
|
||||
if (!orderedSeasons.Contains(move.Season))
|
||||
{
|
||||
affectedSeasons.Add(move.Season);
|
||||
}
|
||||
AdvanceTimeline.Ensure(
|
||||
move.Season,
|
||||
() => new(move.Season, world.OrderHistory[move.Season].Orders));
|
||||
AdvanceTimeline[move.Season].Orders.Add(move);
|
||||
break;
|
||||
|
||||
case SupportHoldOrder supportHold:
|
||||
if (!orderedSeasons.Contains(supportHold.Target.Season))
|
||||
{
|
||||
affectedSeasons.Add(supportHold.Target.Season);
|
||||
}
|
||||
AdvanceTimeline.Ensure(
|
||||
supportHold.Target.Season,
|
||||
() => new(supportHold.Target.Season, world.OrderHistory[supportHold.Target.Season].Orders));
|
||||
AdvanceTimeline[supportHold.Target.Season].Orders.Add(supportHold);
|
||||
break;
|
||||
|
||||
case SupportMoveOrder supportMove:
|
||||
if (!orderedSeasons.Contains(supportMove.Target.Season))
|
||||
{
|
||||
affectedSeasons.Add(supportMove.Target.Season);
|
||||
}
|
||||
AdvanceTimeline.Ensure(
|
||||
supportMove.Target.Season,
|
||||
() => new(supportMove.Target.Season, world.OrderHistory[supportMove.Target.Season].Orders));
|
||||
AdvanceTimeline[supportMove.Target.Season].Orders.Add(supportMove);
|
||||
AdvanceTimeline.Ensure(
|
||||
supportMove.Season,
|
||||
() => new(supportMove.Season, world.OrderHistory[supportMove.Season].Orders));
|
||||
AdvanceTimeline[supportMove.Season].Orders.Add(supportMove);
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (Season season in affectedSeasons)
|
||||
|
||||
// Get the orders in the affected timelines.
|
||||
List<UnitOrder> relevantOrders = AdvanceTimeline.Values.SelectMany(at => at.Orders).ToList();
|
||||
|
||||
// Create a hold strength decision with an associated order for every province with a unit.
|
||||
foreach (UnitOrder order in relevantOrders)
|
||||
{
|
||||
orders.AddRange(world.GivenOrders[season]);
|
||||
HoldStrength[order.Unit.Point] = new(order.Unit.Point, order);
|
||||
}
|
||||
|
||||
// Create the relevant decisions for each order.
|
||||
foreach (UnitOrder order in orders.Cast<UnitOrder>())
|
||||
// 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 = orders
|
||||
List<MoveOrder> incoming = relevantOrders
|
||||
.OfType<MoveOrder>()
|
||||
.Where(order.IsIncoming)
|
||||
.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.
|
||||
this.HoldStrength[order.Unit.Point] = new(order.Unit.Point, order);
|
||||
IsDislodged[order.Unit] = new(order, incoming);
|
||||
|
||||
if (order is MoveOrder move)
|
||||
{
|
||||
// Find supports corresponding to this move.
|
||||
List<SupportMoveOrder> supports = orders
|
||||
List<SupportMoveOrder> supports = relevantOrders
|
||||
.OfType<SupportMoveOrder>()
|
||||
.Where(support => support.IsSupportFor(move))
|
||||
.ToList();
|
||||
|
||||
// Determine if this move is a head-to-head battle.
|
||||
MoveOrder? opposingMove = orders
|
||||
MoveOrder? opposingMove = relevantOrders
|
||||
.OfType<MoveOrder>()
|
||||
.FirstOrDefault(other => other != null && other.IsOpposing(move), null);
|
||||
.FirstOrDefault(other => other!.IsOpposing(move), null);
|
||||
|
||||
// Find competing moves.
|
||||
List<MoveOrder> competing = orders
|
||||
List<MoveOrder> competing = relevantOrders
|
||||
.OfType<MoveOrder>()
|
||||
.Where(move.IsCompeting)
|
||||
.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);
|
||||
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.
|
||||
if (!this.HoldStrength.ContainsKey(move.Point))
|
||||
{
|
||||
this.HoldStrength[move.Point] = new(move.Point);
|
||||
}
|
||||
HoldStrength.Ensure(move.Point, () => new(move.Point));
|
||||
}
|
||||
else if (order is SupportOrder support)
|
||||
{
|
||||
// Create the support decision.
|
||||
this.GivesSupport[support] = new(support, incoming);
|
||||
GivesSupport[support] = new(support, incoming);
|
||||
|
||||
// Ensure a hold strength decision exists for the target's province.
|
||||
if (!this.HoldStrength.ContainsKey(support.Target.Point))
|
||||
{
|
||||
this.HoldStrength[support.Target.Point] = new(support.Target.Point);
|
||||
}
|
||||
HoldStrength.Ensure(support.Target.Point, () => new(support.Target.Point));
|
||||
|
||||
if (support is SupportHoldOrder supportHold)
|
||||
{
|
||||
this.HoldStrength[support.Target.Point].Supports.Add(supportHold);
|
||||
HoldStrength[support.Target.Point].Supports.Add(supportHold);
|
||||
}
|
||||
else if (support is SupportMoveOrder supportMove)
|
||||
{
|
||||
// Ensure a hold strength decision exists for the target's destination.
|
||||
if (!this.HoldStrength.ContainsKey(supportMove.Point))
|
||||
{
|
||||
this.HoldStrength[supportMove.Point] = new(supportMove.Point);
|
||||
}
|
||||
HoldStrength.Ensure(supportMove.Point, () => new(supportMove.Point));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -291,7 +291,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
// This will noop without progress if the decision is already resolved
|
||||
progress |= ResolveDecision(decision, world, decisions, depth: 2);
|
||||
}
|
||||
} while (progress);
|
||||
} while (progress && decisions.Values.Any(decision => !decision.Resolved));
|
||||
|
||||
if (decisions.Values.Any(d => !d.Resolved))
|
||||
{
|
||||
|
@ -304,9 +304,13 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
|
||||
public World UpdateWorld(World world, List<AdjudicationDecision> decisions)
|
||||
{
|
||||
logger.Log(0, "Updating world");
|
||||
Dictionary<MoveOrder, DoesMove> moves = decisions
|
||||
.OfType<DoesMove>()
|
||||
.ToDictionary(dm => dm.Order);
|
||||
Dictionary<Unit, IsDislodged> dislodges = decisions
|
||||
.OfType<IsDislodged>()
|
||||
.ToDictionary(dm => dm.Order.Unit);
|
||||
|
||||
// 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.
|
||||
|
@ -314,25 +318,37 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
List<Unit> createdUnits = new();
|
||||
List<RetreatingUnit> retreats = new();
|
||||
|
||||
// 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)
|
||||
// Populate createdFutures with the timeline fork decisions
|
||||
logger.Log(1, "Processing AdvanceTimeline decisions");
|
||||
foreach (AdvanceTimeline advanceTimeline in decisions.OfType<AdvanceTimeline>())
|
||||
{
|
||||
if (doesMove.Outcome == true)
|
||||
{
|
||||
if (!createdFutures.TryGetValue(doesMove.Order.Season, out Season? future))
|
||||
logger.Log(2, "{0} = {1}", advanceTimeline, advanceTimeline.Outcome?.ToString() ?? "?");
|
||||
if (advanceTimeline.Outcome == true)
|
||||
{
|
||||
// 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;
|
||||
createdFutures[advanceTimeline.Season] = !advanceTimeline.Season.Futures.Any()
|
||||
? advanceTimeline.Season.MakeNext()
|
||||
: advanceTimeline.Season.MakeFork();
|
||||
}
|
||||
createdUnits.Add(doesMove.Order.Unit.Next(doesMove.Order.Location, future));
|
||||
}
|
||||
|
||||
// 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.
|
||||
logger.Log(1, "Processing successful moves");
|
||||
foreach (DoesMove doesMove in moves.Values)
|
||||
{
|
||||
logger.Log(2, "{0} = {1}", doesMove, doesMove.Outcome?.ToString() ?? "?");
|
||||
Season moveSeason = doesMove.Order.Season;
|
||||
if (doesMove.Outcome == true && createdFutures.ContainsKey(moveSeason))
|
||||
{
|
||||
Unit next = doesMove.Order.Unit.Next(doesMove.Order.Location, createdFutures[moveSeason]);
|
||||
logger.Log(3, "Advancing unit to {0}", next);
|
||||
createdUnits.Add(next);
|
||||
}
|
||||
}
|
||||
|
||||
// Process unsuccessful moves, all holds, and all supports.
|
||||
logger.Log(1, "Processing stationary orders");
|
||||
foreach (IsDislodged isDislodged in decisions.OfType<IsDislodged>())
|
||||
{
|
||||
UnitOrder order = isDislodged.Order;
|
||||
|
@ -343,22 +359,26 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!createdFutures.TryGetValue(order.Unit.Season, out Season? future))
|
||||
logger.Log(2, "{0} = {1}", isDislodged, isDislodged.Outcome?.ToString() ?? "?");
|
||||
if (!createdFutures.ContainsKey(order.Unit.Season))
|
||||
{
|
||||
// Any unit given an order is, by definition, at the front of a timeline.
|
||||
future = order.Unit.Season.MakeNext();
|
||||
createdFutures[order.Unit.Season] = future;
|
||||
logger.Log(3, "Skipping order because no future was created");
|
||||
continue;
|
||||
}
|
||||
|
||||
// For each stationary unit that wasn't dislodged, continue it into the future.
|
||||
Season future = createdFutures[order.Unit.Season];
|
||||
if (isDislodged.Outcome == false)
|
||||
{
|
||||
createdUnits.Add(order.Unit.Next(order.Unit.Location, future));
|
||||
// Non-dislodged units continue into the future.
|
||||
Unit next = order.Unit.Next(order.Unit.Location, future);
|
||||
logger.Log(3, "Advancing unit to {0}", next);
|
||||
createdUnits.Add(next);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a retreat for each dislodged unit.
|
||||
// TODO check valid retreats and disbands
|
||||
logger.Log(3, "Creating retreat for {0}", order.Unit);
|
||||
var validRetreats = order.Unit.Location.Adjacents
|
||||
.Select(loc => (future, loc))
|
||||
.ToList();
|
||||
|
@ -367,15 +387,35 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
}
|
||||
}
|
||||
|
||||
// Sort the new orders by season to save for posterity in case of attacks from the future.
|
||||
IEnumerable<KeyValuePair<Season, ReadOnlyCollection<Order>>> newOrders = decisions
|
||||
.OfType<IsDislodged>()
|
||||
.GroupBy(
|
||||
keySelector: d => d.Order.Unit.Season,
|
||||
elementSelector: d => d.Order as Order)
|
||||
.Where(group => !world.GivenOrders.ContainsKey(group.Key))
|
||||
.Select(group => new KeyValuePair<Season, ReadOnlyCollection<Order>>(
|
||||
group.Key, new(group.ToList())));
|
||||
// Record the adjudication results to the season's order history
|
||||
Dictionary<Season, OrderHistory> newHistory = new();
|
||||
foreach (UnitOrder unitOrder in decisions.OfType<IsDislodged>().Select(d => d.Order))
|
||||
{
|
||||
newHistory.Ensure(unitOrder.Unit.Season, () => new());
|
||||
OrderHistory history = newHistory[unitOrder.Unit.Season];
|
||||
// TODO does this add every order to every season??
|
||||
history.Orders.Add(unitOrder);
|
||||
history.IsDislodgedOutcomes[unitOrder.Unit] = dislodges[unitOrder.Unit].Outcome == true;
|
||||
if (unitOrder is MoveOrder moveOrder)
|
||||
{
|
||||
history.DoesMoveOutcomes[moveOrder] = moves[moveOrder].Outcome == true;
|
||||
}
|
||||
}
|
||||
|
||||
// Log the new order history
|
||||
foreach ((Season season, OrderHistory history) in newHistory)
|
||||
{
|
||||
string verb = world.OrderHistory.ContainsKey(season) ? "Updating" : "Adding";
|
||||
logger.Log(1, "{0} history for {1}", verb, season);
|
||||
foreach (UnitOrder order in history.Orders)
|
||||
{
|
||||
logger.Log(2, "{0}", order);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<KeyValuePair<Season, OrderHistory>> updatedHistory = world.OrderHistory
|
||||
.Where(kvp => !newHistory.ContainsKey(kvp.Key))
|
||||
.Concat(newHistory);
|
||||
|
||||
// TODO provide more structured information about order outcomes
|
||||
|
||||
|
@ -383,7 +423,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
seasons: world.Seasons.Concat(createdFutures.Values),
|
||||
units: world.Units.Concat(createdUnits),
|
||||
retreats: retreats,
|
||||
orders: world.GivenOrders.Concat(newOrders));
|
||||
orders: updatedHistory);
|
||||
|
||||
logger.Log(0, "Completed update");
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
@ -423,6 +465,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
logger.Log(depth, "ResolveDecision({0})", decision);
|
||||
return decision.Resolved ? false : decision switch
|
||||
{
|
||||
AdvanceTimeline d => ResolveAdvanceTimeline(d, world, decisions, depth + 1),
|
||||
IsDislodged d => ResolveIsUnitDislodged(d, world, decisions, depth + 1),
|
||||
HasPath d => ResolveDoesMoveHavePath(d, world, decisions, depth + 1),
|
||||
GivesSupport d => ResolveIsSupportGiven(d, world, decisions, depth + 1),
|
||||
|
@ -435,6 +478,92 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
};
|
||||
}
|
||||
|
||||
private bool ResolveAdvanceTimeline(
|
||||
AdvanceTimeline decision,
|
||||
World world,
|
||||
MovementDecisions decisions,
|
||||
int depth)
|
||||
{
|
||||
logger.Log(depth, "AdvanceTimeline({0})", decision.Season);
|
||||
bool progress = false;
|
||||
|
||||
// A season at the head of a timeline always advances.
|
||||
if (!decision.Season.Futures.Any())
|
||||
{
|
||||
progress |= LoggedUpdate(decision, true, depth, "A timeline head always advances");
|
||||
return progress;
|
||||
}
|
||||
|
||||
// The season target of a successful move always advances.
|
||||
IEnumerable<MoveOrder> incomingMoveOrders = decision.Orders
|
||||
.OfType<MoveOrder>()
|
||||
.Where(order => order.Season == decision.Season);
|
||||
logger.Log(depth, "decision.Orders = {0}", string.Join(", ", decision.Orders));
|
||||
logger.Log(depth, "incomingMoveOrders = {0}", string.Join(", ", incomingMoveOrders));
|
||||
foreach (MoveOrder moveOrder in incomingMoveOrders)
|
||||
{
|
||||
DoesMove doesMove = decisions.DoesMove[moveOrder];
|
||||
progress |= ResolveDecision(doesMove, world, decisions, depth + 1);
|
||||
if (doesMove.Outcome == true)
|
||||
{
|
||||
progress |= LoggedUpdate(decision, true, depth, $"Advanced by {doesMove.Order}");
|
||||
return progress;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO needs to also resolve true if anyone moves into this timeline
|
||||
|
||||
// Seasons not at the head of a timeline advance if the outcome of a battle is changed.
|
||||
// The outcome of a battle is changed if:
|
||||
// 1. The outcome of a dislodge decision is changed,
|
||||
// 2. The outcome of an intra-timeline move decision is changed, or
|
||||
// 3. The outcome of an inter-timeline move decision with that season as the destination is
|
||||
// changed.
|
||||
OrderHistory history = world.OrderHistory[decision.Season];
|
||||
bool anyUnresolved = false;
|
||||
foreach (UnitOrder order in decision.Orders)
|
||||
{
|
||||
// TODO these aren't timeline-specific
|
||||
IsDislodged dislodged = decisions.IsDislodged[order.Unit];
|
||||
progress |= ResolveDecision(dislodged, world, decisions, depth + 1);
|
||||
bool previous = history.IsDislodgedOutcomes[order.Unit];
|
||||
if (dislodged.Resolved && dislodged.Outcome != previous)
|
||||
{
|
||||
progress |= LoggedUpdate(
|
||||
decision,
|
||||
true,
|
||||
depth,
|
||||
$"History changed for {order.Unit}: dislodge {previous} => {dislodged.Outcome}");
|
||||
return progress;
|
||||
}
|
||||
anyUnresolved |= !dislodged.Resolved;
|
||||
|
||||
if (order is MoveOrder moveOrder)
|
||||
{
|
||||
DoesMove moves = decisions.DoesMove[moveOrder];
|
||||
progress |= ResolveDecision(moves, world, decisions, depth + 1);
|
||||
bool previousMove = history.DoesMoveOutcomes[moveOrder];
|
||||
if (moves.Resolved && moves.Outcome != previousMove)
|
||||
{
|
||||
progress |= LoggedUpdate(
|
||||
decision,
|
||||
true,
|
||||
depth,
|
||||
$"History changed for {order}: moves {previousMove} => {moves.Outcome}");
|
||||
return progress;
|
||||
}
|
||||
anyUnresolved |= !moves.Resolved;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyUnresolved)
|
||||
{
|
||||
progress |= LoggedUpdate(decision, false, depth, "No resolved changes to history");
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
private bool ResolveIsUnitDislodged(
|
||||
IsDislodged decision,
|
||||
World world,
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
using MultiversalDiplomacy.Orders;
|
||||
|
||||
namespace MultiversalDiplomacy.Model;
|
||||
|
||||
public class OrderHistory
|
||||
{
|
||||
public List<UnitOrder> Orders;
|
||||
|
||||
public Dictionary<Unit, bool> IsDislodgedOutcomes;
|
||||
|
||||
public Dictionary<MoveOrder, bool> DoesMoveOutcomes;
|
||||
|
||||
public OrderHistory()
|
||||
: this(new(), new(), new())
|
||||
{}
|
||||
|
||||
public OrderHistory(
|
||||
List<UnitOrder> orders,
|
||||
Dictionary<Unit, bool> isDislodgedOutcomes,
|
||||
Dictionary<MoveOrder, bool> doesMoveOutcomes)
|
||||
{
|
||||
this.Orders = new(orders);
|
||||
this.IsDislodgedOutcomes = new(isDislodgedOutcomes);
|
||||
this.DoesMoveOutcomes = new(doesMoveOutcomes);
|
||||
}
|
||||
}
|
|
@ -71,7 +71,7 @@ public class Season
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Turn}:{this.Timeline}";
|
||||
return $"{this.Timeline}@{this.Turn}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -42,7 +42,7 @@ public class World
|
|||
/// <summary>
|
||||
/// Orders given to units in each season.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<Season, ReadOnlyCollection<Order>> GivenOrders { get; }
|
||||
public ReadOnlyDictionary<Season, OrderHistory> OrderHistory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Immutable game options.
|
||||
|
@ -59,7 +59,7 @@ public class World
|
|||
Season rootSeason,
|
||||
ReadOnlyCollection<Unit> units,
|
||||
ReadOnlyCollection<RetreatingUnit> retreatingUnits,
|
||||
ReadOnlyDictionary<Season, ReadOnlyCollection<Order>> givenOrders,
|
||||
ReadOnlyDictionary<Season, OrderHistory> orderHistory,
|
||||
Options options)
|
||||
{
|
||||
this.Provinces = provinces;
|
||||
|
@ -68,7 +68,7 @@ public class World
|
|||
this.RootSeason = rootSeason;
|
||||
this.Units = units;
|
||||
this.RetreatingUnits = retreatingUnits;
|
||||
this.GivenOrders = givenOrders;
|
||||
this.OrderHistory = orderHistory;
|
||||
this.Options = options;
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ public class World
|
|||
ReadOnlyCollection<Season>? seasons = null,
|
||||
ReadOnlyCollection<Unit>? units = null,
|
||||
ReadOnlyCollection<RetreatingUnit>? retreatingUnits = null,
|
||||
ReadOnlyDictionary<Season, ReadOnlyCollection<Order>>? givenOrders = null,
|
||||
ReadOnlyDictionary<Season, OrderHistory>? orderHistory = null,
|
||||
Options? options = null)
|
||||
: this(
|
||||
provinces ?? previous.Provinces,
|
||||
|
@ -91,7 +91,7 @@ public class World
|
|||
previous.RootSeason, // Can't change the root season
|
||||
units ?? previous.Units,
|
||||
retreatingUnits ?? previous.RetreatingUnits,
|
||||
givenOrders ?? previous.GivenOrders,
|
||||
orderHistory ?? previous.OrderHistory,
|
||||
options ?? previous.Options)
|
||||
{
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ public class World
|
|||
root,
|
||||
new(new List<Unit>()),
|
||||
new(new List<RetreatingUnit>()),
|
||||
new(new Dictionary<Season, ReadOnlyCollection<Order>>()),
|
||||
new(new Dictionary<Season, OrderHistory>()),
|
||||
new Options());
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ public class World
|
|||
IEnumerable<Season>? seasons = null,
|
||||
IEnumerable<Unit>? units = null,
|
||||
IEnumerable<RetreatingUnit>? retreats = null,
|
||||
IEnumerable<KeyValuePair<Season, ReadOnlyCollection<Order>>>? orders = null)
|
||||
IEnumerable<KeyValuePair<Season, OrderHistory>>? orders = null)
|
||||
=> new World(
|
||||
previous: this,
|
||||
seasons: seasons == null
|
||||
|
@ -135,8 +135,8 @@ public class World
|
|||
retreatingUnits: retreats == null
|
||||
? this.RetreatingUnits
|
||||
: new(retreats.ToList()),
|
||||
givenOrders: orders == null
|
||||
? this.GivenOrders
|
||||
orderHistory: orders == null
|
||||
? this.OrderHistory
|
||||
: new(orders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)));
|
||||
|
||||
/// <summary>
|
||||
|
|
Loading…
Reference in New Issue