Compare commits

...

5 Commits

Author SHA1 Message Date
Tim Van Baak 4fee854c4c Refactor script handlers to return a result type
This moves the point of strictness from the handler to the driver, which makes more sense and keeps it in one place. Drivers choose to be strict when a script result is a failure but still gives a continuation handler. The CLI driver prints an error and continues, while the test driver fails if it wasn't expecting the failure.
2024-09-03 03:20:59 +00:00
Tim Van Baak 569c9021e6 Disable some broken tests for now 2024-09-02 19:52:27 +00:00
Tim Van Baak 3984b814ca Implement assert order-valid 2024-09-01 04:54:28 +00:00
Tim Van Baak f18147f666 Enable suppressing adjudicator output in tests 2024-08-28 21:27:35 +00:00
Tim Van Baak 9f52c78b40 Enable repl output to /dev/null 2024-08-28 21:10:41 +00:00
17 changed files with 319 additions and 229 deletions

View File

@ -54,7 +54,7 @@ public class ReplOptions
// The last null is returned because an EOF means we should quit the repl. // The last null is returned because an EOF means we should quit the repl.
} }
IScriptHandler? handler = new ReplScriptHandler(); IScriptHandler? handler = new ReplScriptHandler(Console.WriteLine);
Console.Write(handler.Prompt); Console.Write(handler.Prompt);
foreach (string? nextInput in GetInputs()) foreach (string? nextInput in GetInputs())
@ -69,13 +69,21 @@ public class ReplOptions
outputWriter?.Flush(); outputWriter?.Flush();
// Delegate all other command parsing to the handler. // Delegate all other command parsing to the handler.
handler = handler.HandleInput(input); var result = handler.HandleInput(input);
// Quit if the handler ends processing, otherwise prompt for the next command. // Report errors if they occured.
if (handler is null) if (!result.Success)
{
Console.WriteLine($"Error: {result.Message}");
}
// Quit if the handler didn't continue processing.
if (result.NextHandler is null)
{ {
break; break;
} }
// Otherwise prompt for the next command.
Console.Write(handler.Prompt); Console.Write(handler.Prompt);
} }

View File

@ -227,7 +227,7 @@ public class OrderParser(World world)
} else if (re.SupportMove.Match(command) is Match smoveMatch && smoveMatch.Success) { } else if (re.SupportMove.Match(command) is Match smoveMatch && smoveMatch.Success) {
return TryParseSupportMoveOrder(world, power, smoveMatch, out order); return TryParseSupportMoveOrder(world, power, smoveMatch, out order);
} else { } else {
throw new NotImplementedException(); return false;
} }
} }

View File

@ -1,72 +1,104 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders;
namespace MultiversalDiplomacy.Script; namespace MultiversalDiplomacy.Script;
public class AdjudicationQueryScriptHandler(World world, bool strict = false) : IScriptHandler public class AdjudicationQueryScriptHandler(
Action<string> WriteLine,
List<OrderValidation> validations,
World world,
IPhaseAdjudicator adjudicator)
: IScriptHandler
{ {
public string Prompt => "valid> "; public string Prompt => "valid> ";
public List<OrderValidation> Validations { get; } = validations;
public World World { get; private set; } = world; public World World { get; private set; } = world;
/// <summary> public ScriptResult HandleInput(string input)
/// Whether unsuccessful commands should terminate the script.
/// </summary>
public bool Strict { get; } = strict;
public IScriptHandler? HandleInput(string input)
{ {
var args = input.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); var args = input.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
if (args.Length == 0 || input.StartsWith('#')) if (args.Length == 0 || input.StartsWith('#'))
{ {
return this; return ScriptResult.Succeed(this);
} }
var command = args[0]; var command = args[0];
switch (command) switch (command)
{ {
case "---": case "---":
Console.WriteLine("Ready for orders"); WriteLine("Ready for orders");
return new GameScriptHandler(World, Strict); return ScriptResult.Succeed(new GameScriptHandler(WriteLine, World, adjudicator));
case "assert" when args.Length == 1: case "assert" when args.Length == 1:
Console.WriteLine("Usage:"); WriteLine("Usage:");
break; break;
case "assert": case "assert":
if (!EvaluateAssertion(args[1])) return Strict ? null : this; return EvaluateAssertion(args[1]);
break;
case "status": case "status":
throw new NotImplementedException(); throw new NotImplementedException();
default: default:
Console.WriteLine($"Unrecognized command: \"{command}\""); return ScriptResult.Fail($"Unrecognized command: \"{command}\"", this);
if (Strict) return null;
break;
} }
return this; return ScriptResult.Succeed(this);
} }
private bool EvaluateAssertion(string assertion) private ScriptResult EvaluateAssertion(string assertion)
{ {
var args = assertion.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); var args = assertion.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
switch (args[0]) switch (args[0])
{ {
case "true": case "true":
return true; return ScriptResult.Succeed(this);
case "false": case "false":
return false; return ScriptResult.Fail("assert false", this);
case "order-valid": case "order-valid":
// Assert order was valid
case "order-invalid": case "order-invalid":
// Assert order was invalid OrderParser re = new(World);
Regex prov = new($"^{re.FullLocation}$", RegexOptions.IgnoreCase);
Match match = prov.Match(args[1]);
if (!match.Success) return ScriptResult.Fail($"Could not parse province from \"{args[1]}\"", this);
string timeline = match.Groups[1].Length > 0
? match.Groups[1].Value
: Season.First.Timeline;
var seasonsInTimeline = World.Timelines.Seasons.Where(season => season.Timeline == timeline);
if (!seasonsInTimeline.Any()) return ScriptResult.Fail($"No seasons in timeline {timeline}", this);
int 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 season = new(timeline, turn);
Province 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
&& World.Map.GetLocation(order.Unit.Location).ProvinceName == 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": case "has-past":
// Assert a timeline's past // Assert a timeline's past
@ -90,8 +122,7 @@ public class AdjudicationQueryScriptHandler(World world, bool strict = false) :
// Assert a unit's support was cut // Assert a unit's support was cut
default: default:
Console.WriteLine($"Unknown assertion \"{args[0]}\""); return ScriptResult.Fail($"Unknown assertion \"{args[0]}\"", this);
return !Strict;
} }
} }
} }

View File

