diff --git a/MultiversalDiplomacy/Model/Regex.cs b/MultiversalDiplomacy/Model/Regex.cs index b6cdbe9..081c933 100644 --- a/MultiversalDiplomacy/Model/Regex.cs +++ b/MultiversalDiplomacy/Model/Regex.cs @@ -28,6 +28,8 @@ public class OrderRegex(World world) public const string MoveVerb = "(-|(?:->)|(?:=>)|(?:attack(?:s)?)|(?:move(?:s)?(?: to)?))"; + public const string ViaConvoy = "(convoy|via convoy|by convoy)"; + public Regex Hold => new($"^{Unit} {HoldVerb}$"); public static ( @@ -49,7 +51,7 @@ public class OrderRegex(World world) match.Groups[7].Value, match.Groups[8].Value); - public Regex Move => new($"^{Unit} {MoveVerb} {FullLocation}$"); + public Regex Move => new($"^{Unit} {MoveVerb} {FullLocation}$(?: {ViaConvoy})?"); public static ( string power, @@ -62,7 +64,8 @@ public class OrderRegex(World world) string timeline2, string province2, string location2, - string turn2) + string turn2, + string viaConvoy) ParseMove(Match match) => (match.Groups[1].Value, match.Groups[2].Value, @@ -78,5 +81,6 @@ public class OrderRegex(World world) match.Groups[11].Length > 1 ? match.Groups[11].Value : match.Groups[12].Value, - match.Groups[13].Value); + match.Groups[13].Value, + match.Groups[14].Value); } \ No newline at end of file diff --git a/MultiversalDiplomacy/Script/SetupScriptHandler.cs b/MultiversalDiplomacy/Script/SetupScriptHandler.cs index 40e517b..2d62082 100644 --- a/MultiversalDiplomacy/Script/SetupScriptHandler.cs +++ b/MultiversalDiplomacy/Script/SetupScriptHandler.cs @@ -1,7 +1,10 @@ using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; using MultiversalDiplomacy.Model; +using static MultiversalDiplomacy.Model.OrderRegex; + namespace MultiversalDiplomacy.Script; /// @@ -9,7 +12,7 @@ namespace MultiversalDiplomacy.Script; /// public class SetupScriptHandler(World world, bool strict = false) : IScriptHandler { - public string Prompt => "5dp> "; + public string Prompt => "setup> "; public World World { get; private set; } = world; @@ -66,18 +69,15 @@ public class SetupScriptHandler(World world, bool strict = false) : IScriptHandl break; case "option" when args.Length < 3: - throw new NotImplementedException(); + throw new NotImplementedException("There are no supported options yet"); - case "unit" when args.Length < 4: - Console.WriteLine("usage: unit [power] [type] [province] "); + case "unit" when args.Length < 2: + Console.WriteLine("usage: unit [power] [type] [province]"); break; case "unit": - string power = args[1]; - string type = args[2]; - string province = args[3]; - string? location = args.Length >= 5 ? args[4] : null; - if (ParseUnit(power, type, province, location, out Unit? newUnit)) { + string unitSpec = input["unit ".Length..]; + if (TryParseUnit(unitSpec, out Unit? newUnit)) { World = World.Update(units: World.Units.Append(newUnit)); Console.WriteLine($"Created {newUnit}"); } else if (Strict) { @@ -96,80 +96,33 @@ public class SetupScriptHandler(World world, bool strict = false) : IScriptHandl return this; } - private bool ParseUnit( - string powerArg, - string typeArg, - string provinceArg, - string? locationArg, - [NotNullWhen(true)] out Unit? newUnit) + private bool TryParseUnit(string unitSpec, [NotNullWhen(true)] out Unit? newUnit) { newUnit = null; - // Parse the power name by substring matching. - var matchingPowers = World.Powers.Where(p => p.StartsWithAnyCase(powerArg)); - if (!matchingPowers.Any()) - { - Console.WriteLine($"No power named \"{powerArg}\""); + OrderRegex re = new(World); + Regex reUnit = new($"^{re.Power} {OrderRegex.Type} {re.Province}(?:{SlashLocation}|{ParenLocation})?$"); + Match match = reUnit.Match(unitSpec); + if (!match.Success) { + Console.WriteLine($"Could not match unit spec \"{unitSpec}\""); return false; } - if (matchingPowers.Count() > 1) - { - Console.WriteLine($"Ambiguous power \"{powerArg}\" between {string.Join(", ", matchingPowers)}"); - return false; - } - string power = matchingPowers.First(); - // Parse the unit type by substring matching. - var matchingTypes = Enum.GetNames().Where(t => t.StartsWithAnyCase(typeArg)); - if (!matchingTypes.Any()) - { - Console.WriteLine($"No unit type \"{typeArg}\""); - return false; - } - if (matchingTypes.Count() > 1) - { - Console.WriteLine($"Ambiguous unit type \"{typeArg}\" between {string.Join(", ", matchingTypes)}"); - return false; - } - UnitType type = Enum.Parse(matchingTypes.First()); + string power = World.Powers.First(p => p.EqualsAnyCase(match.Groups[1].Value)); - // Parse the province by matching the province name or one of its abbreviations, - // allowing location specifications separated by a forward slash, e.g. spa/nc. - if (provinceArg.Contains('/')) { - var split = provinceArg.Split('/', 2); - locationArg ??= split[1]; - provinceArg = split[0]; - } - var matchingProvs = World.Provinces.Where(pr - => pr.Abbreviations.Any(abv => abv.EqualsAnyCase(provinceArg)) - || pr.Name.EqualsAnyCase(provinceArg)); - if (!matchingProvs.Any()) - { - Console.WriteLine($"No province matches \"{provinceArg}\""); - return false; - } - if (matchingProvs.Count() > 1) - { - Console.WriteLine($"Ambiguous province \"{provinceArg}\" between {string.Join(", ", matchingProvs)}"); - return false; - } - Province province = matchingProvs.First(); - Location location; - locationArg ??= type == UnitType.Army ? "land" : "water"; - var matchingLocs = locationArg is null - ? province.Locations.Where(loc - => type == UnitType.Army && loc.Type == LocationType.Land - || type == UnitType.Fleet && loc.Type == LocationType.Water) - : province.Locations.Where(loc - => loc.Name.EqualsAnyCase(locationArg) - || loc.Abbreviation.EqualsAnyCase(locationArg)); - if (!matchingLocs.Any()) { - Console.WriteLine($"Province {province.Name} has no {locationArg} location"); - return false; - } - location = matchingLocs.First(); + string typeName = Enum.GetNames().First(name => name.StartsWithAnyCase(match.Groups[2].Value)); + UnitType type = Enum.Parse(typeName); - newUnit = Unit.Build(location.Key, Season.First, powerArg, type); + 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; } }