using System.Text.Json; using System.Text.Json.Serialization; namespace MultiversalDiplomacy.Model; /// /// The global game state. /// public class World { public static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; /// /// The map variant of the game. /// [JsonIgnore] public Map Map { get; } /// /// The map variant of the game. /// /// /// While this is serialized to JSON, deserialization uses it to populate /// public MapType MapType => this.Map.Type; /// /// The game map. /// [JsonIgnore] public IReadOnlyCollection Provinces => this.Map.Provinces; /// /// The game powers. /// [JsonIgnore] public IReadOnlyCollection Powers => this.Map.Powers; /// /// All units in the multiverse. /// public List Units { get; } /// /// All retreating units in the multiverse. /// public List RetreatingUnits { get; } /// /// Orders given to units in each season. /// public Dictionary OrderHistory { get; } /// /// The state of the multiverse. /// public Timelines Timelines { get; } /// /// Immutable game options. /// public Options Options { get; } [JsonConstructor] public World( MapType mapType, List units, List retreatingUnits, Dictionary orderHistory, Timelines timelines, Options options) { this.Map = Map.FromType(mapType); this.Units = units; this.RetreatingUnits = retreatingUnits; this.OrderHistory = orderHistory; this.Timelines = timelines; this.Options = options; } /// /// Create a new World, providing all state data. /// private World( Map map, List units, List retreatingUnits, Dictionary orderHistory, Timelines timelines, Options options) { this.Map = map; this.Units = units; this.RetreatingUnits = retreatingUnits; this.OrderHistory = orderHistory; this.Timelines = timelines; this.Options = options; } /// /// Create a new World from a previous one, replacing some state data. /// private World( World previous, List? units = null, List? retreatingUnits = null, Dictionary? orderHistory = null, Timelines? timelines = null, Options? options = null) : this( previous.Map, units ?? previous.Units, retreatingUnits ?? previous.RetreatingUnits, orderHistory ?? previous.OrderHistory, timelines ?? previous.Timelines, options ?? previous.Options) { } /// /// Create a new world with specified provinces and powers and an initial season. /// public static World WithMap(Map map) { return new World( map, new([]), new([]), new(new Dictionary()), Timelines.Create(), new Options()); } /// /// Create a new world with the standard Diplomacy provinces and powers. /// public static World WithStandardMap() => WithMap(Map.Classical); public World Update( IEnumerable? units = null, IEnumerable? retreats = null, IEnumerable>? orders = null, Timelines? timelines = null) => new( previous: this, 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)), timelines: timelines ?? this.Timelines); /// /// 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); string 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.Key, Season.First, 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 Vie", "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 => WithStandardMap().AddStandardUnits(); /// /// Returns a unit in a province. Throws if there are duplicate units. /// public Unit GetUnitAt(string provinceName, Season? season = null) { Province province = Map.GetProvince(provinceName); season ??= Season.First; Unit? foundUnit = this.Units.SingleOrDefault( u => Map.GetLocation(u!).Province == province && u!.Season == season, null) ?? throw new KeyNotFoundException($"Unit at {province} at {season} not found"); return foundUnit; } public Unit GetUnitByKey(string designation) => Units.SingleOrDefault(u => u!.Key == designation, null) ?? throw new KeyNotFoundException($"Unit {designation} not found"); }