277 lines
13 KiB
C#
277 lines
13 KiB
C#
using System.Text.RegularExpressions;
|
|
|
|
using MultiversalDiplomacy.Adjudicate;
|
|
using MultiversalDiplomacy.Adjudicate.Decision;
|
|
using MultiversalDiplomacy.Model;
|
|
using MultiversalDiplomacy.Orders;
|
|
|
|
namespace MultiversalDiplomacy.Script;
|
|
|
|
public class AdjudicationQueryScriptHandler(
|
|
Action<string> WriteLine,
|
|
List<OrderValidation> validations,
|
|
List<AdjudicationDecision> adjudications,
|
|
World world,
|
|
IPhaseAdjudicator adjudicator)
|
|
: IScriptHandler
|
|
{
|
|
public string Prompt => "valid> ";
|
|
|
|
public List<OrderValidation> Validations { get; } = validations;
|
|
|
|
public List<AdjudicationDecision> Adjudications { get; } = adjudications;
|
|
|
|
public World World { get; private set; } = world;
|
|
|
|
public ScriptResult HandleInput(string input)
|
|
{
|
|
var args = input.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
|
|
if (args.Length == 0 || input.StartsWith('#'))
|
|
{
|
|
return ScriptResult.Succeed(this);
|
|
}
|
|
|
|
var command = args[0];
|
|
switch (command)
|
|
{
|
|
case "---":
|
|
WriteLine("Ready for orders");
|
|
return ScriptResult.Succeed(new GameScriptHandler(WriteLine, World, adjudicator));
|
|
|
|
case "assert" when args.Length == 1:
|
|
WriteLine("Usage:");
|
|
break;
|
|
|
|
case "assert":
|
|
return EvaluateAssertion(args[1]);
|
|
|
|
case "status":
|
|
throw new NotImplementedException();
|
|
|
|
default:
|
|
return ScriptResult.Fail($"Unrecognized command: \"{command}\"", this);
|
|
}
|
|
|
|
return ScriptResult.Succeed(this);
|
|
}
|
|
|
|
private ScriptResult EvaluateAssertion(string assertion)
|
|
{
|
|
var args = assertion.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
|
|
OrderParser re = new(World);
|
|
Regex prov = new($"^{re.FullLocation}$", RegexOptions.IgnoreCase);
|
|
Match match;
|
|
string timeline;
|
|
IEnumerable<Season> seasonsInTimeline;
|
|
int turn;
|
|
Season season;
|
|
Province province;
|
|
|
|
switch (args[0])
|
|
{
|
|
case "true":
|
|
return ScriptResult.Succeed(this);
|
|
|
|
case "false":
|
|
return ScriptResult.Fail("assert false", this);
|
|
|
|
case "hold-order":
|
|
// The hold-order assertion primarily serves to verify that a unit's order was illegal in cases where
|
|
// a written non-hold order was rejected before order validation and replaced with a hold order.
|
|
match = prov.Match(args[1]);
|
|
|
|
timeline = match.Groups[1].Length > 0
|
|
? match.Groups[1].Value
|
|
: Season.First.Timeline;
|
|
seasonsInTimeline = World.Timelines.Seasons.Where(season => season.Timeline == timeline);
|
|
if (!seasonsInTimeline.Any()) return ScriptResult.Fail($"No seasons in timeline {timeline}", this);
|
|
|
|
turn = match.Groups[4].Length > 0
|
|
? int.Parse(match.Groups[4].Value)
|
|
// If turn is unspecified, use the second-latest turn in the timeline,
|
|
// since we want to assert against the subjects of the orders just adjudicated,
|
|
// and adjudication created a new set of seasons.
|
|
: seasonsInTimeline.Max(season => season.Turn) - 1;
|
|
season = new(timeline, turn);
|
|
|
|
province = World.Map.Provinces.Single(province => province.Is(match.Groups[2].Value));
|
|
|
|
var matchingHolds = Validations.Where(val
|
|
=> val.Valid
|
|
&& val.Order is HoldOrder hold
|
|
&& hold.Unit.Season == season
|
|
&& hold.Unit.GetProvince(World).Name == province.Name);
|
|
if (!matchingHolds.Any()) return ScriptResult.Fail("No matching holds");
|
|
|
|
return ScriptResult.Succeed(this);
|
|
|
|
case "order-valid":
|
|
case "order-invalid":
|
|
match = prov.Match(args[1]);
|
|
if (!match.Success) return ScriptResult.Fail($"Could not parse province from \"{args[1]}\"", this);
|
|
|
|
timeline = match.Groups[1].Length > 0
|
|
? match.Groups[1].Value
|
|
: Season.First.Timeline;
|
|
seasonsInTimeline = World.Timelines.Seasons.Where(season => season.Timeline == timeline);
|
|
if (!seasonsInTimeline.Any()) return ScriptResult.Fail($"No seasons in timeline {timeline}", this);
|
|
|
|
turn = match.Groups[4].Length > 0
|
|
? int.Parse(match.Groups[4].Value)
|
|
// If turn is unspecified, use the second-latest turn in the timeline,
|
|
// since we want to assert against the subjects of the orders just adjudicated,
|
|
// and adjudication created a new set of seasons.
|
|
: seasonsInTimeline.Max(season => season.Turn) - 1;
|
|
season = new(timeline, turn);
|
|
|
|
province = World.Map.Provinces.Single(province => province.Is(match.Groups[2].Value));
|
|
|
|
var matching = Validations.Where(val
|
|
=> val.Order is UnitOrder order
|
|
&& order.Unit.Season == season
|
|
&& order.Unit.GetProvince(World).Name == province.Name);
|
|
if (!matching.Any()) return ScriptResult.Fail("No matching validations");
|
|
|
|
if (args[0] == "order-valid" && !matching.First().Valid) {
|
|
return ScriptResult.Fail($"Order \"{matching.First().Order} is invalid");
|
|
}
|
|
if (args[0] == "order-invalid" && matching.First().Valid) {
|
|
return ScriptResult.Fail($"Order \"{matching.First().Order} is valid");
|
|
}
|
|
return ScriptResult.Succeed(this);
|
|
|
|
case "has-past":
|
|
Regex hasPast = new($"^([a-z]+[0-9]+)>([a-z]+[0-9]+)$");
|
|
match = hasPast.Match(args[1]);
|
|
if (!match.Success) return ScriptResult.Fail("Expected format s1>s2", this);
|
|
|
|
Season future = new(match.Groups[1].Value);
|
|
if (!World.Timelines.Pasts.TryGetValue(future.Key, out Season? actual)) {
|
|
return ScriptResult.Fail($"No such season \"{future}\"");
|
|
}
|
|
|
|
Season expected = new(match.Groups[2].Value);
|
|
if (actual != expected) return ScriptResult.Fail(
|
|
$"Expected past of {future} to be {expected}, but it was {actual}");
|
|
return ScriptResult.Succeed(this);
|
|
|
|
case "not-dislodged":
|
|
case "dislodged":
|
|
re = new(World);
|
|
prov = new($"^{re.FullLocation}$", RegexOptions.IgnoreCase);
|
|
match = prov.Match(args[1]);
|
|
if (!match.Success) return ScriptResult.Fail($"Could not parse province from \"{args[1]}\"", this);
|
|
|
|
timeline = match.Groups[1].Length > 0
|
|
? match.Groups[1].Value
|
|
: Season.First.Timeline;
|
|
seasonsInTimeline = World.Timelines.Seasons.Where(season => season.Timeline == timeline);
|
|
if (!seasonsInTimeline.Any()) return ScriptResult.Fail($"No seasons in timeline {timeline}", this);
|
|
|
|
turn = match.Groups[4].Length > 0
|
|
? int.Parse(match.Groups[4].Value)
|
|
// If turn is unspecified, use the second-latest turn in the timeline,
|
|
// since we want to assert against the subjects of the orders just adjudicated,
|
|
// and adjudication created a new set of seasons.
|
|
: seasonsInTimeline.Max(season => season.Turn) - 1;
|
|
season = new(timeline, turn);
|
|
|
|
province = World.Map.Provinces.Single(province => province.Is(match.Groups[2].Value));
|
|
|
|
var matchingDislodges = Adjudications.Where(adj
|
|
=> adj is IsDislodged dislodge
|
|
&& dislodge.Order.Unit.Season == season
|
|
&& dislodge.Order.Unit.GetProvince(World).Name == province.Name);
|
|
if (!matchingDislodges.Any()) return ScriptResult.Fail("No matching dislodge decisions");
|
|
var isDislodged = matchingDislodges.Cast<IsDislodged>().First();
|
|
|
|
if (args[0] == "not-dislodged" && isDislodged.Outcome != false) {
|
|
return ScriptResult.Fail($"Adjudication {isDislodged} is true");
|
|
}
|
|
if (args[0] == "dislodged" && isDislodged.Outcome != true) {
|
|
return ScriptResult.Fail($"Adjudication {isDislodged} is false");
|
|
}
|
|
return ScriptResult.Succeed(this);
|
|
|
|
case "moves":
|
|
case "no-move":
|
|
re = new(World);
|
|
prov = new($"^{re.FullLocation}$", RegexOptions.IgnoreCase);
|
|
match = prov.Match(args[1]);
|
|
if (!match.Success) return ScriptResult.Fail($"Could not parse province from \"{args[1]}\"", this);
|
|
|
|
timeline = match.Groups[1].Length > 0
|
|
? match.Groups[1].Value
|
|
: Season.First.Timeline;
|
|
seasonsInTimeline = World.Timelines.Seasons.Where(season => season.Timeline == timeline);
|
|
if (!seasonsInTimeline.Any()) return ScriptResult.Fail($"No seasons in timeline {timeline}", this);
|
|
|
|
turn = match.Groups[4].Length > 0
|
|
? int.Parse(match.Groups[4].Value)
|
|
// If turn is unspecified, use the second-latest turn in the timeline,
|
|
// since we want to assert against the subjects of the orders just adjudicated,
|
|
// and adjudication created a new set of seasons.
|
|
: seasonsInTimeline.Max(season => season.Turn) - 1;
|
|
season = new(timeline, turn);
|
|
|
|
province = World.Map.Provinces.Single(province => province.Is(match.Groups[2].Value));
|
|
|
|
var matchingMoves = Adjudications.Where(adj
|
|
=> adj is DoesMove moves
|
|
&& moves.Order.Unit.Season == season
|
|
&& moves.Order.Unit.GetProvince(World).Name == province.Name);
|
|
if (!matchingMoves.Any()) return ScriptResult.Fail("No matching movement decisions");
|
|
var doesMove = matchingMoves.Cast<DoesMove>().First();
|
|
|
|
if (args[0] == "moves" && doesMove.Outcome != true) {
|
|
return ScriptResult.Fail($"Adjudication {doesMove} is false");
|
|
}
|
|
if (args[0] == "no-move" && doesMove.Outcome != false) {
|
|
return ScriptResult.Fail($"Adjudication {doesMove} is true");
|
|
}
|
|
return ScriptResult.Succeed(this);
|
|
|
|
case "support-given":
|
|
case "support-cut":
|
|
re = new(World);
|
|
prov = new($"^{re.FullLocation}$", RegexOptions.IgnoreCase);
|
|
match = prov.Match(args[1]);
|
|
if (!match.Success) return ScriptResult.Fail($"Could not parse province from \"{args[1]}\"", this);
|
|
|
|
timeline = match.Groups[1].Length > 0
|
|
? match.Groups[1].Value
|
|
: Season.First.Timeline;
|
|
seasonsInTimeline = World.Timelines.Seasons.Where(season => season.Timeline == timeline);
|
|
if (!seasonsInTimeline.Any()) return ScriptResult.Fail($"No seasons in timeline {timeline}", this);
|
|
|
|
turn = match.Groups[4].Length > 0
|
|
? int.Parse(match.Groups[4].Value)
|
|
// If turn is unspecified, use the second-latest turn in the timeline,
|
|
// since we want to assert against the subjects of the orders just adjudicated,
|
|
// and adjudication created a new set of seasons.
|
|
: seasonsInTimeline.Max(season => season.Turn) - 1;
|
|
season = new(timeline, turn);
|
|
|
|
province = World.Map.Provinces.Single(province => province.Is(match.Groups[2].Value));
|
|
|
|
var matchingSupports = Adjudications.Where(adj
|
|
=> adj is GivesSupport sup
|
|
&& sup.Order.Unit.Season == season
|
|
&& sup.Order.Unit.GetProvince(World).Name == province.Name);
|
|
if (!matchingSupports.Any()) return ScriptResult.Fail("No matching support decisions");
|
|
var supports = matchingSupports.Cast<GivesSupport>().First();
|
|
|
|
if (args[0] == "support-given" && supports.Outcome != true) {
|
|
return ScriptResult.Fail($"Adjudication {supports} is false");
|
|
}
|
|
if (args[0] == "support-cut" && supports.Outcome != false) {
|
|
return ScriptResult.Fail($"Adjudication {supports} is true");
|
|
}
|
|
return ScriptResult.Succeed(this);
|
|
|
|
default:
|
|
return ScriptResult.Fail($"Unknown assertion \"{args[0]}\"", this);
|
|
}
|
|
}
|
|
}
|