Enable basic World serialization
This commit is contained in:
parent
f1563b8f5f
commit
228ad53cca
|
@ -1,5 +1,3 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace MultiversalDiplomacy.Model;
|
||||
|
||||
/// <summary>
|
||||
|
@ -15,17 +13,22 @@ public class Map
|
|||
/// <summary>
|
||||
/// The game map.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<Province> Provinces { get; }
|
||||
public IReadOnlyCollection<Province> Provinces => _Provinces.AsReadOnly();
|
||||
|
||||
private List<Province> _Provinces { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The game powers.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<Power> Powers { get; }
|
||||
public IReadOnlyCollection<Power> Powers => _Powers.AsReadOnly();
|
||||
|
||||
private List<Power> _Powers { get; }
|
||||
|
||||
private Map(MapType type, IEnumerable<Province> provinces, IEnumerable<Power> powers)
|
||||
{
|
||||
Provinces = new(provinces.ToList());
|
||||
Powers = new(powers.ToList());
|
||||
Type = type;
|
||||
_Provinces = provinces.ToList();
|
||||
_Powers = powers.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -90,8 +93,6 @@ public class Map
|
|||
_ => throw new NotImplementedException($"Unknown variant {type}"),
|
||||
};
|
||||
|
||||
#region Variants
|
||||
|
||||
public static Map Test => _Test.Value;
|
||||
|
||||
private static readonly Lazy<Map> _Test = new(() => {
|
||||
|
@ -117,6 +118,7 @@ public class Map
|
|||
// Define the provinces of the standard world map.
|
||||
List<Province> provinces =
|
||||
[
|
||||
#region Provinces
|
||||
Province.Empty("North Africa", "NAF")
|
||||
.AddLandLocation()
|
||||
.AddCoastLocation(),
|
||||
|
@ -313,6 +315,7 @@ public class Map
|
|||
.AddOceanLocation(),
|
||||
Province.Empty("Western Mediterranean Sea", "WMS", "WES")
|
||||
.AddOceanLocation(),
|
||||
#endregion
|
||||
];
|
||||
|
||||
// Declare some helpers for border definitions
|
||||
|
@ -334,6 +337,7 @@ public class Map
|
|||
void AddBorders(string provinceName, Func<string, Location> LocationType, params string[] borders)
|
||||
=> AddBordersTo(LocationType(provinceName), LocationType, borders);
|
||||
|
||||
#region Borders
|
||||
AddBorders("NAF", Land, "TUN");
|
||||
AddBorders("NAF", Water, "MAO", "WES", "TUN");
|
||||
|
||||
|
@ -547,6 +551,7 @@ public class Map
|
|||
|
||||
AddBorders("WES", Water, "LYO", "TYS", "TUN", "NAF", "MAO");
|
||||
Water("WES").AddBorder(Coast("SPA", "sc"));
|
||||
#endregion
|
||||
|
||||
List<Power> powers =
|
||||
[
|
||||
|
@ -561,6 +566,4 @@ public class Map
|
|||
|
||||
return new(MapType.Classical, provinces, powers);
|
||||
});
|
||||
|
||||
#endregion Variants
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MultiversalDiplomacy.Model;
|
||||
|
||||
/// <summary>
|
||||
/// A shared counter for handing out new timeline designations.
|
||||
/// </summary>
|
||||
public class TimelineFactory
|
||||
[JsonConverter(typeof(TimelineFactoryJsonConverter))]
|
||||
public class TimelineFactory(int nextTimeline)
|
||||
{
|
||||
private static readonly char[] Letters = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
|
||||
|
@ -44,7 +47,9 @@ public class TimelineFactory
|
|||
return new string(result.ToArray());
|
||||
}
|
||||
|
||||
private int nextTimeline = 0;
|
||||
public TimelineFactory() : this(0) { }
|
||||
|
||||
public int nextTimeline = nextTimeline;
|
||||
|
||||
public string NextTimeline() => IntToString(nextTimeline++);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MultiversalDiplomacy.Model;
|
||||
|
||||
internal class TimelineFactoryJsonConverter : JsonConverter<TimelineFactory>
|
||||
{
|
||||
public override TimelineFactory? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
=> new(reader.GetInt32());
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, TimelineFactory value, JsonSerializerOptions options)
|
||||
=> writer.WriteNumberValue(value.nextTimeline);
|
||||
}
|
|
@ -17,30 +17,33 @@ public class World
|
|||
/// <summary>
|
||||
/// The map variant of the game.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// While this is serialized to JSON, deserialization uses it to populate <see cref="Map"/>
|
||||
/// </remarks>
|
||||
public MapType MapType => this.Map.Type;
|
||||
|
||||
/// <summary>
|
||||
/// The game map.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ReadOnlyCollection<Province> Provinces => this.Map.Provinces;
|
||||
public IReadOnlyCollection<Province> Provinces => this.Map.Provinces;
|
||||
|
||||
/// <summary>
|
||||
/// The game powers.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ReadOnlyCollection<Power> Powers => this.Map.Powers;
|
||||
public IReadOnlyCollection<Power> Powers => this.Map.Powers;
|
||||
|
||||
/// <summary>
|
||||
/// The state of the multiverse.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<Season> Seasons { get; }
|
||||
public List<Season> Seasons { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Lookup for seasons by designation.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ReadOnlyDictionary<string, Season> SeasonLookup { get; }
|
||||
public Dictionary<string, Season> SeasonLookup { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The first season of the game.
|
||||
|
@ -51,17 +54,17 @@ public class World
|
|||
/// <summary>
|
||||
/// All units in the multiverse.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<Unit> Units { get; }
|
||||
public List<Unit> Units { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All retreating units in the multiverse.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<RetreatingUnit> RetreatingUnits { get; }
|
||||
public List<RetreatingUnit> RetreatingUnits { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Orders given to units in each season.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, OrderHistory> OrderHistory { get; }
|
||||
public Dictionary<string, OrderHistory> OrderHistory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The shared timeline number generator.
|
||||
|
@ -73,15 +76,36 @@ public class World
|
|||
/// </summary>
|
||||
public Options Options { get; }
|
||||
|
||||
[JsonConstructor]
|
||||
public World(
|
||||
MapType mapType,
|
||||
List<Season> seasons,
|
||||
List<Unit> units,
|
||||
List<RetreatingUnit> retreatingUnits,
|
||||
Dictionary<string, OrderHistory> orderHistory,
|
||||
TimelineFactory timelines,
|
||||
Options options)
|
||||
{
|
||||
this.Map = Map.FromType(mapType);
|
||||
this.Seasons = seasons;
|
||||
this.Units = units;
|
||||
this.RetreatingUnits = retreatingUnits;
|
||||
this.OrderHistory = orderHistory;
|
||||
this.Timelines = timelines;
|
||||
this.Options = options;
|
||||
|
||||
this.SeasonLookup = new(Seasons.ToDictionary(season => $"{season.Timeline}{season.Turn}"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new World, providing all state data.
|
||||
/// </summary>
|
||||
private World(
|
||||
Map map,
|
||||
ReadOnlyCollection<Season> seasons,
|
||||
ReadOnlyCollection<Unit> units,
|
||||
ReadOnlyCollection<RetreatingUnit> retreatingUnits,
|
||||
ReadOnlyDictionary<string, OrderHistory> orderHistory,
|
||||
List<Season> seasons,
|
||||
List<Unit> units,
|
||||
List<RetreatingUnit> retreatingUnits,
|
||||
Dictionary<string, OrderHistory> orderHistory,
|
||||
TimelineFactory timelines,
|
||||
Options options)
|
||||
{
|
||||
|
@ -101,10 +125,10 @@ public class World
|
|||
/// </summary>
|
||||
private World(
|
||||
World previous,
|
||||
ReadOnlyCollection<Season>? seasons = null,
|
||||
ReadOnlyCollection<Unit>? units = null,
|
||||
ReadOnlyCollection<RetreatingUnit>? retreatingUnits = null,
|
||||
ReadOnlyDictionary<string, OrderHistory>? orderHistory = null,
|
||||
List<Season>? seasons = null,
|
||||
List<Unit>? units = null,
|
||||
List<RetreatingUnit>? retreatingUnits = null,
|
||||
Dictionary<string, OrderHistory>? orderHistory = null,
|
||||
Options? options = null)
|
||||
: this(
|
||||
previous.Map,
|
||||
|
@ -219,7 +243,7 @@ public class World
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a continuation of this season if it has no futures, otherwise ceate a fork.
|
||||
/// Create a continuation of this season if it has no futures, otherwise create a fork.
|
||||
/// </summary>
|
||||
public Season ContinueOrFork(Season season)
|
||||
=> GetFutures(season).Any()
|
||||
|
@ -237,6 +261,9 @@ public class World
|
|||
public Season GetSeason(string timeline, int turn)
|
||||
=> GetSeason($"{timeline}{turn}");
|
||||
|
||||
/// <summary>
|
||||
/// Get a season by designation.
|
||||
/// </summary>
|
||||
public Season GetSeason(string designation)
|
||||
=> SeasonLookup[designation];
|
||||
|
||||
|
|
|
@ -9,19 +9,40 @@ namespace MultiversalDiplomacyTests;
|
|||
|
||||
public class SerializationTest
|
||||
{
|
||||
private JsonSerializerOptions Options = new() {
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void SerializeRoundTrip_NewGame()
|
||||
{
|
||||
JsonSerializerOptions options = new() {
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
World world = World.WithStandardMap();
|
||||
string serialized = JsonSerializer.Serialize(world, options);
|
||||
Console.WriteLine(serialized);
|
||||
World? deserialized = JsonSerializer.Deserialize<World>(serialized, options);
|
||||
string serialized = JsonSerializer.Serialize(world, Options);
|
||||
World? deserialized = JsonSerializer.Deserialize<World>(serialized, Options);
|
||||
|
||||
Assert.That(deserialized, Is.Not.Null, "Failed to deserialize");
|
||||
Assert.That(deserialized!.Map, Is.Not.Null, "Failed to deserialize map");
|
||||
Assert.That(deserialized!.Seasons, Is.Not.Null, "Failed to deserialize seasons");
|
||||
Assert.That(deserialized!.Units, Is.Not.Null, "Failed to deserialize units");
|
||||
Assert.That(deserialized!.RetreatingUnits, Is.Not.Null, "Failed to deserialize retreats");
|
||||
Assert.That(deserialized!.OrderHistory, Is.Not.Null, "Failed to deserialize history");
|
||||
Assert.That(deserialized!.Timelines, Is.Not.Null, "Failed to deserialize timelines");
|
||||
Assert.That(deserialized!.Options, Is.Not.Null, "Failed to deserialize options");
|
||||
Assert.That(deserialized.Timelines.nextTimeline, Is.EqualTo(world.Timelines.nextTimeline));
|
||||
|
||||
JsonElement document = JsonSerializer.SerializeToDocument(world, Options).RootElement;
|
||||
Assert.That(
|
||||
document.EnumerateObject().Select(prop => prop.Name),
|
||||
Is.EquivalentTo(new List<string> {
|
||||
"mapType",
|
||||
"seasons",
|
||||
"units",
|
||||
"retreatingUnits",
|
||||
"orderHistory",
|
||||
"options",
|
||||
"timelines",
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -45,25 +66,11 @@ public class SerializationTest
|
|||
setup.UpdateWorld();
|
||||
|
||||
// Serialize the world
|
||||
JsonSerializerOptions options = new() {
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
JsonElement serialized = JsonSerializer.SerializeToDocument(setup.World, options).RootElement;
|
||||
|
||||
Assert.That(
|
||||
serialized.EnumerateObject().Select(prop => prop.Name),
|
||||
Is.EquivalentTo(new List<string> {
|
||||
"mapType",
|
||||
"seasons",
|
||||
"units",
|
||||
"retreatingUnits",
|
||||
"orderHistory",
|
||||
"options",
|
||||
}));
|
||||
string serialized = JsonSerializer.Serialize(setup.World, Options);
|
||||
|
||||
// Deserialize the world
|
||||
World reserialized = JsonSerializer.Deserialize<World>(serialized)
|
||||
Console.WriteLine(serialized);
|
||||
World reserialized = JsonSerializer.Deserialize<World>(serialized, Options)
|
||||
?? throw new AssertionException("Failed to reserialize world");
|
||||
|
||||
// Resume the test case
|
||||
|
|
Loading…
Reference in New Issue