Add movement order validation tests

This commit is contained in:
Jaculabilis 2022-03-22 21:27:06 -07:00
parent 00cac2cb89
commit c50dbf6b46
4 changed files with 295 additions and 19 deletions

View File

@ -51,11 +51,12 @@ internal static class AdjudicatorHelpers
.ToList(); .ToList();
if (nonOrderTypes.Any()) if (nonOrderTypes.Any())
{ {
throw new ArgumentException($"Unknown order type: {nonOrderTypes.Select(t => t.FullName).First()}"); throw new ArgumentException(
$"Unknown order type: {nonOrderTypes.Select(t => t.FullName).First()}");
} }
InvalidateIfNotMatching( InvalidateIfNotMatching(
order => !validOrderTypes.Contains(order.GetType()), order => validOrderTypes.Contains(order.GetType()),
ValidationReason.InvalidOrderTypeForPhase, ValidationReason.InvalidOrderTypeForPhase,
ref orders, ref orders,
ref invalidOrders); ref invalidOrders);
@ -83,7 +84,8 @@ internal static class AdjudicatorHelpers
RetreatOrder retreat => retreat.Power == retreat.Unit.Power, RetreatOrder retreat => retreat.Power == retreat.Unit.Power,
SupportHoldOrder support => support.Power == support.Unit.Power, SupportHoldOrder support => support.Power == support.Unit.Power,
SupportMoveOrder support => support.Power == support.Unit.Power, SupportMoveOrder support => support.Power == support.Unit.Power,
// Any order not given to a unit by definition cannot be given to a unit of the wrong power // Any order not given to a unit, by definition, cannot be given to a unit of the
// wrong power
_ => true, _ => true,
}, },
ValidationReason.InvalidUnitForPower, ValidationReason.InvalidUnitForPower,

View File