@ -6,33 +6,31 @@ using MultiversalDiplomacy.Orders;
namespace MultiversalDiplomacy.Script; namespace MultiversalDiplomacy.Script;
public class GameScriptHandler(World world, bool strict = false) : IScriptHandler public class GameScriptHandler(
Action<string> WriteLine,
World world,
IPhaseAdjudicator adjudicator)
: IScriptHandler
{ {
public string Prompt => "orders> "; public string Prompt => "orders> ";
public World World { get; private set; } = world; public World World { get; private set; } = world;
/// <summary>
/// Whether unsuccessful commands should terminate the script.
/// </summary>
public bool Strict { get; } = strict;
private string? CurrentPower { get; set; } = null; private string? CurrentPower { get; set; } = null;
public List<Order> Orders { get; } = []; public List<Order> Orders { get; } = [];
public IScriptHandler? HandleInput(string input) public ScriptResult HandleInput(string input)
{ {
if (input == "") { if (input == "") {
CurrentPower = null; CurrentPower = null;
return this; return ScriptResult.Succeed(this);
} }
if (input.StartsWith('#')) return this; if (input.StartsWith('#')) return ScriptResult.Succeed(this);
// "---" submits the orders and allows queries about the outcome // "---" submits the orders and allows queries about the outcome
if (input == "---") { if (input == "---") {
Console.WriteLine("Submitting orders for adjudication"); WriteLine("Submitting orders for adjudication");
var adjudicator = MovementPhaseAdjudicator.Instance;
var validation = adjudicator.ValidateOrders(World, Orders); var validation = adjudicator.ValidateOrders(World, Orders);
var validOrders = validation var validOrders = validation
.Where(v => v.Valid) .Where(v => v.Valid)
@ -40,14 +38,14 @@ public class GameScriptHandler(World world, bool strict = false) : IScriptHandle
.ToList(); .ToList();
var adjudication = adjudicator.AdjudicateOrders(World, validOrders); var adjudication = adjudicator.AdjudicateOrders(World, validOrders);
var newWorld = adjudicator.UpdateWorld(World, adjudication); var newWorld = adjudicator.UpdateWorld(World, adjudication);
return new AdjudicationQueryScriptHandler(newWorld, Strict); return ScriptResult.Succeed(new AdjudicationQueryScriptHandler(
WriteLine, validation, newWorld, adjudicator));
} }
// "===" submits the orders and moves immediately to taking the next set of orders // "===" submits the orders and moves immediately to taking the next set of orders
// i.e. it's "---" twice // i.e. it's "---" twice
if (input == "===") { if (input == "===") {
Console.WriteLine("Submitting orders for adjudication"); WriteLine("Submitting orders for adjudication");
var adjudicator = MovementPhaseAdjudicator.Instance;
var validation = adjudicator.ValidateOrders(World, Orders); var validation = adjudicator.ValidateOrders(World, Orders);
var validOrders = validation var validOrders = validation
.Where(v => v.Valid) .Where(v => v.Valid)
@ -55,14 +53,14 @@ public class GameScriptHandler(World world, bool strict = false) : IScriptHandle
.ToList(); .ToList();
var adjudication = adjudicator.AdjudicateOrders(World, validOrders); var adjudication = adjudicator.AdjudicateOrders(World, validOrders);
World = adjudicator.UpdateWorld(World, adjudication); World = adjudicator.UpdateWorld(World, adjudication);
Console.WriteLine("Ready for orders"); WriteLine("Ready for orders");
return this; return ScriptResult.Succeed(this);
} }
// A block of orders for a single power beginning with "{name}:" // A block of orders for a single power beginning with "{name}:"
if (World.Powers.FirstOrDefault(p => input.EqualsAnyCase($"{p}:"), null) is string power) { if (World.Powers.FirstOrDefault(p => input.EqualsAnyCase($"{p}:"), null) is string power) {
CurrentPower = power; CurrentPower = power;
return this; return ScriptResult.Succeed(this);
} }
// If it's not a comment, submit, or order block, assume it's an order. // If it's not a comment, submit, or order block, assume it's an order.
@ -76,21 +74,17 @@ public class GameScriptHandler(World world, bool strict = false) : IScriptHandle
// Outside a power block, the power is prefixed to each order. // Outside a power block, the power is prefixed to each order.
Regex re = new($"^{World.Map.PowerRegex}(?:[:])? (.*)$", RegexOptions.IgnoreCase); Regex re = new($"^{World.Map.PowerRegex}(?:[:])? (.*)$", RegexOptions.IgnoreCase);
var match = re.Match(input); var match = re.Match(input);
if (!match.Success) { if (!match.Success) return ScriptResult.Fail($"Could not determine ordering power in \"{input}\"", this);
Console.WriteLine($"Could not determine ordering power in \"{input}\"");
return Strict ? null : this;
}
orderPower = match.Groups[1].Value; orderPower = match.Groups[1].Value;
orderText = match.Groups[2].Value; orderText = match.Groups[2].Value;
} }
if (OrderParser.TryParseOrder(World, orderPower, orderText, out Order? order)) { if (OrderParser.TryParseOrder(World, orderPower, orderText, out Order? order)) {
Console.WriteLine($"Parsed {orderPower} order: {order}"); WriteLine($"Parsed {orderPower} order: {order}");
Orders.Add(order); Orders.Add(order);
return this; return ScriptResult.Succeed(this);
} }
Console.WriteLine($"Failed to parse \"{orderText}\""); return ScriptResult.Fail($"Failed to parse \"{orderText}\"", this);
return Strict ? null : this;
} }
} }

View File

@ -14,8 +14,5 @@ public interface IScriptHandler
/// <summary> /// <summary>
/// Process a line of input. /// Process a line of input.
/// </summary> /// </summary>
/// <returns> public ScriptResult HandleInput(string input);
/// The handler that should handle the next line of input, or null if script handling should end.
/// </returns>
public IScriptHandler? HandleInput(string input);
} }

View File

