Refactor province and power information into Map

This commit is contained in:
Tim Van Baak 2024-08-11 21:01:05 -07:00
parent 9fd63f4317
commit bd8e0da6b6
7 changed files with 626 additions and 574 deletions

View File

@ -0,0 +1,560 @@
using System.Collections.ObjectModel;
namespace MultiversalDiplomacy.Model;
/// <summary>
/// Encapsulation of the world map and playable powers constituting a Diplomacy variant.
/// </summary>
public class Map {
/// <summary>
/// The game map.
/// </summary>
public ReadOnlyCollection<Province> Provinces { get; }
/// <summary>
/// The game powers.
/// </summary>
public ReadOnlyCollection<Power> Powers { get; }
private Map(IEnumerable<Province> provinces, IEnumerable<Power> powers)
{
Provinces = new(provinces.ToList());
Powers = new(powers.ToList());
}
/// <summary>
/// Get a province by name. Throws if the province is not found.
/// </summary>
public 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();
Province? foundProvince = provinces.SingleOrDefault(
p => p!.Name.ToUpperInvariant() == provinceNameUpper
|| p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper),
null);
if (foundProvince == null) throw new KeyNotFoundException(
$"Province {provinceName} not found");
return foundProvince;
}
/// <summary>
/// Get the location in a province matching a predicate. Throws if there is not exactly one
/// such location.
/// </summary>
private Location GetLocation(string provinceName, Func<Location, bool> predicate)
{
Location? foundLocation = GetProvince(provinceName).Locations.SingleOrDefault(
l => l != null && predicate(l), null);
if (foundLocation == null) throw new KeyNotFoundException(
$"No such location in {provinceName}");
return foundLocation;
}
/// <summary>
/// Get the sole land location of a province.
/// </summary>
public Location GetLand(string provinceName)
=> GetLocation(provinceName, l => l.Type == LocationType.Land);
/// <summary>
/// Get the sole water location of a province, optionally specifying a named coast.
/// </summary>
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);
/// <summary>
/// Get a power by name. Throws if there is not exactly one such power.
/// </summary>
public Power GetPower(string powerName)
=> Powers.SingleOrDefault(p => p!.Name == powerName || p.Name.StartsWith(powerName), null)
?? throw new KeyNotFoundException($"Power {powerName} not found");
public static Map FromType(MapType type)
=> type switch {
MapType.Test => Test,
MapType.Classical => Test,
_ => throw new NotImplementedException($"Unknown variant {type}"),
};
#region Variants
public static Map Test => _Test.Value;
private static readonly Lazy<Map> _Test = new(() => {
Province lef = Province.Time("Left", "Lef")
.AddLandLocation();
Province cen = Province.Empty("Center", "Cen")
.AddLandLocation();
Province rig = Province.Time("Right", "Rig")
.AddLandLocation();
Location center = cen.Locations.First();
center.AddBorder(lef.Locations.First());
center.AddBorder(rig.Locations.First());
Power a = new("Alpha");
Power b = new("Beta");
return new([lef, cen, rig], [a, b]);
});
public static Map Classical => _Classical.Value;
private static readonly Lazy<Map> _Classical = new(() => {
// Define the provinces of the standard world map.
List<Province> provinces =
[
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) => GetProvince(provinceName, provinces)
.Locations.Single(l => l.Type == LocationType.Land);
Location Water(string provinceName) => GetProvince(provinceName, provinces)
.Locations.Single(l => l.Type == LocationType.Water);
Location Coast(string provinceName, string coastName)
=> GetProvince(provinceName, provinces)
.Locations.Single(l => l.Name == coastName || l.Abbreviation == coastName);
static void AddBordersTo(Location location, Func<string, Location> LocationType, params string[] borders)
{
foreach (string bordering in borders)
{
location.AddBorder(LocationType(bordering));
}
}
void AddBorders(string provinceName, Func<string, Location> LocationType, params string[] borders)
=> AddBordersTo(LocationType(provinceName), LocationType, borders);
AddBorders("NAF", Land, "TUN");
AddBorders("NAF", Water, "MAO", "WES", "TUN");
AddBorders("TUN", Land, "NAF");
AddBorders("TUN", Water, "NAF", "WES", "TYS", "ION");
AddBorders("BOH", Land, "MUN", "SIL", "GAL", "VIE", "TYR");
AddBorders("BUD", Land, "VIE", "GAL", "RUM", "SER", "TRI");
AddBorders("GAL", Land, "BOH", "SIL", "WAR", "UKR", "RUM", "BUD", "VIE");
AddBorders("TRI", Land, "TYR", "VIE", "BUD", "SER", "ALB");
AddBorders("TRI", Water, "ALB", "ADR", "VEN");
AddBorders("TYR", Land, "MUN", "BOH", "VIE", "TRI", "VEN", "PIE");
AddBorders("VIE", Land, "TYR", "BOH", "GAL", "BUD", "TRI");
AddBorders("ALB", Land, "TRI", "SER", "GRE");
AddBorders("ALB", Water, "TRI", "ADR", "ION", "GRE");
AddBorders("BUL", Land, "GRE", "SER", "RUM", "CON");
AddBordersTo(Coast("BUL", "ec"), Water, "BLA", "CON");
AddBordersTo(Coast("BUL", "sc"), Water, "CON", "AEG", "GRE");
AddBorders("GRE", Land, "ALB", "SER", "BUL");
AddBorders("GRE", Water, "ALB", "ION", "AEG");
Water("GRE").AddBorder(Coast("BUL", "sc"));
AddBorders("RUM", Land, "BUL", "SER", "BUD", "GAL", "UKR", "SEV");
AddBorders("RUM", Water, "SEV", "BLA");
Water("RUM").AddBorder(Coast("BUL", "ec"));
AddBorders("SER", Land, "BUD", "RUM", "BUL", "GRE", "ALB", "TRI");
AddBorders("CLY", Land, "EDI", "LVP");
AddBorders("CLY", Water, "LVP", "NAO", "NWG", "EDI");
AddBorders("EDI", Land, "YOR", "LVP", "CLY");
AddBorders("EDI", Water, "CLY", "NWG", "NTH", "YOR");
AddBorders("LVP", Land, "CLY", "EDI", "YOR", "WAL");
AddBorders("LVP", Water, "WAL", "IRS", "NAO", "CLY");
AddBorders("LON", Land, "WAL", "YOR");
AddBorders("LON", Water, "WAL", "ENC", "NTH", "YOR");
AddBorders("WAL", Land, "LVP", "YOR", "LON");
AddBorders("WAL", Water, "LON", "ENC", "IRS", "LVP");
AddBorders("YOR", Land, "LON", "WAL", "LVP", "EDI");
AddBorders("YOR", Water, "EDI", "NTH", "LON");
AddBorders("BRE", Land, "PIC", "PAR", "GAS");
AddBorders("BRE", Water, "GAS", "MAO", "ENC", "PIC");
AddBorders("BUR", Land, "BEL", "RUH", "MUN", "MAR", "GAS", "PAR", "PIC");
AddBorders("GAS", Land, "BRE", "PAR", "BUR", "MAR", "SPA");
AddBorders("GAS", Water, "MAO", "BRE");
Water("GAS").AddBorder(Coast("SPA", "nc"));
AddBorders("MAR", Land, "SPA", "GAS", "BUR", "PIE");
AddBorders("MAR", Water, "LYO", "PIE");
Water("MAR").AddBorder(Coast("SPA", "sc"));
AddBorders("PAR", Land, "PIC", "BUR", "GAS", "BRE");
AddBorders("PIC", Land, "BEL", "BUR", "PAR", "BRE");
AddBorders("PIC", Water, "BRE", "ENC", "BEL");
AddBorders("BER", Land, "PRU", "SIL", "MUN", "KIE");
AddBorders("BER", Water, "KIE", "BAL", "PRU");
AddBorders("KIE", Land, "BER", "MUN", "RUH", "HOL", "DEN");
AddBorders("KIE", Water, "HOL", "HEL", "DEN", "BAL", "BER");
AddBorders("MUN", Land, "BUR", "RUH", "KIE", "BER", "SIL", "BOH", "TYR");
AddBorders("PRU", Land, "LVN", "WAR", "SIL", "BER");
AddBorders("PRU", Water, "BER", "BAL", "LVN");
AddBorders("RUH", Land, "KIE", "MUN", "BUR", "BEL", "HOL");
AddBorders("SIL", Land, "PRU", "WAR", "GAL", "BOH", "MUN", "BER");
AddBorders("SPA", Land, "POR", "GAS", "MAR");
AddBordersTo(Coast("SPA", "nc"), Water, "POR", "MAO", "GAS");
AddBordersTo(Coast("SPA", "sc"), Water, "POR", "MAO", "WES", "LYO", "MAR");
AddBorders("POR", Land, "SPA");
AddBorders("POR", Water, "MAO");
Water("POR").AddBorder(Coast("SPA", "nc"));
Water("POR").AddBorder(Coast("SPA", "sc"));
AddBorders("APU", Land, "NAP", "ROM", "VEN");
AddBorders("APU", Water, "VEN", "ADR", "IOS", "NAP");
AddBorders("NAP", Land, "ROM", "APU");
AddBorders("NAP", Water, "APU", "IOS", "TYS", "ROM");
AddBorders("PIE", Land, "MAR", "TYR", "VEN", "TUS");
AddBorders("PIE", Water, "TUS", "LYO", "MAR");
AddBorders("ROM", Land, "TUS", "VEN", "APU", "NAP");
AddBorders("ROM", Water, "NAP", "TYS", "TUS");
AddBorders("TUS", Land, "PIE", "VEN", "ROM");
AddBorders("TUS", Water, "ROM", "TYS", "LYO", "PIE");
AddBorders("VEN", Land, "APU", "ROM", "TUS", "PIE", "TYR", "TRI");
AddBorders("VEN", Water, "TRI", "ADR", "APU");
AddBorders("BEL", Land, "HOL", "RUH", "BUR", "PIC");
AddBorders("BEL", Water, "PIC", "ENC", "NTH", "HOL");
AddBorders("HOL", Land, "BEL", "RUH", "KIE");
AddBorders("HOL", Water, "NTH", "HEL");
AddBorders("FIN", Land, "SWE", "NWY", "STP");
AddBorders("FIN", Water, "SWE", "BOT");
Water("FIN").AddBorder(Coast("STP", "wc"));
AddBorders("LVN", Land, "STP", "MOS", "WAR", "PRU");
AddBorders("LVN", Water, "PRU", "BAL", "BOT");
Water("LVN").AddBorder(Coast("STP", "wc"));
AddBorders("MOS", Land, "SEV", "UKR", "WAR", "LVN", "STP");
AddBorders("SEV", Land, "RUM", "UKR", "MOS", "ARM");
AddBorders("SEV", Water, "ARM", "BLA", "RUM");
AddBorders("STP", Land, "MOS", "LVN", "FIN");
AddBordersTo(Coast("STP", "nc"), Water, "BAR", "NWY");
AddBordersTo(Coast("STP", "wc"), Water, "LVN", "BOT", "FIN");
AddBorders("UKR", Land, "MOS", "SEV", "RUM", "GAL", "WAR");
AddBorders("WAR", Land, "PRU", "LVN", "MOS", "UKR", "GAL", "SIL");
AddBorders("DEN", Land, "KIE", "SWE");
AddBorders("DEN", Water, "KIE", "HEL", "NTH", "SKA", "BAL", "SWE");
AddBorders("NWY", Land, "STP", "FIN", "SWE");
AddBorders("NWY", Water, "BAR", "NWG", "NTH", "SKA", "SWE");
Water("NWY").AddBorder(Coast("STP", "nc"));
AddBorders("SWE", Land, "NWY", "FIN", "DEN");
AddBorders("SWE", Water, "FIN", "BOT", "BAL", "DEN", "SKA", "NWY");
AddBorders("ANK", Land, "ARM", "SMY", "CON");
AddBorders("ANK", Water, "CON", "BLA", "ARM");
AddBorders("ARM", Land, "SEV", "SYR", "SMY", "ANK");
AddBorders("ARM", Water, "ANK", "BLA", "SEV");
AddBorders("CON", Land, "BUL", "ANK", "SMY");
AddBorders("CON", Water, "BLA", "ANK", "SMY", "AEG");
Water("CON").AddBorder(Coast("BUL", "ec"));
Water("CON").AddBorder(Coast("BUL", "sc"));
AddBorders("SMY", Land, "CON", "ANK", "ARM", "SYR");
AddBorders("SMY", Water, "SYR", "EAS", "AEG", "CON");
AddBorders("SYR", Land, "SMY", "ARM");
AddBorders("SYR", Water, "EAS", "SMY");
AddBorders("BAR", Water, "NWG", "NWY");
Water("BAR").AddBorder(Coast("STP", "nc"));
AddBorders("ENC", Water, "LON", "NTH", "BEL", "PIC", "BRE", "MAO", "IRS", "WAL");
AddBorders("HEL", Water, "NTH", "DEN", "BAL", "KIE", "HOL");
AddBorders("IRS", Water, "NAO", "LVP", "WAL", "ENC", "MAO");
AddBorders("MAO", Water, "NAO", "IRS", "ENC", "BRE", "GAS", "POR", "NAF");
Water("MAO").AddBorder(Coast("SPA", "nc"));
Water("MAO").AddBorder(Coast("SPA", "sc"));
AddBorders("NAO", Water, "NWG", "CLY", "LVP", "IRS", "MAO");
AddBorders("NTH", Water, "NWG", "NWY", "SKA", "DEN", "HEL", "HOL", "BEL", "ENC", "LON", "YOR", "EDI");
AddBorders("NWG", Water, "BAR", "NWY", "NTH", "EDI", "CLY", "NAO");
AddBorders("SKA", Water, "NWY", "SWE", "BAL", "DEN", "NTH");
AddBorders("BAL", Water, "BOT", "LVN", "PRU", "BER", "KIE", "HEL", "DEN", "SWE");
AddBorders("BOT", Water, "LVN", "BAL", "SWE", "FIN");
Water("BOT").AddBorder(Coast("STP", "wc"));
AddBorders("ADR", Water, "IOS", "APU", "VEN", "TRI", "ALB");
AddBorders("AEG", Water, "CON", "SMY", "EAS", "IOS", "GRE");
Water("AEG").AddBorder(Coast("BUL", "sc"));
AddBorders("BLA", Water, "RUM", "SEV", "ARM", "ANK", "CON");
Water("BLA").AddBorder(Coast("BUL", "ec"));
AddBorders("EAS", Water, "IOS", "AEG", "SMY", "SYR");
AddBorders("LYO", Water, "MAR", "PIE", "TUS", "TYS", "WES");
Water("LYO").AddBorder(Coast("SPA", "sc"));
AddBorders("IOS", Water, "TUN", "TYS", "NAP", "APU", "ADR", "ALB", "GRE", "AEG");
AddBorders("TYS", Water, "LYO", "TUS", "ROM", "NAP", "IOS", "TUN", "WES");
AddBorders("WES", Water, "LYO", "TYS", "TUN", "NAF", "MAO");
Water("WES").AddBorder(Coast("SPA", "sc"));
List<Power> powers =
[
new("Austria"),
new("England"),
new("France"),
new("Germany"),
new("Italy"),
new("Russia"),
new("Turkey"),
];
return new(provinces, powers);
});
#endregion Variants
}

