using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using MultiversalDiplomacy.Orders; namespace MultiversalDiplomacy.Model; /// /// This class defines the regular expressions that are used to build up larger expressions for matching orders /// and other script inputs. It also provides helper functions to extract the captured order elements as tuples, /// which function as the structured intermediate representation between raw user input and full Order objects. /// public class OrderRegex(World world) { public const string Type = "(A|F|Army|Fleet)"; public const string Timeline = "([A-Za-z]+)"; public const string Turn = "([0-9]+)"; public const string SlashLocation = "(?:/([A-Za-z]+))"; public const string ParenLocation = "(?:\\(([A-Za-z ]+)\\))"; public string FullLocation => $"(?:{Timeline}-)?{world.Map.ProvinceRegex}(?:{SlashLocation}|{ParenLocation})?(?:@{Turn})?"; public string UnitSpec => $"(?:{Type} )?{FullLocation}"; public const string HoldVerb = "(h|hold|holds)"; public const string MoveVerb = "(-|(?:->)|(?:=>)|(?:attack(?:s)?)|(?:move(?:s)?(?: to)?))"; public const string SupportVerb = "(s|support|supports)"; public const string ViaConvoy = "(convoy|via convoy|by convoy)"; public Regex Hold => new( $"^{UnitSpec} {HoldVerb}$", RegexOptions.IgnoreCase); public static ( string type, string timeline, string province, string location, string turn, string holdVerb) ParseHold(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); public Regex Move => new( $"^{UnitSpec} {MoveVerb} {FullLocation}(?: {ViaConvoy})?$", RegexOptions.IgnoreCase); public static ( string type, string timeline, string province, string location, string turn, string moveVerb, string destTimeline, string destProvince, string destLocation, string destTurn, string viaConvoy) ParseMove(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].Length > 0 ? match.Groups[10].Value : match.Groups[11].Value, match.Groups[12].Value, match.Groups[13].Value); public Regex SupportHold => new( $"^{UnitSpec} {SupportVerb} {UnitSpec}$", RegexOptions.IgnoreCase); public static ( string type, string timeline, string province, string location, string turn, string supportVerb, string targetType, string targetTimeline, string targetProvince, string targetLocation, string targetTurn) ParseSupportHold(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); public Regex SupportMove => new( $"{UnitSpec} {SupportVerb} {UnitSpec} {MoveVerb} {FullLocation}$", RegexOptions.IgnoreCase); public static ( string type, string timeline, string province, string location, string turn, string supportVerb, string targetType, string targetTimeline, string targetProvince, string targetLocation, string targetTurn, string moveVerb, string destTimeline, string destProvince, string destLocation, string destTurn) ParseSupportMove(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; Regex reUnit = new( $"^{world.Map.PowerRegex} {Type} {world.Map.ProvinceRegex}(?:{SlashLocation}|{ParenLocation})?$"); Match match = reUnit.Match(unitSpec); if (!match.Success) { Console.WriteLine($"Could not match unit spec \"{unitSpec}\""); return false; } string power = world.Map.Powers.First(p => p.EqualsAnyCase(match.Groups[1].Value)); string typeName = Enum.GetNames().First(name => name.StartsWithAnyCase(match.Groups[2].Value)); UnitType type = Enum.Parse(typeName); Province province = world.Map.Provinces.First(prov => prov.Name.EqualsAnyCase(match.Groups[3].Value) || prov.Abbreviations.Any(abv => abv.EqualsAnyCase(match.Groups[3].Value))); string locationName = match.Groups[4].Length > 0 ? match.Groups[4].Value : match.Groups[5].Value; Location location = province.Locations.First(loc => loc.Name.StartsWithAnyCase(locationName) || loc.Abbreviation.StartsWithAnyCase(locationName)); newUnit = Unit.Build(location.Key, Season.First, power, type); return true; } public static bool TryParseOrder(World world, string power, string command, out Order? order) { order = null; OrderRegex re = new(world); Match match; if ((match = re.Hold.Match(command)).Success) { var hold = ParseHold(match); Unit unit = world.Units.First(unit => world.Map.GetLocation(unit.Location).ProvinceName == hold.province && unit.Season.Timeline == (hold.timeline.Length > 0 ? hold.timeline : "a") && unit.Season.Turn.ToString() == (hold.turn.Length > 0 ? hold.turn : "0")); order = new HoldOrder(power, unit); return true; } else if ((match = re.Move.Match(command)).Success) { var move = ParseMove(match); Unit unit = world.Units.First(unit => world.Map.GetLocation(unit.Location).ProvinceName == move.province && unit.Season.Timeline == (move.timeline.Length > 0 ? move.timeline : "a") && unit.Season.Turn.ToString() == (move.turn.Length > 0 ? move.turn : "0")); order = new MoveOrder(power, unit, Season.First, "l"); return true; } return false; } }