@ -1,3 +1,4 @@
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Model;
namespace MultiversalDiplomacy.Script; namespace MultiversalDiplomacy.Script;
@ -5,16 +6,16 @@ namespace MultiversalDiplomacy.Script;
/// <summary> /// <summary>
/// A script handler for the interactive repl. /// A script handler for the interactive repl.
/// </summary> /// </summary>
public class ReplScriptHandler : IScriptHandler public class ReplScriptHandler(Action<string> WriteLine) : IScriptHandler
{ {
public string Prompt => "5dp> "; public string Prompt => "5dp> ";
public IScriptHandler? HandleInput(string input) public ScriptResult HandleInput(string input)
{ {
var args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries); var args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (args.Length == 0 || input.StartsWith('#')) if (args.Length == 0 || input.StartsWith('#'))
{ {
return this; return ScriptResult.Succeed(this);
} }
var command = args[0]; var command = args[0];
@ -22,40 +23,42 @@ public class ReplScriptHandler : IScriptHandler
{ {
case "help": case "help":
case "?": case "?":
Console.WriteLine("Commands:"); WriteLine("Commands:");
Console.WriteLine(" help, ?: print this message"); WriteLine(" help, ?: print this message");
Console.WriteLine(" map <variant>: start a new game of the given variant"); WriteLine(" map <variant>: start a new game of the given variant");
Console.WriteLine(" stab: stab"); WriteLine(" stab: stab");
break; break;
case "stab": case "stab":
Console.WriteLine("stab"); WriteLine("stab");
break; break;
case "map" when args.Length == 1: case "map" when args.Length == 1:
Console.WriteLine("Usage:"); WriteLine("Usage:");
Console.WriteLine(" map <variant>"); WriteLine(" map <variant>");
Console.WriteLine("Available variants:"); WriteLine("Available variants:");
Console.WriteLine($" {string.Join(", ", Enum.GetNames<MapType>().Select(s => s.ToLowerInvariant()))}"); WriteLine($" {string.Join(", ", Enum.GetNames<MapType>().Select(s => s.ToLowerInvariant()))}");
break; break;
case "map" when args.Length > 1: case "map" when args.Length > 1:
string mapType = args[1].Trim(); string mapType = args[1].Trim();
if (!Enum.TryParse(mapType, ignoreCase: true, out MapType map)) { if (!Enum.TryParse(mapType, ignoreCase: true, out MapType map)) {
Console.WriteLine($"Unknown variant \"{mapType}\""); WriteLine($"Unknown variant \"{mapType}\"");
Console.WriteLine("Available variants:"); WriteLine("Available variants:");
Console.WriteLine($" {string.Join(", ", Enum.GetNames<MapType>().Select(s => s.ToLowerInvariant()))}"); WriteLine($" {string.Join(", ", Enum.GetNames<MapType>().Select(s => s.ToLowerInvariant()))}");
break; break;
} }
World world = World.WithMap(Map.FromType(map)); World world = World.WithMap(Map.FromType(map));
Console.WriteLine($"Created a new {map} game"); WriteLine($"Created a new {map} game");
return new SetupScriptHandler(world); return ScriptResult.Succeed(new SetupScriptHandler(
WriteLine,
world,
MovementPhaseAdjudicator.Instance));
default: default:
Console.WriteLine($"Unrecognized command: \"{command}\""); return ScriptResult.Fail($"Unrecognized command: \"{command}\"", this);
break;
} }
return this; return ScriptResult.Succeed(this);
} }
} }

View File

@ -0,0 +1,36 @@
namespace MultiversalDiplomacy.Script;
/// <summary>
/// The result of an <see cref="IScriptHandler"/> processing a line of input.
/// </summary>
/// <param name="success">Whether processing was successful.</param>
/// <param name="next">The handler to continue script processing with.</param>
/// <param name="message">If processing failed, the error message.</param>
public class ScriptResult(bool success, IScriptHandler? next, string message)
{
/// <summary>
/// Whether processing was successful.
/// </summary>
public bool Success { get; } = success;
/// <summary>
/// The handler to continue script processing with.
/// </summary>
public IScriptHandler? NextHandler { get; } = next;
/// <summary>
/// If processing failed, the error message.
/// </summary>
public string Message { get; } = message;
/// <summary>
/// Mark the processing as successful and continue processing with the next handler.
/// </summary>
public static ScriptResult Succeed(IScriptHandler next) => new(true, next, "");
/// <summary>
/// Mark the processing as a failure and optionally continue with the next handler.
/// </summary>
/// <param name="message">The reason for the processing failure.</param>
public static ScriptResult Fail(string message, IScriptHandler? next = null) => new(false, next, message);
}

View File

@ -1,3 +1,4 @@
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Model;
namespace MultiversalDiplomacy.Script; namespace MultiversalDiplomacy.Script;
@ -5,23 +6,22 @@ namespace MultiversalDiplomacy.Script;
/// <summary> /// <summary>
/// A script handler for modifying a game before it begins. /// A script handler for modifying a game before it begins.
/// </summary> /// </summary>
public class SetupScriptHandler(World world, bool strict = false) : IScriptHandler public class SetupScriptHandler(
Action<string> WriteLine,
World world,
IPhaseAdjudicator adjudicator)
: IScriptHandler
{ {
public string Prompt => "setup> "; public string Prompt => "setup> ";
public World World { get; private set; } = world; public World World { get; private set; } = world;
/// <summary> public ScriptResult HandleInput(string input)
/// Whether unsuccessful commands should terminate the script.
/// </summary>
public bool Strict { get; } = strict;
public IScriptHandler? HandleInput(string input)
{ {
var args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries); var args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (args.Length == 0 || input.StartsWith('#')) if (args.Length == 0 || input.StartsWith('#'))
{ {
return this; return ScriptResult.Succeed(this);
} }
var command = args[0]; var command = args[0];
@ -29,39 +29,39 @@ public class SetupScriptHandler(World world, bool strict = false) : IScriptHandl
{ {
case "help": case "help":
case "?": case "?":
Console.WriteLine("commands:"); WriteLine("commands:");
Console.WriteLine(" begin: complete setup and start the game (alias: ---)"); WriteLine(" begin: complete setup and start the game (alias: ---)");
Console.WriteLine(" list <type>: list things in a game category"); WriteLine(" list <type>: list things in a game category");
Console.WriteLine(" option <name> <value>: set a game option"); WriteLine(" option <name> <value>: set a game option");
Console.WriteLine(" unit <power> <type> <province> [location]: add a unit to the game"); WriteLine(" unit <power> <type> <province> [location]: add a unit to the game");
Console.WriteLine(" <province> may be \"province/location\""); WriteLine(" <province> may be \"province/location\"");
break; break;
case "begin": case "begin":
case "---": case "---":
Console.WriteLine("Starting game"); WriteLine("Starting game");
Console.WriteLine("Ready for orders"); WriteLine("Ready for orders");
return new GameScriptHandler(World, Strict); return ScriptResult.Succeed(new GameScriptHandler(WriteLine, World, adjudicator));
case "list" when args.Length == 1: case "list" when args.Length == 1:
Console.WriteLine("usage:"); WriteLine("usage:");
Console.WriteLine(" list powers: the powers in the game"); WriteLine(" list powers: the powers in the game");
Console.WriteLine(" list units: units created so far"); WriteLine(" list units: units created so far");
break; break;
case "list" when args[1] == "powers": case "list" when args[1] == "powers":
Console.WriteLine("Powers:"); WriteLine("Powers:");
foreach (string powerName in World.Powers) foreach (string powerName in World.Powers)
{ {
Console.WriteLine($" {powerName}"); WriteLine($" {powerName}");
} }
break; break;
case "list" when args[1] == "units": case "list" when args[1] == "units":
Console.WriteLine("Units:"); WriteLine("Units:");
foreach (Unit unit in World.Units) foreach (Unit unit in World.Units)
{ {
Console.WriteLine($" {unit}"); WriteLine($" {unit}");
} }
break; break;
@ -69,26 +69,23 @@ public class SetupScriptHandler(World world, bool strict = false) : IScriptHandl
throw new NotImplementedException("There are no supported options yet"); throw new NotImplementedException("There are no supported options yet");
case "unit" when args.Length < 2: case "unit" when args.Length < 2:
Console.WriteLine("usage: unit [power] [type] [province]</location>"); WriteLine("usage: unit [power] [type] [province]</location>");
break; break;
case "unit": case "unit":
string unitSpec = input["unit ".Length..]; string unitSpec = input["unit ".Length..];
if (OrderParser.TryParseUnit(World, unitSpec, out Unit? newUnit)) { if (OrderParser.TryParseUnit(World, unitSpec, out Unit? newUnit)) {
World = World.Update(units: World.Units.Append(newUnit)); World = World.Update(units: World.Units.Append(newUnit));
Console.WriteLine($"Created {newUnit}"); WriteLine($"Created {newUnit}");
return this; return ScriptResult.Succeed(this);
} }
Console.WriteLine($"Could not match unit spec \"{unitSpec}\""); return ScriptResult.Fail($"Could not match unit spec \"{unitSpec}\"", this);
if (Strict) return null;
break;
default: default:
Console.WriteLine($"Unrecognized command: \"{command}\""); ScriptResult.Fail($"Unrecognized command: \"{command}\"", this);
if (Strict) return null;
break; break;
} }
return this; return ScriptResult.Succeed(this);
} }
} }

