diff --git a/MultiversalDiplomacy/Adjudicate/AdjudicationDictionaryExtensions.cs b/MultiversalDiplomacy/Adjudicate/AdjudicationDictionaryExtensions.cs
new file mode 100644
index 0000000..75c0306
--- /dev/null
+++ b/MultiversalDiplomacy/Adjudicate/AdjudicationDictionaryExtensions.cs
@@ -0,0 +1,21 @@
+namespace System.Collections.Generic;
+
+public static class AdjudicationDictionaryExtensions
+{
+ ///
+ /// Create and add a value to a dictionary only if the key is not already present.
+ ///
+ /// The dictionary to check for the key.
+ /// The key to check and use if it isn't already present.
+ /// A function that returns the value to insert if the key is not present.
+ public static void Ensure(
+ this IDictionary dictionary,
+ TKey key,
+ Func valueFunc)
+ {
+ if (!dictionary.ContainsKey(key))
+ {
+ dictionary[key] = valueFunc();
+ }
+ }
+}
\ No newline at end of file
diff --git a/MultiversalDiplomacy/Adjudicate/Decision/AdvanceTimeline.cs b/MultiversalDiplomacy/Adjudicate/Decision/AdvanceTimeline.cs
index 19f087c..aa4f7e7 100644
--- a/MultiversalDiplomacy/Adjudicate/Decision/AdvanceTimeline.cs
+++ b/MultiversalDiplomacy/Adjudicate/Decision/AdvanceTimeline.cs
@@ -8,6 +8,9 @@ public class AdvanceTimeline : BinaryAdjudicationDecision
public Season Season { get; }
public List Orders { get; }
+ public override string ToString()
+ => $"AdvanceTimeline({Season})";
+
public AdvanceTimeline(Season season, IEnumerable orders)
{
this.Season = season;
diff --git a/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs b/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs
index 7379beb..34e4845 100644
--- a/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs
+++ b/MultiversalDiplomacy/Adjudicate/Decision/MovementDecisions.cs
@@ -16,139 +16,139 @@ public class MovementDecisions
public Dictionary AdvanceTimeline { 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)
- .Concat(this.AdvanceTimeline.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)
{
- 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 orderedSeasons = new();
- foreach (UnitOrder order in orders.Cast())
+ // 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)
{
- _ = 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 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 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())
+ // 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 = orders
+ List incoming = relevantOrders
.OfType()
.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 supports = orders
+ List supports = relevantOrders
.OfType()
.Where(support => support.IsSupportFor(move))
.ToList();
// Determine if this move is a head-to-head battle.
- MoveOrder? opposingMove = orders
+ MoveOrder? opposingMove = relevantOrders
.OfType()
- .FirstOrDefault(other => other != null && other.IsOpposing(move), null);
+ .FirstOrDefault(other => other!.IsOpposing(move), null);
// Find competing moves.
- List competing = orders
+ List competing = relevantOrders
.OfType()
.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));
}
}
}
diff --git a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs
index faab4ce..bb4c712 100644
--- a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs
+++ b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs
@@ -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 decisions)
{
+ logger.Log(0, "Updating world");
Dictionary moves = decisions
.OfType()
.ToDictionary(dm => dm.Order);
+ Dictionary dislodges = decisions
+ .OfType()
+ .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 createdUnits = new();
List retreats = new();
+ // Populate createdFutures with the timeline fork decisions
+ logger.Log(1, "Processing AdvanceTimeline decisions");
+ foreach (AdvanceTimeline advanceTimeline in decisions.OfType())
+ {
+ 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
// 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)
{
- 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))
- {
- // 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(doesMove.Order.Unit.Next(doesMove.Order.Location, future));
+ 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())
{
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>> newOrders = decisions
- .OfType()
- .GroupBy(
- keySelector: d => d.Order.Unit.Season,
- elementSelector: d => d.Order as Order)
- .Where(group => !world.GivenOrders.ContainsKey(group.Key))
- .Select(group => new KeyValuePair>(
- group.Key, new(group.ToList())));
+ // Record the adjudication results to the season's order history
+ Dictionary newHistory = new();
+ foreach (UnitOrder unitOrder in decisions.OfType().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> 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 incomingMoveOrders = decision.Orders
+ .OfType()
+ .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,
diff --git a/MultiversalDiplomacy/Model/OrderHistory.cs b/MultiversalDiplomacy/Model/OrderHistory.cs
new file mode 100644
index 0000000..c52cc45
--- /dev/null
+++ b/MultiversalDiplomacy/Model/OrderHistory.cs
@@ -0,0 +1,28 @@
+using System.Collections.ObjectModel;
+
+using MultiversalDiplomacy.Orders;
+
+namespace MultiversalDiplomacy.Model;
+
+public class OrderHistory
+{
+ public List Orders;
+
+ public Dictionary IsDislodgedOutcomes;
+
+ public Dictionary DoesMoveOutcomes;
+
+ public OrderHistory()
+ : this(new(), new(), new())
+ {}
+
+ public OrderHistory(
+ List orders,
+ Dictionary isDislodgedOutcomes,
+ Dictionary doesMoveOutcomes)
+ {
+ this.Orders = new(orders);
+ this.IsDislodgedOutcomes = new(isDislodgedOutcomes);
+ this.DoesMoveOutcomes = new(doesMoveOutcomes);
+ }
+}
\ No newline at end of file
diff --git a/MultiversalDiplomacy/Model/Season.cs b/MultiversalDiplomacy/Model/Season.cs
index 5337379..cbbc7d8 100644
--- a/MultiversalDiplomacy/Model/Season.cs
+++ b/MultiversalDiplomacy/Model/Season.cs
@@ -71,7 +71,7 @@ public class Season
public override string ToString()
{
- return $"{this.Turn}:{this.Timeline}";
+ return $"{this.Timeline}@{this.Turn}";
}
///
diff --git a/MultiversalDiplomacy/Model/World.cs b/MultiversalDiplomacy/Model/World.cs
index 0d145cb..799933e 100644
--- a/MultiversalDiplomacy/Model/World.cs
+++ b/MultiversalDiplomacy/Model/World.cs
@@ -42,7 +42,7 @@ public class World
///
/// Orders given to units in each season.
///
- public ReadOnlyDictionary> GivenOrders { get; }
+ public ReadOnlyDictionary OrderHistory { get; }
///
/// Immutable game options.
@@ -59,7 +59,7 @@ public class World
Season rootSeason,
ReadOnlyCollection units,
ReadOnlyCollection retreatingUnits,
- ReadOnlyDictionary> givenOrders,
+ ReadOnlyDictionary 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? seasons = null,
ReadOnlyCollection? units = null,
ReadOnlyCollection? retreatingUnits = null,
- ReadOnlyDictionary>? givenOrders = null,
+ ReadOnlyDictionary? 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()),
new(new List()),
- new(new Dictionary>()),
+ new(new Dictionary()),
new Options());
}
@@ -123,7 +123,7 @@ public class World
IEnumerable? seasons = null,
IEnumerable? units = null,
IEnumerable? retreats = null,
- IEnumerable>>? orders = null)
+ IEnumerable>? 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)));
///