Compare commits

...

6 Commits

14 changed files with 124 additions and 89 deletions

View File

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

View File

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

View File

@ -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,

View File

@ -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"

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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