View File

@ -0,0 +1,8 @@
using MultiversalDiplomacy.Adjudicate;
namespace MultiversalDiplomacyTests;
public static class Adjudicator
{
public static MovementPhaseAdjudicator MovementPhase { get; } = new MovementPhaseAdjudicator(NullLogger.Instance);
}

View File

@ -3,6 +3,8 @@ using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders; using MultiversalDiplomacy.Orders;
using NUnit.Framework; using NUnit.Framework;
using static MultiversalDiplomacyTests.Adjudicator;
namespace MultiversalDiplomacyTests; namespace MultiversalDiplomacyTests;
public class DATC_A public class DATC_A
@ -17,7 +19,7 @@ public class DATC_A
.Fleet("North Sea").MovesTo("Picardy").GetReference(out var order); .Fleet("North Sea").MovesTo("Picardy").GetReference(out var order);
// Order should fail. // Order should fail.
setup.ValidateOrders(MovementPhaseAdjudicator.Instance); setup.ValidateOrders(MovementPhase);
Assert.That(order, Is.Invalid(ValidationReason.UnreachableDestination)); Assert.That(order, Is.Invalid(ValidationReason.UnreachableDestination));
} }
@ -59,7 +61,7 @@ public class DATC_A
.Fleet("Kiel").MovesTo("Kiel").GetReference(out var order); .Fleet("Kiel").MovesTo("Kiel").GetReference(out var order);
// Program should not crash. // Program should not crash.
setup.ValidateOrders(MovementPhaseAdjudicator.Instance); setup.ValidateOrders(MovementPhase);
Assert.That(order, Is.Invalid(ValidationReason.DestinationMatchesOrigin)); Assert.That(order, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
} }
@ -77,14 +79,14 @@ public class DATC_A
.Army("Wales").Supports.Fleet("London").MoveTo("Yorkshire"); .Army("Wales").Supports.Fleet("London").MoveTo("Yorkshire");
// The move of the army in Yorkshire is illegal. This makes the support of Liverpool also illegal. // The move of the army in Yorkshire is illegal. This makes the support of Liverpool also illegal.
setup.ValidateOrders(MovementPhaseAdjudicator.Instance); setup.ValidateOrders(MovementPhase);
Assert.That(orderLon, Is.Valid); Assert.That(orderLon, Is.Valid);
Assert.That(orderNth, Is.Invalid(ValidationReason.DestinationMatchesOrigin)); Assert.That(orderNth, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
Assert.That(orderYor, Is.Invalid(ValidationReason.DestinationMatchesOrigin)); Assert.That(orderYor, Is.Invalid(ValidationReason.DestinationMatchesOrigin));
var orderYorRepl = orderYor.GetReplacementReference<HoldOrder>(); var orderYorRepl = orderYor.GetReplacementReference<HoldOrder>();
// Without the support, the Germans have a stronger force. The army in London dislodges the army in Yorkshire. // Without the support, the Germans have a stronger force. The army in London dislodges the army in Yorkshire.
setup.AdjudicateOrders(MovementPhaseAdjudicator.Instance); setup.AdjudicateOrders(MovementPhase);
Assert.That(orderLon, Is.Victorious); Assert.That(orderLon, Is.Victorious);
Assert.That(orderYorRepl, Is.Dislodged); Assert.That(orderYorRepl, Is.Dislodged);
} }
@ -98,7 +100,7 @@ public class DATC_A
.Fleet("London", powerName: "England").MovesTo("North Sea").GetReference(out var order); .Fleet("London", powerName: "England").MovesTo("North Sea").GetReference(out var order);
// Order should fail. // Order should fail.
setup.ValidateOrders(MovementPhaseAdjudicator.Instance); setup.ValidateOrders(MovementPhase);
Assert.That(order, Is.Invalid(ValidationReason.InvalidUnitForPower)); Assert.That(order, Is.Invalid(ValidationReason.InvalidUnitForPower));
} }
@ -112,7 +114,7 @@ public class DATC_A
.Fleet("North Sea").Convoys.Army("London").To("Belgium").GetReference(out var order); .Fleet("North Sea").Convoys.Army("London").To("Belgium").GetReference(out var order);
// Move from London to Belgium should fail. // Move from London to Belgium should fail.
setup.ValidateOrders(MovementPhaseAdjudicator.Instance); setup.ValidateOrders(MovementPhase);
Assert.That(order, Is.Invalid(ValidationReason.InvalidOrderTypeForUnit)); Assert.That(order, Is.Invalid(ValidationReason.InvalidOrderTypeForUnit));
} }
@ -127,12 +129,12 @@ public class DATC_A
["Austria"] ["Austria"]
.Fleet("Trieste").Supports.Fleet("Trieste").Hold().GetReference(out var orderTri); .Fleet("Trieste").Supports.Fleet("Trieste").Hold().GetReference(out var orderTri);
setup.ValidateOrders(MovementPhaseAdjudicator.Instance); setup.ValidateOrders(MovementPhase);
Assert.That(orderTri, Is.Invalid(ValidationReason.NoSelfSupport)); Assert.That(orderTri, Is.Invalid(ValidationReason.NoSelfSupport));
var orderTriRepl = orderTri.GetReplacementReference<HoldOrder>(); var orderTriRepl = orderTri.GetReplacementReference<HoldOrder>();
// The army in Trieste should be dislodged. // The army in Trieste should be dislodged.
setup.AdjudicateOrders(MovementPhaseAdjudicator.Instance); setup.AdjudicateOrders(MovementPhase);
Assert.That(orderTriRepl, Is.Dislodged); Assert.That(orderTriRepl, Is.Dislodged);
} }
@ -145,7 +147,7 @@ public class DATC_A
.Fleet("Rome").MovesTo("Venice").GetReference(out var order); .Fleet("Rome").MovesTo("Venice").GetReference(out var order);
// Move fails. An army can go from Rome to Venice, but a fleet can not. // Move fails. An army can go from Rome to Venice, but a fleet can not.
setup.ValidateOrders(MovementPhaseAdjudicator.Instance); setup.ValidateOrders(MovementPhase);
Assert.That(order, Is.Invalid(ValidationReason.UnreachableDestination)); Assert.That(order, Is.Invalid(ValidationReason.UnreachableDestination));
} }
@ -160,13 +162,13 @@ public class DATC_A
.Army("Apulia").MovesTo("Venice") .Army("Apulia").MovesTo("Venice")
.Fleet("Rome").Supports.Army("Apulia").MoveTo("Venice").GetReference(out var orderRom); .Fleet("Rome").Supports.Army("Apulia").MoveTo("Venice").GetReference(out var orderRom);
setup.ValidateOrders(MovementPhaseAdjudicator.Instance); setup.ValidateOrders(MovementPhase);
// The support of Rome is illegal, because Venice can not be reached from Rome by a fleet. // The support of Rome is illegal, because Venice can not be reached from Rome by a fleet.
Assert.That(orderRom, Is.Invalid(ValidationReason.UnreachableSupport)); Assert.That(orderRom, Is.Invalid(ValidationReason.UnreachableSupport));
// Venice is not dislodged. // Venice is not dislodged.
setup.AdjudicateOrders(MovementPhaseAdjudicator.Instance); setup.AdjudicateOrders(MovementPhase);
Assert.That(orderVen, Is.NotDislodged); Assert.That(orderVen, Is.NotDislodged);
} }
@ -180,12 +182,12 @@ public class DATC_A
["Italy"] ["Italy"]
.Army("Venice").MovesTo("Tyrolia").GetReference(out var orderVen); .Army("Venice").MovesTo("Tyrolia").GetReference(out var orderVen);
setup.ValidateOrders(MovementPhaseAdjudicator.Instance); setup.ValidateOrders(MovementPhase);
Assert.That(orderVie, Is.Valid); Assert.That(orderVie, Is.Valid);
Assert.That(orderVen, Is.Valid); Assert.That(orderVen, Is.Valid);
// The two units bounce. // The two units bounce.
var adjudications = setup.AdjudicateOrders(MovementPhaseAdjudicator.Instance); var adjudications = setup.AdjudicateOrders(MovementPhase);
Assert.That(orderVie, Is.Repelled); Assert.That(orderVie, Is.Repelled);
Assert.That(orderVie, Is.NotDislodged); Assert.That(orderVie, Is.NotDislodged);
Assert.That(orderVen, Is.Repelled); Assert.That(orderVen, Is.Repelled);
@ -204,12 +206,12 @@ public class DATC_A
["Italy"] ["Italy"]
.Army("Venice").MovesTo("Tyrolia").GetReference(out var orderVen); .Army("Venice").MovesTo("Tyrolia").GetReference(out var orderVen);
var validations = setup.ValidateOrders(MovementPhaseAdjudicator.Instance); var validations = setup.ValidateOrders(MovementPhase);
Assert.That(orderVie, Is.Valid); Assert.That(orderVie, Is.Valid);
Assert.That(orderMun, Is.Valid); Assert.That(orderMun, Is.Valid);
Assert.That(orderVen, Is.Valid); Assert.That(orderVen, Is.Valid);
var adjudications = setup.AdjudicateOrders(MovementPhaseAdjudicator.Instance); var adjudications = setup.AdjudicateOrders(MovementPhase);
// The three units bounce. // The three units bounce.
Assert.That(orderVie, Is.Repelled); Assert.That(orderVie, Is.Repelled);
Assert.That(orderVie, Is.NotDislodged); Assert.That(orderVie, Is.NotDislodged);

