Compare commits

..

No commits in common. "c9bd8c8194d1d6781542ed324ae575786588d95b" and "b17ce9485ad607308b7d1865cf5af6fd7aff158d" have entirely different histories.

18 changed files with 139 additions and 248 deletions

View File

@ -61,25 +61,25 @@ public class MovementDecisions
case MoveOrder move: case MoveOrder move:
AdvanceTimeline.Ensure( AdvanceTimeline.Ensure(
move.Season, move.Season,
() => new(move.Season, world.OrderHistory[move.Season.Designation].Orders)); () => new(move.Season, world.OrderHistory[move.Season].Orders));
AdvanceTimeline[move.Season].Orders.Add(move); AdvanceTimeline[move.Season].Orders.Add(move);
break; break;
case SupportHoldOrder supportHold: case SupportHoldOrder supportHold:
AdvanceTimeline.Ensure( AdvanceTimeline.Ensure(
supportHold.Target.Season, supportHold.Target.Season,
() => new(supportHold.Target.Season, world.OrderHistory[supportHold.Target.Season.Designation].Orders)); () => new(supportHold.Target.Season, world.OrderHistory[supportHold.Target.Season].Orders));
AdvanceTimeline[supportHold.Target.Season].Orders.Add(supportHold); AdvanceTimeline[supportHold.Target.Season].Orders.Add(supportHold);
break; break;
case SupportMoveOrder supportMove: case SupportMoveOrder supportMove:
AdvanceTimeline.Ensure( AdvanceTimeline.Ensure(
supportMove.Target.Season, supportMove.Target.Season,
() => new(supportMove.Target.Season, world.OrderHistory[supportMove.Target.Season.Designation].Orders)); () => new(supportMove.Target.Season, world.OrderHistory[supportMove.Target.Season].Orders));
AdvanceTimeline[supportMove.Target.Season].Orders.Add(supportMove); AdvanceTimeline[supportMove.Target.Season].Orders.Add(supportMove);
AdvanceTimeline.Ensure( AdvanceTimeline.Ensure(
supportMove.Season, supportMove.Season,
() => new(supportMove.Season, world.OrderHistory[supportMove.Season.Designation].Orders)); () => new(supportMove.Season, world.OrderHistory[supportMove.Season].Orders));
AdvanceTimeline[supportMove.Season].Orders.Add(supportMove); AdvanceTimeline[supportMove.Season].Orders.Add(supportMove);
break; break;
} }

View File

