5dplomacy/MultiversalDiplomacy/Model/World.cs

244 lines
7.3 KiB
C#
Raw Normal View History

using System.Text.Json;
2024-08-13 15:17:34 +00:00
using System.Text.Json.Serialization;
namespace MultiversalDiplomacy.Model;
/// <summary>
/// The global game state.
/// </summary>
public class World
{
public static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
/// <summary>
/// The map variant of the game.
/// </summary>
2024-08-13 15:17:34 +00:00
[JsonIgnore]
public Map Map { get; }
2024-08-13 15:17:34 +00:00
/// <summary>
/// The map variant of the game.
/// </summary>
2024-08-14 05:14:55 +00:00
/// <remarks>
/// While this is serialized to JSON, deserialization uses it to populate <see cref="Map"/>
/// </remarks>
2024-08-13 15:17:34 +00:00
public MapType MapType => this.Map.Type;
/// <summary>
/// The game map.
/// </summary>
2024-08-13 15:17:34 +00:00
[JsonIgnore]
2024-08-14 05:14:55 +00:00
public IReadOnlyCollection<Province> Provinces => this.Map.Provinces;
/// <summary>
/// The game powers.
/// </summary>
2024-08-13 15:17:34 +00:00
[JsonIgnore]
2024-08-15 14:54:38 +00:00
public IReadOnlyCollection<string> Powers => this.Map.Powers;
/// <summary>
/// All units in the multiverse.
/// </summary>
2024-08-14 05:14:55 +00:00
public List<Unit> Units { get; }
/// <summary>
/// All retreating units in the multiverse.
/// </summary>
2024-08-14 05:14:55 +00:00
public List<RetreatingUnit> RetreatingUnits { get; }
/// <summary>
/// Orders given to units in each season.
/// </summary>
2024-08-14 05:14:55 +00:00
public Dictionary<string, OrderHistory> OrderHistory { get; }
/// <summary>
2024-08-27 02:43:12 +00:00
/// The state of the multiverse.
/// </summary>
public Timelines Timelines { get; }
2022-03-13 07:15:26 +00:00
/// <summary>
/// Immutable game options.
/// </summary>
public Options Options { get; }
2024-08-14 05:14:55 +00:00
[JsonConstructor]
public World(
MapType mapType,
List<Unit> units,
List<RetreatingUnit> retreatingUnits,
Dictionary<string, OrderHistory> orderHistory,
Timelines timelines,
2024-08-14 05:14:55 +00:00
Options options)
{
this.Map = Map.FromType(mapType);
this.Units = units;
this.RetreatingUnits = retreatingUnits;
this.OrderHistory = orderHistory;
this.Timelines = timelines;
this.Options = options;
}
/// <summary>
/// Create a new World, providing all state data.
/// </summary>
private World(
Map map,
2024-08-14 05:14:55 +00:00
List<Unit> units,
List<RetreatingUnit> retreatingUnits,
Dictionary<string, OrderHistory> orderHistory,
Timelines timelines,
2022-03-13 07:15:26 +00:00
Options options)
{
this.Map = map;
this.Units = units;
this.RetreatingUnits = retreatingUnits;
2022-11-09 00:25:47 +00:00
this.OrderHistory = orderHistory;
this.Timelines = timelines;
2022-03-13 07:15:26 +00:00
this.Options = options;
}
/// <summary>
/// Create a new World from a previous one, replacing some state data.
/// </summary>
private World(
World previous,
2024-08-14 05:14:55 +00:00
List<Unit>? units = null,
List<RetreatingUnit>? retreatingUnits = null,
Dictionary<string, OrderHistory>? orderHistory = null,
Timelines? timelines = null,
Options? options = null)
: this(
previous.Map,
units ?? previous.Units,
retreatingUnits ?? previous.RetreatingUnits,
2022-11-09 00:25:47 +00:00
orderHistory ?? previous.OrderHistory,
timelines ?? previous.Timelines,
options ?? previous.Options)
{
}
/// <summary>
/// Create a new world with specified provinces and powers and an initial season.
2022-03-13 07:15:26 +00:00
/// </summary>
public static World WithMap(Map map)
{
return new World(
map,
2024-08-12 17:59:48 +00:00
new([]),
new([]),
new(new Dictionary<string, OrderHistory>()),
Timelines.Create(),
new Options());
}
/// <summary>
/// Create a new world with the standard Diplomacy provinces and powers.
/// </summary>
public static World WithStandardMap()
=> WithMap(Map.Classical);
public World Update(
IEnumerable<Unit>? units = null,
IEnumerable<RetreatingUnit>? retreats = null,
IEnumerable<KeyValuePair<string, OrderHistory>>? orders = null,
Timelines? timelines = null)
2024-08-12 17:59:48 +00:00
=> new(
previous: this,
units: units == null
? this.Units
: new(units.ToList()),
retreatingUnits: retreats == null
? this.RetreatingUnits
: new(retreats.ToList()),
2022-11-09 00:25:47 +00:00
orderHistory: orders == null
? this.OrderHistory
: new(orders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)),
timelines: timelines ?? this.Timelines);
/// <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. Unit specs always describe units in the root season.
/// </summary>
public World AddUnits(params string[] unitSpecs)
{
IEnumerable<Unit> units = unitSpecs.Select(spec =>
{
string[] splits = spec.Split(' ', 4);
2024-08-15 14:54:38 +00:00
string power = Map.GetPower(splits[0]);
UnitType type = splits[1] switch
{
"A" => UnitType.Army,
"F" => UnitType.Fleet,
2022-03-23 04:27:06 +00:00
_ => 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]);
2024-08-15 23:36:50 +00:00
Unit unit = Unit.Build(location.Key, Season.First, power, type);
return unit;
});
return this.Update(units: units);
}
/// <summary>
/// Create a new world with standard Diplomacy initial unit placements.
/// </summary>
public World AddStandardUnits()
{
return this.AddUnits(
"Austria A Bud",
2024-08-16 23:03:37 +00:00
"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"
);
}
2022-03-13 07:15:26 +00:00
/// <summary>
/// A standard Diplomacy game setup.
2022-03-13 07:15:26 +00:00
/// </summary>
2024-08-13 15:17:34 +00:00
public static World Standard => WithStandardMap().AddStandardUnits();
2022-03-13 07:15:26 +00:00
/// <summary>
2022-03-30 00:16:00 +00:00
/// Returns a unit in a province. Throws if there are duplicate units.
2022-03-13 07:15:26 +00:00
/// </summary>
2024-08-13 23:24:43 +00:00
public Unit GetUnitAt(string provinceName, Season? season = null)
2022-03-13 07:15:26 +00:00
{
Province province = Map.GetProvince(provinceName);
2024-08-15 23:36:50 +00:00
season ??= Season.First;
Unit? foundUnit = this.Units.SingleOrDefault(
u => u!.GetProvince(this) == province && u!.Season == season,
null)
?? throw new KeyNotFoundException($"Unit at {province} at {season} not found");
return foundUnit;
2022-03-13 07:15:26 +00:00
}
2024-08-14 15:39:19 +00:00
2024-08-15 13:52:08 +00:00
public Unit GetUnitByKey(string designation)
=> Units.SingleOrDefault(u => u!.Key == designation, null)
2024-08-14 15:39:19 +00:00
?? throw new KeyNotFoundException($"Unit {designation} not found");
}