View File

@ -1,7 +1,8 @@
using MultiversalDiplomacy.Adjudicate; using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Adjudicate.Decision;
using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Model;
using static MultiversalDiplomacyTests.Adjudicator;
using NUnit.Framework; using NUnit.Framework;
namespace MultiversalDiplomacyTests; namespace MultiversalDiplomacyTests;
@ -11,7 +12,7 @@ public class TimeTravelTest
[Test] [Test]
public void MDATC_3_A_1_MoveIntoOwnPastForksTimeline() public void MDATC_3_A_1_MoveIntoOwnPastForksTimeline()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
// Hold to move into the future, then move back into the past. // Hold to move into the future, then move back into the past.
setup[("a", 0)] setup[("a", 0)]
@ -57,7 +58,7 @@ public class TimeTravelTest
[Test] [Test]
public void MDATC_3_A_2_SupportToRepelledPastMoveForksTimeline() public void MDATC_3_A_2_SupportToRepelledPastMoveForksTimeline()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
// Fail to dislodge on the first turn, then support the move so it succeeds. // Fail to dislodge on the first turn, then support the move so it succeeds.
setup[("a", 0)] setup[("a", 0)]
@ -107,7 +108,7 @@ public class TimeTravelTest
[Test] [Test]
public void MDATC_3_A_3_FailedMoveDoesNotForkTimeline() public void MDATC_3_A_3_FailedMoveDoesNotForkTimeline()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
// Hold to create a future, then attempt to attack in the past. // Hold to create a future, then attempt to attack in the past.
setup[("a", 0)] setup[("a", 0)]
@ -147,7 +148,7 @@ public class TimeTravelTest
[Test] [Test]
public void MDATC_3_A_4_SuperfluousSupportDoesNotForkTimeline() public void MDATC_3_A_4_SuperfluousSupportDoesNotForkTimeline()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
// Move, then support the past move even though it succeeded already. // Move, then support the past move even though it succeeded already.
setup[("a", 0)] setup[("a", 0)]
@ -189,7 +190,7 @@ public class TimeTravelTest
[Test] [Test]
public void MDATC_3_A_5_CrossTimelineSupportDoesNotForkHead() public void MDATC_3_A_5_CrossTimelineSupportDoesNotForkHead()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
// London creates two timelines by moving into the past. // London creates two timelines by moving into the past.
setup[("a", 0)] setup[("a", 0)]
@ -242,7 +243,7 @@ public class TimeTravelTest
[Test] [Test]
public void MDATC_3_A_6_CuttingCrossTimelineSupportDoesNotFork() public void MDATC_3_A_6_CuttingCrossTimelineSupportDoesNotFork()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
// As above, only now London creates three timelines. // As above, only now London creates three timelines.
setup[("a", 0)] setup[("a", 0)]

