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