@ -6,7 +6,7 @@ namespace MultiversalDiplomacy.Adjudicate;
/// <summary> /// <summary>
/// Adjudicator for the movement phase. /// Adjudicator for the movement phase.
/// </summary> /// </summary>
internal class MovementPhaseAdjudicator : IPhaseAdjudicator public class MovementPhaseAdjudicator : IPhaseAdjudicator
{ {
public List<OrderValidation> ValidateOrders(World world, List<Order> orders) public List<OrderValidation> ValidateOrders(World world, List<Order> orders)
{ {
@ -35,7 +35,7 @@ internal class MovementPhaseAdjudicator : IPhaseAdjudicator
AdjudicatorHelpers.InvalidateWrongPower(orders, ref orders, ref validationResults); AdjudicatorHelpers.InvalidateWrongPower(orders, ref orders, ref validationResults);
// Since all the order types in this phase are UnitOrders, downcast to get the Unit. // Since all the order types in this phase are UnitOrders, downcast to get the Unit.
List<UnitOrder> unitOrders = orders.OfType<UnitOrder>().ToList(); List<UnitOrder> unitOrders = orders.Cast<UnitOrder>().ToList();
// Invalidate any order given to a unit in the past. // Invalidate any order given to a unit in the past.
AdjudicatorHelpers.InvalidateIfNotMatching( AdjudicatorHelpers.InvalidateIfNotMatching(

View File

@ -96,7 +96,7 @@ public class World
{ {
"A" => UnitType.Army, "A" => UnitType.Army,
"F" => UnitType.Fleet, "F" => UnitType.Fleet,
_ => throw new ArgumentOutOfRangeException($"Unknown unit type {splits[1]}") _ => throw new ApplicationException($"Unknown unit type {splits[1]}")
}; };
Location location = type == UnitType.Army Location location = type == UnitType.Army
? this.GetLand(splits[2]) ? this.GetLand(splits[2])
@ -152,14 +152,20 @@ public class World
/// Get a province by name. Throws if the province is not found. /// Get a province by name. Throws if the province is not found.
/// </summary> /// </summary>
private Province GetProvince(string provinceName) private Province GetProvince(string provinceName)
=> GetProvince(provinceName, this.Provinces);
/// <summary>
/// Get a province by name. Throws if the province is not found.
/// </summary>
private static Province GetProvince(string provinceName, IEnumerable<Province> provinces)
{ {
string provinceNameUpper = provinceName.ToUpperInvariant(); string provinceNameUpper = provinceName.ToUpperInvariant();
Province? foundProvince = this.Provinces.SingleOrDefault( Province? foundProvince = provinces.SingleOrDefault(
p => p != null && p => p != null &&
(p.Name.ToUpperInvariant() == provinceNameUpper (p.Name.ToUpperInvariant() == provinceNameUpper
|| p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper)), || p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper)),
null); null);
if (foundProvince == null) throw new ArgumentOutOfRangeException( if (foundProvince == null) throw new KeyNotFoundException(
$"Province {provinceName} not found"); $"Province {provinceName} not found");
return foundProvince; return foundProvince;
} }
@ -172,7 +178,7 @@ public class World
{ {
Location? foundLocation = GetProvince(provinceName).Locations.SingleOrDefault( Location? foundLocation = GetProvince(provinceName).Locations.SingleOrDefault(
l => l != null && predicate(l), null); l => l != null && predicate(l), null);
if (foundLocation == null) throw new ArgumentException( if (foundLocation == null) throw new KeyNotFoundException(
$"No such location in {provinceName}"); $"No such location in {provinceName}");
return foundLocation; return foundLocation;
} }
@ -201,7 +207,7 @@ public class World
p != null p != null
&& (p.Name == powerName || p.Name.StartsWith(powerName)), && (p.Name == powerName || p.Name.StartsWith(powerName)),
null); null);
if (foundPower == null) throw new ArgumentOutOfRangeException( if (foundPower == null) throw new KeyNotFoundException(
$"Power {powerName} not found"); $"Power {powerName} not found");
return foundPower; return foundPower;
} }
@ -427,15 +433,13 @@ public class World
}; };
// Declare some helpers for border definitions // Declare some helpers for border definitions
Location Land(string provinceName) => standardProvinces Location Land(string provinceName) => GetProvince(provinceName, standardProvinces)
.Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
.Locations.Single(l => l.Type == LocationType.Land); .Locations.Single(l => l.Type == LocationType.Land);
Location Water(string provinceName) => standardProvinces Location Water(string provinceName) => GetProvince(provinceName, standardProvinces)
.Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
.Locations.Single(l => l.Type == LocationType.Water); .Locations.Single(l => l.Type == LocationType.Water);
Location Coast(string provinceName, string coastName) => standardProvinces Location Coast(string provinceName, string coastName)
.Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName)) => GetProvince(provinceName, standardProvinces)
.Locations.Single(l => l.Name == coastName || l.Abbreviation == coastName); .Locations.Single(l => l.Name == coastName || l.Abbreviation == coastName);
Land("NAF").AddBorder(Land("TUN")); Land("NAF").AddBorder(Land("TUN"));
Water("NAF").AddBorder(Water("MAO")); Water("NAF").AddBorder(Water("MAO"));
@ -517,7 +521,165 @@ public class World
Water("GRE").AddBorder(Water("AEG")); Water("GRE").AddBorder(Water("AEG"));
Water("GRE").AddBorder(Coast("BUL", "sc")); Water("GRE").AddBorder(Coast("BUL", "sc"));
// TODO // RUM
// SER
// CLY
// EDI
// LVP
Land("LON").AddBorder(Land("WAL"));
Land("LON").AddBorder(Land("YOR"));
Water("LON").AddBorder(Water("ENC"));
Water("LON").AddBorder(Water("NTH"));
// WAL
// YOR
// BRE
// BUR
// GAS
// MAR
// PAR
// PIC
Land("BER").AddBorder(Land("PRU"));
Land("BER").AddBorder(Land("SIL"));
Land("BER").AddBorder(Land("MUN"));
Land("BER").AddBorder(Land("KIE"));
Water("BER").AddBorder(Water("KIE"));
Water("BER").AddBorder(Water("BAL"));
Water("BER").AddBorder(Water("PRU"));
Land("KIE").AddBorder(Land("BER"));
Land("KIE").AddBorder(Land("MUN"));
Land("KIE").AddBorder(Land("RUH"));
Land("KIE").AddBorder(Land("HOL"));
Land("KIE").AddBorder(Land("DEN"));
Water("KIE").AddBorder(Water("HOL"));
Water("KIE").AddBorder(Water("HEL"));
Water("KIE").AddBorder(Water("DEN"));
Water("KIE").AddBorder(Water("BAL"));
Water("KIE").AddBorder(Water("BER"));
Land("MUN").AddBorder(Land("BUR"));
Land("MUN").AddBorder(Land("RUH"));
Land("MUN").AddBorder(Land("KIE"));
Land("MUN").AddBorder(Land("BER"));
Land("MUN").AddBorder(Land("SIL"));
Land("MUN").AddBorder(Land("BOH"));
Land("MUN").AddBorder(Land("TYR"));
// PRU
// RUH
// SIL
// SPA
// POR
// APU
// NAP
// PIE
// ROM
// TUS
// VEN
// BEL
Land("HOL").AddBorder(Land("BEL"));
Land("HOL").AddBorder(Land("RUH"));
Land("HOL").AddBorder(Land("KIE"));
Water("HOL").AddBorder(Water("NTH"));
Water("HOL").AddBorder(Water("HEL"));
// FIN
// LVN
// MOS
// SEV
// STP
// UKR
// WAR
// DEN
// NWY
// SWE
// ANK
// ARM
// CON
// SMY
// SYR
// BAR
// ENC
// HEL
// IRS
// MAO
// NAO
Water("NTH").AddBorder(Water("NWG"));
Water("NTH").AddBorder(Water("NWY"));
Water("NTH").AddBorder(Water("SKA"));
Water("NTH").AddBorder(Water("DEN"));
Water("NTH").AddBorder(Water("HEL"));
Water("NTH").AddBorder(Water("HOL"));
Water("NTH").AddBorder(Water("BEL"));
Water("NTH").AddBorder(Water("ENC"));
Water("NTH").AddBorder(Water("LON"));
Water("NTH").AddBorder(Water("YOR"));
Water("NTH").AddBorder(Water("EDI"));
// NWS
// SKA
// BAL
// GOB
// ADS
// AEG
// BLA
// EMS
// GOL
Water("IOS").AddBorder(Water("TUN")); Water("IOS").AddBorder(Water("TUN"));
Water("IOS").AddBorder(Water("TYS")); Water("IOS").AddBorder(Water("TYS"));
@ -528,7 +690,9 @@ public class World
Water("IOS").AddBorder(Water("GRE")); Water("IOS").AddBorder(Water("GRE"));
Water("IOS").AddBorder(Water("AEG")); Water("IOS").AddBorder(Water("AEG"));
// TODO // TYS
// WMS
return new(standardProvinces); return new(standardProvinces);
} }