@ -312,9 +312,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// All moves to a particular season in a single phase result in the same future. Keep a // All moves to a particular season in a single phase result in the same future. Keep a
// record of when a future season has been created. // record of when a future season has been created.
Dictionary<Season, Season> createdFutures = []; Dictionary<Season, Season> createdFutures = new();
List<Unit> createdUnits = []; List<Unit> createdUnits = new();
List<RetreatingUnit> retreats = []; List<RetreatingUnit> retreats = new();
// Populate createdFutures with the timeline fork decisions // Populate createdFutures with the timeline fork decisions
logger.Log(1, "Processing AdvanceTimeline decisions"); logger.Log(1, "Processing AdvanceTimeline decisions");
@ -324,7 +324,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
if (advanceTimeline.Outcome == true) if (advanceTimeline.Outcome == true)
{ {
// A timeline that doesn't have a future yet simply continues. Otherwise, it forks. // A timeline that doesn't have a future yet simply continues. Otherwise, it forks.
createdFutures[advanceTimeline.Season] = world.ContinueOrFork(advanceTimeline.Season); createdFutures[advanceTimeline.Season] = !world.GetFutures(advanceTimeline.Season).Any()
? advanceTimeline.Season.MakeNext()
: advanceTimeline.Season.MakeFork();
} }
} }
@ -384,11 +386,11 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
} }
// Record the adjudication results to the season's order history // Record the adjudication results to the season's order history
Dictionary<string, OrderHistory> newHistory = []; Dictionary<Season, OrderHistory> newHistory = new();
foreach (UnitOrder unitOrder in decisions.OfType<IsDislodged>().Select(d => d.Order)) foreach (UnitOrder unitOrder in decisions.OfType<IsDislodged>().Select(d => d.Order))
{ {
newHistory.Ensure(unitOrder.Unit.Season.Designation, () => new()); newHistory.Ensure(unitOrder.Unit.Season, () => new());
OrderHistory history = newHistory[unitOrder.Unit.Season.Designation]; OrderHistory history = newHistory[unitOrder.Unit.Season];
// TODO does this add every order to every season?? // TODO does this add every order to every season??
history.Orders.Add(unitOrder); history.Orders.Add(unitOrder);
history.IsDislodgedOutcomes[unitOrder.Unit] = dislodges[unitOrder.Unit].Outcome == true; history.IsDislodgedOutcomes[unitOrder.Unit] = dislodges[unitOrder.Unit].Outcome == true;
@ -399,7 +401,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
} }
// Log the new order history // Log the new order history
foreach ((string season, OrderHistory history) in newHistory) foreach ((Season season, OrderHistory history) in newHistory)
{ {
string verb = world.OrderHistory.ContainsKey(season) ? "Updating" : "Adding"; string verb = world.OrderHistory.ContainsKey(season) ? "Updating" : "Adding";
logger.Log(1, "{0} history for {1}", verb, season); logger.Log(1, "{0} history for {1}", verb, season);
@ -409,7 +411,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
} }
} }
IEnumerable<KeyValuePair<string, OrderHistory>> updatedHistory = world.OrderHistory IEnumerable<KeyValuePair<Season, OrderHistory>> updatedHistory = world.OrderHistory
.Where(kvp => !newHistory.ContainsKey(kvp.Key)) .Where(kvp => !newHistory.ContainsKey(kvp.Key))
.Concat(newHistory); .Concat(newHistory);
@ -494,7 +496,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
IEnumerable<MoveOrder> newIncomingMoves = decision.Orders IEnumerable<MoveOrder> newIncomingMoves = decision.Orders
.OfType<MoveOrder>() .OfType<MoveOrder>()
.Where(order => order.Season == decision.Season .Where(order => order.Season == decision.Season
&& !world.OrderHistory[order.Season.Designation].DoesMoveOutcomes.ContainsKey(order)); && !world.OrderHistory[order.Season].DoesMoveOutcomes.ContainsKey(order));
foreach (MoveOrder moveOrder in newIncomingMoves) foreach (MoveOrder moveOrder in newIncomingMoves)
{ {
DoesMove doesMove = decisions.DoesMove[moveOrder]; DoesMove doesMove = decisions.DoesMove[moveOrder];
@ -511,7 +513,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
// 1. The outcome of a dislodge decision is changed, // 1. The outcome of a dislodge decision is changed,
// 2. The outcome of an intra-timeline move decision is changed, or // 2. The outcome of an intra-timeline move decision is changed, or
// 3. The outcome of an inter-timeline move decision with that season as the destination is changed. // 3. The outcome of an inter-timeline move decision with that season as the destination is changed.
OrderHistory history = world.OrderHistory[decision.Season.Designation]; OrderHistory history = world.OrderHistory[decision.Season];
bool anyUnresolved = false; bool anyUnresolved = false;
foreach (UnitOrder order in decision.Orders) foreach (UnitOrder order in decision.Orders)
{ {

View File

@ -1,15 +0,0 @@
using CommandLine;
namespace MultiversalDiplomacy.CommandLine;
[Verb("image", HelpText = "Generate an image of a game state.")]
public class ImageOptions
{
[Value(0, HelpText = "Input file describing the game state to visualize, or - to read from stdin.")]
public string? InputFile { get; set; }
public static void Execute(ImageOptions args)
{
throw new NotImplementedException();
}
}

View File

@ -1,18 +0,0 @@
using CommandLine;
namespace MultiversalDiplomacy.CommandLine;
[Verb("repl", HelpText = "Begin an interactive 5dplomacy session.")]
public class ReplOptions
{
[Option('i', "input", HelpText = "Begin the repl session by executing the commands in this file.")]
public string? InputFile { get; set; }
[Option('o', "output", HelpText = "Echo the repl session to this file. Specify a directory to autogenerate a filename.")]
public string? OutputFile { get; set; }
public static void Execute(ReplOptions args)
{
throw new NotImplementedException();
}
}

View File

@ -1,5 +1,3 @@
using System.Text.Json.Serialization;
namespace MultiversalDiplomacy.Model; namespace MultiversalDiplomacy.Model;
/// <summary> /// <summary>
@ -11,12 +9,8 @@ public class Location
{ {
/// <summary> /// <summary>
/// The province to which this location belongs. /// The province to which this location belongs.
/// </summary>
[JsonIgnore]
public Province Province { get; } public Province Province { get; }
public string ProvinceName => Province.Name;
/// <summary> /// <summary>
/// The location's full human-readable name. /// The location's full human-readable name.
/// </summary> /// </summary>
@ -35,7 +29,6 @@ public class Location
/// <summary> /// <summary>
/// The locations that border this location. /// The locations that border this location.
/// </summary> /// </summary>
[JsonIgnore]
public IEnumerable<Location> Adjacents => this.AdjacentList; public IEnumerable<Location> Adjacents => this.AdjacentList;
private List<Location> AdjacentList { get; set; } private List<Location> AdjacentList { get; set; }

View File

@ -5,13 +5,7 @@ namespace MultiversalDiplomacy.Model;
/// <summary> /// <summary>
/// Encapsulation of the world map and playable powers constituting a Diplomacy variant. /// Encapsulation of the world map and playable powers constituting a Diplomacy variant.
/// </summary> /// </summary>
public class Map public class Map {
{
/// <summary>
/// The map type.
/// </summary>
public MapType Type { get; }
/// <summary> /// <summary>
/// The game map. /// The game map.
/// </summary> /// </summary>
@ -22,7 +16,7 @@ public class Map
/// </summary> /// </summary>
public ReadOnlyCollection<Power> Powers { get; } public ReadOnlyCollection<Power> Powers { get; }
private Map(MapType type, IEnumerable<Province> provinces, IEnumerable<Power> powers) private Map(IEnumerable<Province> provinces, IEnumerable<Power> powers)
{ {
Provinces = new(provinces.ToList()); Provinces = new(provinces.ToList());
Powers = new(powers.ToList()); Powers = new(powers.ToList());
@ -108,7 +102,7 @@ public class Map
Power a = new("Alpha"); Power a = new("Alpha");
Power b = new("Beta"); Power b = new("Beta");
return new(MapType.Test, [lef, cen, rig], [a, b]); return new([lef, cen, rig], [a, b]);
}); });
public static Map Classical => _Classical.Value; public static Map Classical => _Classical.Value;
@ -559,7 +553,7 @@ public class Map
new("Turkey"), new("Turkey"),
]; ];
return new(MapType.Classical, provinces, powers); return new(provinces, powers);
}); });
#endregion Variants #endregion Variants

