Compare commits

...

2 Commits

Author SHA1 Message Date
Tim Van Baak f9f8ea2b5a Add script-based test framework 2024-08-17 21:24:59 -07:00
Tim Van Baak b2461b3736 Add strict mode to setup handler 2024-08-17 21:18:35 -07:00
13 changed files with 213 additions and 8 deletions

View File

@ -0,0 +1,43 @@
using MultiversalDiplomacy.Model;
namespace MultiversalDiplomacy.Script;
public class GameScriptHandler(World world, bool strict = false) : IScriptHandler
{
public string Prompt => "game> ";
public World World { get; private set; } = world;
/// <summary>
/// Whether unsuccessful commands should terminate the script.
/// </summary>
public bool Strict { get; } = strict;
private string? CurrentPower = null;
public IScriptHandler? HandleInput(string input)
{
if (input == "") {
CurrentPower = null;
return this;
}
// --- submits the orders for validation to allow for assertions about it
if (input == "---") {
// TODO submit orders
// TODO return a new handler that handles asserts
}
// A block of orders for a single power beginning with "{name}:"
if (World.Powers.FirstOrDefault(p => input.EqualsAnyCase($"{p}:"), null) is string power) {
CurrentPower = power;
return this;
}
// TODO parse order, including "{power} {order}" for one-offs
Console.WriteLine($"{CurrentPower}: {input}");
return this;
}
}

View File