View File

@ -0,0 +1,110 @@
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders;
using NUnit.Framework;
namespace MultiversalDiplomacyTests;
public class MovementAdjudicatorTest
{
[Test]
public void Validation_ValidHold()
{
TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason());
setup["Germany"]
.Army("Mun").Holds().GetReference(out var order);
setup.ValidateOrders(new MovementPhaseAdjudicator());
Assert.Multiple(() =>
{
Assert.That(order.Validation.Valid, Is.True, "Unexpected validation result");
Assert.That(
order.Validation.Reason,
Is.EqualTo(ValidationReason.Valid),
"Unexpected validation reason");
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
});
}
[Test]
public void Validation_ValidMove()
{
TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason());
setup["Germany"]
.Army("Mun").MovesTo("Tyr").GetReference(out var order);
setup.ValidateOrders(new MovementPhaseAdjudicator());
Assert.Multiple(() =>
{
Assert.That(order.Validation.Valid, Is.True, "Unexpected validation result");
Assert.That(
order.Validation.Reason,
Is.EqualTo(ValidationReason.Valid),
"Unexpected validation reason");
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
});
}
[Test]
public void Validation_ValidConvoy()
{
TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason());
setup["Germany"]
.Fleet("Nth").Convoys.Army("Hol").To("Lon").GetReference(out var order);
setup.ValidateOrders(new MovementPhaseAdjudicator());
Assert.Multiple(() =>
{
Assert.That(order.Validation.Valid, Is.True, "Unexpected validation result");
Assert.That(
order.Validation.Reason,
Is.EqualTo(ValidationReason.Valid),
"Unexpected validation reason");
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
});
}
[Test]
public void Validation_ValidSupportHold()
{
TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason());
setup["Germany"]
.Army("Mun").Supports.Army("Kie").Hold().GetReference(out var order);
setup.ValidateOrders(new MovementPhaseAdjudicator());
Assert.Multiple(() =>
{
Assert.That(order.Validation.Valid, Is.True, "Unexpected validation result");
Assert.That(
order.Validation.Reason,
Is.EqualTo(ValidationReason.Valid),
"Unexpected validation reason");
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
});
}
[Test]
public void Validation_ValidSupportMove()
{
TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason());
setup["Germany"]
.Army("Mun").Supports.Army("Kie").MoveTo("Ber").GetReference(out var order);
setup.ValidateOrders(new MovementPhaseAdjudicator());
Assert.Multiple(() =>
{
Assert.That(order.Validation.Valid, Is.True, "Unexpected validation result");
Assert.That(
order.Validation.Reason,
Is.EqualTo(ValidationReason.Valid),
"Unexpected validation reason");
Assert.That(order.Replacement, Is.Null, "Unexpected order replacement");
});
}
}