View File

@ -20,10 +20,4 @@ public static class ModelExtensions
{ {
return $"{coord.season.Timeline}-{coord.province.Abbreviations[0]}@{coord.season.Turn}"; return $"{coord.season.Timeline}-{coord.province.Abbreviations[0]}@{coord.season.Turn}";
} }
}
public static World ContinueOrFork(this World world, Season season, out Season future)
{
future = world.ContinueOrFork(season);
return world.Update(seasons: world.Seasons.Append(future));
}
}

View File

@ -13,7 +13,7 @@ public class OrderHistory
public Dictionary<MoveOrder, bool> DoesMoveOutcomes; public Dictionary<MoveOrder, bool> DoesMoveOutcomes;
public OrderHistory() public OrderHistory()
: this([], [], []) : this(new(), new(), new())
{} {}
public OrderHistory( public OrderHistory(

View File

@ -5,10 +5,10 @@ namespace MultiversalDiplomacy.Model;
/// <summary> /// <summary>
/// Represents a state of the map produced by a set of move orders on a previous season. /// Represents a state of the map produced by a set of move orders on a previous season.
/// </summary> /// </summary>
public class Season(string? past, int turn, string timeline) public class Season
{ {
/// <summary> /// <summary>
/// The first turn number. This is defined to reduce confusion about whether the first turn is 0 or 1. /// The first turn number.
/// </summary> /// </summary>
public const int FIRST_TURN = 0; public const int FIRST_TURN = 0;
@ -17,19 +17,19 @@ public class Season(string? past, int turn, string timeline)
/// If this season is an alternate timeline root, the past is from the origin timeline. /// If this season is an alternate timeline root, the past is from the origin timeline.
/// The initial season does not have a past. /// The initial season does not have a past.
/// </summary> /// </summary>
public string? Past { get; } = past; public string? Past { get; }
/// <summary> /// <summary>
/// The current turn, beginning at 0. Each season (spring and fall) is one turn. /// The current turn, beginning at 0. Each season (spring and fall) is one turn.
/// Phases that only occur after the fall phase occur when Turn % 2 == 1. /// Phases that only occur after the fall phase occur when Turn % 2 == 1.
/// The current year is (Turn / 2) + 1901. /// The current year is (Turn / 2) + 1901.
/// </summary> /// </summary>
public int Turn { get; } = turn; public int Turn { get; }
/// <summary> /// <summary>
/// The timeline to which this season belongs. /// The timeline to which this season belongs.
/// </summary> /// </summary>
public string Timeline { get; } = timeline; public string Timeline { get; }
/// <summary> /// <summary>
/// The multiversal designation of this season. /// The multiversal designation of this season.
@ -37,5 +37,50 @@ public class Season(string? past, int turn, string timeline)
[JsonIgnore] [JsonIgnore]
public string Designation => $"{this.Timeline}{this.Turn}"; public string Designation => $"{this.Timeline}{this.Turn}";
/// <summary>
/// The season's multiversal location as a timeline-turn tuple for convenience.
/// </summary>
[JsonIgnore]
public (string Timeline, int Turn) Coord => (this.Timeline, this.Turn);
/// <summary>
/// The shared timeline number generator.
/// </summary>
[JsonIgnore]
private TimelineFactory Timelines { get; }
private Season(Season? past, int turn, string timeline, TimelineFactory factory)
{
this.Past = past?.ToString();
this.Turn = turn;
this.Timeline = timeline;
this.Timelines = factory;
}
public override string ToString() => Designation; public override string ToString() => Designation;
/// <summary>
/// Create a root season at the beginning of time.
/// </summary>
public static Season MakeRoot()
{
TimelineFactory factory = new TimelineFactory();
return new Season(
past: null,
turn: FIRST_TURN,
timeline: factory.NextTimeline(),
factory: factory);
}
/// <summary>
/// Create a season immediately after this one in the same timeline.
/// </summary>
public Season MakeNext()
=> new(this, Turn + 1, Timeline, Timelines);
/// <summary>
/// Create a season immediately after this one in a new timeline.
/// </summary>
public Season MakeFork()
=> new(this, Turn + 1, Timelines.NextTimeline(), Timelines);
} }

View File

@ -3,7 +3,7 @@ namespace MultiversalDiplomacy.Model;
/// <summary> /// <summary>
/// A shared counter for handing out new timeline designations. /// A shared counter for handing out new timeline designations.
/// </summary> /// </summary>
public class TimelineFactory internal class TimelineFactory
{ {
private static readonly char[] Letters = [ private static readonly char[] Letters = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',

View File

@ -1,5 +1,3 @@
using System.Text.Json.Serialization;
namespace MultiversalDiplomacy.Model; namespace MultiversalDiplomacy.Model;
/// <summary> /// <summary>
@ -20,7 +18,6 @@ public class Unit
/// <summary> /// <summary>
/// The province where the unit is. /// The province where the unit is.
/// </summary> /// </summary>
[JsonIgnore]
public Province Province => this.Location.Province; public Province Province => this.Location.Province;
/// <summary> /// <summary>
@ -62,11 +59,11 @@ public class Unit
/// method after accepting a build order. /// method after accepting a build order.
/// </summary> /// </summary>
public static Unit Build(Location location, Season season, Power power, UnitType type) public static Unit Build(Location location, Season season, Power power, UnitType type)
=> new(past: null, location, season, power, type); => new Unit(past: null, location, season, power, type);
/// <summary> /// <summary>
/// Advance this unit's timeline to a new location and season. /// Advance this unit's timeline to a new location and season.
/// </summary> /// </summary>
public Unit Next(Location location, Season season) public Unit Next(Location location, Season season)
=> new(past: this, location, season, this.Power, this.Type); => new Unit(past: this, location, season, this.Power, this.Type);
} }

View File

@ -1,5 +1,6 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
using MultiversalDiplomacy.Orders;
namespace MultiversalDiplomacy.Model; namespace MultiversalDiplomacy.Model;
@ -11,24 +12,16 @@ public class World
/// <summary> /// <summary>
/// The map variant of the game. /// The map variant of the game.
/// </summary> /// </summary>
[JsonIgnore] public readonly Map Map;
public Map Map { get; }
/// <summary>
/// The map variant of the game.
/// </summary>
public MapType MapType => this.Map.Type;
/// <summary> /// <summary>
/// The game map. /// The game map.
/// </summary> /// </summary>
[JsonIgnore]
public ReadOnlyCollection<Province> Provinces => this.Map.Provinces; public ReadOnlyCollection<Province> Provinces => this.Map.Provinces;
/// <summary> /// <summary>
/// The game powers. /// The game powers.
/// </summary> /// </summary>
[JsonIgnore]
public ReadOnlyCollection<Power> Powers => this.Map.Powers; public ReadOnlyCollection<Power> Powers => this.Map.Powers;
/// <summary> /// <summary>
@ -39,13 +32,11 @@ public class World
/// <summary> /// <summary>
/// Lookup for seasons by designation. /// Lookup for seasons by designation.
/// </summary> /// </summary>
[JsonIgnore]
public ReadOnlyDictionary<string, Season> SeasonLookup { get; } public ReadOnlyDictionary<string, Season> SeasonLookup { get; }
/// <summary> /// <summary>
/// The first season of the game. /// The first season of the game.
/// </summary> /// </summary>
[JsonIgnore]
public Season RootSeason => GetSeason("a0"); public Season RootSeason => GetSeason("a0");
/// <summary> /// <summary>
@ -61,12 +52,7 @@ public class World
/// <summary> /// <summary>
/// Orders given to units in each season. /// Orders given to units in each season.
/// </summary> /// </summary>
public ReadOnlyDictionary<string, OrderHistory> OrderHistory { get; } public ReadOnlyDictionary<Season, OrderHistory> OrderHistory { get; }
/// <summary>
/// The shared timeline number generator.
/// </summary>
public TimelineFactory Timelines { get; }
/// <summary> /// <summary>
/// Immutable game options. /// Immutable game options.
@ -81,8 +67,7 @@ public class World
ReadOnlyCollection<Season> seasons, ReadOnlyCollection<Season> seasons,
ReadOnlyCollection<Unit> units, ReadOnlyCollection<Unit> units,
ReadOnlyCollection<RetreatingUnit> retreatingUnits, ReadOnlyCollection<RetreatingUnit> retreatingUnits,
ReadOnlyDictionary<string, OrderHistory> orderHistory, ReadOnlyDictionary<Season, OrderHistory> orderHistory,
TimelineFactory timelines,
Options options) Options options)
{ {
this.Map = map; this.Map = map;
@ -90,7 +75,6 @@ public class World
this.Units = units; this.Units = units;
this.RetreatingUnits = retreatingUnits; this.RetreatingUnits = retreatingUnits;
this.OrderHistory = orderHistory; this.OrderHistory = orderHistory;
this.Timelines = timelines;
this.Options = options; this.Options = options;
this.SeasonLookup = new(Seasons.ToDictionary(season => $"{season.Timeline}{season.Turn}")); this.SeasonLookup = new(Seasons.ToDictionary(season => $"{season.Timeline}{season.Turn}"));
@ -104,7 +88,7 @@ public class World
ReadOnlyCollection<Season>? seasons = null, ReadOnlyCollection<Season>? seasons = null,
ReadOnlyCollection<Unit>? units = null, ReadOnlyCollection<Unit>? units = null,
ReadOnlyCollection<RetreatingUnit>? retreatingUnits = null, ReadOnlyCollection<RetreatingUnit>? retreatingUnits = null,
ReadOnlyDictionary<string, OrderHistory>? orderHistory = null, ReadOnlyDictionary<Season, OrderHistory>? orderHistory = null,
Options? options = null) Options? options = null)
: this( : this(
previous.Map, previous.Map,
@ -112,7 +96,6 @@ public class World
units ?? previous.Units, units ?? previous.Units,
retreatingUnits ?? previous.RetreatingUnits, retreatingUnits ?? previous.RetreatingUnits,
orderHistory ?? previous.OrderHistory, orderHistory ?? previous.OrderHistory,
previous.Timelines,
options ?? previous.Options) options ?? previous.Options)
{ {
} }
@ -122,14 +105,12 @@ public class World
/// </summary> /// </summary>
public static World WithMap(Map map) public static World WithMap(Map map)
{ {
TimelineFactory timelines = new();
return new World( return new World(
map, map,
new([new(past: null, Season.FIRST_TURN, timelines.NextTimeline())]), new([Season.MakeRoot()]),
new([]), new([]),
new([]), new([]),
new(new Dictionary<string, OrderHistory>()), new(new Dictionary<Season, OrderHistory>()),
timelines,
new Options()); new Options());
} }
@ -143,7 +124,7 @@ public class World
IEnumerable<Season>? seasons = null, IEnumerable<Season>? seasons = null,
IEnumerable<Unit>? units = null, IEnumerable<Unit>? units = null,
IEnumerable<RetreatingUnit>? retreats = null, IEnumerable<RetreatingUnit>? retreats = null,
IEnumerable<KeyValuePair<string, OrderHistory>>? orders = null) IEnumerable<KeyValuePair<Season, OrderHistory>>? orders = null)
=> new( => new(
previous: this, previous: this,
seasons: seasons == null seasons: seasons == null
@ -219,17 +200,28 @@ public class World
} }
/// <summary> /// <summary>
/// Create a continuation of this season if it has no futures, otherwise ceate a fork. /// Create a season immediately after this one in the same timeline.
/// </summary> /// </summary>
public Season ContinueOrFork(Season season) public World ContinueSeason(string season)
=> GetFutures(season).Any() => Update(seasons: Seasons.Append(SeasonLookup[season].MakeNext()));
? new(season.Designation, season.Turn + 1, Timelines.NextTimeline())
: new(season.Designation, season.Turn + 1, season.Timeline); /// <summary>
/// Create a season immediately after this one in the same timeline.
/// </summary>
public World ContinueSeason(Season season) => ContinueSeason(season.ToString());
/// <summary>
/// Create a season immediately after this one in a new timeline.
/// </summary>
public World ForkSeason(string season)
=> Update(seasons: Seasons.Append(SeasonLookup[season].MakeFork()));
/// <summary> /// <summary>
/// A standard Diplomacy game setup. /// A standard Diplomacy game setup.
/// </summary> /// </summary>
public static World Standard => WithStandardMap().AddStandardUnits(); public static World Standard => World
.WithStandardMap()
.AddStandardUnits();
/// <summary> /// <summary>
/// Get a season by coordinate. Throws if the season is not found. /// Get a season by coordinate. Throws if the season is not found.
@ -296,10 +288,11 @@ public class World
/// <summary> /// <summary>
/// Returns a unit in a province. Throws if there are duplicate units. /// Returns a unit in a province. Throws if there are duplicate units.
/// </summary> /// </summary>
public Unit GetUnitAt(string provinceName, Season? season = null) public Unit GetUnitAt(string provinceName, (string timeline, int turn)? seasonCoord = null)
{ {
Province province = Map.GetProvince(provinceName); Province province = Map.GetProvince(provinceName);
season ??= RootSeason; seasonCoord ??= (this.RootSeason.Timeline, this.RootSeason.Turn);
Season season = GetSeason(seasonCoord.Value.timeline, seasonCoord.Value.turn);
Unit? foundUnit = this.Units.SingleOrDefault( Unit? foundUnit = this.Units.SingleOrDefault(
u => u!.Province == province && u.Season == season, u => u!.Province == province && u.Season == season,
null) null)

View File

@ -9,15 +9,9 @@ internal class Program
static void Main(string[] args) static void Main(string[] args)
{ {
var parser = Parser.Default; var parser = Parser.Default;
var parseResult = parser.ParseArguments( var parseResult = parser.ParseArguments<AdjudicateOptions>(args);
args,
typeof(AdjudicateOptions),
typeof(ImageOptions),
typeof(ReplOptions));
parseResult parseResult
.WithParsed<AdjudicateOptions>(AdjudicateOptions.Execute) .WithParsed(AdjudicateOptions.Execute);
.WithParsed<ImageOptions>(ImageOptions.Execute)
.WithParsed<ReplOptions>(ReplOptions.Execute);
} }
} }

