diff --git a/MultiversalDiplomacy/Adjudicate/AdjudicatorHelpers.cs b/MultiversalDiplomacy/Adjudicate/AdjudicatorHelpers.cs
index e613539..9e73438 100644
--- a/MultiversalDiplomacy/Adjudicate/AdjudicatorHelpers.cs
+++ b/MultiversalDiplomacy/Adjudicate/AdjudicatorHelpers.cs
@@ -51,11 +51,12 @@ internal static class AdjudicatorHelpers
.ToList();
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(
- order => !validOrderTypes.Contains(order.GetType()),
+ order => validOrderTypes.Contains(order.GetType()),
ValidationReason.InvalidOrderTypeForPhase,
ref orders,
ref invalidOrders);
@@ -83,7 +84,8 @@ internal static class AdjudicatorHelpers
RetreatOrder retreat => retreat.Power == retreat.Unit.Power,
SupportHoldOrder 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,
},
ValidationReason.InvalidUnitForPower,
diff --git a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs
index 9011f70..27305fe 100644
--- a/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs
+++ b/MultiversalDiplomacy/Adjudicate/MovementPhaseAdjudicator.cs
@@ -6,7 +6,7 @@ namespace MultiversalDiplomacy.Adjudicate;
///
/// Adjudicator for the movement phase.
///
-internal class MovementPhaseAdjudicator : IPhaseAdjudicator
+public class MovementPhaseAdjudicator : IPhaseAdjudicator
{
public List ValidateOrders(World world, List orders)
{
@@ -35,7 +35,7 @@ internal class MovementPhaseAdjudicator : IPhaseAdjudicator
AdjudicatorHelpers.InvalidateWrongPower(orders, ref orders, ref validationResults);
// Since all the order types in this phase are UnitOrders, downcast to get the Unit.
- List unitOrders = orders.OfType().ToList();
+ List unitOrders = orders.Cast().ToList();
// Invalidate any order given to a unit in the past.
AdjudicatorHelpers.InvalidateIfNotMatching(
diff --git a/MultiversalDiplomacy/Model/World.cs b/MultiversalDiplomacy/Model/World.cs
index 8360bb7..7b08cce 100644
--- a/MultiversalDiplomacy/Model/World.cs
+++ b/MultiversalDiplomacy/Model/World.cs
@@ -96,7 +96,7 @@ public class World
{
"A" => UnitType.Army,
"F" => UnitType.Fleet,
- _ => throw new ArgumentOutOfRangeException($"Unknown unit type {splits[1]}")
+ _ => throw new ApplicationException($"Unknown unit type {splits[1]}")
};
Location location = type == UnitType.Army
? this.GetLand(splits[2])
@@ -152,14 +152,20 @@ public class World
/// Get a province by name. Throws if the province is not found.
///
private Province GetProvince(string provinceName)
+ => GetProvince(provinceName, this.Provinces);
+
+ ///
+ /// Get a province by name. Throws if the province is not found.
+ ///
+ private static Province GetProvince(string provinceName, IEnumerable provinces)
{
string provinceNameUpper = provinceName.ToUpperInvariant();
- Province? foundProvince = this.Provinces.SingleOrDefault(
+ Province? foundProvince = provinces.SingleOrDefault(
p => p != null &&
(p.Name.ToUpperInvariant() == provinceNameUpper
|| p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper)),
null);
- if (foundProvince == null) throw new ArgumentOutOfRangeException(
+ if (foundProvince == null) throw new KeyNotFoundException(
$"Province {provinceName} not found");
return foundProvince;
}
@@ -172,7 +178,7 @@ public class World
{
Location? foundLocation = GetProvince(provinceName).Locations.SingleOrDefault(
l => l != null && predicate(l), null);
- if (foundLocation == null) throw new ArgumentException(
+ if (foundLocation == null) throw new KeyNotFoundException(
$"No such location in {provinceName}");
return foundLocation;
}
@@ -201,7 +207,7 @@ public class World
p != null
&& (p.Name == powerName || p.Name.StartsWith(powerName)),
null);
- if (foundPower == null) throw new ArgumentOutOfRangeException(
+ if (foundPower == null) throw new KeyNotFoundException(
$"Power {powerName} not found");
return foundPower;
}
@@ -427,15 +433,13 @@ public class World
};
// Declare some helpers for border definitions
- Location Land(string provinceName) => standardProvinces
- .Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
+ Location Land(string provinceName) => GetProvince(provinceName, standardProvinces)
.Locations.Single(l => l.Type == LocationType.Land);
- Location Water(string provinceName) => standardProvinces
- .Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
+ Location Water(string provinceName) => GetProvince(provinceName, standardProvinces)
.Locations.Single(l => l.Type == LocationType.Water);
- Location Coast(string provinceName, string coastName) => standardProvinces
- .Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
- .Locations.Single(l => l.Name == coastName || l.Abbreviation == coastName);
+ Location Coast(string provinceName, string coastName)
+ => GetProvince(provinceName, standardProvinces)
+ .Locations.Single(l => l.Name == coastName || l.Abbreviation == coastName);
Land("NAF").AddBorder(Land("TUN"));
Water("NAF").AddBorder(Water("MAO"));
@@ -517,7 +521,165 @@ public class World
Water("GRE").AddBorder(Water("AEG"));
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("TYS"));
@@ -528,7 +690,9 @@ public class World
Water("IOS").AddBorder(Water("GRE"));
Water("IOS").AddBorder(Water("AEG"));
- // TODO
+ // TYS
+
+ // WMS
return new(standardProvinces);
}
diff --git a/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs b/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs
new file mode 100644
index 0000000..8c81001
--- /dev/null
+++ b/MultiversalDiplomacyTests/MovementAdjudicatorTest.cs
@@ -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");
+ });
+ }
+}
\ No newline at end of file