View File

@ -1,9 +1,10 @@
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Adjudicate.Decision; using MultiversalDiplomacy.Adjudicate.Decision;
using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Model;
using NUnit.Framework; using NUnit.Framework;
using static MultiversalDiplomacyTests.Adjudicator;
namespace MultiversalDiplomacyTests; namespace MultiversalDiplomacyTests;
public class MovementAdjudicatorTest public class MovementAdjudicatorTest
@ -11,7 +12,7 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Validation_ValidHold() public void Validation_ValidHold()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup["Germany"] setup["Germany"]
.Army("Mun").Holds().GetReference(out var order); .Army("Mun").Holds().GetReference(out var order);
@ -24,7 +25,7 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Validation_ValidMove() public void Validation_ValidMove()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup["Germany"] setup["Germany"]
.Army("Mun").MovesTo("Tyr").GetReference(out var order); .Army("Mun").MovesTo("Tyr").GetReference(out var order);
@ -37,7 +38,7 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Validation_ValidConvoy() public void Validation_ValidConvoy()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup["Germany"] setup["Germany"]
.Fleet("Nth").Convoys.Army("Hol").To("Lon").GetReference(out var order); .Fleet("Nth").Convoys.Army("Hol").To("Lon").GetReference(out var order);
@ -50,7 +51,7 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Validation_ValidSupportHold() public void Validation_ValidSupportHold()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup["Germany"] setup["Germany"]
.Army("Mun").Supports.Army("Kie").Hold().GetReference(out var order); .Army("Mun").Supports.Army("Kie").Hold().GetReference(out var order);
@ -63,7 +64,7 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Validation_ValidSupportMove() public void Validation_ValidSupportMove()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup["Germany"] setup["Germany"]
.Army("Mun").Supports.Army("Kie").MoveTo("Ber").GetReference(out var order); .Army("Mun").Supports.Army("Kie").MoveTo("Ber").GetReference(out var order);
@ -76,12 +77,12 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Adjudication_Hold() public void Adjudication_Hold()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup["Germany"] setup["Germany"]
.Army("Mun").Holds().GetReference(out var order); .Army("Mun").Holds().GetReference(out var order);
setup.ValidateOrders(); setup.ValidateOrders();
setup.AdjudicateOrders(MovementPhaseAdjudicator.Instance); setup.AdjudicateOrders(MovementPhase);
var adjMun = order.Adjudications; var adjMun = order.Adjudications;
Assert.That(adjMun.All(adj => adj.Resolved), Is.True); Assert.That(adjMun.All(adj => adj.Resolved), Is.True);
@ -96,7 +97,7 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Adjudication_Move() public void Adjudication_Move()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup["Germany"] setup["Germany"]
.Army("Mun").MovesTo("Tyr").GetReference(out var order); .Army("Mun").MovesTo("Tyr").GetReference(out var order);
@ -122,7 +123,7 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Adjudication_Support() public void Adjudication_Support()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup["Germany"] setup["Germany"]
.Army("Mun").MovesTo("Tyr").GetReference(out var move) .Army("Mun").MovesTo("Tyr").GetReference(out var move)
.Army("Boh").Supports.Army("Mun").MoveTo("Tyr").GetReference(out var support); .Army("Boh").Supports.Army("Mun").MoveTo("Tyr").GetReference(out var support);
@ -156,7 +157,7 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Update_SingleHold() public void Update_SingleHold()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup["Germany"] setup["Germany"]
.Army("Mun").Holds().GetReference(out var mun); .Army("Mun").Holds().GetReference(out var mun);
@ -183,7 +184,7 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Update_DoubleHold() public void Update_DoubleHold()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup[("a", 0)] setup[("a", 0)]
.GetReference(out Season s1) .GetReference(out Season s1)
["Germany"] ["Germany"]
@ -233,7 +234,7 @@ public class MovementAdjudicatorTest
[Test] [Test]
public void Update_DoubleMove() public void Update_DoubleMove()
{ {
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup[("a", 0)] setup[("a", 0)]
.GetReference(out Season s1) .GetReference(out Season s1)
["Germany"] ["Germany"]

View File

@ -0,0 +1,10 @@
using MultiversalDiplomacy.Adjudicate.Logging;
namespace MultiversalDiplomacyTests;
public class NullLogger : IAdjudicatorLogger
{
public static NullLogger Instance { get; } = new();
public void Log(int contextLevel, string message, params object[] args) {}
}

View File

@ -15,12 +15,6 @@ public class ReplDriver(IScriptHandler initialHandler, bool echo = false)
/// </summary> /// </summary>
bool Echo { get; } = echo; bool Echo { get; } = echo;
/// <summary>
/// Input a multiline string into the repl. Call <see cref="AssertReady"/> or <see cref="AssertClosed"/> at the end so the
/// statement is valid.
/// </summary>
public ReplDriver this[string input] => ExecuteAll(input);
public ReplDriver ExecuteAll(string multiline) public ReplDriver ExecuteAll(string multiline)
{ {
var lines = multiline.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var lines = multiline.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
@ -33,19 +27,22 @@ public class ReplDriver(IScriptHandler initialHandler, bool echo = false)
$"Cannot execute \"{inputLine}\", handler quit. Last input was \"{LastInput}\""); $"Cannot execute \"{inputLine}\", handler quit. Last input was \"{LastInput}\"");
if (Echo) Console.WriteLine($"{Handler.Prompt}{inputLine}"); if (Echo) Console.WriteLine($"{Handler.Prompt}{inputLine}");
Handler = Handler.HandleInput(inputLine); var result = Handler.HandleInput(inputLine);
if (!result.Success) Assert.Fail($"Script failed at \"{inputLine}\": {result.Message}");
Handler = result.NextHandler;
LastInput = inputLine; LastInput = inputLine;
return this; return this;
} }
public void AssertReady() public void AssertFails(string inputLine)
{ {
if (Handler is null) Assert.Fail($"Handler terminated after \"{LastInput}\""); if (Handler is null) throw new AssertionException(
} $"Cannot execute \"{inputLine}\", handler quit. Last input was \"{LastInput}\"");
if (Echo) Console.WriteLine($"{Handler.Prompt}{inputLine}");
public void AssertClosed() var result = Handler.HandleInput(inputLine);
{ if (result.Success) Assert.Fail($"Expected \"{inputLine}\" to fail, but it succeeded.");
if (Handler is not null) Assert.Fail($"Handler did not terminate after \"{LastInput}\"");
} }
} }