View File

@ -43,14 +43,14 @@ public class TimeTravelTest
// Confirm that there is a unit in Tyr b1 originating from Mun a1 // Confirm that there is a unit in Tyr b1 originating from Mun a1
Season fork = world.GetSeason("b1"); Season fork = world.GetSeason("b1");
Unit originalUnit = world.GetUnitAt("Mun", s0); Unit originalUnit = world.GetUnitAt("Mun", s0.Coord);
Unit aMun0 = world.GetUnitAt("Mun", s1); Unit aMun0 = world.GetUnitAt("Mun", s1.Coord);
Unit aTyr = world.GetUnitAt("Tyr", fork); Unit aTyr = world.GetUnitAt("Tyr", fork.Coord);
Assert.That(aTyr.Past, Is.EqualTo(mun1.Order.Unit)); Assert.That(aTyr.Past, Is.EqualTo(mun1.Order.Unit));
Assert.That(aTyr.Past?.Past, Is.EqualTo(mun0.Order.Unit)); Assert.That(aTyr.Past?.Past, Is.EqualTo(mun0.Order.Unit));
// Confirm that there is a unit in Mun b1 originating from Mun a0 // Confirm that there is a unit in Mun b1 originating from Mun a0
Unit aMun1 = world.GetUnitAt("Mun", fork); Unit aMun1 = world.GetUnitAt("Mun", fork.Coord);
Assert.That(aMun1.Past, Is.EqualTo(originalUnit)); Assert.That(aMun1.Past, Is.EqualTo(originalUnit));
} }
@ -92,7 +92,7 @@ public class TimeTravelTest
// Confirm that an alternate future is created. // Confirm that an alternate future is created.
World world = setup.UpdateWorld(); World world = setup.UpdateWorld();
Season fork = world.GetSeason("b1"); Season fork = world.GetSeason("b1");
Unit tyr1 = world.GetUnitAt("Tyr", fork); Unit tyr1 = world.GetUnitAt("Tyr", fork.Coord);
Assert.That( Assert.That(
tyr1.Past, tyr1.Past,
Is.EqualTo(mun0.Order.Unit), Is.EqualTo(mun0.Order.Unit),

View File

@ -206,7 +206,7 @@ public class MovementAdjudicatorTest
Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1)); Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1));
// Confirm the unit was created in the future // Confirm the unit was created in the future
Unit u2 = updated.GetUnitAt("Mun", s2); Unit u2 = updated.GetUnitAt("Mun", s2.Coord);
Assert.That(updated.Units.Count, Is.EqualTo(2)); Assert.That(updated.Units.Count, Is.EqualTo(2));
Assert.That(u2, Is.Not.EqualTo(mun1.Order.Unit)); Assert.That(u2, Is.Not.EqualTo(mun1.Order.Unit));
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit)); Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit));
@ -228,7 +228,7 @@ public class MovementAdjudicatorTest
// Update the world again // Update the world again
updated = setup.UpdateWorld(); updated = setup.UpdateWorld();
Season s3 = updated.GetSeason(s2.Timeline, s2.Turn + 1); Season s3 = updated.GetSeason(s2.Timeline, s2.Turn + 1);
Unit u3 = updated.GetUnitAt("Mun", s3); Unit u3 = updated.GetUnitAt("Mun", s3.Coord);
Assert.That(u3.Past, Is.EqualTo(mun2.Order.Unit)); Assert.That(u3.Past, Is.EqualTo(mun2.Order.Unit));
} }
@ -256,7 +256,7 @@ public class MovementAdjudicatorTest
Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1)); Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1));
// Confirm the unit was created in the future // Confirm the unit was created in the future
Unit u2 = updated.GetUnitAt("Tyr", s2); Unit u2 = updated.GetUnitAt("Tyr", s2.Coord);
Assert.That(updated.Units.Count, Is.EqualTo(2)); Assert.That(updated.Units.Count, Is.EqualTo(2));
Assert.That(u2, Is.Not.EqualTo(mun1.Order.Unit)); Assert.That(u2, Is.Not.EqualTo(mun1.Order.Unit));
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit)); Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit));
@ -278,7 +278,7 @@ public class MovementAdjudicatorTest
// Update the world again // Update the world again
updated = setup.UpdateWorld(); updated = setup.UpdateWorld();
Season s3 = updated.GetSeason(s2.Timeline, s2.Turn + 1); Season s3 = updated.GetSeason(s2.Timeline, s2.Turn + 1);
Unit u3 = updated.GetUnitAt("Mun", s3); Unit u3 = updated.GetUnitAt("Mun", s3.Coord);
Assert.That(u3.Past, Is.EqualTo(u2)); Assert.That(u3.Past, Is.EqualTo(u2));
} }
} }

