diff --git a/MultiversalDiplomacy/Model/OrderParser.cs b/MultiversalDiplomacy/Model/OrderParser.cs index a1e96f6..7ffda4e 100644 --- a/MultiversalDiplomacy/Model/OrderParser.cs +++ b/MultiversalDiplomacy/Model/OrderParser.cs @@ -223,73 +223,77 @@ public class OrderParser(World world) } else if (re.Move.Match(command) is Match moveMatch && moveMatch.Success) { return TryParseMoveOrder(world, power, moveMatch, out order); } else if (re.SupportHold.Match(command) is Match sholdMatch && sholdMatch.Success) { - // DATC 4.B.4: coast specification in support orders - throw new NotImplementedException(); + return TryParseSupportHoldOrder(world, power, sholdMatch, out order); } else if (re.SupportMove.Match(command) is Match smoveMatch && smoveMatch.Success) { - throw new NotImplementedException(); + return TryParseSupportMoveOrder(world, power, smoveMatch, out order); } else { throw new NotImplementedException(); } } - public static bool TryParseHoldOrder(World world, string power, Match match, [NotNullWhen(true)] out Order? order) + public static bool TryParseOrderSubject( + World world, + string parsedTimeline, + string parsedTurn, + string parsedProvince, + [NotNullWhen(true)] out Unit? subject) { - order = null; - var hold = ParseHold(match); - - string timeline = hold.timeline.Length > 0 - ? hold.timeline + subject = null; + + string timeline = parsedTimeline.Length > 0 + ? parsedTimeline // If timeline is unspecified, use the root timeline : Season.First.Timeline; var seasonsInTimeline = world.Timelines.Seasons.Where(season => season.Timeline == timeline); if (!seasonsInTimeline.Any()) return false; - int turn = hold.turn.Length > 0 - ? int.Parse(hold.turn) + int turn = parsedTurn.Length > 0 + ? int.Parse(parsedTurn) // If turn is unspecified, use the latest turn in the timeline : seasonsInTimeline.Max(season => season.Turn); - Province province = world.Map.Provinces.Single(province => province.Is(hold.province)); + Province province = world.Map.Provinces.Single(province => province.Is(parsedProvince)); - Unit? subject = world.Units.FirstOrDefault(unit + // Because only one unit can be in a province at a time, the province is sufficient to identify the subject + // and the location is ignored. This also satisfies DATC 4.B.5, which requires that a wrong coast for the + // subject be ignored. + subject = world.Units.FirstOrDefault(unit => world.Map.GetLocation(unit!.Location).ProvinceName == province.Name && unit!.Season.Timeline == timeline && unit!.Season.Turn == turn, null); - if (subject is null) return false; + return subject is not null; + } + + public static bool TryParseHoldOrder( + World world, + string power, + Match match, + [NotNullWhen(true)] out Order? order) + { + order = null; + var hold = ParseHold(match); + + if (!TryParseOrderSubject(world, hold.timeline, hold.turn, hold.province, out Unit? subject)) { + return false; + } order = new HoldOrder(power, subject); return true; } - public static bool TryParseMoveOrder(World world, string power, Match match, [NotNullWhen(true)] out Order? order) + public static bool TryParseMoveOrder( + World world, + string power, + Match match, + [NotNullWhen(true)] out Order? order) { order = null; var move = ParseMove(match); - string timeline = move.timeline.Length > 0 - ? move.timeline - // If timeline is unspecified, use the root timeline - : Season.First.Timeline; - var seasonsInTimeline = world.Timelines.Seasons.Where(season => season.Timeline == timeline); - if (!seasonsInTimeline.Any()) return false; - - int turn = move.turn.Length > 0 - ? int.Parse(move.turn) - // If turn is unspecified, use the latest turn in the timeline - : seasonsInTimeline.Max(season => season.Turn); - - Province province = world.Map.Provinces.Single(province => province.Is(move.province)); - - // Because only one unit can be in a province at a time, the province is sufficient to identify the subject - // and the location is ignored. This also satisfies DATC 4.B.5, which requires that a wrong coast for the - // subject be ignored. - Unit? subject = world.Units.FirstOrDefault(unit - => world.Map.GetLocation(unit!.Location).ProvinceName == province.Name - && unit!.Season.Timeline == timeline - && unit!.Season.Turn == turn, - null); - if (subject is null) return false; + if (!TryParseOrderSubject(world, move.timeline, move.turn, move.province, out Unit? subject)) { + return false; + } string destTimeline = move.destTimeline.Length > 0 ? move.destTimeline @@ -333,4 +337,29 @@ public class OrderParser(World world) order = new MoveOrder(power, subject, new(destTimeline, destTurn), destLocationKey); return true; } + + public static bool TryParseSupportHoldOrder( + World world, + string power, + Match match, + [NotNullWhen(true)] out Order? order) + { + order = null; + var support = ParseSupportHold(match); + throw new NotImplementedException(); + } + + public static bool TryParseSupportMoveOrder( + World world, + string power, + Match match, + [NotNullWhen(true)] out Order? order) + { + 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. + } }