@ -7,12 +7,17 @@ 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) : IScriptHandler public class SetupScriptHandler(World world, bool strict = false) : IScriptHandler
{ {
public string Prompt => "5dp> "; public string Prompt => "5dp> ";
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;
public IScriptHandler? HandleInput(string input) public IScriptHandler? HandleInput(string input)
{ {
var args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries); var args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
@ -27,7 +32,7 @@ public class SetupScriptHandler(World world) : IScriptHandler
case "help": case "help":
case "?": case "?":
Console.WriteLine("commands:"); Console.WriteLine("commands:");
Console.WriteLine(" begin: complete setup and start the game"); Console.WriteLine(" begin: complete setup and start the game (alias: ---)");
Console.WriteLine(" list <type>: list things in a game category"); Console.WriteLine(" list <type>: list things in a game category");
Console.WriteLine(" option <name> <value>: set a game option"); Console.WriteLine(" option <name> <value>: set a game option");
Console.WriteLine(" unit <power> <type> <province> [location]: add a unit to the game"); Console.WriteLine(" unit <power> <type> <province> [location]: add a unit to the game");
@ -35,7 +40,8 @@ public class SetupScriptHandler(World world) : IScriptHandler
break; break;
case "begin": case "begin":
return null; // TODO case "---":
return new GameScriptHandler(World, Strict);
case "list" when args.Length == 1: case "list" when args.Length == 1:
Console.WriteLine("usage:"); Console.WriteLine("usage:");
@ -74,14 +80,16 @@ public class SetupScriptHandler(World world) : IScriptHandler
if (ParseUnit(power, type, province, location, out Unit? newUnit)) { if (ParseUnit(power, type, province, location, out Unit? newUnit)) {
World = World.Update(units: World.Units.Append(newUnit)); World = World.Update(units: World.Units.Append(newUnit));
Console.WriteLine($"Created {newUnit}"); Console.WriteLine($"Created {newUnit}");
} else if (Strict) {
return null;
} }
break; break;
default: default:
// noop on comments that begin with # // noop on comments that begin with #
if (!command.StartsWith('#')) { if (command.StartsWith('#')) break;
Console.WriteLine($"Unrecognized command: {command}"); Console.WriteLine($"Unrecognized command: {command}");
} if (Strict) return null;
break; break;
} }

View File

@ -1,8 +1,6 @@
using MultiversalDiplomacy.Adjudicate; using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Adjudicate.Decision;
using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders; using MultiversalDiplomacy.Orders;
using NUnit.Framework; using NUnit.Framework;
namespace MultiversalDiplomacyTests; namespace MultiversalDiplomacyTests;

View File

@ -18,4 +18,10 @@
<PackageReference Include="coverlet.collector" Version="3.1.0" /> <PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Scripts/**/*.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,28 @@
using NUnit.Framework;
using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Script;
namespace MultiversalDiplomacyTests;
public class ScriptTests
{
static IEnumerable<TestCaseData> DatcTestCases()
{
foreach (var path in Directory.EnumerateFiles("Scripts/DATC"))
{
yield return new TestCaseData(path)
.SetName($"{{m}}({Path.GetFileNameWithoutExtension(path)})");
}
}
[TestCaseSource(nameof(DatcTestCases))]
public void Test_DATC(string testScriptPath)
{
IScriptHandler? handler = new SetupScriptHandler(World.WithStandardMap(), strict: true);
foreach (string input in File.ReadAllLines(testScriptPath)) {
handler = handler?.HandleInput(input);
Assert.That(handler, Is.Not.Null, $"Script quit unexpectedly at \"{input}\"");
}
}
}

View File

@ -0,0 +1,14 @@
# 6.A.1. TEST CASE, MOVING TO AN AREA THAT IS NOT A NEIGHBOUR
# Check if an illegal move (without convoy) will fail.
unit England F North Sea
---
England:
F North Sea - Picardy
---
# Order should fail.
assert North Sea holds

View File

@ -0,0 +1,14 @@
# 6.A.2. TEST CASE, MOVE ARMY TO SEA
# Check if an army could not be moved to open sea.
unit England A Liverpool
---
England:
A Liverpool - Irish Sea
---
# Order should fail.
assert Liverpool holds

View File

@ -0,0 +1,14 @@
# 6.A.3. TEST CASE, MOVE FLEET TO LAND
# Check whether a fleet cannot move to land.
unit Germany Army Kiel
---
Germany:
F Kiel - Munich
---
# Order should fail.
assert Kiel holds

View File

@ -0,0 +1,14 @@
# 6.A.4. TEST CASE, MOVE TO OWN SECTOR
# Moving to the same sector is an illegal move (2023 rulebook, page 7, "An Army can be ordered to move into an adjacent inland or coastal province.").
unit Germany Army Kiel
---
Germany:
F Kiel - Kiel
---
# Program should not crash.
assert Kiel holds

View File

@ -0,0 +1,31 @@
# 6.A.5. TEST CASE, MOVE TO OWN SECTOR WITH CONVOY
# Moving to the same sector is still illegal with convoy (2023 rulebook, page 7, "Note: An Army can move across water provinces from one coastal province to another...").
unit England F North Sea
unit England A Yorkshire
unit England A Liverpool
unit Germany F London
unit Germany A Wales
---
England:
F North Sea Convoys A Yorkshire - Yorkshire
A Yorkshire - Yorkshire
A Liverpool Supports A Yorkshire - Yorkshire
Germany:
F London - Yorkshire
A Wales Supports F London - Yorkshire
---
# The move of the army in Yorkshire is illegal.
assert Yorkshire holds
# This makes the support of Liverpool also illegal and without the support, the Germans have a stronger force.
assert North Sea holds
assert Liverpool holds
assert London moves
# The army in London dislodges the army in Yorkshire.
assert Wales supports
assert Yorkshire dislodged

View File

@ -0,0 +1,16 @@
# 6.A.6. TEST CASE, ORDERING A UNIT OF ANOTHER COUNTRY
# Check whether someone cannot order a unit that is not his own unit.
unit England F London
# A German unit is included here so Germany isn't considered dead
unit Germany A Munich
---
Germany:
F London - North Sea
---
# Order should fail.
assert London holds

View File

@ -0,0 +1,16 @@
# 6.A.7. TEST CASE, ONLY ARMIES CAN BE CONVOYED
# A fleet cannot be convoyed.
unit England F London
unit England North Sea
---
England:
F London - Belgium
F North Sea Convoys A London - Belgium
---
# Move from London to Belgium should fail.
assert London holds

View File

@ -0,0 +1,3 @@
# DATC test scripts
These test scripts are copied from DATC v3.1.