2022-03-15 22:43:06 +00:00
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
2022-03-30 03:40:19 +00:00
|
|
|
using MultiversalDiplomacy.Orders;
|
|
|
|
|
2022-03-13 04:29:00 +00:00
|
|
|
namespace MultiversalDiplomacy.Model;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The global game state.
|
|
|
|
/// </summary>
|
|
|
|
public class World
|
|
|
|
{
|
2024-08-12 04:01:05 +00:00
|
|
|
/// <summary>
|
|
|
|
/// The map variant of the game.
|
|
|
|
/// </summary>
|
|
|
|
public readonly Map Map;
|
|
|
|
|
2022-03-13 04:29:00 +00:00
|
|
|
/// <summary>
|
|
|
|
/// The game map.
|
|
|
|
/// </summary>
|
2024-08-12 04:01:05 +00:00
|
|
|
public ReadOnlyCollection<Province> Provinces => this.Map.Provinces;
|
2022-03-13 04:29:00 +00:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The game powers.
|
|
|
|
/// </summary>
|
2024-08-12 04:01:05 +00:00
|
|
|
public ReadOnlyCollection<Power> Powers => this.Map.Powers;
|
2022-03-13 04:29:00 +00:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The state of the multiverse.
|
|
|
|
/// </summary>
|
2022-03-15 22:43:06 +00:00
|
|
|
public ReadOnlyCollection<Season> Seasons { get; }
|
2022-03-13 04:29:00 +00:00
|
|
|
|
2022-03-29 05:34:57 +00:00
|
|
|
/// <summary>
|
|
|
|
/// The first season of the game.
|
|
|
|
/// </summary>
|
|
|
|
public Season RootSeason { get; }
|
|
|
|
|
2022-03-13 04:29:00 +00:00
|
|
|
/// <summary>
|
|
|
|
/// All units in the multiverse.
|
|
|
|
/// </summary>
|
2022-03-15 22:43:06 +00:00
|
|
|
public ReadOnlyCollection<Unit> Units { get; }
|
2022-03-13 04:29:00 +00:00
|
|
|
|
2022-03-24 14:25:51 +00:00
|
|
|
/// <summary>
|
|
|
|
/// All retreating units in the multiverse.
|
|
|
|
/// </summary>
|
|
|
|
public ReadOnlyCollection<RetreatingUnit> RetreatingUnits { get; }
|
|
|
|
|
2022-03-30 03:40:19 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Orders given to units in each season.
|
|
|
|
/// </summary>
|
2022-11-09 00:25:47 +00:00
|
|
|
public ReadOnlyDictionary<Season, OrderHistory> OrderHistory { get; }
|
2022-03-30 03:40:19 +00:00
|
|
|
|
2022-03-13 07:15:26 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Immutable game options.
|
|
|
|
/// </summary>
|
|
|
|
public Options Options { get; }
|
|
|
|
|
2022-03-29 05:34:57 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Create a new World, providing all state data.
|
|
|
|
/// </summary>
|
2022-03-15 22:43:06 +00:00
|
|
|
private World(
|
2024-08-12 04:01:05 +00:00
|
|
|
Map map,
|
2022-03-15 22:43:06 +00:00
|
|
|
ReadOnlyCollection<Season> seasons,
|
2022-03-29 05:34:57 +00:00
|
|
|
Season rootSeason,
|
2022-03-15 22:43:06 +00:00
|
|
|
ReadOnlyCollection<Unit> units,
|
2022-03-24 14:25:51 +00:00
|
|
|
ReadOnlyCollection<RetreatingUnit> retreatingUnits,
|
2022-11-09 00:25:47 +00:00
|
|
|
ReadOnlyDictionary<Season, OrderHistory> orderHistory,
|
2022-03-13 07:15:26 +00:00
|
|
|
Options options)
|
2022-03-13 04:29:00 +00:00
|
|
|
{
|
2024-08-12 04:01:05 +00:00
|
|
|
this.Map = map;
|
2022-03-13 04:29:00 +00:00
|
|
|
this.Seasons = seasons;
|
2022-03-29 05:34:57 +00:00
|
|
|
this.RootSeason = rootSeason;
|
2022-03-13 04:29:00 +00:00
|
|
|
this.Units = units;
|
2022-03-24 14:25:51 +00:00
|
|
|
this.RetreatingUnits = retreatingUnits;
|
2022-11-09 00:25:47 +00:00
|
|
|
this.OrderHistory = orderHistory;
|
2022-03-13 07:15:26 +00:00
|
|
|
this.Options = options;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
2022-03-29 05:34:57 +00:00
|
|
|
/// Create a new World from a previous one, replacing some state data.
|
|
|
|
/// </summary>
|
|
|
|
private World(
|
|
|
|
World previous,
|
|
|
|
ReadOnlyCollection<Season>? seasons = null,
|
|
|
|
ReadOnlyCollection<Unit>? units = null,
|
|
|
|
ReadOnlyCollection<RetreatingUnit>? retreatingUnits = null,
|
2022-11-09 00:25:47 +00:00
|
|
|
ReadOnlyDictionary<Season, OrderHistory>? orderHistory = null,
|
2022-03-29 05:34:57 +00:00
|
|
|
Options? options = null)
|
|
|
|
: this(
|
2024-08-12 04:01:05 +00:00
|
|
|
previous.Map,
|
2022-03-29 05:34:57 +00:00
|
|
|
seasons ?? previous.Seasons,
|
|
|
|
previous.RootSeason, // Can't change the root season
|
|
|
|
units ?? previous.Units,
|
|
|
|
retreatingUnits ?? previous.RetreatingUnits,
|
2022-11-09 00:25:47 +00:00
|
|
|
orderHistory ?? previous.OrderHistory,
|
2022-03-29 05:34:57 +00:00
|
|
|
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>
|
2024-08-12 04:01:05 +00:00
|
|
|
public static World WithMap(Map map)
|
2022-03-29 05:34:57 +00:00
|
|
|
{
|
|
|
|
Season root = Season.MakeRoot();
|
|
|
|
return new World(
|
2024-08-12 04:01:05 +00:00
|
|
|
map,
|
2022-03-29 05:34:57 +00:00
|
|
|
new(new List<Season> { root }),
|
|
|
|
root,
|
2022-03-15 22:43:06 +00:00
|
|
|
new(new List<Unit>()),
|
2022-03-24 14:25:51 +00:00
|
|
|
new(new List<RetreatingUnit>()),
|
2022-11-09 00:25:47 +00:00
|
|
|
new(new Dictionary<Season, OrderHistory>()),
|
2022-03-15 22:43:06 +00:00
|
|
|
new Options());
|
2022-03-29 05:34:57 +00:00
|
|
|
}
|
2022-03-15 22:43:06 +00:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Create a new world with the standard Diplomacy provinces and powers.
|
|
|
|
/// </summary>
|
|
|
|
public static World WithStandardMap()
|
2024-08-12 04:01:05 +00:00
|
|
|
=> WithMap(Map.Classical);
|
2022-03-15 22:43:06 +00:00
|
|
|
|
2022-03-29 05:34:57 +00:00
|
|
|
public World Update(
|
|
|
|
IEnumerable<Season>? seasons = null,
|
|
|
|
IEnumerable<Unit>? units = null,
|
2022-03-30 03:40:19 +00:00
|
|
|
IEnumerable<RetreatingUnit>? retreats = null,
|
2022-11-09 00:25:47 +00:00
|
|
|
IEnumerable<KeyValuePair<Season, OrderHistory>>? orders = null)
|
2022-03-24 14:25:51 +00:00
|
|
|
=> new World(
|
2022-03-29 05:34:57 +00:00
|
|
|
previous: this,
|
2022-03-30 03:40:19 +00:00
|
|
|
seasons: seasons == null
|
|
|
|
? this.Seasons
|
|
|
|
: new(seasons.ToList()),
|
|
|
|
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
|
2022-03-30 03:40:19 +00:00
|
|
|
: new(orders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)));
|
2022-03-15 22:43:06 +00:00
|
|
|
|
|
|
|
/// <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
|
2022-03-29 05:34:57 +00:00
|
|
|
/// abbreviation should be used. Unit specs always describe units in the root season.
|
2022-03-15 22:43:06 +00:00
|
|
|
/// </summary>
|
2022-03-29 05:34:57 +00:00
|
|
|
public World AddUnits(params string[] unitSpecs)
|
2022-03-15 22:43:06 +00:00
|
|
|
{
|
|
|
|
IEnumerable<Unit> units = unitSpecs.Select(spec =>
|
|
|
|
{
|
|
|
|
string[] splits = spec.Split(' ', 4);
|
2024-08-12 04:01:05 +00:00
|
|
|
Power power = Map.GetPower(splits[0]);
|
2022-03-15 22:43:06 +00:00
|
|
|
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]}")
|
2022-03-15 22:43:06 +00:00
|
|
|
};
|
|
|
|
Location location = type == UnitType.Army
|
2024-08-12 04:01:05 +00:00
|
|
|
? Map.GetLand(splits[2])
|
2022-03-15 22:43:06 +00:00
|
|
|
: splits.Length == 3
|
2024-08-12 04:01:05 +00:00
|
|
|
? Map.GetWater(splits[2])
|
|
|
|
: Map.GetWater(splits[2], splits[3]);
|
2022-03-29 05:34:57 +00:00
|
|
|
Unit unit = Unit.Build(location, this.RootSeason, power, type);
|
2022-03-15 22:43:06 +00:00
|
|
|
return unit;
|
|
|
|
});
|
2022-03-29 05:34:57 +00:00
|
|
|
return this.Update(units: units);
|
2022-03-15 22:43:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Create a new world with standard Diplomacy initial unit placements.
|
|
|
|
/// </summary>
|
2022-03-29 05:34:57 +00:00
|
|
|
public World AddStandardUnits()
|
2022-03-15 22:43:06 +00:00
|
|
|
{
|
2022-03-29 05:34:57 +00:00
|
|
|
return this.AddUnits(
|
2022-03-15 22:43:06 +00:00
|
|
|
"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"
|
|
|
|
);
|
|
|
|
}
|
2022-03-13 07:15:26 +00:00
|
|
|
|
|
|
|
/// <summary>
|
2022-03-15 22:43:06 +00:00
|
|
|
/// A standard Diplomacy game setup.
|
2022-03-13 07:15:26 +00:00
|
|
|
/// </summary>
|
2022-03-15 22:43:06 +00:00
|
|
|
public static World Standard => World
|
2022-03-13 07:15:26 +00:00
|
|
|
.WithStandardMap()
|
2022-03-29 05:34:57 +00:00
|
|
|
.AddStandardUnits();
|
2022-03-13 07:15:26 +00:00
|
|
|
|
2022-03-30 00:16:00 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Get a season by coordinate. Throws if the season is not found.
|
|
|
|
/// </summary>
|
2024-08-12 16:28:56 +00:00
|
|
|
public Season GetSeason(string timeline, int turn)
|
2022-03-30 00:16:00 +00:00
|
|
|
{
|
|
|
|
Season? foundSeason = this.Seasons.SingleOrDefault(
|
2022-04-07 22:48:46 +00:00
|
|
|
s => s!.Turn == turn && s.Timeline == timeline,
|
2024-08-12 16:28:56 +00:00
|
|
|
null)
|
|
|
|
?? throw new KeyNotFoundException($"Season {timeline}@{turn} not found");
|
2022-03-30 00:16:00 +00:00
|
|
|
return foundSeason;
|
|
|
|
}
|
|
|
|
|
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-12 16:28:56 +00:00
|
|
|
public Unit GetUnitAt(string provinceName, (string timeline, int turn)? seasonCoord = null)
|
2022-03-13 07:15:26 +00:00
|
|
|
{
|
2024-08-12 04:01:05 +00:00
|
|
|
Province province = Map.GetProvince(provinceName);
|
2024-08-12 16:28:56 +00:00
|
|
|
seasonCoord ??= (this.RootSeason.Timeline, this.RootSeason.Turn);
|
|
|
|
Season season = GetSeason(seasonCoord.Value.timeline, seasonCoord.Value.turn);
|
2022-03-15 22:43:06 +00:00
|
|
|
Unit? foundUnit = this.Units.SingleOrDefault(
|
2022-04-07 22:48:46 +00:00
|
|
|
u => u!.Province == province && u.Season == season,
|
2024-08-12 16:28:56 +00:00
|
|
|
null)
|
|
|
|
?? throw new KeyNotFoundException($"Unit at {province} at {season} not found");
|
2022-03-15 22:43:06 +00:00
|
|
|
return foundUnit;
|
2022-03-13 07:15:26 +00:00
|
|
|
}
|
2024-08-12 16:28:56 +00:00
|
|
|
}
|