View File

@ -9,18 +9,21 @@ namespace MultiversalDiplomacyTests;
public class ReplTest public class ReplTest
{ {
private static ReplDriver StandardRepl() => new( private static ReplDriver StandardRepl() => new(
new SetupScriptHandler(World.WithStandardMap(), strict: true)); new SetupScriptHandler(
(msg) => {/* discard */},
World.WithStandardMap(),
Adjudicator.MovementPhase));
[Test] [Test]
public void SetupHandler() public void SetupHandler()
{ {
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit Germany A Munich unit Germany A Munich
unit Austria Army Tyrolia unit Austria Army Tyrolia
unit England F Lon unit England F Lon
"""].AssertReady(); """);
Assert.That(repl.Handler, Is.TypeOf<SetupScriptHandler>()); Assert.That(repl.Handler, Is.TypeOf<SetupScriptHandler>());
SetupScriptHandler handler = (SetupScriptHandler)repl.Handler!; SetupScriptHandler handler = (SetupScriptHandler)repl.Handler!;
@ -29,9 +32,7 @@ public class ReplTest
Assert.That(handler.World.GetUnitAt("Tyr"), Is.Not.Null); Assert.That(handler.World.GetUnitAt("Tyr"), Is.Not.Null);
Assert.That(handler.World.GetUnitAt("Lon"), Is.Not.Null); Assert.That(handler.World.GetUnitAt("Lon"), Is.Not.Null);
repl[""" repl.Execute("---");
---
"""].AssertReady();
Assert.That(repl.Handler, Is.TypeOf<GameScriptHandler>()); Assert.That(repl.Handler, Is.TypeOf<GameScriptHandler>());
} }
@ -41,7 +42,7 @@ public class ReplTest
{ {
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit Germany A Mun unit Germany A Mun
unit Austria A Tyr unit Austria A Tyr
unit England F Lon unit England F Lon
@ -50,7 +51,7 @@ public class ReplTest
Austria: Army Tyrolia - Vienna Austria: Army Tyrolia - Vienna
England: England:
Lon h Lon h
"""].AssertReady(); """);
Assert.That(repl.Handler, Is.TypeOf<GameScriptHandler>()); Assert.That(repl.Handler, Is.TypeOf<GameScriptHandler>());
GameScriptHandler handler = (GameScriptHandler)repl.Handler!; GameScriptHandler handler = (GameScriptHandler)repl.Handler!;
@ -62,9 +63,7 @@ public class ReplTest
World before = handler.World; World before = handler.World;
repl[""" repl.Execute("---");
---
"""].AssertReady();
Assert.That(repl.Handler, Is.TypeOf<AdjudicationQueryScriptHandler>()); Assert.That(repl.Handler, Is.TypeOf<AdjudicationQueryScriptHandler>());
var newHandler = (AdjudicationQueryScriptHandler)repl.Handler!; var newHandler = (AdjudicationQueryScriptHandler)repl.Handler!;
@ -77,94 +76,88 @@ public class ReplTest
{ {
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit Germany A Munich unit Germany A Munich
--- ---
--- ---
assert true assert true
"""].AssertReady(); """);
repl["assert false"].AssertClosed(); repl.AssertFails("assert false");
} }
[Test] [Test]
public void AssertInvalidOrder() public void AssertOrderValidity()
{ {
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit Germany A Mun unit Germany A Mun
--- ---
Germany A Mun - Mars Germany A Mun - Stp
--- ---
"""].AssertReady(); """);
// Assertion should pass for an invalid order // Order should be invalid
repl["assert order-invalid Mun"].AssertReady(); repl.Execute("assert order-invalid Mun");
// Assertion should fail for an invalid order repl.AssertFails("assert order-valid Mun");
repl["assert order-valid Mun"].AssertClosed();
}
[Test] repl.ExecuteAll("""
public void AssertValidOrder()
{
var repl = StandardRepl();
repl["""
unit Germany A Mun
--- ---
Germany A Mun - Tyr Germany A Mun - Tyr
--- ---
"""].AssertReady(); """);
// Assertion should pass for a valid order // Order should be valid
repl["assert order-valid Mun"].AssertReady(); repl.Execute("assert order-valid Mun");
// Assertion should fail for a valid order repl.AssertFails("assert order-invalid Mun");
repl["assert order-invalid Mun"].AssertClosed();
} }
[Test] [Test]
public void AssertSeasonPast() public void AssertSeasonPast()
{ {
Assert.Ignore();
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit England F London unit England F London
--- ---
--- ---
"""].AssertReady(); """);
// Assertion should pass for a season's past // Assertion should pass for a season's past
repl["assert has-past a1>a0"].AssertReady(); repl.Execute("assert has-past a1>a0");
// Assertion should fail for an incorrect past // Assertion should fail for an incorrect past
repl["assert has-past a0>a1"].AssertClosed(); repl.AssertFails("assert has-past a0>a1");
} }
[Test] [Test]
public void AssertHolds() public void AssertHolds()
{ {
Assert.Ignore();
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit Germany A Mun unit Germany A Mun
unit Austria A Tyr unit Austria A Tyr
--- ---
Germany Mun - Tyr Germany Mun - Tyr
--- ---
"""].AssertReady(); """);
// Assertion should pass for a repelled move // Assertion should pass for a repelled move
repl["assert holds Tyr"].AssertReady(); repl.Execute("assert holds Tyr");
// Assertion should fail for a repelled move // Assertion should fail for a repelled move
repl["assert dislodged Tyr"].AssertClosed(); repl.Execute("assert dislodged Tyr");
} }
[Test] [Test]
public void AssertDislodged() public void AssertDislodged()
{ {
Assert.Ignore();
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit Germany A Mun unit Germany A Mun
unit Germany A Boh unit Germany A Boh
unit Austria A Tyr unit Austria A Tyr
@ -172,57 +165,60 @@ public class ReplTest
Germany Mun - Tyr Germany Mun - Tyr
Germany Boh s Mun - Tyr Germany Boh s Mun - Tyr
--- ---
"""].AssertReady(); """);
// Assertion should pass for a dislodge // Assertion should pass for a dislodge
repl["assert dislodged Tyr"].AssertReady(); repl.Execute("assert dislodged Tyr");
// Assertion should fail for a repelled move // Assertion should fail for a repelled move
repl["assert holds Tyr"].AssertClosed(); repl.AssertFails("assert holds Tyr");
} }
[Test] [Test]
public void AssertMoves() public void AssertMoves()
{ {
Assert.Ignore();
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit Germany A Mun unit Germany A Mun
--- ---
Germany Mun - Tyr Germany Mun - Tyr
--- ---
"""].AssertReady(); """);
// Assertion should pass for a move // Assertion should pass for a move
repl["assert moves Mun"].AssertReady(); repl.Execute("assert moves Mun");
// Assertion should fail for a successful move // Assertion should fail for a successful move
repl["assert no-move Mun"].AssertClosed(); repl.AssertFails("assert no-move Mun");
} }
[Test] [Test]
public void AssertRepelled() public void AssertRepelled()
{ {
Assert.Ignore();
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit Germany A Mun unit Germany A Mun
unit Austria A Tyr unit Austria A Tyr
--- ---
Germany Mun - Tyr Germany Mun - Tyr
--- ---
"""].AssertReady(); """);
// Assertion should pass for a repelled move // Assertion should pass for a repelled move
repl["assert no-move Mun"].AssertReady(); repl.Execute("assert no-move Mun");
// Assertion should fail for no move // Assertion should fail for no move
repl["assert moves Mun"].AssertClosed(); repl.AssertFails("assert moves Mun");
} }
[Test] [Test]
public void AssertSupports() public void AssertSupports()
{ {
Assert.Ignore();
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit Germany A Mun unit Germany A Mun
unit Germany A Boh unit Germany A Boh
unit Austria A Tyr unit Austria A Tyr
@ -231,19 +227,20 @@ public class ReplTest
Mun - Tyr Mun - Tyr
Boh s Mun - Tyr Boh s Mun - Tyr
--- ---
"""].AssertReady(); """);
// `supports` and `cut` are opposites // `supports` and `cut` are opposites
repl["assert supports Boh"].AssertReady(); repl.Execute("assert supports Boh");
repl["assert cut Boh"].AssertClosed(); repl.AssertFails("assert cut Boh");
} }
[Test] [Test]
public void AssertCutSupport() public void AssertCutSupport()
{ {
Assert.Ignore();
var repl = StandardRepl(); var repl = StandardRepl();
repl[""" repl.ExecuteAll("""
unit Germany A Mun unit Germany A Mun
unit Germany A Boh unit Germany A Boh
unit Austria A Tyr unit Austria A Tyr
@ -255,10 +252,10 @@ public class ReplTest
Italy Vienna - Boh Italy Vienna - Boh
--- ---
"""].AssertReady(); """);
// `supports` and `cut` are opposites // `supports` and `cut` are opposites
repl["assert cut Boh"].AssertReady(); repl.Execute("assert cut Boh");
repl["assert supports Boh"].AssertClosed(); repl.AssertFails("assert supports Boh");
} }
} }

