diff --git a/MultiversalDiplomacy/Map/Map.cs b/MultiversalDiplomacy/Map/Map.cs
deleted file mode 100644
index dc70b58..0000000
--- a/MultiversalDiplomacy/Map/Map.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using MultiversalDiplomacy.Model;
-
-namespace MultiversalDiplomacy.Map;
-
-///
-/// A collection of provinces. Provides shortcut functions for referencing provinces.
-///
-public abstract class Map
-{
- public abstract IEnumerable Provinces { get; }
-
- ///
- /// Returns the sole army-accessible location of a province.
- ///
- public Location Land(string provinceName)
- => Provinces
- .Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
- .Locations.Single(l => l.Type == LocationType.Land);
-
- ///
- /// Returns the sole fleet-accessible location of a province.
- ///
- public Location Water(string provinceName)
- => Provinces
- .Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
- .Locations.Single(l => l.Type == LocationType.Water);
-
- ///
- /// Returns the specified fleet-accessible location of a province with distinct coasts.
- ///
- public Location Coast(string provinceName, string coastName)
- => Provinces
- .Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
- .Locations.Single(l => l.Name == coastName || l.Abbreviation == coastName);
-}
\ No newline at end of file
diff --git a/MultiversalDiplomacy/Map/StandardMap.cs b/MultiversalDiplomacy/Map/StandardMap.cs
deleted file mode 100644
index e2f8a18..0000000
--- a/MultiversalDiplomacy/Map/StandardMap.cs
+++ /dev/null
@@ -1,311 +0,0 @@
-using System.Reflection;
-
-using MultiversalDiplomacy.Model;
-
-namespace MultiversalDiplomacy.Map;
-
-///
-/// The standard Diplomacy map.
-///
-public class StandardMap : Map
-{
- public override IEnumerable Provinces { get; }
-
- public static StandardMap Instance { get; } = new StandardMap();
-
- private StandardMap()
- {
- this.Provinces = new List()
- {
- Province.Empty("North Africa", "NAF")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Tunis", "TUN")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Bohemia", "BOH")
- .AddLandLocation(),
- Province.Supply("Budapest", "BUD")
- .AddLandLocation(),
- Province.Empty("Galacia", "GAL")
- .AddLandLocation(),
- Province.Supply("Trieste", "TRI")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Tyrolia", "TYR")
- .AddLandLocation(),
- Province.Time("Vienna", "VIE")
- .AddLandLocation(),
- Province.Empty("Albania", "ALB")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Bulgaria", "BUL")
- .AddLandLocation()
- .AddCoastLocation("east coast", "ec")
- .AddCoastLocation("south coast", "sc"),
- Province.Supply("Greece", "GRE")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Rumania", "RUM", "RMA")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Serbia", "SER")
- .AddLandLocation(),
- Province.Empty("Clyde", "CLY")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Edinburgh", "EDI")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Liverpool", "LVP", "LPL")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Time("London", "LON")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Wales", "WAL")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Yorkshire", "YOR")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Brest", "BRE")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Burgundy", "BUR")
- .AddLandLocation(),
- Province.Empty("Gascony", "GAS")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Marseilles", "MAR")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Time("Paris", "PAR")
- .AddLandLocation(),
- Province.Empty("Picardy", "PIC")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Time("Berlin", "BER")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Kiel", "KIE")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Munich", "MUN")
- .AddLandLocation(),
- Province.Empty("Prussia", "PRU")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Ruhr", "RUH", "RHR")
- .AddLandLocation(),
- Province.Empty("Silesia", "SIL")
- .AddLandLocation(),
- Province.Supply("Spain", "SPA")
- .AddLandLocation()
- .AddCoastLocation("north coast", "nc")
- .AddCoastLocation("south coast", "sc"),
- Province.Supply("Portugal", "POR")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Apulia", "APU")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Naples", "NAP")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Piedmont", "PIE")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Time("Rome", "ROM", "RME")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Tuscany", "TUS")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Venice", "VEN")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Belgium", "BEL")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Holland", "HOL")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Finland", "FIN")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Livonia", "LVN", "LVA")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Time("Moscow", "MOS")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Sevastopol", "SEV")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Saint Petersburg", "STP")
- .AddLandLocation()
- .AddCoastLocation("north coast", "nc")
- .AddCoastLocation("west coast", "wc"),
- Province.Empty("Ukraine", "UKR")
- .AddLandLocation(),
- Province.Supply("Warsaw", "WAR")
- .AddLandLocation(),
- Province.Supply("Denmark", "DEN")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Norway", "NWY")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Sweden", "SWE")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Ankara", "ANK")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Armenia", "ARM")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Time("Constantinople", "CON")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Supply("Smyrna", "SMY")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Syria", "SYR")
- .AddLandLocation()
- .AddCoastLocation(),
- Province.Empty("Barents Sea", "BAR")
- .AddOceanLocation(),
- Province.Empty("English Channel", "ENC", "ECH")
- .AddOceanLocation(),
- Province.Empty("Heligoland Bight", "HEL", "HGB")
- .AddOceanLocation(),
- Province.Empty("Irish Sea", "IRS", "IRI")
- .AddOceanLocation(),
- Province.Empty("Mid-Atlantic Ocean", "MAO", "MID")
- .AddOceanLocation(),
- Province.Empty("North Atlantic Ocean", "NAO", "NAT")
- .AddOceanLocation(),
- Province.Empty("North Sea", "NTH", "NTS")
- .AddOceanLocation(),
- Province.Empty("Norwegian Sea", "NWS", "NWG")
- .AddOceanLocation(),
- Province.Empty("Skagerrak", "SKA", "SKG")
- .AddOceanLocation(),
- Province.Empty("Baltic Sea", "BAL")
- .AddOceanLocation(),
- Province.Empty("Guld of Bothnia", "GOB", "BOT")
- .AddOceanLocation(),
- Province.Empty("Adriatic Sea", "ADS", "ADR")
- .AddOceanLocation(),
- Province.Empty("Aegean Sea", "AEG")
- .AddOceanLocation(),
- Province.Empty("Black Sea", "BLA")
- .AddOceanLocation(),
- Province.Empty("Eastern Mediterranean Sea", "EMS", "EAS")
- .AddOceanLocation(),
- Province.Empty("Gulf of Lyons", "GOL", "LYO")
- .AddOceanLocation(),
- Province.Empty("Ionian Sea", "IOS", "ION", "INS")
- .AddOceanLocation(),
- Province.Empty("Tyrrhenian Sea", "TYS", "TYN")
- .AddOceanLocation(),
- Province.Empty("Western Mediterranean Sea", "WMS", "WES")
- .AddOceanLocation(),
- };
-
- Land("NAF").AddBorder(Land("TUN"));
- Water("NAF").AddBorder(Water("MAO"));
- Water("NAF").AddBorder(Water("WES"));
- Water("NAF").AddBorder(Water("TUN"));
-
- Land("TUN").AddBorder(Land("NAF"));
- Water("TUN").AddBorder(Water("NAF"));
- Water("TUN").AddBorder(Water("WES"));
- Water("TUN").AddBorder(Water("TYS"));
- Water("TUN").AddBorder(Water("ION"));
-
- Land("BOH").AddBorder(Land("MUN"));
- Land("BOH").AddBorder(Land("SIL"));
- Land("BOH").AddBorder(Land("GAL"));
- Land("BOH").AddBorder(Land("VIE"));
- Land("BOH").AddBorder(Land("TYR"));
-
- Land("BUD").AddBorder(Land("VIE"));
- Land("BUD").AddBorder(Land("GAL"));
- Land("BUD").AddBorder(Land("RUM"));
- Land("BUD").AddBorder(Land("SER"));
- Land("BUD").AddBorder(Land("TRI"));
-
- Land("GAL").AddBorder(Land("BOH"));
- Land("GAL").AddBorder(Land("SIL"));
- Land("GAL").AddBorder(Land("WAR"));
- Land("GAL").AddBorder(Land("UKR"));
- Land("GAL").AddBorder(Land("RUM"));
- Land("GAL").AddBorder(Land("BUD"));
- Land("GAL").AddBorder(Land("VIE"));
-
- Land("TRI").AddBorder(Land("VEN"));
- Land("TRI").AddBorder(Land("TYR"));
- Land("TRI").AddBorder(Land("VIE"));
- Land("TRI").AddBorder(Land("BUD"));
- Land("TRI").AddBorder(Land("SER"));
- Land("TRI").AddBorder(Land("ALB"));
- Water("TRI").AddBorder(Water("ALB"));
- Water("TRI").AddBorder(Water("ADR"));
- Water("TRI").AddBorder(Water("VEN"));
-
- Land("TYR").AddBorder(Land("MUN"));
- Land("TYR").AddBorder(Land("BOH"));
- Land("TYR").AddBorder(Land("VIE"));
- Land("TYR").AddBorder(Land("TRI"));
- Land("TYR").AddBorder(Land("VEN"));
- Land("TYR").AddBorder(Land("PIE"));
-
- Land("VIE").AddBorder(Land("TYR"));
- Land("VIE").AddBorder(Land("BOH"));
- Land("VIE").AddBorder(Land("GAL"));
- Land("VIE").AddBorder(Land("BUD"));
- Land("VIE").AddBorder(Land("TRI"));
-
- Land("ALB").AddBorder(Land("TRI"));
- Land("ALB").AddBorder(Land("SER"));
- Land("ALB").AddBorder(Land("GRE"));
- Water("ALB").AddBorder(Water("TRI"));
- Water("ALB").AddBorder(Water("ADR"));
- Water("ALB").AddBorder(Water("ION"));
- Water("ALB").AddBorder(Water("GRE"));
-
- Land("BUL").AddBorder(Land("GRE"));
- Land("BUL").AddBorder(Land("SER"));
- Land("BUL").AddBorder(Land("RUM"));
- Land("BUL").AddBorder(Land("CON"));
- Coast("BUL", "ec").AddBorder(Water("BLA"));
- Coast("BUL", "ec").AddBorder(Water("CON"));
- Coast("BUL", "sc").AddBorder(Water("CON"));
- Coast("BUL", "sc").AddBorder(Water("AEG"));
- Coast("BUL", "sc").AddBorder(Water("GRE"));
-
- Land("GRE").AddBorder(Land("ALB"));
- Land("GRE").AddBorder(Land("SER"));
- Land("GRE").AddBorder(Land("BUL"));
- Water("GRE").AddBorder(Water("ALB"));
- Water("GRE").AddBorder(Water("ION"));
- Water("GRE").AddBorder(Water("AEG"));
- Water("GRE").AddBorder(Coast("BUL", "sc"));
-
- // TODO
-
- Water("IOS").AddBorder(Water("TUN"));
- Water("IOS").AddBorder(Water("TYS"));
- Water("IOS").AddBorder(Water("NAP"));
- Water("IOS").AddBorder(Water("APU"));
- Water("IOS").AddBorder(Water("ADR"));
- Water("IOS").AddBorder(Water("ALB"));
- Water("IOS").AddBorder(Water("GRE"));
- Water("IOS").AddBorder(Water("AEG"));
-
- // TODO
- }
-}
diff --git a/MultiversalDiplomacy/Model/Options.cs b/MultiversalDiplomacy/Model/Options.cs
new file mode 100644
index 0000000..db6e5c3
--- /dev/null
+++ b/MultiversalDiplomacy/Model/Options.cs
@@ -0,0 +1,8 @@
+namespace MultiversalDiplomacy.Model;
+
+///
+/// Object representing immutable configuration options about the game.
+///
+public class Options
+{
+}
\ No newline at end of file
diff --git a/MultiversalDiplomacy/Model/World.cs b/MultiversalDiplomacy/Model/World.cs
index 54cb12a..939a3a5 100644
--- a/MultiversalDiplomacy/Model/World.cs
+++ b/MultiversalDiplomacy/Model/World.cs
@@ -25,15 +25,512 @@ public class World
///
public IEnumerable Units { get; }
+ ///
+ /// Immutable game options.
+ ///
+ public Options Options { get; }
+
public World(
IEnumerable provinces,
IEnumerable powers,
IEnumerable seasons,
- IEnumerable units)
+ IEnumerable units,
+ Options options)
{
this.Provinces = provinces;
this.Powers = powers;
this.Seasons = seasons;
this.Units = units;
+ this.Options = options;
+ }
+
+ ///
+ /// Create a new world with no map, powers, or units, and a root season.
+ ///
+ public static World Empty => new World(
+ new List(),
+ new List(),
+ new List { Season.MakeRoot() },
+ new List(),
+ new Options());
+
+ ///
+ /// Create a world with a standard map, powers, and initial unit placements.
+ ///
+ public static World Standard => Empty
+ .WithStandardMap()
+ .WithStandardPowers()
+ .WithStandardUnits();
+
+ ///
+ /// Get a province by name. Throws if the province is not found.
+ ///
+ private Province GetProvince(string provinceName)
+ {
+ string provinceNameUpper = provinceName.ToUpperInvariant();
+ Province? foundProvince = this.Provinces.FirstOrDefault(
+ p => p != null &&
+ (p.Name.ToUpperInvariant() == provinceNameUpper
+ || p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper)),
+ null);
+ if (foundProvince == null) throw new ArgumentOutOfRangeException(
+ $"Province {provinceName} not found");
+ return foundProvince;
+ }
+
+ ///
+ /// Get the location in a province matching a predicate. Throws if there is not exactly one
+ /// such location.
+ ///
+ private Location GetLocation(string provinceName, Func predicate)
+ {
+ Location? foundLocation = GetProvince(provinceName).Locations.SingleOrDefault(
+ l => l != null && predicate(l), null);
+ if (foundLocation == null) throw new ArgumentException(
+ $"No such location in {provinceName}");
+ return foundLocation;
+ }
+
+ ///
+ /// Get the sole land location of a province.
+ ///
+ public Location GetLand(string provinceName)
+ => GetLocation(provinceName, l => l.Type == LocationType.Land);
+
+ ///
+ /// Get the sole water location of a province, optionally specifying a named coast.
+ ///
+ public Location GetWater(string provinceName, string? coastName = null)
+ => coastName == null
+ ? GetLocation(provinceName, l => l.Type == LocationType.Water)
+ : GetLocation(provinceName, l => l.Name == coastName || l.Abbreviation == coastName);
+
+ ///
+ /// Get a power by name. Throws if the power is not found.
+ ///
+ public Power GetPower(string powerName)
+ {
+ Power? foundPower = this.Powers.FirstOrDefault(
+ p =>
+ p != null
+ && (p.Name == powerName || p.Name.StartsWith(powerName)),
+ null);
+ if (foundPower == null) throw new ArgumentOutOfRangeException(
+ $"Power {powerName} not found");
+ return foundPower;
+ }
+
+ ///
+ /// Create a new world from this one with new provinces.
+ ///
+ public World WithMap(IEnumerable provinces)
+ {
+ if (this.Units.Any()) throw new InvalidOperationException(
+ "Provinces cannot be changed once units have been placed on the map");
+ return new World(provinces, this.Powers, this.Seasons, this.Units, this.Options);
+ }
+
+ ///
+ /// Create a new world from this one with the standard Diplomacy provinces.
+ ///
+ public World WithStandardMap()
+ {
+ // Define the provinces of the standard world map.
+ List standardProvinces = new List
+ {
+ Province.Empty("North Africa", "NAF")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Tunis", "TUN")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Bohemia", "BOH")
+ .AddLandLocation(),
+ Province.Supply("Budapest", "BUD")
+ .AddLandLocation(),
+ Province.Empty("Galacia", "GAL")
+ .AddLandLocation(),
+ Province.Supply("Trieste", "TRI")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Tyrolia", "TYR")
+ .AddLandLocation(),
+ Province.Time("Vienna", "VIE")
+ .AddLandLocation(),
+ Province.Empty("Albania", "ALB")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Bulgaria", "BUL")
+ .AddLandLocation()
+ .AddCoastLocation("east coast", "ec")
+ .AddCoastLocation("south coast", "sc"),
+ Province.Supply("Greece", "GRE")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Rumania", "RUM", "RMA")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Serbia", "SER")
+ .AddLandLocation(),
+ Province.Empty("Clyde", "CLY")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Edinburgh", "EDI")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Liverpool", "LVP", "LPL")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Time("London", "LON")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Wales", "WAL")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Yorkshire", "YOR")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Brest", "BRE")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Burgundy", "BUR")
+ .AddLandLocation(),
+ Province.Empty("Gascony", "GAS")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Marseilles", "MAR")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Time("Paris", "PAR")
+ .AddLandLocation(),
+ Province.Empty("Picardy", "PIC")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Time("Berlin", "BER")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Kiel", "KIE")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Munich", "MUN")
+ .AddLandLocation(),
+ Province.Empty("Prussia", "PRU")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Ruhr", "RUH", "RHR")
+ .AddLandLocation(),
+ Province.Empty("Silesia", "SIL")
+ .AddLandLocation(),
+ Province.Supply("Spain", "SPA")
+ .AddLandLocation()
+ .AddCoastLocation("north coast", "nc")
+ .AddCoastLocation("south coast", "sc"),
+ Province.Supply("Portugal", "POR")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Apulia", "APU")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Naples", "NAP")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Piedmont", "PIE")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Time("Rome", "ROM", "RME")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Tuscany", "TUS")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Venice", "VEN")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Belgium", "BEL")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Holland", "HOL")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Finland", "FIN")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Livonia", "LVN", "LVA")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Time("Moscow", "MOS")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Sevastopol", "SEV")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Saint Petersburg", "STP")
+ .AddLandLocation()
+ .AddCoastLocation("north coast", "nc")
+ .AddCoastLocation("west coast", "wc"),
+ Province.Empty("Ukraine", "UKR")
+ .AddLandLocation(),
+ Province.Supply("Warsaw", "WAR")
+ .AddLandLocation(),
+ Province.Supply("Denmark", "DEN")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Norway", "NWY")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Sweden", "SWE")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Ankara", "ANK")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Armenia", "ARM")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Time("Constantinople", "CON")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Supply("Smyrna", "SMY")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Syria", "SYR")
+ .AddLandLocation()
+ .AddCoastLocation(),
+ Province.Empty("Barents Sea", "BAR")
+ .AddOceanLocation(),
+ Province.Empty("English Channel", "ENC", "ECH")
+ .AddOceanLocation(),
+ Province.Empty("Heligoland Bight", "HEL", "HGB")
+ .AddOceanLocation(),
+ Province.Empty("Irish Sea", "IRS", "IRI")
+ .AddOceanLocation(),
+ Province.Empty("Mid-Atlantic Ocean", "MAO", "MID")
+ .AddOceanLocation(),
+ Province.Empty("North Atlantic Ocean", "NAO", "NAT")
+ .AddOceanLocation(),
+ Province.Empty("North Sea", "NTH", "NTS")
+ .AddOceanLocation(),
+ Province.Empty("Norwegian Sea", "NWS", "NWG")
+ .AddOceanLocation(),
+ Province.Empty("Skagerrak", "SKA", "SKG")
+ .AddOceanLocation(),
+ Province.Empty("Baltic Sea", "BAL")
+ .AddOceanLocation(),
+ Province.Empty("Guld of Bothnia", "GOB", "BOT")
+ .AddOceanLocation(),
+ Province.Empty("Adriatic Sea", "ADS", "ADR")
+ .AddOceanLocation(),
+ Province.Empty("Aegean Sea", "AEG")
+ .AddOceanLocation(),
+ Province.Empty("Black Sea", "BLA")
+ .AddOceanLocation(),
+ Province.Empty("Eastern Mediterranean Sea", "EMS", "EAS")
+ .AddOceanLocation(),
+ Province.Empty("Gulf of Lyons", "GOL", "LYO")
+ .AddOceanLocation(),
+ Province.Empty("Ionian Sea", "IOS", "ION", "INS")
+ .AddOceanLocation(),
+ Province.Empty("Tyrrhenian Sea", "TYS", "TYN")
+ .AddOceanLocation(),
+ Province.Empty("Western Mediterranean Sea", "WMS", "WES")
+ .AddOceanLocation(),
+ };
+
+ // Declare some helpers for border definitions
+ Location Land(string provinceName) => standardProvinces
+ .Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
+ .Locations.Single(l => l.Type == LocationType.Land);
+ Location Water(string provinceName) => standardProvinces
+ .Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
+ .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);
+
+ Land("NAF").AddBorder(Land("TUN"));
+ Water("NAF").AddBorder(Water("MAO"));
+ Water("NAF").AddBorder(Water("WES"));
+ Water("NAF").AddBorder(Water("TUN"));
+
+ Land("TUN").AddBorder(Land("NAF"));
+ Water("TUN").AddBorder(Water("NAF"));
+ Water("TUN").AddBorder(Water("WES"));
+ Water("TUN").AddBorder(Water("TYS"));
+ Water("TUN").AddBorder(Water("ION"));
+
+ Land("BOH").AddBorder(Land("MUN"));
+ Land("BOH").AddBorder(Land("SIL"));
+ Land("BOH").AddBorder(Land("GAL"));
+ Land("BOH").AddBorder(Land("VIE"));
+ Land("BOH").AddBorder(Land("TYR"));
+
+ Land("BUD").AddBorder(Land("VIE"));
+ Land("BUD").AddBorder(Land("GAL"));
+ Land("BUD").AddBorder(Land("RUM"));
+ Land("BUD").AddBorder(Land("SER"));
+ Land("BUD").AddBorder(Land("TRI"));
+
+ Land("GAL").AddBorder(Land("BOH"));
+ Land("GAL").AddBorder(Land("SIL"));
+ Land("GAL").AddBorder(Land("WAR"));
+ Land("GAL").AddBorder(Land("UKR"));
+ Land("GAL").AddBorder(Land("RUM"));
+ Land("GAL").AddBorder(Land("BUD"));
+ Land("GAL").AddBorder(Land("VIE"));
+
+ Land("TRI").AddBorder(Land("VEN"));
+ Land("TRI").AddBorder(Land("TYR"));
+ Land("TRI").AddBorder(Land("VIE"));
+ Land("TRI").AddBorder(Land("BUD"));
+ Land("TRI").AddBorder(Land("SER"));
+ Land("TRI").AddBorder(Land("ALB"));
+ Water("TRI").AddBorder(Water("ALB"));
+ Water("TRI").AddBorder(Water("ADR"));
+ Water("TRI").AddBorder(Water("VEN"));
+
+ Land("TYR").AddBorder(Land("MUN"));
+ Land("TYR").AddBorder(Land("BOH"));
+ Land("TYR").AddBorder(Land("VIE"));
+ Land("TYR").AddBorder(Land("TRI"));
+ Land("TYR").AddBorder(Land("VEN"));
+ Land("TYR").AddBorder(Land("PIE"));
+
+ Land("VIE").AddBorder(Land("TYR"));
+ Land("VIE").AddBorder(Land("BOH"));
+ Land("VIE").AddBorder(Land("GAL"));
+ Land("VIE").AddBorder(Land("BUD"));
+ Land("VIE").AddBorder(Land("TRI"));
+
+ Land("ALB").AddBorder(Land("TRI"));
+ Land("ALB").AddBorder(Land("SER"));
+ Land("ALB").AddBorder(Land("GRE"));
+ Water("ALB").AddBorder(Water("TRI"));
+ Water("ALB").AddBorder(Water("ADR"));
+ Water("ALB").AddBorder(Water("ION"));
+ Water("ALB").AddBorder(Water("GRE"));
+
+ Land("BUL").AddBorder(Land("GRE"));
+ Land("BUL").AddBorder(Land("SER"));
+ Land("BUL").AddBorder(Land("RUM"));
+ Land("BUL").AddBorder(Land("CON"));
+ Coast("BUL", "ec").AddBorder(Water("BLA"));
+ Coast("BUL", "ec").AddBorder(Water("CON"));
+ Coast("BUL", "sc").AddBorder(Water("CON"));
+ Coast("BUL", "sc").AddBorder(Water("AEG"));
+ Coast("BUL", "sc").AddBorder(Water("GRE"));
+
+ Land("GRE").AddBorder(Land("ALB"));
+ Land("GRE").AddBorder(Land("SER"));
+ Land("GRE").AddBorder(Land("BUL"));
+ Water("GRE").AddBorder(Water("ALB"));
+ Water("GRE").AddBorder(Water("ION"));
+ Water("GRE").AddBorder(Water("AEG"));
+ Water("GRE").AddBorder(Coast("BUL", "sc"));
+
+ // TODO
+
+ Water("IOS").AddBorder(Water("TUN"));
+ Water("IOS").AddBorder(Water("TYS"));
+ Water("IOS").AddBorder(Water("NAP"));
+ Water("IOS").AddBorder(Water("APU"));
+ Water("IOS").AddBorder(Water("ADR"));
+ Water("IOS").AddBorder(Water("ALB"));
+ Water("IOS").AddBorder(Water("GRE"));
+ Water("IOS").AddBorder(Water("AEG"));
+
+ // TODO
+
+ return this.WithMap(standardProvinces);
+ }
+
+ ///
+ /// Create a new world from this one with new powers.
+ ///
+ public World WithPowers(IEnumerable powers)
+ => new World(this.Provinces, powers, this.Seasons, this.Units, this.Options);
+
+ ///
+ /// Create a new world from this one with new powers created with the given names.
+ ///
+ public World WithPowers(IEnumerable powerNames)
+ => WithPowers(powerNames.Select(name => new Model.Power(name)));
+
+ ///
+ /// Create a new world from this one with new powers created with the given names.
+ ///
+ public World WithPowers(params string[] powerNames)
+ => WithPowers(powerNames.AsEnumerable());
+
+ ///
+ /// Create a new world from this one with the standard Diplomacy powers.
+ ///
+ public World WithStandardPowers()
+ => WithPowers("Austria", "England", "France", "Germany", "Italy", "Russia", "Turkey");
+
+ ///
+ /// Create a new world from this one with new units created from unit specs. Units specs are
+ /// in the format " []". If the province or coast name has a
+ /// space in it, the abbreviation should be used.
+ ///
+ public World WithUnits(IEnumerable unitSpec)
+ {
+ IEnumerable units = unitSpec.Select(spec =>
+ {
+ string[] splits = spec.Split(' ', 4);
+ Power power = this.GetPower(splits[0]);
+ UnitType type = splits[1] switch
+ {
+ "A" => UnitType.Army,
+ "F" => UnitType.Fleet,
+ _ => throw new ArgumentOutOfRangeException($"Unknown unit type {splits[1]}")
+ };
+ Location location = type == UnitType.Army
+ ? this.GetLand(splits[2])
+ : splits.Length == 3
+ ? this.GetWater(splits[2])
+ : this.GetWater(splits[2], splits[3]);
+ Unit unit = Unit.Build(location, this.Seasons.First(), power, type);
+ return unit;
+ });
+ return new World(this.Provinces, this.Powers, this.Seasons, units, this.Options);
+ }
+
+ ///
+ /// Create a new world from this one with new units created from unit specs. Units specs are
+ /// in the format " []".
+ ///
+ public World WithUnits(params string[] unitSpec)
+ => this.WithUnits(unitSpec.AsEnumerable());
+
+ ///
+ /// Create a new world from this one with new units created according to the standard Diplomacy
+ /// initial unit deployments.
+ ///
+ public World WithStandardUnits()
+ {
+ return this.WithUnits(
+ "Austria A Bud",
+ "Austria A Vir",
+ "Austria F Tri",
+ "England A Lvp",
+ "England F Edi",
+ "England F Lon",
+ "France A Mar",
+ "France A Par",
+ "France F Bre",
+ "Germany A Ber",
+ "Germany A Mun",
+ "Germany F Kie",
+ "Italy A Rom",
+ "Italy A Ven",
+ "Italy F Nap",
+ "Russia A Mos",
+ "Russia A War",
+ "Russia F Sev",
+ "Russia F Stp wc",
+ "Turkey A Con",
+ "Turkey A Smy",
+ "Turkey F Ank"
+ );
}
}
\ No newline at end of file
diff --git a/MultiversalDiplomacyTests/AdjudicatorTests.cs b/MultiversalDiplomacyTests/AdjudicatorTests.cs
index 576c2d8..9aa5aea 100644
--- a/MultiversalDiplomacyTests/AdjudicatorTests.cs
+++ b/MultiversalDiplomacyTests/AdjudicatorTests.cs
@@ -11,14 +11,16 @@ public class AdjudicatorTests
[Test]
public void OrderValidationTest()
{
- IPhaseAdjudicator rubberStamp = new TestAdjudicator(orders => {
- return orders.Select(o => o.Validate(ValidationReason.Valid));
+ IPhaseAdjudicator rubberStamp = new TestAdjudicator((world, orders) =>
+ {
+ return orders.Select(o => o.Validate(ValidationReason.Valid)).ToList();
});
- Power power = new Power(nameof(Power));
+ World world = World.Empty.WithPowers("Power");
+ Power power = world.GetPower("Power");
Order order = new NullOrder(power);
List orders = new List { order };
- IEnumerable results = rubberStamp.ValidateOrders(orders);
+ IEnumerable results = rubberStamp.ValidateOrders(world, orders);
Assert.That(results.Count(), Is.EqualTo(1));
Assert.That(results.First().Order, Is.EqualTo(order));
diff --git a/MultiversalDiplomacyTests/MapTests.cs b/MultiversalDiplomacyTests/MapTests.cs
index 21e1e5b..b349f61 100644
--- a/MultiversalDiplomacyTests/MapTests.cs
+++ b/MultiversalDiplomacyTests/MapTests.cs
@@ -1,4 +1,3 @@
-using MultiversalDiplomacy.Map;
using MultiversalDiplomacy.Model;
using NUnit.Framework;
@@ -51,8 +50,14 @@ public class MapTests
[Test]
public void LandAndSeaBorders()
{
- Map map = StandardMap.Instance;
- Assert.That(map.Land("NAF").Adjacents.Count(), Is.EqualTo(1), "Expected 1 bordering land province");
- Assert.That(map.Water("NAF").Adjacents.Count(), Is.EqualTo(3), "Expected 3 bordering sea provinces");
+ World map = World.Empty.WithStandardMap();
+ Assert.That(
+ map.GetLand("NAF").Adjacents.Count(),
+ Is.EqualTo(1),
+ "Expected 1 bordering land province");
+ Assert.That(
+ map.GetWater("NAF").Adjacents.Count(),
+ Is.EqualTo(3),
+ "Expected 3 bordering sea provinces");
}
}
\ No newline at end of file
diff --git a/MultiversalDiplomacyTests/TestAdjudicator.cs b/MultiversalDiplomacyTests/TestAdjudicator.cs
index 0404b94..c03a053 100644
--- a/MultiversalDiplomacyTests/TestAdjudicator.cs
+++ b/MultiversalDiplomacyTests/TestAdjudicator.cs
@@ -1,18 +1,19 @@
using MultiversalDiplomacy.Adjudicate;
+using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders;
namespace MultiversalDiplomacyTests;
public class TestAdjudicator : IPhaseAdjudicator
{
- private Func, IEnumerable> ValidateOrdersCallback;
+ private Func, List> ValidateOrdersCallback;
public TestAdjudicator(
- Func, IEnumerable> validateOrdersCallback)
+ Func, List> validateOrdersCallback)
{
this.ValidateOrdersCallback = validateOrdersCallback;
}
- public IEnumerable ValidateOrders(IEnumerable orders)
- => this.ValidateOrdersCallback.Invoke(orders);
+ public List ValidateOrders(World world, List orders)
+ => this.ValidateOrdersCallback.Invoke(world, orders);
}
\ No newline at end of file
diff --git a/MultiversalDiplomacyTests/TestMap.cs b/MultiversalDiplomacyTests/TestMap.cs
deleted file mode 100644
index 7bdf68e..0000000
--- a/MultiversalDiplomacyTests/TestMap.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using MultiversalDiplomacy.Map;
-using MultiversalDiplomacy.Model;
-
-namespace MultiversalDiplomacyTests;
-
-public class TestMap : Map
-{
- public override IEnumerable Provinces { get; }
-
- public static TestMap Instance { get; } = new TestMap();
-
- private TestMap()
- {
- Provinces = new List()
- {
- Province.Supply("Left", "LEF")
- .AddLandLocation(),
- Province.Supply("Right", "RIG")
- .AddLandLocation(),
- Province.Empty("Center", "CEN")
- .AddLandLocation(),
- };
- Land("LEF").AddBorder(Land("RIG"));
- Land("LEF").AddBorder(Land("CEN"));
-
- Land("RIG").AddBorder(Land("LEF"));
- Land("RIG").AddBorder(Land("CEN"));
-
- Land("CEN").AddBorder(Land("LEF"));
- Land("CEN").AddBorder(Land("RIG"));
- }
-}
diff --git a/MultiversalDiplomacyTests/UnitTests.cs b/MultiversalDiplomacyTests/UnitTests.cs
index 91ee6ab..aaac446 100644
--- a/MultiversalDiplomacyTests/UnitTests.cs
+++ b/MultiversalDiplomacyTests/UnitTests.cs
@@ -1,4 +1,3 @@
-using MultiversalDiplomacy.Map;
using MultiversalDiplomacy.Model;
using NUnit.Framework;
@@ -10,17 +9,17 @@ public class UnitTests
[Test]
public void MovementTest()
{
- Map map = TestMap.Instance;
- Location left = map.Land("LEF"), right = map.Land("RIG"), center = map.Land("CEN");
- Power pw1 = new Power("First");
- Season s1 = Season.MakeRoot();
- Unit u1 = Unit.Build(left, s1, pw1, UnitType.Army);
+ World world = World.Empty.WithStandardMap().WithPowers("First");
+ Location Mun = world.GetLand("Mun"), Boh = world.GetLand("Boh"), Tyr = world.GetLand("Tyr");
+ Power pw1 = world.GetPower("First");
+ Season s1 = world.Seasons.First();
+ Unit u1 = Unit.Build(Mun, s1, pw1, UnitType.Army);
Season s2 = s1.MakeNext();
- Unit u2 = u1.Next(right, s2);
+ Unit u2 = u1.Next(Boh, s2);
Season s3 = s2.MakeNext();
- Unit u3 = u2.Next(center, s3);
+ Unit u3 = u2.Next(Tyr, s3);
Assert.That(u3.Past, Is.EqualTo(u2), "Missing unit past");
Assert.That(u2.Past, Is.EqualTo(u1), "Missing unit past");
@@ -30,8 +29,8 @@ public class UnitTests
Assert.That(u2.Season, Is.EqualTo(s2), "Unexpected unit season");
Assert.That(u3.Season, Is.EqualTo(s3), "Unexpected unit season");
- Assert.That(u1.Location, Is.EqualTo(left), "Unexpected unit location");
- Assert.That(u2.Location, Is.EqualTo(right), "Unexpected unit location");
- Assert.That(u3.Location, Is.EqualTo(center), "Unexpected unit location");
+ Assert.That(u1.Location, Is.EqualTo(Mun), "Unexpected unit location");
+ Assert.That(u2.Location, Is.EqualTo(Boh), "Unexpected unit location");
+ Assert.That(u3.Location, Is.EqualTo(Tyr), "Unexpected unit location");
}
}
\ No newline at end of file