View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
namespace MultiversalDiplomacy.Model;
[JsonConverter(typeof(JsonStringEnumConverter<MapType>))]
public enum MapType {
/// <summary>
/// A minimal test map.
/// </summary>
Test,
/// <summary>
/// The standard Diplomacy map.
/// </summary>
Classical,
}

View File

@ -9,15 +9,20 @@ namespace MultiversalDiplomacy.Model;
/// </summary> /// </summary>
public class World public class World
{ {
/// <summary>
/// The map variant of the game.
/// </summary>
public readonly Map Map;
/// <summary> /// <summary>
/// The game map. /// The game map.
/// </summary> /// </summary>
public ReadOnlyCollection<Province> Provinces { get; } public ReadOnlyCollection<Province> Provinces => this.Map.Provinces;
/// <summary> /// <summary>
/// The game powers. /// The game powers.
/// </summary> /// </summary>
public ReadOnlyCollection<Power> Powers { get; } public ReadOnlyCollection<Power> Powers => this.Map.Powers;
/// <summary> /// <summary>
/// The state of the multiverse. /// The state of the multiverse.
@ -53,8 +58,7 @@ public class World
/// Create a new World, providing all state data. /// Create a new World, providing all state data.
/// </summary> /// </summary>
private World( private World(
ReadOnlyCollection<Province> provinces, Map map,
ReadOnlyCollection<Power> powers,
ReadOnlyCollection<Season> seasons, ReadOnlyCollection<Season> seasons,
Season rootSeason, Season rootSeason,
ReadOnlyCollection<Unit> units, ReadOnlyCollection<Unit> units,
@ -62,8 +66,7 @@ public class World
ReadOnlyDictionary<Season, OrderHistory> orderHistory, ReadOnlyDictionary<Season, OrderHistory> orderHistory,
Options options) Options options)
{ {
this.Provinces = provinces; this.Map = map;
this.Powers = powers;
this.Seasons = seasons; this.Seasons = seasons;
this.RootSeason = rootSeason; this.RootSeason = rootSeason;
this.Units = units; this.Units = units;
@ -77,16 +80,13 @@ public class World
/// </summary> /// </summary>
private World( private World(
World previous, World previous,
ReadOnlyCollection<Province>? provinces = null,
ReadOnlyCollection<Power>? powers = null,
ReadOnlyCollection<Season>? seasons = null, ReadOnlyCollection<Season>? seasons = null,
ReadOnlyCollection<Unit>? units = null, ReadOnlyCollection<Unit>? units = null,
ReadOnlyCollection<RetreatingUnit>? retreatingUnits = null, ReadOnlyCollection<RetreatingUnit>? retreatingUnits = null,
ReadOnlyDictionary<Season, OrderHistory>? orderHistory = null, ReadOnlyDictionary<Season, OrderHistory>? orderHistory = null,
Options? options = null) Options? options = null)
: this( : this(
provinces ?? previous.Provinces, previous.Map,
powers ?? previous.Powers,
seasons ?? previous.Seasons, seasons ?? previous.Seasons,
previous.RootSeason, // Can't change the root season previous.RootSeason, // Can't change the root season
units ?? previous.Units, units ?? previous.Units,
@ -99,12 +99,11 @@ public class World
/// <summary> /// <summary>
/// Create a new world with specified provinces and powers and an initial season. /// Create a new world with specified provinces and powers and an initial season.
/// </summary> /// </summary>
public static World WithMap(IEnumerable<Province> provinces, IEnumerable<Power> powers) public static World WithMap(Map map)
{ {
Season root = Season.MakeRoot(); Season root = Season.MakeRoot();
return new World( return new World(
new(provinces.ToList()), map,
new(powers.ToList()),
new(new List<Season> { root }), new(new List<Season> { root }),
root, root,
new(new List<Unit>()), new(new List<Unit>()),
@ -117,7 +116,7 @@ public class World
/// Create a new world with the standard Diplomacy provinces and powers. /// Create a new world with the standard Diplomacy provinces and powers.
/// </summary> /// </summary>
public static World WithStandardMap() public static World WithStandardMap()
=> WithMap(StandardProvinces, StandardPowers); => WithMap(Map.Classical);
public World Update( public World Update(
IEnumerable<Season>? seasons = null, IEnumerable<Season>? seasons = null,
@ -149,7 +148,7 @@ public class World
IEnumerable<Unit> units = unitSpecs.Select(spec => IEnumerable<Unit> units = unitSpecs.Select(spec =>
{ {
string[] splits = spec.Split(' ', 4); string[] splits = spec.Split(' ', 4);
Power power = this.GetPower(splits[0]); Power power = Map.GetPower(splits[0]);
UnitType type = splits[1] switch UnitType type = splits[1] switch
{ {
"A" => UnitType.Army, "A" => UnitType.Army,
@ -157,10 +156,10 @@ public class World
_ => throw new ApplicationException($"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]) ? Map.GetLand(splits[2])
: splits.Length == 3 : splits.Length == 3
? this.GetWater(splits[2]) ? Map.GetWater(splits[2])
: this.GetWater(splits[2], splits[3]); : Map.GetWater(splits[2], splits[3]);
Unit unit = Unit.Build(location, this.RootSeason, power, type); Unit unit = Unit.Build(location, this.RootSeason, power, type);
return unit; return unit;
}); });
@ -205,54 +204,6 @@ public class World
.WithStandardMap() .WithStandardMap()
.AddStandardUnits(); .AddStandardUnits();
/// <summary>
/// Get a province by name. Throws if the province is not found.
/// </summary>
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();
Province? foundProvince = provinces.SingleOrDefault(
p => p!.Name.ToUpperInvariant() == provinceNameUpper
|| p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper),
null);
if (foundProvince == null) throw new KeyNotFoundException(
$"Province {provinceName} not found");
return foundProvince;
}
/// <summary>
/// Get the location in a province matching a predicate. Throws if there is not exactly one
/// such location.
/// </summary>
private Location GetLocation(string provinceName, Func<Location, bool> predicate)
{
Location? foundLocation = GetProvince(provinceName).Locations.SingleOrDefault(
l => l != null && predicate(l), null);
if (foundLocation == null) throw new KeyNotFoundException(
$"No such location in {provinceName}");
return foundLocation;
}
/// <summary>
/// Get the sole land location of a province.
/// </summary>
public Location GetLand(string provinceName)
=> GetLocation(provinceName, l => l.Type == LocationType.Land);
/// <summary>
/// Get the sole water location of a province, optionally specifying a named coast.
/// </summary>
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);
/// <summary> /// <summary>
/// Get a season by coordinate. Throws if the season is not found. /// Get a season by coordinate. Throws if the season is not found.
/// </summary> /// </summary>
@ -266,25 +217,12 @@ public class World
return foundSeason; return foundSeason;
} }
/// <summary>
/// Get a power by name. Throws if there is not exactly one such power.
/// </summary>
public Power GetPower(string powerName)
{
Power? foundPower = this.Powers.SingleOrDefault(
p => p!.Name == powerName || p.Name.StartsWith(powerName),
null);
if (foundPower == null) throw new KeyNotFoundException(
$"Power {powerName} not found");
return foundPower;
}
/// <summary> /// <summary>
/// Returns a unit in a province. Throws if there are duplicate units. /// Returns a unit in a province. Throws if there are duplicate units.
/// </summary> /// </summary>
public Unit GetUnitAt(string provinceName, (int turn, int timeline)? seasonCoord = null) public Unit GetUnitAt(string provinceName, (int turn, int timeline)? seasonCoord = null)
{ {
Province province = GetProvince(provinceName); Province province = Map.GetProvince(provinceName);
seasonCoord ??= (this.RootSeason.Turn, this.RootSeason.Timeline); seasonCoord ??= (this.RootSeason.Turn, this.RootSeason.Timeline);
Season season = GetSeason(seasonCoord.Value.turn, seasonCoord.Value.timeline); Season season = GetSeason(seasonCoord.Value.turn, seasonCoord.Value.timeline);
Unit? foundUnit = this.Units.SingleOrDefault( Unit? foundUnit = this.Units.SingleOrDefault(
@ -294,466 +232,4 @@ public class World
$"Unit at {province} at {season} not found"); $"Unit at {province} at {season} not found");
return foundUnit; return foundUnit;
} }
/// <summary>
/// The standard Diplomacy provinces.
/// </summary>
public static ReadOnlyCollection<Province> StandardProvinces
{
get
{
// Define the provinces of the standard world map.
List<Province> standardProvinces = new List<Province>
{
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) => GetProvince(provinceName, standardProvinces)
.Locations.Single(l => l.Type == LocationType.Land);
Location Water(string provinceName) => GetProvince(provinceName, standardProvinces)
.Locations.Single(l => l.Type == LocationType.Water);
Location Coast(string provinceName, string coastName)
=> GetProvince(provinceName, standardProvinces)
.Locations.Single(l => l.Name == coastName || l.Abbreviation == coastName);
static void AddBordersTo(Location location, Func<string, Location> LocationType, params string[] borders)
{
foreach (string bordering in borders)
{
location.AddBorder(LocationType(bordering));
}
}
void AddBorders(string provinceName, Func<string, Location> LocationType, params string[] borders)
=> AddBordersTo(LocationType(provinceName), LocationType, borders);
AddBorders("NAF", Land, "TUN");
AddBorders("NAF", Water, "MAO", "WES", "TUN");
AddBorders("TUN", Land, "NAF");
AddBorders("TUN", Water, "NAF", "WES", "TYS", "ION");
AddBorders("BOH", Land, "MUN", "SIL", "GAL", "VIE", "TYR");
AddBorders("BUD", Land, "VIE", "GAL", "RUM", "SER", "TRI");
AddBorders("GAL", Land, "BOH", "SIL", "WAR", "UKR", "RUM", "BUD", "VIE");
AddBorders("TRI", Land, "TYR", "VIE", "BUD", "SER", "ALB");
AddBorders("TRI", Water, "ALB", "ADR", "VEN");
AddBorders("TYR", Land, "MUN", "BOH", "VIE", "TRI", "VEN", "PIE");
AddBorders("VIE", Land, "TYR", "BOH", "GAL", "BUD", "TRI");
AddBorders("ALB", Land, "TRI", "SER", "GRE");
AddBorders("ALB", Water, "TRI", "ADR", "ION", "GRE");
AddBorders("BUL", Land, "GRE", "SER", "RUM", "CON");
AddBordersTo(Coast("BUL", "ec"), Water, "BLA", "CON");
AddBordersTo(Coast("BUL", "sc"), Water, "CON", "AEG", "GRE");
AddBorders("GRE", Land, "ALB", "SER", "BUL");
AddBorders("GRE", Water, "ALB", "ION", "AEG");
Water("GRE").AddBorder(Coast("BUL", "sc"));
AddBorders("RUM", Land, "BUL", "SER", "BUD", "GAL", "UKR", "SEV");
AddBorders("RUM", Water, "SEV", "BLA");
Water("RUM").AddBorder(Coast("BUL", "ec"));
AddBorders("SER", Land, "BUD", "RUM", "BUL", "GRE", "ALB", "TRI");
AddBorders("CLY", Land, "EDI", "LVP");
AddBorders("CLY", Water, "LVP", "NAO", "NWG", "EDI");
AddBorders("EDI", Land, "YOR", "LVP", "CLY");
AddBorders("EDI", Water, "CLY", "NWG", "NTH", "YOR");
AddBorders("LVP", Land, "CLY", "EDI", "YOR", "WAL");
AddBorders("LVP", Water, "WAL", "IRS", "NAO", "CLY");
AddBorders("LON", Land, "WAL", "YOR");
AddBorders("LON", Water, "WAL", "ENC", "NTH", "YOR");
AddBorders("WAL", Land, "LVP", "YOR", "LON");
AddBorders("WAL", Water, "LON", "ENC", "IRS", "LVP");
AddBorders("YOR", Land, "LON", "WAL", "LVP", "EDI");
AddBorders("YOR", Water, "EDI", "NTH", "LON");
AddBorders("BRE", Land, "PIC", "PAR", "GAS");
AddBorders("BRE", Water, "GAS", "MAO", "ENC", "PIC");
AddBorders("BUR", Land, "BEL", "RUH", "MUN", "MAR", "GAS", "PAR", "PIC");
AddBorders("GAS", Land, "BRE", "PAR", "BUR", "MAR", "SPA");
AddBorders("GAS", Water, "MAO", "BRE");
Water("GAS").AddBorder(Coast("SPA", "nc"));
AddBorders("MAR", Land, "SPA", "GAS", "BUR", "PIE");
AddBorders("MAR", Water, "LYO", "PIE");
Water("MAR").AddBorder(Coast("SPA", "sc"));
AddBorders("PAR", Land, "PIC", "BUR", "GAS", "BRE");
AddBorders("PIC", Land, "BEL", "BUR", "PAR", "BRE");
AddBorders("PIC", Water, "BRE", "ENC", "BEL");
AddBorders("BER", Land, "PRU", "SIL", "MUN", "KIE");
AddBorders("BER", Water, "KIE", "BAL", "PRU");
AddBorders("KIE", Land, "BER", "MUN", "RUH", "HOL", "DEN");
AddBorders("KIE", Water, "HOL", "HEL", "DEN", "BAL", "BER");
AddBorders("MUN", Land, "BUR", "RUH", "KIE", "BER", "SIL", "BOH", "TYR");
AddBorders("PRU", Land, "LVN", "WAR", "SIL", "BER");
AddBorders("PRU", Water, "BER", "BAL", "LVN");
AddBorders("RUH", Land, "KIE", "MUN", "BUR", "BEL", "HOL");
AddBorders("SIL", Land, "PRU", "WAR", "GAL", "BOH", "MUN", "BER");
AddBorders("SPA", Land, "POR", "GAS", "MAR");
AddBordersTo(Coast("SPA", "nc"), Water, "POR", "MAO", "GAS");
AddBordersTo(Coast("SPA", "sc"), Water, "POR", "MAO", "WES", "LYO", "MAR");
AddBorders("POR", Land, "SPA");
AddBorders("POR", Water, "MAO");
Water("POR").AddBorder(Coast("SPA", "nc"));
Water("POR").AddBorder(Coast("SPA", "sc"));
AddBorders("APU", Land, "NAP", "ROM", "VEN");
AddBorders("APU", Water, "VEN", "ADR", "IOS", "NAP");
AddBorders("NAP", Land, "ROM", "APU");
AddBorders("NAP", Water, "APU", "IOS", "TYS", "ROM");
AddBorders("PIE", Land, "MAR", "TYR", "VEN", "TUS");
AddBorders("PIE", Water, "TUS", "LYO", "MAR");
AddBorders("ROM", Land, "TUS", "VEN", "APU", "NAP");
AddBorders("ROM", Water, "NAP", "TYS", "TUS");
AddBorders("TUS", Land, "PIE", "VEN", "ROM");
AddBorders("TUS", Water, "ROM", "TYS", "LYO", "PIE");
AddBorders("VEN", Land, "APU", "ROM", "TUS", "PIE", "TYR", "TRI");
AddBorders("VEN", Water, "TRI", "ADR", "APU");
AddBorders("BEL", Land, "HOL", "RUH", "BUR", "PIC");
AddBorders("BEL", Water, "PIC", "ENC", "NTH", "HOL");
AddBorders("HOL", Land, "BEL", "RUH", "KIE");
AddBorders("HOL", Water, "NTH", "HEL");
AddBorders("FIN", Land, "SWE", "NWY", "STP");
AddBorders("FIN", Water, "SWE", "BOT");
Water("FIN").AddBorder(Coast("STP", "wc"));
AddBorders("LVN", Land, "STP", "MOS", "WAR", "PRU");
AddBorders("LVN", Water, "PRU", "BAL", "BOT");
Water("LVN").AddBorder(Coast("STP", "wc"));
AddBorders("MOS", Land, "SEV", "UKR", "WAR", "LVN", "STP");
AddBorders("SEV", Land, "RUM", "UKR", "MOS", "ARM");
AddBorders("SEV", Water, "ARM", "BLA", "RUM");
AddBorders("STP", Land, "MOS", "LVN", "FIN");
AddBordersTo(Coast("STP", "nc"), Water, "BAR", "NWY");
AddBordersTo(Coast("STP", "wc"), Water, "LVN", "BOT", "FIN");
AddBorders("UKR", Land, "MOS", "SEV", "RUM", "GAL", "WAR");
AddBorders("WAR", Land, "PRU", "LVN", "MOS", "UKR", "GAL", "SIL");
AddBorders("DEN", Land, "KIE", "SWE");
AddBorders("DEN", Water, "KIE", "HEL", "NTH", "SKA", "BAL", "SWE");
AddBorders("NWY", Land, "STP", "FIN", "SWE");
AddBorders("NWY", Water, "BAR", "NWG", "NTH", "SKA", "SWE");
Water("NWY").AddBorder(Coast("STP", "nc"));
AddBorders("SWE", Land, "NWY", "FIN", "DEN");
AddBorders("SWE", Water, "FIN", "BOT", "BAL", "DEN", "SKA", "NWY");
AddBorders("ANK", Land, "ARM", "SMY", "CON");
AddBorders("ANK", Water, "CON", "BLA", "ARM");
AddBorders("ARM", Land, "SEV", "SYR", "SMY", "ANK");
AddBorders("ARM", Water, "ANK", "BLA", "SEV");
AddBorders("CON", Land, "BUL", "ANK", "SMY");
AddBorders("CON", Water, "BLA", "ANK", "SMY", "AEG");
Water("CON").AddBorder(Coast("BUL", "ec"));
Water("CON").AddBorder(Coast("BUL", "sc"));
AddBorders("SMY", Land, "CON", "ANK", "ARM", "SYR");
AddBorders("SMY", Water, "SYR", "EAS", "AEG", "CON");
AddBorders("SYR", Land, "SMY", "ARM");
AddBorders("SYR", Water, "EAS", "SMY");
AddBorders("BAR", Water, "NWG", "NWY");
Water("BAR").AddBorder(Coast("STP", "nc"));
AddBorders("ENC", Water, "LON", "NTH", "BEL", "PIC", "BRE", "MAO", "IRS", "WAL");
AddBorders("HEL", Water, "NTH", "DEN", "BAL", "KIE", "HOL");
AddBorders("IRS", Water, "NAO", "LVP", "WAL", "ENC", "MAO");
AddBorders("MAO", Water, "NAO", "IRS", "ENC", "BRE", "GAS", "POR", "NAF");
Water("MAO").AddBorder(Coast("SPA", "nc"));
Water("MAO").AddBorder(Coast("SPA", "sc"));
AddBorders("NAO", Water, "NWG", "CLY", "LVP", "IRS", "MAO");
AddBorders("NTH", Water, "NWG", "NWY", "SKA", "DEN", "HEL", "HOL", "BEL", "ENC", "LON", "YOR", "EDI");
AddBorders("NWG", Water, "BAR", "NWY", "NTH", "EDI", "CLY", "NAO");
AddBorders("SKA", Water, "NWY", "SWE", "BAL", "DEN", "NTH");
AddBorders("BAL", Water, "BOT", "LVN", "PRU", "BER", "KIE", "HEL", "DEN", "SWE");
AddBorders("BOT", Water, "LVN", "BAL", "SWE", "FIN");
Water("BOT").AddBorder(Coast("STP", "wc"));
AddBorders("ADR", Water, "IOS", "APU", "VEN", "TRI", "ALB");
AddBorders("AEG", Water, "CON", "SMY", "EAS", "IOS", "GRE");
Water("AEG").AddBorder(Coast("BUL", "sc"));
AddBorders("BLA", Water, "RUM", "SEV", "ARM", "ANK", "CON");
Water("BLA").AddBorder(Coast("BUL", "ec"));
AddBorders("EAS", Water, "IOS", "AEG", "SMY", "SYR");
AddBorders("LYO", Water, "MAR", "PIE", "TUS", "TYS", "WES");
Water("LYO").AddBorder(Coast("SPA", "sc"));
AddBorders("IOS", Water, "TUN", "TYS", "NAP", "APU", "ADR", "ALB", "GRE", "AEG");
AddBorders("TYS", Water, "LYO", "TUS", "ROM", "NAP", "IOS", "TUN", "WES");
AddBorders("WES", Water, "LYO", "TYS", "TUN", "NAF", "MAO");
Water("WES").AddBorder(Coast("SPA", "sc"));
return new(standardProvinces);
}
}
/// <summary>
/// The standard Diplomacy powers.
/// </summary>
public static ReadOnlyCollection<Power> StandardPowers
{
get => new(new List<Power>
{
new Power("Austria"),
new Power("England"),
new Power("France"),
new Power("Germany"),
new Power("Italy"),
new Power("Russia"),
new Power("Turkey"),
});
}
} }

View File

@ -6,10 +6,10 @@ namespace MultiversalDiplomacyTests;
public class MapTests public class MapTests
{ {
IEnumerable<Location> LocationClosure(Location location) static IEnumerable<Location> LocationClosure(Location location)
{ {
IEnumerable<Location> visited = new List<Location>(); IEnumerable<Location> visited = [];
IEnumerable<Location> toVisit = new List<Location>() { location }; IEnumerable<Location> toVisit = [location];
while (toVisit.Any()) while (toVisit.Any())
{ {
@ -50,7 +50,7 @@ public class MapTests
[Test] [Test]
public void LandAndSeaBorders() public void LandAndSeaBorders()
{ {
World map = World.WithStandardMap(); Map map = Map.Classical;
Assert.That( Assert.That(
map.GetLand("NAF").Adjacents.Count(), map.GetLand("NAF").Adjacents.Count(),
Is.EqualTo(1), Is.EqualTo(1),

View File

@ -331,7 +331,7 @@ public class TestCaseBuilder
=> this.Builder[(seasonCoord.turn, seasonCoord.timeline)]; => this.Builder[(seasonCoord.turn, seasonCoord.timeline)];
public IPowerContext this[string powerName] public IPowerContext this[string powerName]
=> new PowerContext(this, this.Builder.World.GetPower(powerName)); => new PowerContext(this, this.Builder.World.Map.GetPower(powerName));
public ISeasonContext GetReference(out Season season) public ISeasonContext GetReference(out Season season)
{ {
@ -363,8 +363,8 @@ public class TestCaseBuilder
{ {
Power power = powerName == null Power power = powerName == null
? this.Power ? this.Power
: this.Builder.World.GetPower(powerName); : this.Builder.World.Map.GetPower(powerName);
Location location = this.Builder.World.GetLand(provinceName); Location location = this.Builder.World.Map.GetLand(provinceName);
Unit unit = this.Builder.GetOrBuildUnit( Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Army); power, location, this.SeasonContext.Season, UnitType.Army);
return new UnitContext(this, unit); return new UnitContext(this, unit);
@ -374,8 +374,8 @@ public class TestCaseBuilder
{ {
Power power = powerName == null Power power = powerName == null
? this.Power ? this.Power
: this.Builder.World.GetPower(powerName); : this.Builder.World.Map.GetPower(powerName);
Location location = this.Builder.World.GetWater(provinceName, coast); Location location = this.Builder.World.Map.GetWater(provinceName, coast);
Unit unit = this.Builder.GetOrBuildUnit( Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Fleet); power, location, this.SeasonContext.Season, UnitType.Fleet);
return new UnitContext(this, unit); return new UnitContext(this, unit);
@ -413,8 +413,8 @@ public class TestCaseBuilder
string? coast = null) string? coast = null)
{ {
Location destination = this.Unit.Type == UnitType.Army Location destination = this.Unit.Type == UnitType.Army
? this.Builder.World.GetLand(provinceName) ? this.Builder.World.Map.GetLand(provinceName)
: this.Builder.World.GetWater(provinceName, coast); : this.Builder.World.Map.GetWater(provinceName, coast);
Season destSeason = season ?? this.SeasonContext.Season; Season destSeason = season ?? this.SeasonContext.Season;
MoveOrder moveOrder = new MoveOrder( MoveOrder moveOrder = new MoveOrder(
this.PowerContext.Power, this.PowerContext.Power,
@ -451,8 +451,8 @@ public class TestCaseBuilder
{ {
Power power = powerName == null Power power = powerName == null
? this.PowerContext.Power ? this.PowerContext.Power
: this.Builder.World.GetPower(powerName); : this.Builder.World.Map.GetPower(powerName);
Location location = this.Builder.World.GetLand(provinceName); Location location = this.Builder.World.Map.GetLand(provinceName);
Unit unit = this.Builder.GetOrBuildUnit( Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Army); power, location, this.SeasonContext.Season, UnitType.Army);
return new ConvoyDestinationContext(this, unit); return new ConvoyDestinationContext(this, unit);
@ -465,8 +465,8 @@ public class TestCaseBuilder
{ {
Power power = powerName == null Power power = powerName == null
? this.PowerContext.Power ? this.PowerContext.Power
: this.Builder.World.GetPower(powerName); : this.Builder.World.Map.GetPower(powerName);
Location location = this.Builder.World.GetWater(provinceName, coast); Location location = this.Builder.World.Map.GetWater(provinceName, coast);
Unit unit = this.Builder.GetOrBuildUnit( Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Fleet); power, location, this.SeasonContext.Season, UnitType.Fleet);
return new ConvoyDestinationContext(this, unit); return new ConvoyDestinationContext(this, unit);
@ -492,7 +492,7 @@ public class TestCaseBuilder
public IOrderDefinedContext<ConvoyOrder> To(string provinceName) public IOrderDefinedContext<ConvoyOrder> To(string provinceName)
{ {
Location location = this.Builder.World.GetLand(provinceName); Location location = this.Builder.World.Map.GetLand(provinceName);
ConvoyOrder order = new ConvoyOrder( ConvoyOrder order = new ConvoyOrder(
this.PowerContext.Power, this.PowerContext.Power,
this.UnitContext.Unit, this.UnitContext.Unit,
@ -526,8 +526,8 @@ public class TestCaseBuilder
{ {
Power power = powerName == null Power power = powerName == null
? this.PowerContext.Power ? this.PowerContext.Power
: this.Builder.World.GetPower(powerName); : this.Builder.World.Map.GetPower(powerName);
Location location = this.Builder.World.GetLand(provinceName); Location location = this.Builder.World.Map.GetLand(provinceName);
Season destSeason = season ?? this.SeasonContext.Season; Season destSeason = season ?? this.SeasonContext.Season;
Unit unit = this.Builder.GetOrBuildUnit( Unit unit = this.Builder.GetOrBuildUnit(
power, location, destSeason, UnitType.Army); power, location, destSeason, UnitType.Army);
@ -541,8 +541,8 @@ public class TestCaseBuilder
{ {
Power power = powerName == null Power power = powerName == null
? this.PowerContext.Power ? this.PowerContext.Power
: this.Builder.World.GetPower(powerName); : this.Builder.World.Map.GetPower(powerName);
Location location = this.Builder.World.GetWater(provinceName, coast); Location location = this.Builder.World.Map.GetWater(provinceName, coast);
Unit unit = this.Builder.GetOrBuildUnit( Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Fleet); power, location, this.SeasonContext.Season, UnitType.Fleet);
return new SupportTypeContext(this, unit); return new SupportTypeContext(this, unit);
@ -582,8 +582,8 @@ public class TestCaseBuilder
string? coast = null) string? coast = null)
{ {
Location destination = this.Target.Type == UnitType.Army Location destination = this.Target.Type == UnitType.Army
? this.Builder.World.GetLand(provinceName) ? this.Builder.World.Map.GetLand(provinceName)
: this.Builder.World.GetWater(provinceName, coast); : this.Builder.World.Map.GetWater(provinceName, coast);
Season targetDestSeason = season ?? this.Target.Season; Season targetDestSeason = season ?? this.Target.Season;
SupportMoveOrder order = new SupportMoveOrder( SupportMoveOrder order = new SupportMoveOrder(
this.PowerContext.Power, this.PowerContext.Power,

View File

@ -40,7 +40,7 @@ class TestCaseBuilderTest
Assert.That(fleetSTP.Type, Is.EqualTo(UnitType.Fleet), "Unit created with wrong type"); Assert.That(fleetSTP.Type, Is.EqualTo(UnitType.Fleet), "Unit created with wrong type");
Assert.That( Assert.That(
fleetSTP.Location, fleetSTP.Location,
Is.EqualTo(setup.World.GetWater("STP", "wc")), Is.EqualTo(setup.World.Map.GetWater("STP", "wc")),
"Unit created on wrong coast"); "Unit created on wrong coast");
} }
@ -74,7 +74,7 @@ class TestCaseBuilderTest
Assert.That(orderBer, Is.InstanceOf<MoveOrder>(), "Unexpected order type"); Assert.That(orderBer, Is.InstanceOf<MoveOrder>(), "Unexpected order type");
Assert.That( Assert.That(
(orderBer as MoveOrder)?.Location, (orderBer as MoveOrder)?.Location,
Is.EqualTo(setup.World.GetLand("Kiel")), Is.EqualTo(setup.World.Map.GetLand("Kiel")),
"Unexpected move order destination"); "Unexpected move order destination");
UnitOrder orderPru = orders.Single(OrderForProvince("Prussia")); UnitOrder orderPru = orders.Single(OrderForProvince("Prussia"));
@ -88,7 +88,7 @@ class TestCaseBuilderTest
"Unexpected convoy order target"); "Unexpected convoy order target");
Assert.That( Assert.That(
(orderNth as ConvoyOrder)?.Location, (orderNth as ConvoyOrder)?.Location,
Is.EqualTo(setup.World.GetLand("Holland")), Is.EqualTo(setup.World.Map.GetLand("Holland")),
"Unexpected convoy order destination"); "Unexpected convoy order destination");
UnitOrder orderKie = orders.Single(OrderForProvince("Kiel")); UnitOrder orderKie = orders.Single(OrderForProvince("Kiel"));
@ -99,7 +99,7 @@ class TestCaseBuilderTest
"Unexpected convoy order target"); "Unexpected convoy order target");
Assert.That( Assert.That(
(orderKie as SupportMoveOrder)?.Location, (orderKie as SupportMoveOrder)?.Location,
Is.EqualTo(setup.World.GetLand("Holland")), Is.EqualTo(setup.World.Map.GetLand("Holland")),
"Unexpected convoy order destination"); "Unexpected convoy order destination");
UnitOrder orderMun = orders.Single(OrderForProvince("Munich")); UnitOrder orderMun = orders.Single(OrderForProvince("Munich"));
@ -124,11 +124,11 @@ class TestCaseBuilderTest
Assert.That(orderMun, Is.Not.Null, "Expected order reference"); Assert.That(orderMun, Is.Not.Null, "Expected order reference");
Assert.That( Assert.That(
orderMun.Order.Power, orderMun.Order.Power,
Is.EqualTo(setup.World.GetPower("Germany")), Is.EqualTo(setup.World.Map.GetPower("Germany")),
"Wrong power"); "Wrong power");
Assert.That( Assert.That(
orderMun.Order.Unit.Location, orderMun.Order.Unit.Location,
Is.EqualTo(setup.World.GetLand("Mun")), Is.EqualTo(setup.World.Map.GetLand("Mun")),
"Wrong unit"); "Wrong unit");
Assert.That( Assert.That(

View File

@ -10,10 +10,10 @@ public class UnitTests
public void MovementTest() public void MovementTest()
{ {
World world = World.WithStandardMap(); World world = World.WithStandardMap();
Location Mun = world.GetLand("Mun"), Location Mun = world.Map.GetLand("Mun"),
Boh = world.GetLand("Boh"), Boh = world.Map.GetLand("Boh"),
Tyr = world.GetLand("Tyr"); Tyr = world.Map.GetLand("Tyr");
Power pw1 = world.GetPower("Austria"); Power pw1 = world.Map.GetPower("Austria");
Season s1 = world.RootSeason; Season s1 = world.RootSeason;
Unit u1 = Unit.Build(Mun, s1, pw1, UnitType.Army); Unit u1 = Unit.Build(Mun, s1, pw1, UnitType.Army);