View File

@ -22,11 +22,18 @@ public class ScriptTests
Assert.Ignore("Script tests postponed until parsing tests are done"); Assert.Ignore("Script tests postponed until parsing tests are done");
string filename = Path.GetFileName(testScriptPath); string filename = Path.GetFileName(testScriptPath);
int line = 0; int line = 0;
IScriptHandler? handler = new SetupScriptHandler(World.WithStandardMap(), strict: true); IScriptHandler handler = new SetupScriptHandler(
(msg) => {/* discard */},
World.WithStandardMap(),
Adjudicator.MovementPhase);
foreach (string input in File.ReadAllLines(testScriptPath)) { foreach (string input in File.ReadAllLines(testScriptPath)) {
line++; line++;
handler = handler?.HandleInput(input); var result = handler.HandleInput(input);
if (handler is null) Assert.Fail($"Script {filename} quit unexpectedly at line {line}: \"{input}\""); if (!result.Success) throw new AssertionException(
$"Script {filename} error at line {line}: {result.Message}");
if (result.NextHandler is null) throw new AssertionException(
$"Script {filename} quit unexpectedly at line {line}: \"{input}\"");
handler = result.NextHandler;
} }
} }
} }

View File

@ -1,11 +1,12 @@
using System.Text.Json; using System.Text.Json;
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Adjudicate.Decision; using MultiversalDiplomacy.Adjudicate.Decision;
using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Model;
using NUnit.Framework; using NUnit.Framework;
using static MultiversalDiplomacyTests.Adjudicator;
namespace MultiversalDiplomacyTests; namespace MultiversalDiplomacyTests;
public class SerializationTest public class SerializationTest
@ -74,7 +75,7 @@ public class SerializationTest
public void SerializeRoundTrip_MDATC_3_A_2() public void SerializeRoundTrip_MDATC_3_A_2()
{ {
// Set up MDATC 3.A.2 // Set up MDATC 3.A.2
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance); TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhase);
setup[("a", 0)] setup[("a", 0)]
.GetReference(out Season s0) .GetReference(out Season s0)
["Germany"] ["Germany"]
@ -107,7 +108,7 @@ public class SerializationTest
}); });
// Resume the test case // Resume the test case
setup = new(reserialized, MovementPhaseAdjudicator.Instance); setup = new(reserialized, MovementPhase);
setup[("a", 1)] setup[("a", 1)]
["Germany"] ["Germany"]
.Army("Mun").Supports.Army("Mun", season: s0).MoveTo("Tyr").GetReference(out var mun1) .Army("Mun").Supports.Army("Mun", season: s0).MoveTo("Tyr").GetReference(out var mun1)