using NUnit.Framework; using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Orders; namespace MultiversalDiplomacyTests; public class OrderParserTest { private static TestCaseData Test(string order, params string[] expected) => new TestCaseData(order, expected).SetName($"{{m}}(\"{order}\")"); static IEnumerable HoldRegexMatchesTestCases() { // Full specification yield return Test( "Army a-Munich/l@0 holds", "Army", "a", "Munich", "l", "0", "holds"); // Case insensitivity yield return Test( "fleet B-lon/C@0 H", "fleet", "B", "lon", "C", "0", "H"); // All optionals missing yield return Test( "ROM h", "", "", "ROM", "", "", "h"); // No confusion of unit type and timeline yield return Test( "A F-STP hold", "A", "F", "STP", "", "", "hold"); // Province with space in name yield return Test( "Fleet North Sea Hold", "Fleet", "", "North Sea", "", "", "Hold"); // Parenthesis location yield return Test( "F Spain(nc) holds", "F", "", "Spain", "nc", "", "holds"); } [TestCaseSource(nameof(HoldRegexMatchesTestCases))] public void HoldRegexMatches(string order, string[] expected) { OrderParser re = new(World.WithStandardMap()); var match = re.Hold.Match(order); Assert.True(match.Success, "Match failed"); var (type, timeline, province, location, turn, holdVerb) = OrderParser.ParseHold(match); string[] actual = [type, timeline, province, location, turn, holdVerb]; // 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"); } static IEnumerable MoveRegexMatchesTestCases() { // Full specification yield return Test( "Army a-Munich/l@0 - a-Tyrolia/l@0", "Army", "a", "Munich", "l", "0", "-", "a", "Tyrolia", "l", "0", ""); // Case insensitivity yield return Test( "fleet B-lon/C@0 - B-enc/W@0", "fleet", "B", "lon", "C", "0", "-", "B", "enc", "W", "0", ""); // All optionals missing yield return Test( "ROM - VIE", "", "", "ROM", "", "", "-", "", "VIE", "", "", ""); // No confusion of unit type and timeline yield return Test( "A F-STP - MOS", "A", "F", "STP", "", "", "-", "", "MOS", "", "", ""); // No confusion of timeline and hold verb yield return Test( "A Mun - h-Tyr", "A", "", "Mun", "", "", "-", "h", "Tyr", "", "", ""); // No confusion of timeline and support verb yield return Test( "A Mun - s-Tyr", "A", "", "Mun", "", "", "-", "s", "Tyr", "", "", ""); // Elements with spaces yield return Test( "Western Mediterranean Sea moves to Gulf of Lyons via convoy", "", "", "Western Mediterranean Sea", "", "", "moves to", "", "Gulf of Lyons", "", "", "via convoy"); // Parenthesis location yield return Test( "F Spain(nc) - Spain(sc)", "F", "", "Spain", "nc", "", "-", "", "Spain", "sc", "", ""); // Timeline designation spells out a province yield return Test( "A tyr-MUN(vie) - mun-TYR/vie", "A", "tyr", "MUN", "vie", "", "-", "mun", "TYR", "vie", "", ""); } [TestCaseSource(nameof(MoveRegexMatchesTestCases))] public void MoveRegexMatches(string order, string[] expected) { OrderParser re = new(World.WithStandardMap()); var match = re.Move.Match(order); Assert.True(match.Success, "Match failed"); var (type, timeline, province, location, turn, moveVerb, destTimeline, destProvince, destLocation, destTurn, viaConvoy) = OrderParser.ParseMove(match); string[] actual = [type, timeline, province, location, turn, moveVerb, destTimeline, destProvince, destLocation, destTurn, viaConvoy]; // 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"); } static IEnumerable SupportHoldRegexMatchesTestCases() { // Full specification yield return Test( "Army a-Munich/l@0 s A a-Tyrolia/l@0", "Army", "a", "Munich", "l", "0", "s", "A", "a", "Tyrolia", "l", "0"); // Case insensitivity yield return Test( "fleet B-lon/C@0 SUPPORTS B-enc/W@0", "fleet", "B", "lon", "C", "0", "SUPPORTS", "", "B", "enc", "W", "0"); // All optionals missing yield return Test( "ROM s VIE", "", "", "ROM", "", "", "s", "", "", "VIE", "", ""); // No confusion of unit type and timeline yield return Test( "A F-STP s MOS", "A", "F", "STP", "", "", "s", "", "", "MOS", "", ""); // No confusion of timeline and support verb yield return Test( "A Mun s Tyr", "A", "", "Mun", "", "", "s", "", "", "Tyr", "", ""); // Elements with spaces yield return Test( "Western Mediterranean Sea supports Gulf of Lyons", "", "", "Western Mediterranean Sea", "", "", "supports", "", "", "Gulf of Lyons", "", ""); // Parenthesis location yield return Test( "F Spain(nc) s Spain(sc)", "F", "", "Spain", "nc", "", "s", "", "", "Spain", "sc", ""); // Timeline designation spells out a province yield return Test( "A tyr-MUN(vie) s mun-TYR/vie", "A", "tyr", "MUN", "vie", "", "s", "", "mun", "TYR", "vie", ""); } [TestCaseSource(nameof(SupportHoldRegexMatchesTestCases))] public void SupportHoldRegexMatches(string order, string[] expected) { OrderParser re = new(World.WithStandardMap()); var match = re.SupportHold.Match(order); Assert.True(match.Success, "Match failed"); var (type, timeline, province, location, turn, supportVerb, targetType, targetTimeline, targetProvince, targetLocation, targetTurn) = OrderParser.ParseSupportHold(match); string[] actual = [type, timeline, province, location, turn, supportVerb, targetType, targetTimeline, targetProvince, targetLocation, targetTurn]; // 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"); } static IEnumerable SupportMoveRegexMatchesTestCases() { // Full specification yield return Test( "Army a-Munich/l@0 s A a-Tyrolia/l@0 - a-Vienna/l@0", "Army", "a", "Munich", "l", "0", "s", "A", "a", "Tyrolia", "l", "0", "-", "a", "Vienna", "l", "0"); // Case insensitivity yield return Test( "fleet B-lon/C@0 SUPPORTS B-enc/W@0 MOVE TO B-nts/W@0", "fleet", "B", "lon", "C", "0", "SUPPORTS", "", "B", "enc", "W", "0", "MOVE TO", "B", "nts", "W", "0"); // All optionals missing yield return Test( "ROM s VIE - TYR", "", "", "ROM", "", "", "s", "", "", "VIE", "", "", "-", "", "TYR", "", ""); // No confusion of unit type and timeline yield return Test( "A F-STP S MOS - A-UKR", "A", "F", "STP", "", "", "S", "", "", "MOS", "", "", "-", "A", "UKR", "", ""); // Elements with spaces yield return Test( "Western Mediterranean Sea supports Gulf of Lyons move to North Sea", "", "", "Western Mediterranean Sea", "", "", "supports", "", "", "Gulf of Lyons", "", "", "move to", "", "North Sea", "", ""); } [TestCaseSource(nameof(SupportMoveRegexMatchesTestCases))] public void SupportMoveRegexMatches(string order, string[] expected) { OrderParser re = new(World.WithStandardMap()); var match = re.SupportMove.Match(order); Assert.True(match.Success, "Match failed"); var (type, timeline, province, location, turn, supportVerb, targetType, targetTimeline, targetProvince, targetLocation, targetTurn, moveVerb, destTimeline, destProvince, destLocation, destTurn) = OrderParser.ParseSupportMove(match); string[] actual = [type, timeline, province, location, turn, supportVerb, 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() { World world = World.WithStandardMap().AddUnits("Germany A Mun"); OrderParser re = new(world); var match = re.Move.Match("A Mun - Tyr"); var success = OrderParser.TryParseMoveOrder(world, "Germany", match, out Order? order); Assert.That(success, Is.True); Assert.That(order, Is.TypeOf()); MoveOrder move = (MoveOrder)order!; Assert.That(move.Power, Is.EqualTo("Germany")); Assert.That(move.Unit.Key, Is.EqualTo("A a-Munich/l@0")); Assert.That(move.Location, Is.EqualTo("Tyrolia/l")); Assert.That(move.Season.Key, Is.EqualTo("a0")); } [Test] public void OrderDisambiguation() { World world = World.WithStandardMap().AddUnits("Germany A Mun"); OrderParser.TryParseOrder(world, "Germany", "Mun h", out Order? parsed); Assert.That(parsed?.ToString(), Is.EqualTo("G A a-Munich/l@0 holds")); } [Test] public void UnitTypeDisambiguatesCoastalLocation() { World world = World.WithStandardMap().AddUnits("England F Nth", "Germany A Ruhr"); Assert.That( OrderParser.TryParseOrder(world, "England", "North Sea - Holland", out Order? fleetOrder), "Failed to parse fleet order"); Assert.That( OrderParser.TryParseOrder(world, "Germany", "Ruhr - Holland", out Order? armyOrder), "Failed to parse army order"); Assert.That(fleetOrder, Is.TypeOf(), "Unexpected fleet order"); Assert.That(armyOrder, Is.TypeOf(), "Unexpected army order"); Location fleetDest = world.Map.GetLocation(((MoveOrder)fleetOrder!).Location); Location armyDest = world.Map.GetLocation(((MoveOrder)armyOrder!).Location); Assert.That(fleetDest.ProvinceName, Is.EqualTo(armyDest.ProvinceName)); Assert.That(fleetDest.Type, Is.EqualTo(LocationType.Water), "Unexpected fleet movement location"); Assert.That(armyDest.Type, Is.EqualTo(LocationType.Land), "Unexpected army movement location"); } [Test] public void UnitTypeOverrulesNonsenseLocation() { World world = World.WithStandardMap().AddUnits("England F Nth", "Germany A Ruhr"); Assert.That( OrderParser.TryParseOrder(world, "England", "F North Sea - Holland/l", out Order? fleetOrder), "Failed to parse fleet order"); Assert.That( OrderParser.TryParseOrder(world, "Germany", "A Ruhr - Holland/w", out Order? armyOrder), "Failed to parse army order"); Assert.That(fleetOrder, Is.TypeOf(), "Unexpected fleet order"); Assert.That(armyOrder, Is.TypeOf(), "Unexpected army order"); Location fleetDest = world.Map.GetLocation(((MoveOrder)fleetOrder!).Location); Location armyDest = world.Map.GetLocation(((MoveOrder)armyOrder!).Location); Assert.That(fleetDest.ProvinceName, Is.EqualTo(armyDest.ProvinceName)); Assert.That(fleetDest.Type, Is.EqualTo(LocationType.Water), "Unexpected fleet movement location"); Assert.That(armyDest.Type, Is.EqualTo(LocationType.Land), "Unexpected army movement location"); } [Test] public void DisambiguateSingleAccessibleCoast() { World world = World.WithStandardMap().AddUnits("France F Gascony", "France F Marseilles"); Assert.That( OrderParser.TryParseOrder(world, "France", "Gascony - Spain", out Order? northOrder), "Failed to parse north coast order"); Assert.That( OrderParser.TryParseOrder(world, "France", "Marseilles - Spain", out Order? southOrder), "Failed to parse south coast order"); Assert.That(northOrder, Is.TypeOf(), "Unexpected north coast order"); Assert.That(southOrder, Is.TypeOf(), "Unexpected south coast order"); Location north = world.Map.GetLocation(((MoveOrder)northOrder!).Location); Location south = world.Map.GetLocation(((MoveOrder)southOrder!).Location); Assert.That(north.Name, Is.EqualTo("north coast"), "Unexpected disambiguation"); Assert.That(south.Name, Is.EqualTo("south coast"), "Unexpected disambiguation"); } [Test] public void DisambiguateMultipleAccessibleCoasts() { World world = World.WithStandardMap().AddUnits("France F Portugal"); Assert.That( OrderParser.TryParseOrder(world, "France", "Portugal - Spain", out Order? _), Is.False, "Should not parse ambiguous coastal move"); } [Test] public void DisambiguateSupportToSingleAccessibleCoast() { World world = World.WithStandardMap().AddUnits("France F Gascony", "France F Marseilles"); Assert.That( OrderParser.TryParseOrder(world, "France", "Gascony S Marseilles - Spain", out Order? northOrder), "Failed to parse north coast order"); Assert.That( OrderParser.TryParseOrder(world, "France", "Marseilles S Gascony - Spain", out Order? southOrder), "Failed to parse south coast order"); Assert.That(northOrder, Is.TypeOf(), "Unexpected north coast order"); Assert.That(southOrder, Is.TypeOf(), "Unexpected south coast order"); Location northTarget = ((SupportMoveOrder)northOrder!).Location; Location southTarget = ((SupportMoveOrder)southOrder!).Location; Assert.That(northTarget.Name, Is.EqualTo("south coast"), "Unexpected disambiguation"); Assert.That(southTarget.Name, Is.EqualTo("north coast"), "Unexpected disambiguation"); } [Test] public void DisambiguateSupportToMultipleAccessibleCoasts() { World world = World.WithStandardMap().AddUnits("France F Portugal", "France F Marseilles"); Assert.That( OrderParser.TryParseOrder(world, "France", "Marseilles S Portugal - Spain", out Order? _), Is.False, "Should not parse ambiguous coastal support"); } }