Implement AdvanceTimeline resolution

This commit is contained in:
Jaculabilis 2022-11-08 16:25:47 -08:00
parent 7471a035f0
commit 39c3aabe45
7 changed files with 290 additions and 109 deletions

View File

@ -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();
}
}
}

View File

@ -8,6 +8,9 @@ public class AdvanceTimeline : BinaryAdjudicationDecision
public Season Season { get; } public Season Season { get; }
public List<UnitOrder> Orders { get; } public List<UnitOrder> Orders { get; }
public override string ToString()
=> $"AdvanceTimeline({Season})";
public AdvanceTimeline(Season season, IEnumerable<UnitOrder> orders) public AdvanceTimeline(Season season, IEnumerable<UnitOrder> orders)
{ {
this.Season = season; this.Season = season;

View File

@ -16,139 +16,139 @@ public class MovementDecisions
public Dictionary<Season, AdvanceTimeline> AdvanceTimeline { get; } public Dictionary<Season, AdvanceTimeline> AdvanceTimeline { get; }
public IEnumerable<AdjudicationDecision> Values => public IEnumerable<AdjudicationDecision> Values =>
this.IsDislodged.Values.Cast<AdjudicationDecision>() IsDislodged.Values.Cast<AdjudicationDecision>()
.Concat(this.HasPath.Values) .Concat(HasPath.Values)
.Concat(this.GivesSupport.Values) .Concat(GivesSupport.Values)
.Concat(this.HoldStrength.Values) .Concat(HoldStrength.Values)
.Concat(this.AttackStrength.Values) .Concat(AttackStrength.Values)
.Concat(this.DefendStrength.Values) .Concat(DefendStrength.Values)
.Concat(this.PreventStrength.Values) .Concat(PreventStrength.Values)
.Concat(this.DoesMove.Values) .Concat(DoesMove.Values)
.Concat(this.AdvanceTimeline.Values); .Concat(AdvanceTimeline.Values);
public MovementDecisions(World world, List<Order> orders) public MovementDecisions(World world, List<Order> orders)
{ {
this.IsDislodged = new(); IsDislodged = new();
this.HasPath = new(); HasPath = new();
this.GivesSupport = new(); GivesSupport = new();
this.HoldStrength = new(); HoldStrength = new();
this.AttackStrength = new(); AttackStrength = new();
this.DefendStrength = new(); DefendStrength = new();
this.PreventStrength = new(); PreventStrength = new();
this.DoesMove = new(); DoesMove = new();
this.AdvanceTimeline = new(); AdvanceTimeline = new();
// Record which seasons are referenced by the order set. // The orders argument only contains the submitted orders. The adjudicator will need to adjudicate not only
HashSet<Season> orderedSeasons = new(); // presently submitted orders, but also previously submitted orders if present orders affect the past. This
foreach (UnitOrder order in orders.Cast<UnitOrder>()) // 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. // Create timeline decisions for each season potentially affected by the submitted orders.
// In the event that those seasons don't end up affected (all moves to it fail, all // Since adjudication is deterministic and pure, if none of the affecting orders succeed,
// supports to it are cut), it is still safe to re-adjudicate everything because // the adjudication decisions for the extra seasons will resolve the same way and the
// adjudication is deterministic and doesn't produce side effects. // advance decision for the timeline will resolve false.
HashSet<Season> affectedSeasons = new();
foreach (Order order in orders) foreach (Order order in orders)
{ {
switch (order) switch (order)
{ {
case MoveOrder move: case MoveOrder move:
if (!orderedSeasons.Contains(move.Season)) AdvanceTimeline.Ensure(
{ move.Season,
affectedSeasons.Add(move.Season); () => new(move.Season, world.OrderHistory[move.Season].Orders));
} AdvanceTimeline[move.Season].Orders.Add(move);
break; break;
case SupportHoldOrder supportHold: case SupportHoldOrder supportHold:
if (!orderedSeasons.Contains(supportHold.Target.Season)) AdvanceTimeline.Ensure(
{ supportHold.Target.Season,
affectedSeasons.Add(supportHold.Target.Season); () => new(supportHold.Target.Season, world.OrderHistory[supportHold.Target.Season].Orders));
} AdvanceTimeline[supportHold.Target.Season].Orders.Add(supportHold);
break; break;
case SupportMoveOrder supportMove: case SupportMoveOrder supportMove:
if (!orderedSeasons.Contains(supportMove.Target.Season)) AdvanceTimeline.Ensure(
{ supportMove.Target.Season,
affectedSeasons.Add(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; 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. // Create all other relevant decisions for each order in the affected timelines.
foreach (UnitOrder order in orders.Cast<UnitOrder>()) foreach (UnitOrder order in relevantOrders)
{ {
// Create a dislodge decision for this unit. // Create a dislodge decision for this unit.
List<MoveOrder> incoming = orders List<MoveOrder> incoming = relevantOrders
.OfType<MoveOrder>() .OfType<MoveOrder>()
.Where(order.IsIncoming) .Where(order.IsIncoming)
.ToList(); .ToList();
this.IsDislodged[order.Unit] = new(order, incoming); 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);
if (order is MoveOrder move) if (order is MoveOrder move)
{ {
// Find supports corresponding to this move. // Find supports corresponding to this move.
List<SupportMoveOrder> supports = orders List<SupportMoveOrder> supports = relevantOrders
.OfType<SupportMoveOrder>() .OfType<SupportMoveOrder>()
.Where(support => support.IsSupportFor(move)) .Where(support => support.IsSupportFor(move))
.ToList(); .ToList();
// Determine if this move is a head-to-head battle. // Determine if this move is a head-to-head battle.
MoveOrder? opposingMove = orders MoveOrder? opposingMove = relevantOrders
.OfType<MoveOrder>() .OfType<MoveOrder>()
.FirstOrDefault(other => other != null && other.IsOpposing(move), null); .FirstOrDefault(other => other!.IsOpposing(move), null);
// Find competing moves. // Find competing moves.
List<MoveOrder> competing = orders List<MoveOrder> competing = relevantOrders
.OfType<MoveOrder>() .OfType<MoveOrder>()
.Where(move.IsCompeting) .Where(move.IsCompeting)
.ToList(); .ToList();
// Create the move-related decisions. // Create the move-related decisions.
this.HasPath[move] = new(move); HasPath[move] = new(move);
this.AttackStrength[move] = new(move, supports, opposingMove); AttackStrength[move] = new(move, supports, opposingMove);
this.DefendStrength[move] = new(move, supports); DefendStrength[move] = new(move, supports);
this.PreventStrength[move] = new(move, supports, opposingMove); PreventStrength[move] = new(move, supports, opposingMove);
this.DoesMove[move] = new(move, opposingMove, competing); DoesMove[move] = new(move, opposingMove, competing);
// Ensure a hold strength decision exists for the destination. // Ensure a hold strength decision exists for the destination.
if (!this.HoldStrength.ContainsKey(move.Point)) HoldStrength.Ensure(move.Point, () => new(move.Point));
{
this.HoldStrength[move.Point] = new(move.Point);
}
} }
else if (order is SupportOrder support) else if (order is SupportOrder support)
{ {
// Create the support decision. // 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. // Ensure a hold strength decision exists for the target's province.
if (!this.HoldStrength.ContainsKey(support.Target.Point)) HoldStrength.Ensure(support.Target.Point, () => new(support.Target.Point));
{
this.HoldStrength[support.Target.Point] = new(support.Target.Point);
}
if (support is SupportHoldOrder supportHold) 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) else if (support is SupportMoveOrder supportMove)
{ {
// Ensure a hold strength decision exists for the target's destination. // Ensure a hold strength decision exists for the target's destination.
if (!this.HoldStrength.ContainsKey(supportMove.Point)) HoldStrength.Ensure(supportMove.Point, () => new(supportMove.Point));
{
this.HoldStrength[supportMove.Point] = new(supportMove.Point);
}
} }
} }
} }

View File

@ -291,7 +291,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// This will noop without progress if the decision is already resolved // This will noop without progress if the decision is already resolved
progress |= ResolveDecision(decision, world, decisions, depth: 2); progress |= ResolveDecision(decision, world, decisions, depth: 2);
} }
} while (progress); } while (progress && decisions.Values.Any(decision => !decision.Resolved));
if (decisions.Values.Any(d => !d.Resolved)) if (decisions.Values.Any(d => !d.Resolved))
{ {
@ -304,9 +304,13 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
public World UpdateWorld(World world, List<AdjudicationDecision> decisions) public World UpdateWorld(World world, List<AdjudicationDecision> decisions)
{ {
logger.Log(0, "Updating world");
Dictionary<MoveOrder, DoesMove> moves = decisions Dictionary<MoveOrder, DoesMove> moves = decisions
.OfType<DoesMove>() .OfType<DoesMove>()
.ToDictionary(dm => dm.Order); .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 // 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.
@ -314,25 +318,37 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
List<Unit> createdUnits = new(); List<Unit> createdUnits = new();
List<RetreatingUnit> retreats = new(); List<RetreatingUnit> retreats = new();
// Populate createdFutures with the timeline fork decisions
logger.Log(1, "Processing AdvanceTimeline decisions");
foreach (AdvanceTimeline advanceTimeline in decisions.OfType<AdvanceTimeline>())
{
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.
createdFutures[advanceTimeline.Season] = !advanceTimeline.Season.Futures.Any()
? advanceTimeline.Season.MakeNext()
: advanceTimeline.Season.MakeFork();
}
}
// Successful move orders result in the unit moving to the destination and creating a 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. // 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) foreach (DoesMove doesMove in moves.Values)
{ {
if (doesMove.Outcome == true) logger.Log(2, "{0} = {1}", doesMove, doesMove.Outcome?.ToString() ?? "?");
Season moveSeason = doesMove.Order.Season;
if (doesMove.Outcome == true && createdFutures.ContainsKey(moveSeason))
{ {
if (!createdFutures.TryGetValue(doesMove.Order.Season, out Season? future)) Unit next = doesMove.Order.Unit.Next(doesMove.Order.Location, createdFutures[moveSeason]);
{ logger.Log(3, "Advancing unit to {0}", next);
// A timeline that doesn't have a future yet simply continues. Otherwise, it forks. createdUnits.Add(next);
future = !doesMove.Order.Season.Futures.Any()
? doesMove.Order.Season.MakeNext()
: doesMove.Order.Season.MakeFork();
createdFutures[doesMove.Order.Season] = future;
}
createdUnits.Add(doesMove.Order.Unit.Next(doesMove.Order.Location, future));
} }
} }
// Process unsuccessful moves, all holds, and all supports. // Process unsuccessful moves, all holds, and all supports.
logger.Log(1, "Processing stationary orders");
foreach (IsDislodged isDislodged in decisions.OfType<IsDislodged>()) foreach (IsDislodged isDislodged in decisions.OfType<IsDislodged>())
{ {
UnitOrder order = isDislodged.Order; UnitOrder order = isDislodged.Order;
@ -343,22 +359,26 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
continue; 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. logger.Log(3, "Skipping order because no future was created");
future = order.Unit.Season.MakeNext(); continue;
createdFutures[order.Unit.Season] = future;
} }
// For each stationary unit that wasn't dislodged, continue it into the future. Season future = createdFutures[order.Unit.Season];
if (isDislodged.Outcome == false) 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 else
{ {
// Create a retreat for each dislodged unit. // Create a retreat for each dislodged unit.
// TODO check valid retreats and disbands // TODO check valid retreats and disbands
logger.Log(3, "Creating retreat for {0}", order.Unit);
var validRetreats = order.Unit.Location.Adjacents var validRetreats = order.Unit.Location.Adjacents
.Select(loc => (future, loc)) .Select(loc => (future, loc))
.ToList(); .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. // Record the adjudication results to the season's order history
IEnumerable<KeyValuePair<Season, ReadOnlyCollection<Order>>> newOrders = decisions Dictionary<Season, OrderHistory> newHistory = new();
.OfType<IsDislodged>() foreach (UnitOrder unitOrder in decisions.OfType<IsDislodged>().Select(d => d.Order))
.GroupBy( {
keySelector: d => d.Order.Unit.Season, newHistory.Ensure(unitOrder.Unit.Season, () => new());
elementSelector: d => d.Order as Order) OrderHistory history = newHistory[unitOrder.Unit.Season];
.Where(group => !world.GivenOrders.ContainsKey(group.Key)) // TODO does this add every order to every season??
.Select(group => new KeyValuePair<Season, ReadOnlyCollection<Order>>( history.Orders.Add(unitOrder);
group.Key, new(group.ToList()))); 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 // TODO provide more structured information about order outcomes
@ -383,7 +423,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
seasons: world.Seasons.Concat(createdFutures.Values), seasons: world.Seasons.Concat(createdFutures.Values),
units: world.Units.Concat(createdUnits), units: world.Units.Concat(createdUnits),
retreats: retreats, retreats: retreats,
orders: world.GivenOrders.Concat(newOrders)); orders: updatedHistory);
logger.Log(0, "Completed update");
return updated; return updated;
} }
@ -423,6 +465,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
logger.Log(depth, "ResolveDecision({0})", decision); logger.Log(depth, "ResolveDecision({0})", decision);
return decision.Resolved ? false : decision switch return decision.Resolved ? false : decision switch
{ {
AdvanceTimeline d => ResolveAdvanceTimeline(d, world, decisions, depth + 1),
IsDislodged d => ResolveIsUnitDislodged(d, world, decisions, depth + 1), IsDislodged d => ResolveIsUnitDislodged(d, world, decisions, depth + 1),
HasPath d => ResolveDoesMoveHavePath(d, world, decisions, depth + 1), HasPath d => ResolveDoesMoveHavePath(d, world, decisions, depth + 1),
GivesSupport d => ResolveIsSupportGiven(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( private bool ResolveIsUnitDislodged(
IsDislodged decision, IsDislodged decision,
World world, World world,

View File

@ -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);
}
}

View File

@ -71,7 +71,7 @@ public class Season
public override string ToString() public override string ToString()
{ {
return $"{this.Turn}:{this.Timeline}"; return $"{this.Timeline}@{this.Turn}";
} }
/// <summary> /// <summary>

View File

@ -42,7 +42,7 @@ public class World
/// <summary> /// <summary>
/// Orders given to units in each season. /// Orders given to units in each season.
/// </summary> /// </summary>
public ReadOnlyDictionary<Season, ReadOnlyCollection<Order>> GivenOrders { get; } public ReadOnlyDictionary<Season, OrderHistory> OrderHistory { get; }
/// <summary> /// <summary>
/// Immutable game options. /// Immutable game options.
@ -59,7 +59,7 @@ public class World
Season rootSeason, Season rootSeason,
ReadOnlyCollection<Unit> units, ReadOnlyCollection<Unit> units,
ReadOnlyCollection<RetreatingUnit> retreatingUnits, ReadOnlyCollection<RetreatingUnit> retreatingUnits,
ReadOnlyDictionary<Season, ReadOnlyCollection<Order>> givenOrders, ReadOnlyDictionary<Season, OrderHistory> orderHistory,
Options options) Options options)
{ {
this.Provinces = provinces; this.Provinces = provinces;
@ -68,7 +68,7 @@ public class World
this.RootSeason = rootSeason; this.RootSeason = rootSeason;
this.Units = units; this.Units = units;
this.RetreatingUnits = retreatingUnits; this.RetreatingUnits = retreatingUnits;
this.GivenOrders = givenOrders; this.OrderHistory = orderHistory;
this.Options = options; this.Options = options;
} }
@ -82,7 +82,7 @@ public class World
ReadOnlyCollection<Season>? seasons = null, ReadOnlyCollection<Season>? seasons = null,
ReadOnlyCollection<Unit>? units = null, ReadOnlyCollection<Unit>? units = null,
ReadOnlyCollection<RetreatingUnit>? retreatingUnits = null, ReadOnlyCollection<RetreatingUnit>? retreatingUnits = null,
ReadOnlyDictionary<Season, ReadOnlyCollection<Order>>? givenOrders = null, ReadOnlyDictionary<Season, OrderHistory>? orderHistory = null,
Options? options = null) Options? options = null)
: this( : this(
provinces ?? previous.Provinces, provinces ?? previous.Provinces,
@ -91,7 +91,7 @@ public class World
previous.RootSeason, // Can't change the root season previous.RootSeason, // Can't change the root season
units ?? previous.Units, units ?? previous.Units,
retreatingUnits ?? previous.RetreatingUnits, retreatingUnits ?? previous.RetreatingUnits,
givenOrders ?? previous.GivenOrders, orderHistory ?? previous.OrderHistory,
options ?? previous.Options) options ?? previous.Options)
{ {
} }
@ -109,7 +109,7 @@ public class World
root, root,
new(new List<Unit>()), new(new List<Unit>()),
new(new List<RetreatingUnit>()), new(new List<RetreatingUnit>()),
new(new Dictionary<Season, ReadOnlyCollection<Order>>()), new(new Dictionary<Season, OrderHistory>()),
new Options()); new Options());
} }
@ -123,7 +123,7 @@ public class World
IEnumerable<Season>? seasons = null, IEnumerable<Season>? seasons = null,
IEnumerable<Unit>? units = null, IEnumerable<Unit>? units = null,
IEnumerable<RetreatingUnit>? retreats = null, IEnumerable<RetreatingUnit>? retreats = null,
IEnumerable<KeyValuePair<Season, ReadOnlyCollection<Order>>>? orders = null) IEnumerable<KeyValuePair<Season, OrderHistory>>? orders = null)
=> new World( => new World(
previous: this, previous: this,
seasons: seasons == null seasons: seasons == null
@ -135,8 +135,8 @@ public class World
retreatingUnits: retreats == null retreatingUnits: retreats == null
? this.RetreatingUnits ? this.RetreatingUnits
: new(retreats.ToList()), : new(retreats.ToList()),
givenOrders: orders == null orderHistory: orders == null
? this.GivenOrders ? this.OrderHistory
: new(orders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); : new(orders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)));
/// <summary> /// <summary>