From 5b32786904b17c3073a6ae70121c068162c4af39 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Thu, 5 Sep 2024 05:27:48 +0000 Subject: [PATCH] Implement support-move parsing --- MultiversalDiplomacy/Model/OrderParser.cs | 57 ++++++++++- .../Script/AdjudicationQueryScriptHandler.cs | 12 +-- MultiversalDiplomacyTests/ReplDriver.cs | 2 +- MultiversalDiplomacyTests/ReplTest.cs | 96 +++++++++---------- 4 files changed, 105 insertions(+), 62 deletions(-) diff --git a/MultiversalDiplomacy/Model/OrderParser.cs b/MultiversalDiplomacy/Model/OrderParser.cs index 2dbfdac..6878f3b 100644 --- a/MultiversalDiplomacy/Model/OrderParser.cs +++ b/MultiversalDiplomacy/Model/OrderParser.cs @@ -370,9 +370,60 @@ public class OrderParser(World world) { order = null; var support = ParseSupportMove(match); - throw new NotImplementedException(); - // It is possible to support a move to an inaccessible coast if another coast is accessible to the subject. - // DATC 4.B.4 prefers that automatic adjudicators strictly require matching coasts in supports. + if (!TryParseOrderSubject(world, support.timeline, support.turn, support.province, out Unit? subject)) { + return false; + } + + if (!TryParseOrderSubject( + world, support.targetTimeline, support.targetTurn, support.targetProvince, out Unit? target)) + { + return false; + } + + string destTimeline = support.destTimeline.Length > 0 + ? support.destTimeline + // If the destination is unspecified, use the target's + : target.Season.Timeline; + + int destTurn = support.destTurn.Length > 0 + ? int.Parse(support.destTurn) + // If the destination is unspecified, use the unit's + : target.Season.Turn; + + var destProvince = world.Map.Provinces.Single(province => province.Is(support.destProvince)); + + // DATC 4.B.6 requires that "irrelevant" locations like army to Spain nc be ignored. + // To satisfy this, any location of the wrong type is categorically ignored, so for an army the + // "north coast" location effectively doesn't exist here. + // Note that target is used instead of subject, since it is possible to support a move to an inaccessible + // coast as long as the subject can reach the province and the target can reach the location. + var unitLocations = destProvince.Locations.Where(loc => loc.Type switch { + LocationType.Land => target.Type == UnitType.Army, + LocationType.Water => target.Type == UnitType.Fleet, + _ => false, + }); + // DATC 4.6.B also requires that unknown coasts be ignored. To satisfy this, an additional filter by name. + // Doing both of these filters means "A - Spain/nc" is as meaningful as "F - Spain/wc". + var matchingLocations = unitLocations.Where(loc => loc.Is(support.destLocation)); + + // If one location matched, use that location. If the coast is inaccessible to the target, the order will + // be invalidated by a path check later to satisfy DATC 4.B.3. + string? destLocationKey = matchingLocations.FirstOrDefault(defaultValue: null)?.Key; + + if (destLocationKey is null) { + // If no location matched, location was omitted, nonexistent, or the wrong type. + // If one location is accessible, DATC 4.B.2 requires that it be used. + // If more than one location is accessible, DATC 4.B.1 requires the order fail. + + // TODO check which locations are accessible per the above + destLocationKey = unitLocations.First().Key; + + // return false; + } + + var destLocation = world.Map.GetLocation(destLocationKey); + order = new SupportMoveOrder(power, subject, target, new(destTimeline, destTurn), destLocation); + return true; } } diff --git a/MultiversalDiplomacy/Script/AdjudicationQueryScriptHandler.cs b/MultiversalDiplomacy/Script/AdjudicationQueryScriptHandler.cs index 620b63d..48cb34e 100644 --- a/MultiversalDiplomacy/Script/AdjudicationQueryScriptHandler.cs +++ b/MultiversalDiplomacy/Script/AdjudicationQueryScriptHandler.cs @@ -120,10 +120,8 @@ public class AdjudicationQueryScriptHandler( return ScriptResult.Succeed(this); case "holds": - // Assert a unit successfully held - case "dislodged": - // Assert a unit was dislodged + return ScriptResult.Fail($"Unknown assertion \"{args[0]}\"", this); case "moves": case "no-move": @@ -163,11 +161,9 @@ public class AdjudicationQueryScriptHandler( } return ScriptResult.Succeed(this); - case "supports": - // Assert a unit's support was given - - case "cut": - // Assert a unit's support was cut + case "support-given": + case "support-cut": + return ScriptResult.Fail($"Unknown assertion \"{args[0]}\"", this); default: return ScriptResult.Fail($"Unknown assertion \"{args[0]}\"", this); diff --git a/MultiversalDiplomacyTests/ReplDriver.cs b/MultiversalDiplomacyTests/ReplDriver.cs index fbb89b2..54444ed 100644 --- a/MultiversalDiplomacyTests/ReplDriver.cs +++ b/MultiversalDiplomacyTests/ReplDriver.cs @@ -17,7 +17,7 @@ public class ReplDriver(IScriptHandler initialHandler, bool echo = false) public ReplDriver ExecuteAll(string multiline) { - var lines = multiline.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + var lines = multiline.Split('\n', StringSplitOptions.TrimEntries); return lines.Aggregate(this, (repl, line) => repl.Execute(line)); } diff --git a/MultiversalDiplomacyTests/ReplTest.cs b/MultiversalDiplomacyTests/ReplTest.cs index 9f3adac..c90dd26 100644 --- a/MultiversalDiplomacyTests/ReplTest.cs +++ b/MultiversalDiplomacyTests/ReplTest.cs @@ -191,10 +191,44 @@ public class ReplTest repl.AssertFails("assert support-given Mun"); } + [Test] + public void AssertSupportMove() + { + var repl = StandardRepl(); + + repl.ExecuteAll(""" + unit Germany A Berlin + unit Germany A Bohemia + unit Austria A Tyrolia + --- + Germany: + Berlin - Silesia + Bohemia s Berlin - Silesia + --- + """); + + // Support is given + repl.Execute("assert support-given Boh"); + repl.AssertFails("assert support-cut Boh"); + + repl.ExecuteAll(""" + --- + Germany: + Silesia - Munich + Bohemia s Silesia - Munich + + Austria Tyrolia - Bohemia + --- + """); + + // Support is cut + repl.AssertFails("assert support-given Boh"); + repl.Execute("assert support-cut Boh"); + } + [Test] public void AssertDislodged() { - Assert.Ignore(); var repl = StandardRepl(); repl.ExecuteAll(""" @@ -203,60 +237,22 @@ public class ReplTest unit Austria A Tyr --- Germany Mun - Tyr + --- + """); + + // Move repelled + repl.Execute("assert holds Tyr"); + repl.AssertFails("assert dislodged Tyr"); + + repl.ExecuteAll(""" + --- + Germany Mun - Tyr Germany Boh s Mun - Tyr --- """); - // Assertion should pass for a dislodge + // Move succeeds repl.Execute("assert dislodged Tyr"); - // Assertion should fail for a repelled move repl.AssertFails("assert holds Tyr"); } - - [Test] - public void AssertSupports() - { - Assert.Ignore(); - var repl = StandardRepl(); - - repl.ExecuteAll(""" - unit Germany A Mun - unit Germany A Boh - unit Austria A Tyr - --- - Germany: - Mun - Tyr - Boh s Mun - Tyr - --- - """); - - // `supports` and `cut` are opposites - repl.Execute("assert supports Boh"); - repl.AssertFails("assert cut Boh"); - } - - [Test] - public void AssertCutSupport() - { - Assert.Ignore(); - var repl = StandardRepl(); - - repl.ExecuteAll(""" - unit Germany A Mun - unit Germany A Boh - unit Austria A Tyr - unit Italy A Vienna - --- - Germany: - Mun - Tyr - Boh s Mun - Tyr - - Italy Vienna - Boh - --- - """); - - // `supports` and `cut` are opposites - repl.Execute("assert cut Boh"); - repl.AssertFails("assert supports Boh"); - } }