Compare commits
6 Commits
25d903d91a
...
9a64609605
Author | SHA1 | Date |
---|---|---|
Tim Van Baak | 9a64609605 | |
Tim Van Baak | 889e9d173b | |
Tim Van Baak | f21b1e500c | |
Tim Van Baak | ff9e6196ad | |
Tim Van Baak | a02e8121eb | |
Tim Van Baak | 3d664208b5 |
|
@ -1,6 +1,8 @@
|
||||||
using MultiversalDiplomacy.Model;
|
using MultiversalDiplomacy.Model;
|
||||||
using MultiversalDiplomacy.Orders;
|
using MultiversalDiplomacy.Orders;
|
||||||
|
|
||||||
|
using static MultiversalDiplomacy.Model.Location;
|
||||||
|
|
||||||
namespace MultiversalDiplomacy.Adjudicate.Decision;
|
namespace MultiversalDiplomacy.Adjudicate.Decision;
|
||||||
|
|
||||||
public class MovementDecisions
|
public class MovementDecisions
|
||||||
|
@ -8,7 +10,7 @@ public class MovementDecisions
|
||||||
public Dictionary<Unit, IsDislodged> IsDislodged { get; }
|
public Dictionary<Unit, IsDislodged> IsDislodged { get; }
|
||||||
public Dictionary<MoveOrder, HasPath> HasPath { get; }
|
public Dictionary<MoveOrder, HasPath> HasPath { get; }
|
||||||
public Dictionary<SupportOrder, GivesSupport> GivesSupport { get; }
|
public Dictionary<SupportOrder, GivesSupport> GivesSupport { get; }
|
||||||
public Dictionary<(Province, string), HoldStrength> HoldStrength { get; }
|
public Dictionary<(string, string), HoldStrength> HoldStrength { get; }
|
||||||
public Dictionary<MoveOrder, AttackStrength> AttackStrength { get; }
|
public Dictionary<MoveOrder, AttackStrength> AttackStrength { get; }
|
||||||
public Dictionary<MoveOrder, DefendStrength> DefendStrength { get; }
|
public Dictionary<MoveOrder, DefendStrength> DefendStrength { get; }
|
||||||
public Dictionary<MoveOrder, PreventStrength> PreventStrength { get; }
|
public Dictionary<MoveOrder, PreventStrength> PreventStrength { get; }
|
||||||
|
@ -91,10 +93,10 @@ public class MovementDecisions
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
(Province province, string season) UnitPoint(Unit unit)
|
(string province, string season) UnitPoint(Unit unit)
|
||||||
=> (world.Map.GetLocation(unit.Location).Province, unit.Season.Key);
|
=> (world.Map.GetLocation(unit.Location).Province.Name, unit.Season.Key);
|
||||||
(Province province, string season) MovePoint(MoveOrder move)
|
(string province, string season) MovePoint(MoveOrder move)
|
||||||
=> (move.Province, move.Season.Key);
|
=> (SplitKey(move.Location).province, move.Season.Key);
|
||||||
|
|
||||||
// Create a hold strength decision with an associated order for every province with a unit.
|
// Create a hold strength decision with an associated order for every province with a unit.
|
||||||
foreach (UnitOrder order in relevantOrders)
|
foreach (UnitOrder order in relevantOrders)
|
||||||
|
@ -108,13 +110,23 @@ public class MovementDecisions
|
||||||
bool IsIncoming(UnitOrder me, MoveOrder other)
|
bool IsIncoming(UnitOrder me, MoveOrder other)
|
||||||
=> me != other
|
=> me != other
|
||||||
&& other.Season == me.Unit.Season
|
&& other.Season == me.Unit.Season
|
||||||
&& other.Province == world.Map.GetLocation(me.Unit).Province;
|
&& SplitKey(other.Location).province == world.Map.GetLocation(me.Unit).Province.Name;
|
||||||
|
|
||||||
|
bool IsSupportFor(SupportMoveOrder me, MoveOrder move)
|
||||||
|
=> me.Target == move.Unit
|
||||||
|
&& me.Season == move.Season
|
||||||
|
&& me.Location.Key == move.Location;
|
||||||
|
|
||||||
bool AreOpposing(MoveOrder one, MoveOrder two)
|
bool AreOpposing(MoveOrder one, MoveOrder two)
|
||||||
=> one.Season == two.Unit.Season
|
=> one.Season == two.Unit.Season
|
||||||
&& two.Season == one.Unit.Season
|
&& two.Season == one.Unit.Season
|
||||||
&& one.Province == world.Map.GetLocation(two.Unit).Province
|
&& SplitKey(one.Location).province == world.Map.GetLocation(two.Unit).Province.Name
|
||||||
&& two.Province == world.Map.GetLocation(one.Unit).Province;
|
&& 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.
|
// Create all other relevant decisions for each order in the affected timelines.
|
||||||
foreach (UnitOrder order in relevantOrders)
|
foreach (UnitOrder order in relevantOrders)
|
||||||
|
@ -131,7 +143,7 @@ public class MovementDecisions
|
||||||
// Find supports corresponding to this move.
|
// Find supports corresponding to this move.
|
||||||
List<SupportMoveOrder> supports = relevantOrders
|
List<SupportMoveOrder> supports = relevantOrders
|
||||||
.OfType<SupportMoveOrder>()
|
.OfType<SupportMoveOrder>()
|
||||||
.Where(support => support.IsSupportFor(move))
|
.Where(support => IsSupportFor(support, move))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Determine if this move is a head-to-head battle.
|
// Determine if this move is a head-to-head battle.
|
||||||
|
@ -142,7 +154,7 @@ public class MovementDecisions
|
||||||
// Find competing moves.
|
// Find competing moves.
|
||||||
List<MoveOrder> competing = relevantOrders
|
List<MoveOrder> competing = relevantOrders
|
||||||
.OfType<MoveOrder>()
|
.OfType<MoveOrder>()
|
||||||
.Where(move.IsCompeting)
|
.Where(other => AreCompeting(move, other))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Create the move-related decisions.
|
// Create the move-related decisions.
|
||||||
|
@ -153,7 +165,7 @@ public class MovementDecisions
|
||||||
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.
|
||||||
HoldStrength.Ensure(MovePoint(move), () => new(move.Province, move.Season));
|
HoldStrength.Ensure(MovePoint(move), () => new(world.Map.GetLocation(move.Location).Province, move.Season));
|
||||||
}
|
}
|
||||||
else if (order is SupportOrder support)
|
else if (order is SupportOrder support)
|
||||||
{
|
{
|
||||||
|
@ -173,7 +185,7 @@ public class MovementDecisions
|
||||||
{
|
{
|
||||||
// Ensure a hold strength decision exists for the target's destination.
|
// Ensure a hold strength decision exists for the target's destination.
|
||||||
HoldStrength.Ensure(
|
HoldStrength.Ensure(
|
||||||
(supportMove.Province, supportMove.Season.Key),
|
(supportMove.Province.Name, supportMove.Season.Key),
|
||||||
() => new(supportMove.Province, supportMove.Season));
|
() => new(supportMove.Province, supportMove.Season));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ using MultiversalDiplomacy.Adjudicate.Logging;
|
||||||
using MultiversalDiplomacy.Model;
|
using MultiversalDiplomacy.Model;
|
||||||
using MultiversalDiplomacy.Orders;
|
using MultiversalDiplomacy.Orders;
|
||||||
|
|
||||||
|
using static MultiversalDiplomacy.Model.Location;
|
||||||
|
|
||||||
namespace MultiversalDiplomacy.Adjudicate;
|
namespace MultiversalDiplomacy.Adjudicate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -69,15 +71,15 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
|
|
||||||
// Trivial check: armies cannot move to water and fleets cannot move to land.
|
// Trivial check: armies cannot move to water and fleets cannot move to land.
|
||||||
AdjudicatorHelpers.InvalidateIfNotMatching(
|
AdjudicatorHelpers.InvalidateIfNotMatching(
|
||||||
order => (order.Unit.Type == UnitType.Army && order.Location.Type == LocationType.Land)
|
order => (order.Unit.Type == UnitType.Army && world.Map.GetLocation(order.Location).Type == LocationType.Land)
|
||||||
|| (order.Unit.Type == UnitType.Fleet && order.Location.Type == LocationType.Water),
|
|| (order.Unit.Type == UnitType.Fleet && world.Map.GetLocation(order.Location).Type == LocationType.Water),
|
||||||
ValidationReason.IllegalDestinationType,
|
ValidationReason.IllegalDestinationType,
|
||||||
ref moveOrders,
|
ref moveOrders,
|
||||||
ref validationResults);
|
ref validationResults);
|
||||||
|
|
||||||
// Trivial check: a unit cannot move to where it already is.
|
// Trivial check: a unit cannot move to where it already is.
|
||||||
AdjudicatorHelpers.InvalidateIfNotMatching(
|
AdjudicatorHelpers.InvalidateIfNotMatching(
|
||||||
order => !(order.Location.Key == order.Unit.Location && order.Season == order.Unit.Season),
|
order => !(order.Location == order.Unit.Location && order.Season == order.Unit.Season),
|
||||||
ValidationReason.DestinationMatchesOrigin,
|
ValidationReason.DestinationMatchesOrigin,
|
||||||
ref moveOrders,
|
ref moveOrders,
|
||||||
ref validationResults);
|
ref validationResults);
|
||||||
|
@ -90,7 +92,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
ILookup<bool, MoveOrder> moveOrdersByAdjacency = moveOrders
|
ILookup<bool, MoveOrder> moveOrdersByAdjacency = moveOrders
|
||||||
.ToLookup(order =>
|
.ToLookup(order =>
|
||||||
// Map adjacency
|
// Map adjacency
|
||||||
world.Map.GetLocation(order.Unit).Adjacents.Contains(order.Location)
|
world.Map.GetLocation(order.Unit).Adjacents.Select(loc => loc.Key).Contains(order.Location)
|
||||||
// Turn adjacency
|
// Turn adjacency
|
||||||
&& Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1
|
&& Math.Abs(order.Unit.Season.Turn - order.Season.Turn) <= 1
|
||||||
// Timeline adjacency
|
// Timeline adjacency
|
||||||
|
@ -339,7 +341,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
Season moveSeason = doesMove.Order.Season;
|
Season moveSeason = doesMove.Order.Season;
|
||||||
if (doesMove.Outcome == true && createdFutures.TryGetValue(moveSeason, out Season future))
|
if (doesMove.Outcome == true && createdFutures.TryGetValue(moveSeason, out Season future))
|
||||||
{
|
{
|
||||||
Unit next = doesMove.Order.Unit.Next(doesMove.Order.Location.Key, future);
|
Unit next = doesMove.Order.Unit.Next(doesMove.Order.Location, future);
|
||||||
logger.Log(3, "Advancing unit to {0}", next);
|
logger.Log(3, "Advancing unit to {0}", next);
|
||||||
createdUnits.Add(next);
|
createdUnits.Add(next);
|
||||||
}
|
}
|
||||||
|
@ -618,7 +620,10 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
|
|
||||||
if (!potentialDislodger)
|
if (!potentialDislodger)
|
||||||
{
|
{
|
||||||
progress |= LoggedUpdate(decision, false, depth, "No invader can move");
|
string reason = decision.Incoming.Count == 0
|
||||||
|
? "No unit is attacking"
|
||||||
|
: "All attacks failed";
|
||||||
|
progress |= LoggedUpdate(decision, false, depth, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
return progress;
|
return progress;
|
||||||
|
@ -635,7 +640,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
|
|
||||||
// If the origin and destination are adjacent, then there is a path.
|
// If the origin and destination are adjacent, then there is a path.
|
||||||
if (// Map adjacency
|
if (// Map adjacency
|
||||||
world.Map.GetLocation(decision.Order.Unit).Adjacents.Contains(decision.Order.Location)
|
world.Map.GetLocation(decision.Order.Unit).Adjacents.Select(loc => loc.Key).Contains(decision.Order.Location)
|
||||||
// Turn adjacency
|
// Turn adjacency
|
||||||
&& Math.Abs(decision.Order.Unit.Season.Turn - decision.Order.Season.Turn) <= 1
|
&& Math.Abs(decision.Order.Unit.Season.Turn - decision.Order.Season.Turn) <= 1
|
||||||
// Timeline adjacency
|
// Timeline adjacency
|
||||||
|
@ -750,7 +755,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
if (givesSupport.Outcome == true) min += 1;
|
if (givesSupport.Outcome == true) min += 1;
|
||||||
if (givesSupport.Outcome != false) max += 1;
|
if (givesSupport.Outcome != false) max += 1;
|
||||||
}
|
}
|
||||||
progress |= LoggedUpdate(decision, min, max, depth, "Updated based on unit's supports");
|
progress |= LoggedUpdate(decision, min, max, depth, $"Updated based on {decision.Supports.Count} hold supports");
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -776,7 +781,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
// If there is a head to head battle, a unit at the destination that isn't moving away, or
|
// If there is a head to head battle, a unit at the destination that isn't moving away, or
|
||||||
// a unit at the destination that will fail to move away, then the attacking unit will have
|
// a unit at the destination that will fail to move away, then the attacking unit will have
|
||||||
// to dislodge it.
|
// to dislodge it.
|
||||||
UnitOrder? destOrder = decisions.HoldStrength[(decision.Order.Province, decision.Order.Season.Key)].Order;
|
UnitOrder? destOrder = decisions.HoldStrength[(SplitKey(decision.Order.Location).province, decision.Order.Season.Key)].Order;
|
||||||
DoesMove? destMoveAway = destOrder is MoveOrder moveAway
|
DoesMove? destMoveAway = destOrder is MoveOrder moveAway
|
||||||
? decisions.DoesMove[moveAway]
|
? decisions.DoesMove[moveAway]
|
||||||
: null;
|
: null;
|
||||||
|
@ -786,7 +791,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
}
|
}
|
||||||
if (// In any case here, there will have to be a unit at the destination with an order,
|
if (// In any case here, there will have to be a unit at the destination with an order,
|
||||||
// which means that destOrder will have to be populated. Including this in the if
|
// which means that destOrder will have to be populated. Including this in the if
|
||||||
//condition lets the compiler know it won't be null in the if block.
|
// condition lets the compiler know it won't be null in the if block.
|
||||||
destOrder != null
|
destOrder != null
|
||||||
&& (// Is head to head
|
&& (// Is head to head
|
||||||
decision.OpposingMove != null
|
decision.OpposingMove != null
|
||||||
|
@ -815,7 +820,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
if (givesSupport.Outcome == true) min += 1;
|
if (givesSupport.Outcome == true) min += 1;
|
||||||
if (givesSupport.Outcome != false) max += 1;
|
if (givesSupport.Outcome != false) max += 1;
|
||||||
}
|
}
|
||||||
progress |= LoggedUpdate(decision, min, max, depth, "Updated with supports from other powers");
|
progress |= LoggedUpdate(decision, min, max, depth, $"Updated with {decision.Supports.Count} (?) move supports from third parties");
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -837,7 +842,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
}
|
}
|
||||||
// Force min to zero in case of an attempt to disloge a unit of the same power.
|
// Force min to zero in case of an attempt to disloge a unit of the same power.
|
||||||
if (decision.Order.Unit.Power == destPower) min = 0;
|
if (decision.Order.Unit.Power == destPower) min = 0;
|
||||||
progress |= LoggedUpdate(decision, min, max, depth, "Updated with supports");
|
progress |= LoggedUpdate(decision, min, max, depth, $"Updated with {decision.Supports.Count} (?) move supports");
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -853,7 +858,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
if (givesSupport.Outcome == true) min += 1;
|
if (givesSupport.Outcome == true) min += 1;
|
||||||
if (givesSupport.Outcome != false) max += 1;
|
if (givesSupport.Outcome != false) max += 1;
|
||||||
}
|
}
|
||||||
progress |= LoggedUpdate(decision, min, max, depth, "Updated with supports from all powers");
|
progress |= LoggedUpdate(decision, min, max, depth, $"Updated with {decision.Supports.Count} move supports from all powers");
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -878,7 +883,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
if (givesSupport.Outcome == true) min += 1;
|
if (givesSupport.Outcome == true) min += 1;
|
||||||
if (givesSupport.Outcome != false) max += 1;
|
if (givesSupport.Outcome != false) max += 1;
|
||||||
}
|
}
|
||||||
progress |= LoggedUpdate(decision, min, max, depth, "Updated based on unit's supports");
|
progress |= LoggedUpdate(decision, min, max, depth, $"Updated based on {decision.Supports.Count} supports");
|
||||||
|
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
@ -932,7 +937,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
min = 0;
|
min = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress |= LoggedUpdate(decision, min, max, depth, "Updated based on unit's supports");
|
progress |= LoggedUpdate(decision, min, max, depth, $"Updated based on {decision.Supports.Count} supports");
|
||||||
|
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
@ -955,7 +960,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
// strength.
|
// strength.
|
||||||
NumericAdjudicationDecision defense = decision.OpposingMove != null
|
NumericAdjudicationDecision defense = decision.OpposingMove != null
|
||||||
? decisions.DefendStrength[decision.OpposingMove]
|
? decisions.DefendStrength[decision.OpposingMove]
|
||||||
: decisions.HoldStrength[(decision.Order.Province, decision.Order.Season.Key)];
|
: decisions.HoldStrength[(SplitKey(decision.Order.Location).province, decision.Order.Season.Key)];
|
||||||
progress |= ResolveDecision(defense, world, decisions, depth + 1);
|
progress |= ResolveDecision(defense, world, decisions, depth + 1);
|
||||||
|
|
||||||
// If the attack doesn't beat the defense, resolve the move to false.
|
// If the attack doesn't beat the defense, resolve the move to false.
|
||||||
|
@ -990,7 +995,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
decision,
|
decision,
|
||||||
attack.MinValue > defense.MaxValue && beatsAllCompetingMoves,
|
attack.MinValue > defense.MaxValue && beatsAllCompetingMoves,
|
||||||
depth,
|
depth,
|
||||||
"Updated based on competing moves");
|
$"Updated based on {decision.Competing.Count} competing moves");
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ public static class PathFinder
|
||||||
/// Determines if a convoy path exists for a move order.
|
/// Determines if a convoy path exists for a move order.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool ConvoyPathExists(World world, MoveOrder order)
|
public static bool ConvoyPathExists(World world, MoveOrder order)
|
||||||
=> ConvoyPathExists(world, order.Unit, order.Location, order.Season);
|
=> ConvoyPathExists(world, order.Unit, world.Map.GetLocation(order.Location), order.Season);
|
||||||
|
|
||||||
private static bool ConvoyPathExists(
|
private static bool ConvoyPathExists(
|
||||||
World world,
|
World world,
|
||||||
|
|
|
@ -53,6 +53,12 @@ public class Location
|
||||||
this.AdjacentList = new List<Location>();
|
this.AdjacentList = new List<Location>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static (string province, string location) SplitKey(string locationKey)
|
||||||
|
{
|
||||||
|
var split = locationKey.Split(['/'], 2);
|
||||||
|
return (split[0], split[1]);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return this.Name == "land" || this.Name == "water"
|
return this.Name == "land" || this.Name == "water"
|
||||||
|
|
|
@ -10,9 +10,10 @@ namespace MultiversalDiplomacy.Model;
|
||||||
public struct Season(string timeline, int turn)
|
public struct Season(string timeline, int turn)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The first turn number. This is defined to reduce confusion about whether the first turn is 0 or 1.
|
/// The root season of every game. This is defined to avoid any confusion about what the first turn or timeline
|
||||||
|
/// should be or what season to use to key into a fresh <see cref="Timelines"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int FIRST_TURN = 0;
|
public static readonly Season First = new(Timelines.IntToString(0), 0);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The timeline to which this season belongs.
|
/// The timeline to which this season belongs.
|
||||||
|
|
|
@ -75,10 +75,7 @@ public class Timelines(int next, Dictionary<string, Season?> pasts)
|
||||||
/// Create a new multiverse with an initial season.
|
/// Create a new multiverse with an initial season.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Timelines Create()
|
public static Timelines Create()
|
||||||
{
|
=> new(StringToInt(Season.First.Timeline) + 1, new() { {Season.First.Key, null} });
|
||||||
Season first = new(IntToString(0), Season.FIRST_TURN);
|
|
||||||
return new Timelines(1, new() { {first.Key, null} });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a continuation of a season if it has no futures, otherwise create a fork.
|
/// Create a continuation of a season if it has no futures, otherwise create a fork.
|
||||||
|
@ -119,7 +116,6 @@ public class Timelines(int next, Dictionary<string, Season?> pasts)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Season GetTimelineRoot(Season season)
|
public Season GetTimelineRoot(Season season)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"GetTimelineRoot({season.Key})");
|
|
||||||
return Pasts[season.Key] is Season past && season.Timeline == past.Timeline
|
return Pasts[season.Key] is Season past && season.Timeline == past.Timeline
|
||||||
? GetTimelineRoot(past)
|
? GetTimelineRoot(past)
|
||||||
: season;
|
: season;
|
||||||
|
|
|
@ -181,7 +181,7 @@ public class World
|
||||||
: splits.Length == 3
|
: splits.Length == 3
|
||||||
? Map.GetWater(splits[2])
|
? Map.GetWater(splits[2])
|
||||||
: Map.GetWater(splits[2], splits[3]);
|
: Map.GetWater(splits[2], splits[3]);
|
||||||
Unit unit = Unit.Build(location.Key, new("a0"), power, type);
|
Unit unit = Unit.Build(location.Key, Season.First, power, type);
|
||||||
return unit;
|
return unit;
|
||||||
});
|
});
|
||||||
return this.Update(units: units);
|
return this.Update(units: units);
|
||||||
|
@ -229,7 +229,7 @@ public class World
|
||||||
public Unit GetUnitAt(string provinceName, Season? season = null)
|
public Unit GetUnitAt(string provinceName, Season? season = null)
|
||||||
{
|
{
|
||||||
Province province = Map.GetProvince(provinceName);
|
Province province = Map.GetProvince(provinceName);
|
||||||
season ??= new("a0");
|
season ??= Season.First;
|
||||||
Unit? foundUnit = this.Units.SingleOrDefault(
|
Unit? foundUnit = this.Units.SingleOrDefault(
|
||||||
u => Map.GetLocation(u!).Province == province && u!.Season == season,
|
u => Map.GetLocation(u!).Province == province && u!.Season == season,
|
||||||
null)
|
null)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using MultiversalDiplomacy.Model;
|
using MultiversalDiplomacy.Model;
|
||||||
|
|
||||||
|
using static MultiversalDiplomacy.Model.Location;
|
||||||
|
|
||||||
namespace MultiversalDiplomacy.Orders;
|
namespace MultiversalDiplomacy.Orders;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -15,14 +17,9 @@ public class MoveOrder : UnitOrder
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The destination location to which the unit should move.
|
/// The destination location to which the unit should move.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Location Location { get; }
|
public string Location { get; }
|
||||||
|
|
||||||
/// <summary>
|
public MoveOrder(string power, Unit unit, Season season, string location)
|
||||||
/// The destination province to which the unit should move.
|
|
||||||
/// </summary>
|
|
||||||
public Province Province => this.Location.Province;
|
|
||||||
|
|
||||||
public MoveOrder(string power, Unit unit, Season season, Location location)
|
|
||||||
: base (power, unit)
|
: base (power, unit)
|
||||||
{
|
{
|
||||||
this.Season = season;
|
this.Season = season;
|
||||||
|
@ -31,14 +28,6 @@ public class MoveOrder : UnitOrder
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{this.Unit} -> {Season.Timeline}-{Province}@{Season.Turn}";
|
return $"{this.Unit} -> {Season.Timeline}-{SplitKey(Location).province}@{Season.Turn}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns whether another move order has the same destination as this order.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsCompeting(MoveOrder other)
|
|
||||||
=> this != other
|
|
||||||
&& this.Season == other.Season
|
|
||||||
&& this.Province == other.Province;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,4 @@ public class SupportMoveOrder : SupportOrder
|
||||||
{
|
{
|
||||||
return $"{this.Unit} S {this.Target} -> {(this.Province, this.Season).ToShort()}";
|
return $"{this.Unit} S {this.Target} -> {(this.Province, this.Season).ToShort()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSupportFor(MoveOrder move)
|
|
||||||
=> this.Target == move.Unit
|
|
||||||
&& this.Season == move.Season
|
|
||||||
&& this.Location == move.Location;
|
|
||||||
}
|
}
|
|
@ -14,6 +14,31 @@ public class SerializationTest
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SerializeRoundTrip_Timelines()
|
||||||
|
{
|
||||||
|
Timelines one = Timelines.Create();
|
||||||
|
|
||||||
|
string serial1 = JsonSerializer.Serialize(one, Options);
|
||||||
|
Timelines two = JsonSerializer.Deserialize<Timelines>(serial1, Options)
|
||||||
|
?? throw new AssertionException("Failed to deserialize");
|
||||||
|
|
||||||
|
Assert.That(two.Next, Is.EqualTo(one.Next), "Failed to reserialize next timeline");
|
||||||
|
Assert.That(two.Pasts, Is.EquivalentTo(one.Pasts), "Failed to reserialize pasts");
|
||||||
|
|
||||||
|
Timelines three = two
|
||||||
|
.WithNewSeason(Season.First, out var a1)
|
||||||
|
.WithNewSeason(a1, out var a2)
|
||||||
|
.WithNewSeason(a1, out var b2);
|
||||||
|
|
||||||
|
string serial2 = JsonSerializer.Serialize(three, Options);
|
||||||
|
Timelines four = JsonSerializer.Deserialize<Timelines>(serial2, Options)
|
||||||
|
?? throw new AssertionException("Failed to deserialize");
|
||||||
|
|
||||||
|
Assert.That(four.Next, Is.EqualTo(three.Next), "Failed to reserialize next timeline");
|
||||||
|
Assert.That(four.Pasts, Is.EquivalentTo(three.Pasts), "Failed to reserialize pasts");
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void SerializeRoundTrip_NewGame()
|
public void SerializeRoundTrip_NewGame()
|
||||||
{
|
{
|
||||||
|
@ -65,36 +90,42 @@ public class SerializationTest
|
||||||
Assert.That(tyr0, Is.NotDislodged);
|
Assert.That(tyr0, Is.NotDislodged);
|
||||||
setup.UpdateWorld();
|
setup.UpdateWorld();
|
||||||
|
|
||||||
Assert.That(setup.World.OrderHistory["a0"].Orders.Count, Is.GreaterThan(0), "Missing orders");
|
Assert.That(setup.World.OrderHistory[s0.Key].Orders.Count, Is.GreaterThan(0), "Missing orders");
|
||||||
Assert.That(setup.World.OrderHistory["a0"].DoesMoveOutcomes.Count, Is.GreaterThan(0), "Missing moves");
|
Assert.That(setup.World.OrderHistory[s0.Key].DoesMoveOutcomes.Count, Is.GreaterThan(0), "Missing moves");
|
||||||
Assert.That(setup.World.OrderHistory["a0"].IsDislodgedOutcomes.Count, Is.GreaterThan(0), "Missing dislodges");
|
Assert.That(setup.World.OrderHistory[s0.Key].IsDislodgedOutcomes.Count, Is.GreaterThan(0), "Missing dislodges");
|
||||||
|
|
||||||
|
// Assert.Ignore("Serialization doesn't fully work yet");
|
||||||
|
|
||||||
// Serialize and deserialize the world
|
// Serialize and deserialize the world
|
||||||
string serialized = JsonSerializer.Serialize(setup.World, Options);
|
string serialized = JsonSerializer.Serialize(setup.World, Options);
|
||||||
|
Console.WriteLine(serialized);
|
||||||
World reserialized = JsonSerializer.Deserialize<World>(serialized, Options)
|
World reserialized = JsonSerializer.Deserialize<World>(serialized, Options)
|
||||||
?? throw new AssertionException("Failed to reserialize world");
|
?? throw new AssertionException("Failed to reserialize world");
|
||||||
|
|
||||||
Assert.Multiple(() => {
|
Assert.Multiple(() => {
|
||||||
Assert.That(reserialized.OrderHistory["a0"].Orders.Count, Is.GreaterThan(0), "Missing orders");
|
Assert.That(reserialized.OrderHistory[s0.Key].Orders.Count, Is.GreaterThan(0), "Missing orders");
|
||||||
Assert.That(reserialized.OrderHistory["a0"].DoesMoveOutcomes.Count, Is.GreaterThan(0), "Missing moves");
|
Assert.That(reserialized.OrderHistory[s0.Key].DoesMoveOutcomes.Count, Is.GreaterThan(0), "Missing moves");
|
||||||
Assert.That(reserialized.OrderHistory["a0"].IsDislodgedOutcomes.Count, Is.GreaterThan(0), "Missing dislodges");
|
Assert.That(reserialized.OrderHistory[s0.Key].IsDislodgedOutcomes.Count, Is.GreaterThan(0), "Missing dislodges");
|
||||||
|
Assert.That(reserialized.Timelines.Pasts, Is.Not.Empty, "Missing timeline history");
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Ignore("Serialization doesn't fully work yet");
|
|
||||||
|
|
||||||
// Resume the test case
|
// Resume the test case
|
||||||
setup = new(reserialized, MovementPhaseAdjudicator.Instance);
|
setup = new(reserialized, MovementPhaseAdjudicator.Instance);
|
||||||
setup[("a", 1)]
|
setup[("a", 1)]
|
||||||
["Germany"]
|
["Germany"]
|
||||||
.Army("Mun").Supports.Army("Mun", season: new("a0")).MoveTo("Tyr").GetReference(out var mun1)
|
.Army("Mun").Supports.Army("Mun", season: s0).MoveTo("Tyr").GetReference(out var mun1)
|
||||||
["Austria"]
|
["Austria"]
|
||||||
.Army("Tyr").Holds();
|
.Army("Tyr").Holds();
|
||||||
|
|
||||||
setup.ValidateOrders();
|
setup.ValidateOrders();
|
||||||
Assert.That(mun1, Is.Valid);
|
Assert.That(mun1, Is.Valid);
|
||||||
var adjudications = setup.AdjudicateOrders();
|
var adjudications = setup.AdjudicateOrders();
|
||||||
|
foreach (var adj in adjudications)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{adj}");
|
||||||
|
}
|
||||||
|
|
||||||
Assert.That(mun1, Is.NotCut);
|
Assert.That(mun1, Is.NotCut);
|
||||||
Console.WriteLine(string.Join(", ", adjudications.Select(a => a.ToString())));
|
|
||||||
DoesMove mun0move = adjudications.OfType<DoesMove>().Single(move => move.Order.Unit.Key == mun0.Order.Unit.Key);
|
DoesMove mun0move = adjudications.OfType<DoesMove>().Single(move => move.Order.Unit.Key == mun0.Order.Unit.Key);
|
||||||
Assert.That(mun0move.Outcome, Is.True);
|
Assert.That(mun0move.Outcome, Is.True);
|
||||||
IsDislodged tyr0dislodge = adjudications.OfType<IsDislodged>().Single(dis => dis.Order.Unit.Key == tyr0.Order.Unit.Key);
|
IsDislodged tyr0dislodge = adjudications.OfType<IsDislodged>().Single(dis => dis.Order.Unit.Key == tyr0.Order.Unit.Key);
|
||||||
|
|
|
@ -423,7 +423,7 @@ public class TestCaseBuilder
|
||||||
this.PowerContext.Power,
|
this.PowerContext.Power,
|
||||||
this.Unit,
|
this.Unit,
|
||||||
destSeason,
|
destSeason,
|
||||||
destination);
|
destination.Key);
|
||||||
this.Builder.OrderList.Add(moveOrder);
|
this.Builder.OrderList.Add(moveOrder);
|
||||||
return new OrderDefinedContext<MoveOrder>(this, moveOrder);
|
return new OrderDefinedContext<MoveOrder>(this, moveOrder);
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ class TestCaseBuilderTest
|
||||||
Assert.That(orderBer, Is.InstanceOf<MoveOrder>(), "Unexpected order type");
|
Assert.That(orderBer, Is.InstanceOf<MoveOrder>(), "Unexpected order type");
|
||||||
Assert.That(
|
Assert.That(
|
||||||
(orderBer as MoveOrder)?.Location,
|
(orderBer as MoveOrder)?.Location,
|
||||||
Is.EqualTo(setup.World.Map.GetLand("Kiel")),
|
Is.EqualTo(setup.World.Map.GetLand("Kiel").Key),
|
||||||
"Unexpected move order destination");
|
"Unexpected move order destination");
|
||||||
|
|
||||||
UnitOrder orderPru = orders.Single(OrderForProvince("Prussia"));
|
UnitOrder orderPru = orders.Single(OrderForProvince("Prussia"));
|
||||||
|
|
|
@ -2,9 +2,9 @@ using MultiversalDiplomacy.Model;
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
namespace MultiversalDiplomacyTests.Model;
|
namespace MultiversalDiplomacyTests;
|
||||||
|
|
||||||
public class TimelineFactoryTest
|
public class TimelinesTest
|
||||||
{
|
{
|
||||||
[TestCase(0, "a")]
|
[TestCase(0, "a")]
|
||||||
[TestCase(1, "b")]
|
[TestCase(1, "b")]
|
||||||
|
@ -36,12 +36,12 @@ public class TimelineFactoryTest
|
||||||
public void NoSharedFactoryState()
|
public void NoSharedFactoryState()
|
||||||
{
|
{
|
||||||
Timelines one = Timelines.Create()
|
Timelines one = Timelines.Create()
|
||||||
.WithNewSeason(new Season("a0"), out var s1)
|
.WithNewSeason(Season.First, out var s1)
|
||||||
.WithNewSeason(new Season("a0"), out var s2)
|
.WithNewSeason(Season.First, out var s2)
|
||||||
.WithNewSeason(new Season("a0"), out var s3);
|
.WithNewSeason(Season.First, out var s3);
|
||||||
Timelines two = Timelines.Create()
|
Timelines two = Timelines.Create()
|
||||||
.WithNewSeason(new Season("a0"), out var s4)
|
.WithNewSeason(Season.First, out var s4)
|
||||||
.WithNewSeason(new Season("a0"), out var s5);
|
.WithNewSeason(Season.First, out var s5);
|
||||||
|
|
||||||
Assert.That(s1.Timeline, Is.EqualTo("a"));
|
Assert.That(s1.Timeline, Is.EqualTo("a"));
|
||||||
Assert.That(s2.Timeline, Is.EqualTo("b"));
|
Assert.That(s2.Timeline, Is.EqualTo("b"));
|
||||||
|
@ -54,14 +54,14 @@ public class TimelineFactoryTest
|
||||||
public void TimelineForking()
|
public void TimelineForking()
|
||||||
{
|
{
|
||||||
Timelines timelines = Timelines.Create()
|
Timelines timelines = Timelines.Create()
|
||||||
.WithNewSeason("a0", out var a1)
|
.WithNewSeason(Season.First, out var a1)
|
||||||
.WithNewSeason(a1, out var a2)
|
.WithNewSeason(a1, out var a2)
|
||||||
.WithNewSeason(a2, out var a3)
|
.WithNewSeason(a2, out var a3)
|
||||||
.WithNewSeason(a1, out var b2)
|
.WithNewSeason(a1, out var b2)
|
||||||
.WithNewSeason(b2, out var b3)
|
.WithNewSeason(b2, out var b3)
|
||||||
.WithNewSeason(a1, out var c2)
|
.WithNewSeason(a1, out var c2)
|
||||||
.WithNewSeason(a2, out var d3);
|
.WithNewSeason(a2, out var d3);
|
||||||
Season a0 = new("a0");
|
Season a0 = Season.First;
|
||||||
|
|
||||||
Assert.That(
|
Assert.That(
|
||||||
timelines.Pasts.Keys,
|
timelines.Pasts.Keys,
|
||||||
|
@ -76,13 +76,13 @@ public class TimelineFactoryTest
|
||||||
Assert.That(c2.Timeline, Is.EqualTo("c"), "Unexpected second alt");
|
Assert.That(c2.Timeline, Is.EqualTo("c"), "Unexpected second alt");
|
||||||
Assert.That(d3.Timeline, Is.EqualTo("d"), "Unexpected third alt");
|
Assert.That(d3.Timeline, Is.EqualTo("d"), "Unexpected third alt");
|
||||||
|
|
||||||
Assert.That(a1.Turn, Is.EqualTo(Season.FIRST_TURN + 1), "Unexpected a1 turn number");
|
Assert.That(a1.Turn, Is.EqualTo(Season.First.Turn + 1), "Unexpected a1 turn number");
|
||||||
Assert.That(a2.Turn, Is.EqualTo(Season.FIRST_TURN + 2), "Unexpected a2 turn number");
|
Assert.That(a2.Turn, Is.EqualTo(Season.First.Turn + 2), "Unexpected a2 turn number");
|
||||||
Assert.That(a3.Turn, Is.EqualTo(Season.FIRST_TURN + 3), "Unexpected a3 turn number");
|
Assert.That(a3.Turn, Is.EqualTo(Season.First.Turn + 3), "Unexpected a3 turn number");
|
||||||
Assert.That(b2.Turn, Is.EqualTo(Season.FIRST_TURN + 2), "Unexpected b2 turn number");
|
Assert.That(b2.Turn, Is.EqualTo(Season.First.Turn + 2), "Unexpected b2 turn number");
|
||||||
Assert.That(b3.Turn, Is.EqualTo(Season.FIRST_TURN + 3), "Unexpected b3 turn number");
|
Assert.That(b3.Turn, Is.EqualTo(Season.First.Turn + 3), "Unexpected b3 turn number");
|
||||||
Assert.That(c2.Turn, Is.EqualTo(Season.FIRST_TURN + 2), "Unexpected c2 turn number");
|
Assert.That(c2.Turn, Is.EqualTo(Season.First.Turn + 2), "Unexpected c2 turn number");
|
||||||
Assert.That(d3.Turn, Is.EqualTo(Season.FIRST_TURN + 3), "Unexpected d3 turn number");
|
Assert.That(d3.Turn, Is.EqualTo(Season.First.Turn + 3), "Unexpected d3 turn number");
|
||||||
|
|
||||||
Assert.That(timelines.GetTimelineRoot(a0), Is.EqualTo(a0), "Expected timeline root to be reflexive");
|
Assert.That(timelines.GetTimelineRoot(a0), Is.EqualTo(a0), "Expected timeline root to be reflexive");
|
||||||
Assert.That(timelines.GetTimelineRoot(a3), Is.EqualTo(a0), "Expected trunk timeline to have root");
|
Assert.That(timelines.GetTimelineRoot(a3), Is.EqualTo(a0), "Expected trunk timeline to have root");
|
|
@ -13,7 +13,7 @@ public class UnitTests
|
||||||
Location Mun = world.Map.GetLand("Mun"),
|
Location Mun = world.Map.GetLand("Mun"),
|
||||||
Boh = world.Map.GetLand("Boh"),
|
Boh = world.Map.GetLand("Boh"),
|
||||||
Tyr = world.Map.GetLand("Tyr");
|
Tyr = world.Map.GetLand("Tyr");
|
||||||
Season a0 = new("a0");
|
Season a0 = Season.First;
|
||||||
Unit u1 = Unit.Build(Mun.Key, a0, "Austria", UnitType.Army);
|
Unit u1 = Unit.Build(Mun.Key, a0, "Austria", UnitType.Army);
|
||||||
|
|
||||||
world = world.WithNewSeason(a0, out Season a1);
|
world = world.WithNewSeason(a0, out Season a1);
|
||||||
|
|
Loading…
Reference in New Issue