diff --git a/MultiversalDiplomacy/Model/OrderParser.cs b/MultiversalDiplomacy/Model/OrderParser.cs index b8c223d..c9dd424 100644 --- a/MultiversalDiplomacy/Model/OrderParser.cs +++ b/MultiversalDiplomacy/Model/OrderParser.cs @@ -28,12 +28,14 @@ public class OrderParser(World world) public const string HoldVerb = "(h|hold|holds)"; - public const string MoveVerb = "(-|(?:->)|(?:=>)|(?:attack(?:s)?)|(?:move(?:s)?(?: to)?))"; + public const string MoveVerb = "(-|(?:->)|(?:=>)|(?:to)|(?:attack(?:s)?)|(?:move(?:s)?(?: to)?))"; public const string SupportVerb = "(s|support|supports)"; public const string ViaConvoy = "(convoy|via convoy|by convoy)"; + public const string ConvoyVerb = "(c|convoy|convoys)"; + public Regex UnitDeclaration = new( $"^{world.Map.PowerRegex} {Type} {world.Map.ProvinceRegex}(?:{SlashLocation}|{ParenLocation})?$", RegexOptions.IgnoreCase); @@ -183,6 +185,51 @@ public class OrderParser(World world) : match.Groups[18].Value, match.Groups[19].Value); + public Regex Convoy => new( + $"{UnitSpec} {ConvoyVerb} {UnitSpec} {MoveVerb} {FullLocation}$", + RegexOptions.IgnoreCase); + + public static ( + string type, + string timeline, + string province, + string location, + string turn, + string convoyVerb, + string targetType, + string targetTimeline, + string targetProvince, + string targetLocation, + string targetTurn, + string moveVerb, + string destTimeline, + string destProvince, + string destLocation, + string destTurn) + ParseConvoy(Match match) => ( + match.Groups[1].Value, + match.Groups[2].Value, + match.Groups[3].Value, + match.Groups[4].Length > 0 + ? match.Groups[4].Value + : match.Groups[5].Value, + match.Groups[6].Value, + match.Groups[7].Value, + match.Groups[8].Value, + match.Groups[9].Value, + match.Groups[10].Value, + match.Groups[11].Length > 0 + ? match.Groups[11].Value + : match.Groups[12].Value, + match.Groups[13].Value, + match.Groups[14].Value, + match.Groups[15].Value, + match.Groups[16].Value, + match.Groups[17].Length > 0 + ? match.Groups[17].Value + : match.Groups[18].Value, + match.Groups[19].Value); + public static bool TryParseUnit(World world, string unitSpec, [NotNullWhen(true)] out Unit? newUnit) { newUnit = null; diff --git a/MultiversalDiplomacyTests/OrderParserTest.cs b/MultiversalDiplomacyTests/OrderParserTest.cs index 360893c..01cb3f3 100644 --- a/MultiversalDiplomacyTests/OrderParserTest.cs +++ b/MultiversalDiplomacyTests/OrderParserTest.cs @@ -198,6 +198,47 @@ public class OrderParserTest Assert.That(actual, Is.EqualTo(expected), "Unexpected parse results"); } + static IEnumerable ConvoyRegexMatchesTestCases() + { + // Full specification + yield return Test( + "Fleet a-Nth/w@0 c A a-London/l@0 - a-Belgium/l@0", + "Fleet", "a", "Nth", "w", "0", "c", "A", "a", "London", "l", "0", "-", "a", "Belgium", "l", "0"); + // Case insensitivity + yield return Test( + "fleet B-nth/W@0 CONVOYS a B-lon/L@0 MOVE TO B-bel/L@0", + "fleet", "B", "nth", "W", "0", "CONVOYS", "a", "B", "lon", "L", "0", "MOVE TO", "B", "bel", "L", "0"); + // All optionals missing + yield return Test( + "TYN c ROM - TUN", + "", "", "TYN", "", "", "c", "", "", "ROM", "", "", "-", "", "TUN", "", ""); + // No confusion of unit type and timeline + yield return Test( + "F A-BOT C FIN - A-LVN", + "F", "A", "BOT", "", "", "C", "", "", "FIN", "", "", "-", "A", "LVN", "", ""); + // Elements with spaces + yield return Test( + "Western Mediterranean Sea convoys Spain move to North Africa", + "", "", "Western Mediterranean Sea", "", "", "convoys", "", "", "Spain", "", "", "move to", "", "North Africa", "", ""); + } + + [TestCaseSource(nameof(ConvoyRegexMatchesTestCases))] + public void ConvoyRegexMatches(string order, string[] expected) + { + OrderParser re = new(World.WithStandardMap()); + var match = re.Convoy.Match(order); + Assert.True(match.Success, "Match failed"); + var (type, timeline, province, location, turn, convoyVerb, + targetType, targetTimeline, targetProvince, targetLocation, targetTurn, moveVerb, + destTimeline, destProvince, destLocation, destTurn) = OrderParser.ParseConvoy(match); + string[] actual = [type, timeline, province, location, turn, convoyVerb, + targetType, targetTimeline, targetProvince, targetLocation, targetTurn, moveVerb, + destTimeline, destProvince, destLocation, destTurn]; + // Use EquivalentTo for more detailed error message + Assert.That(actual, Is.EquivalentTo(expected), "Unexpected parse results"); + Assert.That(actual, Is.EqualTo(expected), "Unexpected parse results"); + } + [Test] public void OrderParsingTest() {