Add movement order validation tests
This commit is contained in:
parent
00cac2cb89
commit
c50dbf6b46
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,14 +433,12 @@ 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"));
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue