Basic order parsing and a unit test
This commit is contained in:
parent
24e80af7ef
commit
4f276df6c1
@ -59,6 +59,12 @@ public class Location
|
||||
return (split[0], split[1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a name is the name or abbreviation of this location.
|
||||
/// </summary>
|
||||
public bool Is(string name)
|
||||
=> name.EqualsAnyCase(Name) || name.EqualsAnyCase(Abbreviation);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.Name == "land" || this.Name == "water"
|
||||
|
@ -57,12 +57,8 @@ public class Map
|
||||
/// Get a province by name. Throws if the province is not found.
|
||||
/// </summary>
|
||||
private static Province GetProvince(string provinceName, IEnumerable<Province> provinces)
|
||||
=> provinces.SingleOrDefault(
|
||||
p => p!.Name.Equals(provinceName, StringComparison.InvariantCultureIgnoreCase)
|
||||
|| p.Abbreviations.Any(
|
||||
a => a.Equals(provinceName, StringComparison.InvariantCultureIgnoreCase)),
|
||||
null)
|
||||
?? throw new KeyNotFoundException($"Province {provinceName} not found");
|
||||
=> provinces.SingleOrDefault(province => province!.Is(provinceName), null)
|
||||
?? throw new KeyNotFoundException($"Province {provinceName} not found");
|
||||
|
||||
/// <summary>
|
||||
/// Get the location in a province matching a predicate. Throws if there is not exactly one
|
||||
|
@ -50,6 +50,12 @@ public class Province
|
||||
return this.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a name is the name or abbreviation of this province.
|
||||
/// </summary>
|
||||
public bool Is(string name)
|
||||
=> name.EqualsAnyCase(Name) || Abbreviations.Any(name.EqualsAnyCase);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new province with no supply center.
|
||||
/// </summary>
|
||||
|
@ -174,7 +174,6 @@ public class OrderRegex(World world)
|
||||
$"^{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;
|
||||
}
|
||||
|
||||
@ -196,28 +195,123 @@ public class OrderRegex(World world)
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryParseOrder(World world, string power, string command, out Order? order) {
|
||||
public static bool TryParseOrder(World world, string power, string command, [NotNullWhen(true)] 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;
|
||||
if (re.Hold.Match(command) is Match holdMatch && holdMatch.Success) {
|
||||
return TryParseHoldOrder(world, power, holdMatch, out order);
|
||||
} 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();
|
||||
} else if (re.SupportMove.Match(command) is Match smoveMatch && smoveMatch.Success) {
|
||||
throw new NotImplementedException();
|
||||
} else {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryParseHoldOrder(World world, string power, Match match, [NotNullWhen(true)] out Order? order)
|
||||
{
|
||||
order = null;
|
||||
var hold = ParseHold(match);
|
||||
|
||||
string timeline = hold.timeline.Length > 0
|
||||
? hold.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 = hold.turn.Length > 0
|
||||
? int.Parse(hold.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(hold.province));
|
||||
|
||||
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;
|
||||
|
||||
order = new HoldOrder(power, subject);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
string destTimeline = move.destTimeline.Length > 0
|
||||
? move.destTimeline
|
||||
// If the destination is unspecified, use the unit's
|
||||
: subject.Season.Timeline;
|
||||
|
||||
int destTurn = move.destTurn.Length > 0
|
||||
? int.Parse(move.destTurn)
|
||||
// If the destination is unspecified, use the unit's
|
||||
: subject.Season.Turn;
|
||||
|
||||
var destProvince = world.Map.Provinces.Single(province => province.Is(move.destProvince));
|
||||
|
||||
// DATC 4.B.6 requires that "irrelevant" locations like army to Spain nc be ignored.
|
||||
// To satisfy this, any location of the wrong type is categorically ignored, so for an army the
|
||||
// "north coast" location effectively doesn't exist here.
|
||||
var unitLocations = destProvince.Locations.Where(loc => loc.Type switch {
|
||||
LocationType.Land => subject.Type == UnitType.Army,
|
||||
LocationType.Water => subject.Type == UnitType.Fleet,
|
||||
_ => false,
|
||||
});
|
||||
// DATC 4.6.B also requires that unknown coasts be ignored. To satisfy this, an additional filter by name.
|
||||
// Doing both of these filters means "A - Spain/nc" is as meaningful as "F - Spain/wc".
|
||||
var matchingLocations = unitLocations.Where(loc => loc.Is(move.destLocation));
|
||||
|
||||
// If one location matched, use that location. If the coast is inaccessible to the subject, the order will
|
||||
// be invalidated by a path check later to satisfy DATC 4.B.3.
|
||||
string? destLocationKey = matchingLocations.FirstOrDefault(defaultValue: null)?.Key;
|
||||
|
||||
if (destLocationKey is null) {
|
||||
// If no location matched, location was omitted, nonexistent, or the wrong type.
|
||||
// If one location is accessible, DATC 4.B.2 requires that it be used.
|
||||
// If more than one location is accessible, DATC 4.B.1 requires the order fail.
|
||||
|
||||
// TODO check which locations are accessible per the above
|
||||
destLocationKey = unitLocations.First().Key;
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
order = new MoveOrder(power, subject, new(destTimeline, destTurn), destLocationKey);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ public class World
|
||||
public Dictionary<string, OrderHistory> OrderHistory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The shared timeline number generator.
|
||||
/// The state of the multiverse.
|
||||
/// </summary>
|
||||
public Timelines Timelines { get; }
|
||||
|
||||
|
@ -15,7 +15,7 @@ public class MoveOrder : UnitOrder
|
||||
public Season Season { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The destination location to which the unit should move.
|
||||
/// The destination province/location to which the unit should move.
|
||||
/// </summary>
|
||||
public string Location { get; }
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
using MultiversalDiplomacy.Model;
|
||||
using MultiversalDiplomacy.Orders;
|
||||
|
||||
namespace MultiversalDiplomacyTests;
|
||||
|
||||
@ -196,4 +197,23 @@ public class RegexTest
|
||||
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");
|
||||
OrderRegex re = new(world);
|
||||
|
||||
var match = re.Move.Match("A Mun - Tyr");
|
||||
var success = OrderRegex.TryParseMoveOrder(world, "Germany", match, out Order? order);
|
||||
|
||||
Assert.That(success, Is.True);
|
||||
Assert.That(order, Is.TypeOf<MoveOrder>());
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user