using System.Collections.ObjectModel; using MultiversalDiplomacy.Orders; namespace MultiversalDiplomacy.Model; /// /// The global game state. /// public class World { /// /// The map variant of the game. /// public readonly Map Map; /// /// The game map. /// public ReadOnlyCollection Provinces => this.Map.Provinces; /// /// The game powers. /// public ReadOnlyCollection Powers => this.Map.Powers; /// /// The state of the multiverse. /// public ReadOnlyCollection Seasons { get; } /// /// The first season of the game. /// public Season RootSeason { get; } /// /// All units in the multiverse. /// public ReadOnlyCollection Units { get; } /// /// All retreating units in the multiverse. /// public ReadOnlyCollection RetreatingUnits { get; } /// /// Orders given to units in each season. /// public ReadOnlyDictionary OrderHistory { get; } /// /// Immutable game options. /// public Options Options { get; } /// /// Create a new World, providing all state data. /// private World( Map map, ReadOnlyCollection seasons, Season rootSeason, ReadOnlyCollection units, ReadOnlyCollection retreatingUnits, ReadOnlyDictionary orderHistory, Options options) { this.Map = map; this.Seasons = seasons; this.RootSeason = rootSeason; this.Units = units; this.RetreatingUnits = retreatingUnits; this.OrderHistory = orderHistory; this.Options = options; } /// /// Create a new World from a previous one, replacing some state data. /// private World( World previous, ReadOnlyCollection? seasons = null, ReadOnlyCollection? units = null, ReadOnlyCollection? retreatingUnits = null, ReadOnlyDictionary? orderHistory = null, Options? options = null) : this( previous.Map, seasons ?? previous.Seasons, previous.RootSeason, // Can't change the root season units ?? previous.Units, retreatingUnits ?? previous.RetreatingUnits, orderHistory ?? previous.OrderHistory, options ?? previous.Options) { } /// /// Create a new world with specified provinces and powers and an initial season. /// public static World WithMap(Map map) { Season root = Season.MakeRoot(); return new World( map, new(new List { root }), root, new(new List()), new(new List()), new(new Dictionary()), new Options()); } /// /// Create a new world with the standard Diplomacy provinces and powers. /// public static World WithStandardMap() => WithMap(Map.Classical); public World Update( IEnumerable? seasons = null, IEnumerable? units = null, IEnumerable? retreats = null, IEnumerable>? orders = null) => new World( previous: this, seasons: seasons == null ? this.Seasons : new(seasons.ToList()), units: units == null ? this.Units : new(units.ToList()), retreatingUnits: retreats == null ? this.RetreatingUnits : new(retreats.ToList()), orderHistory: orders == null ? this.OrderHistory : new(orders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))); /// /// Create a new world 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. Unit specs always describe units in the root season. /// public World AddUnits(params string[] unitSpecs) { IEnumerable units = unitSpecs.Select(spec => { string[] splits = spec.Split(' ', 4); Power power = Map.GetPower(splits[0]); UnitType type = splits[1] switch { "A" => UnitType.Army, "F" => UnitType.Fleet, _ => throw new ApplicationException($"Unknown unit type {splits[1]}") }; Location location = type == UnitType.Army ? Map.GetLand(splits[2]) : splits.Length == 3 ? Map.GetWater(splits[2]) : Map.GetWater(splits[2], splits[3]); Unit unit = Unit.Build(location, this.RootSeason, power, type); return unit; }); return this.Update(units: units); } /// /// Create a new world with standard Diplomacy initial unit placements. /// public World AddStandardUnits() { return this.AddUnits( "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" ); } /// /// A standard Diplomacy game setup. /// public static World Standard => World .WithStandardMap() .AddStandardUnits(); /// /// Get a season by coordinate. Throws if the season is not found. /// public Season GetSeason(string timeline, int turn) { Season? foundSeason = this.Seasons.SingleOrDefault( s => s!.Turn == turn && s.Timeline == timeline, null) ?? throw new KeyNotFoundException($"Season {timeline}@{turn} not found"); return foundSeason; } /// /// Returns a unit in a province. Throws if there are duplicate units. /// public Unit GetUnitAt(string provinceName, (string timeline, int turn)? seasonCoord = null) { Province province = Map.GetProvince(provinceName); seasonCoord ??= (this.RootSeason.Timeline, this.RootSeason.Turn); Season season = GetSeason(seasonCoord.Value.timeline, seasonCoord.Value.turn); Unit? foundUnit = this.Units.SingleOrDefault( u => u!.Province == province && u.Season == season, null) ?? throw new KeyNotFoundException($"Unit at {province} at {season} not found"); return foundUnit; } }