Refactor World to avoid double enumeration

If an enumerable that created objects were passed, it would duplicate the objects when re-enumerated, which breaks all the reference equality logic.
This commit is contained in:
Jaculabilis 2022-03-15 15:43:06 -07:00
parent 18c5435c96
commit b0a8100641
4 changed files with 443 additions and 424 deletions

View File

@ -1,3 +1,5 @@
using System.Collections.ObjectModel;
namespace MultiversalDiplomacy.Model; namespace MultiversalDiplomacy.Model;
/// <summary> /// <summary>
@ -8,33 +10,33 @@ public class World
/// <summary> /// <summary>
/// The game map. /// The game map.
/// </summary> /// </summary>
public IEnumerable<Province> Provinces { get; } public ReadOnlyCollection<Province> Provinces { get; }
/// <summary> /// <summary>
/// The game powers. /// The game powers.
/// </summary> /// </summary>
public IEnumerable<Power> Powers { get; } public ReadOnlyCollection<Power> Powers { get; }
/// <summary> /// <summary>
/// The state of the multiverse. /// The state of the multiverse.
/// </summary> /// </summary>
public IEnumerable<Season> Seasons { get; } public ReadOnlyCollection<Season> Seasons { get; }
/// <summary> /// <summary>
/// All units in the multiverse. /// All units in the multiverse.
/// </summary> /// </summary>
public IEnumerable<Unit> Units { get; } public ReadOnlyCollection<Unit> Units { get; }
/// <summary> /// <summary>
/// Immutable game options. /// Immutable game options.
/// </summary> /// </summary>
public Options Options { get; } public Options Options { get; }
public World( private World(
IEnumerable<Province> provinces, ReadOnlyCollection<Province> provinces,
IEnumerable<Power> powers, ReadOnlyCollection<Power> powers,
IEnumerable<Season> seasons, ReadOnlyCollection<Season> seasons,
IEnumerable<Unit> units, ReadOnlyCollection<Unit> units,
Options options) Options options)
{ {
this.Provinces = provinces; this.Provinces = provinces;
@ -45,21 +47,105 @@ public class World
} }
/// <summary> /// <summary>
/// Create a new world with no map, powers, or units, and a root season. /// Create a new world with specified provinces and powers.
/// </summary> /// </summary>
public static World Empty => new World( public static World WithMap(IEnumerable<Province> provinces, IEnumerable<Power> powers)
new List<Province>(), => new World(
new List<Power>(), new(provinces.ToList()),
new List<Season> { Season.MakeRoot() }, new(powers.ToList()),
new List<Unit>(), new(new List<Season>()),
new Options()); new(new List<Unit>()),
new Options());
/// <summary> /// <summary>
/// Create a world with a standard map, powers, and initial unit placements. /// Create a new world with the standard Diplomacy provinces and powers.
/// </summary> /// </summary>
public static World Standard => Empty public static World WithStandardMap()
=> WithMap(StandardProvinces, StandardPowers);
/// <summary>
/// Create a new world with new seasons.
/// </summary>
public World WithSeasons(IEnumerable<Season> seasons)
=> new World(this.Provinces, this.Powers, new(seasons.ToList()), this.Units, this.Options);
/// <summary>
/// Create a new world with an initial season.
/// </summary>
public World WithInitialSeason()
=> WithSeasons(new List<Season> { Season.MakeRoot() });
/// <summary>
/// Create a new world with new units.
/// </summary>
public World WithUnits(IEnumerable<Unit> units)
=> new World(this.Provinces, this.Powers, this.Seasons, new(units.ToList()), this.Options);
/// <summary>
/// Create a new world with new units created from unit specs. Units specs are in the format
/// "<power> <A/F> <province> [<coast>]". If the province or coast name has a space in it, the
/// abbreviation should be used.
/// </summary>
public World WithUnits(params string[] unitSpecs)
{
IEnumerable<Unit> units = unitSpecs.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 this.WithUnits(units);
}
/// <summary>
/// Create a new world with standard Diplomacy initial unit placements.
/// </summary>
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"
);
}
/// <summary>
/// A standard Diplomacy game setup.
/// </summary>
public static World Standard => World
.WithStandardMap() .WithStandardMap()
.WithStandardPowers() .WithInitialSeason()
.WithStandardUnits(); .WithStandardUnits();
/// <summary> /// <summary>
@ -68,7 +154,7 @@ public class World
private Province GetProvince(string provinceName) private Province GetProvince(string provinceName)
{ {
string provinceNameUpper = provinceName.ToUpperInvariant(); string provinceNameUpper = provinceName.ToUpperInvariant();
Province? foundProvince = this.Provinces.FirstOrDefault( Province? foundProvince = this.Provinces.SingleOrDefault(
p => p != null && p => p != null &&
(p.Name.ToUpperInvariant() == provinceNameUpper (p.Name.ToUpperInvariant() == provinceNameUpper
|| p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper)), || p.Abbreviations.Any(a => a.ToUpperInvariant() == provinceNameUpper)),
@ -106,11 +192,11 @@ public class World
: GetLocation(provinceName, l => l.Name == coastName || l.Abbreviation == coastName); : GetLocation(provinceName, l => l.Name == coastName || l.Abbreviation == coastName);
/// <summary> /// <summary>
/// Get a power by name. Throws if the power is not found. /// Get a power by name. Throws if there is not exactly one such power.
/// </summary> /// </summary>
public Power GetPower(string powerName) public Power GetPower(string powerName)
{ {
Power? foundPower = this.Powers.FirstOrDefault( Power? foundPower = this.Powers.SingleOrDefault(
p => p =>
p != null p != null
&& (p.Name == powerName || p.Name.StartsWith(powerName)), && (p.Name == powerName || p.Name.StartsWith(powerName)),
@ -121,416 +207,347 @@ public class World
} }
/// <summary> /// <summary>
/// Create a new world from this one with new provinces. /// Returns a unit in a province. Throws if there is not exactly one such unit.
/// </summary> /// </summary>
public World WithMap(IEnumerable<Province> provinces) public Unit? GetUnitAt(string provinceName)
{ {
if (this.Units.Any()) throw new InvalidOperationException( Province province = GetProvince(provinceName);
"Provinces cannot be changed once units have been placed on the map"); Unit? foundUnit = this.Units.SingleOrDefault(
return new World(provinces, this.Powers, this.Seasons, this.Units, this.Options); u => u != null && u.Location.Province == province,
null);
return foundUnit;
} }
/// <summary> /// <summary>
/// Create a new world from this one with the standard Diplomacy provinces. /// The standard Diplomacy provinces.
/// </summary> /// </summary>
public World WithStandardMap() public static ReadOnlyCollection<Province> StandardProvinces
{ {
// Define the provinces of the standard world map. get
List<Province> standardProvinces = new List<Province>
{ {
Province.Empty("North Africa", "NAF") // Define the provinces of the standard world map.
.AddLandLocation() List<Province> standardProvinces = new List<Province>
.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);
}
/// <summary>
/// Create a new world from this one with new powers.
/// </summary>
public World WithPowers(IEnumerable<Power> powers)
=> new World(this.Provinces, powers, this.Seasons, this.Units, this.Options);
/// <summary>
/// Create a new world from this one with new powers created with the given names.
/// </summary>
public World WithPowers(IEnumerable<string> powerNames)
=> WithPowers(powerNames.Select(name => new Model.Power(name)));
/// <summary>
/// Create a new world from this one with new powers created with the given names.
/// </summary>
public World WithPowers(params string[] powerNames)
=> WithPowers(powerNames.AsEnumerable());
/// <summary>
/// Create a new world from this one with the standard Diplomacy powers.
/// </summary>
public World WithStandardPowers()
=> WithPowers("Austria", "England", "France", "Germany", "Italy", "Russia", "Turkey");
/// <summary>
/// Create a new world from this one with new units created from unit specs. Units specs are
/// in the format "<power> <A/F> <province> [<coast>]". If the province or coast name has a
/// space in it, the abbreviation should be used.
/// </summary>
public World WithUnits(IEnumerable<string> unitSpec)
{
IEnumerable<Unit> units = unitSpec.Select(spec =>
{
string[] splits = spec.Split(' ', 4);
Power power = this.GetPower(splits[0]);
UnitType type = splits[1] switch
{ {
"A" => UnitType.Army, Province.Empty("North Africa", "NAF")
"F" => UnitType.Fleet, .AddLandLocation()
_ => throw new ArgumentOutOfRangeException($"Unknown unit type {splits[1]}") .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(),
}; };
Location location = type == UnitType.Army
? this.GetLand(splits[2]) // Declare some helpers for border definitions
: splits.Length == 3 Location Land(string provinceName) => standardProvinces
? this.GetWater(splits[2]) .Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
: this.GetWater(splits[2], splits[3]); .Locations.Single(l => l.Type == LocationType.Land);
Unit unit = Unit.Build(location, this.Seasons.First(), power, type); Location Water(string provinceName) => standardProvinces
return unit; .Single(p => p.Name == provinceName || p.Abbreviations.Contains(provinceName))
}); .Locations.Single(l => l.Type == LocationType.Water);
return new World(this.Provinces, this.Powers, this.Seasons, units, this.Options); 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 new(standardProvinces);
}
} }
/// <summary> /// <summary>
/// Create a new world from this one with new units created from unit specs. Units specs are /// The standard Diplomacy powers.
/// in the format "<power> <A/F> <province> [<coast>]".
/// </summary> /// </summary>
public World WithUnits(params string[] unitSpec) public static ReadOnlyCollection<Power> StandardPowers
=> this.WithUnits(unitSpec.AsEnumerable());
/// <summary>
/// Create a new world from this one with new units created according to the standard Diplomacy
/// initial unit deployments.
/// </summary>
public World WithStandardUnits()
{ {
return this.WithUnits( get => new(new List<Power>
"Austria A Bud", {
"Austria A Vir", new Power("Austria"),
"Austria F Tri", new Power("England"),
"England A Lvp", new Power("France"),
"England F Edi", new Power("Germany"),
"England F Lon", new Power("Italy"),
"France A Mar", new Power("Russia"),
"France A Par", new Power("Turkey"),
"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"
);
} }
} }

View File

@ -15,8 +15,8 @@ public class AdjudicatorTests
{ {
return orders.Select(o => o.Validate(ValidationReason.Valid)).ToList(); return orders.Select(o => o.Validate(ValidationReason.Valid)).ToList();
}); });
World world = World.Empty.WithPowers("Power"); World world = World.WithStandardMap().WithInitialSeason();
Power power = world.GetPower("Power"); Power power = world.GetPower("Austria");
Order order = new NullOrder(power); Order order = new NullOrder(power);
List<Order> orders = new List<Order> { order }; List<Order> orders = new List<Order> { order };

View File

@ -50,7 +50,7 @@ public class MapTests
[Test] [Test]
public void LandAndSeaBorders() public void LandAndSeaBorders()
{ {
World map = World.Empty.WithStandardMap(); World map = World.WithStandardMap();
Assert.That( Assert.That(
map.GetLand("NAF").Adjacents.Count(), map.GetLand("NAF").Adjacents.Count(),
Is.EqualTo(1), Is.EqualTo(1),

View File

@ -9,9 +9,11 @@ public class UnitTests
[Test] [Test]
public void MovementTest() public void MovementTest()
{ {
World world = World.Empty.WithStandardMap().WithPowers("First"); World world = World.WithStandardMap().WithInitialSeason();
Location Mun = world.GetLand("Mun"), Boh = world.GetLand("Boh"), Tyr = world.GetLand("Tyr"); Location Mun = world.GetLand("Mun"),
Power pw1 = world.GetPower("First"); Boh = world.GetLand("Boh"),
Tyr = world.GetLand("Tyr");
Power pw1 = world.GetPower("Austria");
Season s1 = world.Seasons.First(); Season s1 = world.Seasons.First();
Unit u1 = Unit.Build(Mun, s1, pw1, UnitType.Army); Unit u1 = Unit.Build(Mun, s1, pw1, UnitType.Army);