View File

@ -9,22 +9,30 @@ public class SeasonTests
[Test] [Test]
public void TimelineForking() public void TimelineForking()
{ {
World world = World.WithMap(Map.Test); World world = World
Season a0 = world.GetSeason("a0"); .WithMap(Map.Test)
world = world .ContinueSeason("a0")
.ContinueOrFork(a0, out Season a1) .ContinueSeason("a1")
.ContinueOrFork(a1, out Season a2) .ContinueSeason("a2")
.ContinueOrFork(a2, out Season a3) .ForkSeason("a1")
.ContinueOrFork(a1, out Season b2) .ContinueSeason("b2")
.ContinueOrFork(b2, out Season b3) .ForkSeason("a1")
.ContinueOrFork(a1, out Season c2) .ForkSeason("a2");
.ContinueOrFork(a2, out Season d3);
Assert.That( Assert.That(
world.Seasons.Select(season => season.ToString()), world.Seasons.Select(season => season.ToString()),
Is.EquivalentTo(new List<string> { "a0", "a1", "a2", "a3", "b2", "b3", "c2", "d3" }), Is.EquivalentTo(new List<string> { "a0", "a1", "a2", "a3", "b2", "b3", "c2", "d3" }),
"Unexpected seasons"); "Unexpected seasons");
Season a0 = world.GetSeason("a0");
Season a1 = world.GetSeason("a1");
Season a2 = world.GetSeason("a2");
Season a3 = world.GetSeason("a3");
Season b2 = world.GetSeason("b2");
Season b3 = world.GetSeason("b3");
Season c2 = world.GetSeason("c2");
Season d3 = world.GetSeason("d3");
Assert.That(a0.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline"); Assert.That(a0.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");
Assert.That(a1.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline"); Assert.That(a1.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");
Assert.That(a2.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline"); Assert.That(a2.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");

View File

@ -1,98 +0,0 @@
using System.Text.Json;
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Model;
using NUnit.Framework;
namespace MultiversalDiplomacyTests;
public class SerializationTest
{
[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);
Assert.That(deserialized, Is.Not.Null, "Failed to deserialize");
}
[Test]
public void SerializeRoundTrip_MDATC_3_A_2()
{
// Set up MDATC 3.A.2
TestCaseBuilder setup = new(World.WithStandardMap(), MovementPhaseAdjudicator.Instance);
setup[("a", 0)]
.GetReference(out Season s0)
["Germany"]
.Army("Mun").MovesTo("Tyr").GetReference(out var mun0)
["Austria"]
.Army("Tyr").Holds().GetReference(out var tyr0);
setup.ValidateOrders();
Assert.That(mun0, Is.Valid);
Assert.That(tyr0, Is.Valid);
setup.AdjudicateOrders();
Assert.That(mun0, Is.Repelled);
Assert.That(tyr0, Is.NotDislodged);
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",
}));
// Deserialize the world
World reserialized = JsonSerializer.Deserialize<World>(serialized)
?? throw new AssertionException("Failed to reserialize world");
// Resume the test case
setup = new(reserialized, MovementPhaseAdjudicator.Instance);
setup[("a", 1)]
["Germany"]
.Army("Mun").Supports.Army("Mun", season: reserialized.GetSeason("a0")).MoveTo("Tyr").GetReference(out var mun1)
["Austria"]
.Army("Tyr").Holds();
setup.ValidateOrders();
Assert.That(mun1, Is.Valid);
setup.AdjudicateOrders();
Assert.That(mun1, Is.NotCut);
Assert.That(mun0, Is.Victorious);
Assert.That(tyr0, Is.Dislodged);
// Confirm that an alternate future is created.
World world = setup.UpdateWorld();
Season fork = world.GetSeason("b1");
Unit tyr1 = world.GetUnitAt("Tyr", fork);
Assert.That(
tyr1.Past,
Is.EqualTo(mun0.Order.Unit),
"Expected A Mun a0 to advance to Tyr b1");
Assert.That(
world.RetreatingUnits.Count,
Is.EqualTo(1),
"Expected A Tyr a0 to be in retreat");
Assert.That(world.RetreatingUnits.First().Unit, Is.EqualTo(tyr0.Order.Unit));
}
}

View File

@ -17,10 +17,12 @@ public class UnitTests
Season a0 = world.RootSeason; Season a0 = world.RootSeason;
Unit u1 = Unit.Build(Mun, a0, pw1, UnitType.Army); Unit u1 = Unit.Build(Mun, a0, pw1, UnitType.Army);
world = world.ContinueOrFork(a0, out Season a1); world = world.ContinueSeason(a0);
Season a1 = world.GetSeason("a1");
Unit u2 = u1.Next(Boh, a1); Unit u2 = u1.Next(Boh, a1);
_ = world.ContinueOrFork(a1, out Season a2); world = world.ContinueSeason(a1);
Season a2 = world.GetSeason("a2");
Unit u3 = u2.Next(Tyr, a2); Unit u3 = u2.Next(Tyr, a2);
Assert.That(u3.Past, Is.EqualTo(u2), "Missing unit past"); Assert.That(u3.Past, Is.EqualTo(u2), "Missing unit past");