Compare commits
7 Commits
b17ce9485a
...
c9bd8c8194
Author | SHA1 | Date |
---|---|---|
Tim Van Baak | c9bd8c8194 | |
Tim Van Baak | 5989970c42 | |
Tim Van Baak | cc2c29980a | |
Tim Van Baak | 984676f587 | |
Tim Van Baak | fd8c725286 | |
Tim Van Baak | 0dec1e1eec | |
Tim Van Baak | 27ffaccd20 |
|
@ -61,25 +61,25 @@ public class MovementDecisions
|
|||
case MoveOrder move:
|
||||
AdvanceTimeline.Ensure(
|
||||
move.Season,
|
||||
() => new(move.Season, world.OrderHistory[move.Season].Orders));
|
||||
() => new(move.Season, world.OrderHistory[move.Season.Designation].Orders));
|
||||
AdvanceTimeline[move.Season].Orders.Add(move);
|
||||
break;
|
||||
|
||||
case SupportHoldOrder supportHold:
|
||||
AdvanceTimeline.Ensure(
|
||||
supportHold.Target.Season,
|
||||
() => new(supportHold.Target.Season, world.OrderHistory[supportHold.Target.Season].Orders));
|
||||
() => new(supportHold.Target.Season, world.OrderHistory[supportHold.Target.Season.Designation].Orders));
|
||||
AdvanceTimeline[supportHold.Target.Season].Orders.Add(supportHold);
|
||||
break;
|
||||
|
||||
case SupportMoveOrder supportMove:
|
||||
AdvanceTimeline.Ensure(
|
||||
supportMove.Target.Season,
|
||||
() => new(supportMove.Target.Season, world.OrderHistory[supportMove.Target.Season].Orders));
|
||||
() => new(supportMove.Target.Season, world.OrderHistory[supportMove.Target.Season.Designation].Orders));
|
||||
AdvanceTimeline[supportMove.Target.Season].Orders.Add(supportMove);
|
||||
AdvanceTimeline.Ensure(
|
||||
supportMove.Season,
|
||||
() => new(supportMove.Season, world.OrderHistory[supportMove.Season].Orders));
|
||||
() => new(supportMove.Season, world.OrderHistory[supportMove.Season.Designation].Orders));
|
||||
AdvanceTimeline[supportMove.Season].Orders.Add(supportMove);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
// record of when a future season has been created.
|
||||
Dictionary<Season, Season> createdFutures = new();
|
||||
List<Unit> createdUnits = new();
|
||||
List<RetreatingUnit> retreats = new();
|
||||
Dictionary<Season, Season> createdFutures = [];
|
||||
List<Unit> createdUnits = [];
|
||||
List<RetreatingUnit> retreats = [];
|
||||
|
||||
// Populate createdFutures with the timeline fork decisions
|
||||
logger.Log(1, "Processing AdvanceTimeline decisions");
|
||||
|
@ -324,9 +324,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
if (advanceTimeline.Outcome == true)
|
||||
{
|
||||
// A timeline that doesn't have a future yet simply continues. Otherwise, it forks.
|
||||
createdFutures[advanceTimeline.Season] = !world.GetFutures(advanceTimeline.Season).Any()
|
||||
? advanceTimeline.Season.MakeNext()
|
||||
: advanceTimeline.Season.MakeFork();
|
||||
createdFutures[advanceTimeline.Season] = world.ContinueOrFork(advanceTimeline.Season);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -386,11 +384,11 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
}
|
||||
|
||||
// Record the adjudication results to the season's order history
|
||||
Dictionary<Season, OrderHistory> newHistory = new();
|
||||
Dictionary<string, OrderHistory> newHistory = [];
|
||||
foreach (UnitOrder unitOrder in decisions.OfType<IsDislodged>().Select(d => d.Order))
|
||||
{
|
||||
newHistory.Ensure(unitOrder.Unit.Season, () => new());
|
||||
OrderHistory history = newHistory[unitOrder.Unit.Season];
|
||||
newHistory.Ensure(unitOrder.Unit.Season.Designation, () => new());
|
||||
OrderHistory history = newHistory[unitOrder.Unit.Season.Designation];
|
||||
// TODO does this add every order to every season??
|
||||
history.Orders.Add(unitOrder);
|
||||
history.IsDislodgedOutcomes[unitOrder.Unit] = dislodges[unitOrder.Unit].Outcome == true;
|
||||
|
@ -401,7 +399,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
}
|
||||
|
||||
// Log the new order history
|
||||
foreach ((Season season, OrderHistory history) in newHistory)
|
||||
foreach ((string season, OrderHistory history) in newHistory)
|
||||
{
|
||||
string verb = world.OrderHistory.ContainsKey(season) ? "Updating" : "Adding";
|
||||
logger.Log(1, "{0} history for {1}", verb, season);
|
||||
|
@ -411,7 +409,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
}
|
||||
}
|
||||
|
||||
IEnumerable<KeyValuePair<Season, OrderHistory>> updatedHistory = world.OrderHistory
|
||||
IEnumerable<KeyValuePair<string, OrderHistory>> updatedHistory = world.OrderHistory
|
||||
.Where(kvp => !newHistory.ContainsKey(kvp.Key))
|
||||
.Concat(newHistory);
|
||||
|
||||
|
@ -496,7 +494,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
IEnumerable<MoveOrder> newIncomingMoves = decision.Orders
|
||||
.OfType<MoveOrder>()
|
||||
.Where(order => order.Season == decision.Season
|
||||
&& !world.OrderHistory[order.Season].DoesMoveOutcomes.ContainsKey(order));
|
||||
&& !world.OrderHistory[order.Season.Designation].DoesMoveOutcomes.ContainsKey(order));
|
||||
foreach (MoveOrder moveOrder in newIncomingMoves)
|
||||
{
|
||||
DoesMove doesMove = decisions.DoesMove[moveOrder];
|
||||
|
@ -513,7 +511,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
|||
// 1. The outcome of a dislodge decision is changed,
|
||||
// 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.
|
||||
OrderHistory history = world.OrderHistory[decision.Season];
|
||||
OrderHistory history = world.OrderHistory[decision.Season.Designation];
|
||||
bool anyUnresolved = false;
|
||||
foreach (UnitOrder order in decision.Orders)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MultiversalDiplomacy.Model;
|
||||
|
||||
/// <summary>
|
||||
|
@ -9,8 +11,12 @@ public class Location
|
|||
{
|
||||
/// <summary>
|
||||
/// The province to which this location belongs.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Province Province { get; }
|
||||
|
||||
public string ProvinceName => Province.Name;
|
||||
|
||||
/// <summary>
|
||||
/// The location's full human-readable name.
|
||||
/// </summary>
|
||||
|
@ -29,6 +35,7 @@ public class Location
|
|||
/// <summary>
|
||||
/// The locations that border this location.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Location> Adjacents => this.AdjacentList;
|
||||
private List<Location> AdjacentList { get; set; }
|
||||
|
||||
|
|
|
@ -5,7 +5,13 @@ namespace MultiversalDiplomacy.Model;
|
|||
/// <summary>
|
||||
/// Encapsulation of the world map and playable powers constituting a Diplomacy variant.
|
||||
/// </summary>
|
||||
public class Map {
|
||||
public class Map
|
||||
{
|
||||
/// <summary>
|
||||
/// The map type.
|
||||
/// </summary>
|
||||
public MapType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The game map.
|
||||
/// </summary>
|
||||
|
@ -16,7 +22,7 @@ public class Map {
|
|||
/// </summary>
|
||||
public ReadOnlyCollection<Power> Powers { get; }
|
||||
|
||||
private Map(IEnumerable<Province> provinces, IEnumerable<Power> powers)
|
||||
private Map(MapType type, IEnumerable<Province> provinces, IEnumerable<Power> powers)
|
||||
{
|
||||
Provinces = new(provinces.ToList());
|
||||
Powers = new(powers.ToList());
|
||||
|
@ -102,7 +108,7 @@ public class Map {
|
|||
Power a = new("Alpha");
|
||||
Power b = new("Beta");
|
||||
|
||||
return new([lef, cen, rig], [a, b]);
|
||||
return new(MapType.Test, [lef, cen, rig], [a, b]);
|
||||
});
|
||||
|
||||
public static Map Classical => _Classical.Value;
|
||||
|
@ -553,7 +559,7 @@ public class Map {
|
|||
new("Turkey"),
|
||||
];
|
||||
|
||||
return new(provinces, powers);
|
||||
return new(MapType.Classical, provinces, powers);
|
||||
});
|
||||
|
||||
#endregion Variants
|
||||
|
|
|
@ -20,4 +20,10 @@ public static class ModelExtensions
|
|||
{
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ public class OrderHistory
|
|||
public Dictionary<MoveOrder, bool> DoesMoveOutcomes;
|
||||
|
||||
public OrderHistory()
|
||||
: this(new(), new(), new())
|
||||
: this([], [], [])
|
||||
{}
|
||||
|
||||
public OrderHistory(
|
||||
|
|
|
@ -5,10 +5,10 @@ namespace MultiversalDiplomacy.Model;
|
|||
/// <summary>
|
||||
/// Represents a state of the map produced by a set of move orders on a previous season.
|
||||
/// </summary>
|
||||
public class Season
|
||||
public class Season(string? past, int turn, string timeline)
|
||||
{
|
||||
/// <summary>
|
||||
/// The first turn number.
|
||||
/// The first turn number. This is defined to reduce confusion about whether the first turn is 0 or 1.
|
||||
/// </summary>
|
||||
public const int FIRST_TURN = 0;
|
||||
|
||||
|
@ -17,19 +17,19 @@ public class Season
|
|||
/// If this season is an alternate timeline root, the past is from the origin timeline.
|
||||
/// The initial season does not have a past.
|
||||
/// </summary>
|
||||
public string? Past { get; }
|
||||
public string? Past { get; } = past;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// The current year is (Turn / 2) + 1901.
|
||||
/// </summary>
|
||||
public int Turn { get; }
|
||||
public int Turn { get; } = turn;
|
||||
|
||||
/// <summary>
|
||||
/// The timeline to which this season belongs.
|
||||
/// </summary>
|
||||
public string Timeline { get; }
|
||||
public string Timeline { get; } = timeline;
|
||||
|
||||
/// <summary>
|
||||
/// The multiversal designation of this season.
|
||||
|
@ -37,50 +37,5 @@ public class Season
|
|||
[JsonIgnore]
|
||||
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;
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace MultiversalDiplomacy.Model;
|
|||
/// <summary>
|
||||
/// A shared counter for handing out new timeline designations.
|
||||
/// </summary>
|
||||
internal class TimelineFactory
|
||||
public class TimelineFactory
|
||||
{
|
||||
private static readonly char[] Letters = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MultiversalDiplomacy.Model;
|
||||
|
||||
/// <summary>
|
||||
|
@ -18,6 +20,7 @@ public class Unit
|
|||
/// <summary>
|
||||
/// The province where the unit is.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Province Province => this.Location.Province;
|
||||
|
||||
/// <summary>
|
||||
|
@ -59,11 +62,11 @@ public class Unit
|
|||
/// method after accepting a build order.
|
||||
/// </summary>
|
||||
public static Unit Build(Location location, Season season, Power power, UnitType type)
|
||||
=> new Unit(past: null, location, season, power, type);
|
||||
=> new(past: null, location, season, power, type);
|
||||
|
||||
/// <summary>
|
||||
/// Advance this unit's timeline to a new location and season.
|
||||
/// </summary>
|
||||
public Unit Next(Location location, Season season)
|
||||
=> new Unit(past: this, location, season, this.Power, this.Type);
|
||||
=> new(past: this, location, season, this.Power, this.Type);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
using MultiversalDiplomacy.Orders;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MultiversalDiplomacy.Model;
|
||||
|
||||
|
@ -12,16 +11,24 @@ public class World
|
|||
/// <summary>
|
||||
/// The map variant of the game.
|
||||
/// </summary>
|
||||
public readonly Map Map;
|
||||
[JsonIgnore]
|
||||
public Map Map { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The map variant of the game.
|
||||
/// </summary>
|
||||
public MapType MapType => this.Map.Type;
|
||||
|
||||
/// <summary>
|
||||
/// The game map.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ReadOnlyCollection<Province> Provinces => this.Map.Provinces;
|
||||
|
||||
/// <summary>
|
||||
/// The game powers.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ReadOnlyCollection<Power> Powers => this.Map.Powers;
|
||||
|
||||
/// <summary>
|
||||
|
@ -32,11 +39,13 @@ public class World
|
|||
/// <summary>
|
||||
/// Lookup for seasons by designation.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ReadOnlyDictionary<string, Season> SeasonLookup { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The first season of the game.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Season RootSeason => GetSeason("a0");
|
||||
|
||||
/// <summary>
|
||||
|
@ -52,7 +61,12 @@ public class World
|
|||
/// <summary>
|
||||
/// Orders given to units in each season.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<Season, OrderHistory> OrderHistory { get; }
|
||||
public ReadOnlyDictionary<string, OrderHistory> OrderHistory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The shared timeline number generator.
|
||||
/// </summary>
|
||||
public TimelineFactory Timelines { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Immutable game options.
|
||||
|
@ -67,7 +81,8 @@ public class World
|
|||
ReadOnlyCollection<Season> seasons,
|
||||
ReadOnlyCollection<Unit> units,
|
||||
ReadOnlyCollection<RetreatingUnit> retreatingUnits,
|
||||
ReadOnlyDictionary<Season, OrderHistory> orderHistory,
|
||||
ReadOnlyDictionary<string, OrderHistory> orderHistory,
|
||||
TimelineFactory timelines,
|
||||
Options options)
|
||||
{
|
||||
this.Map = map;
|
||||
|
@ -75,6 +90,7 @@ public class World
|
|||
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}"));
|
||||
|
@ -88,7 +104,7 @@ public class World
|
|||
ReadOnlyCollection<Season>? seasons = null,
|
||||
ReadOnlyCollection<Unit>? units = null,
|
||||
ReadOnlyCollection<RetreatingUnit>? retreatingUnits = null,
|
||||
ReadOnlyDictionary<Season, OrderHistory>? orderHistory = null,
|
||||
ReadOnlyDictionary<string, OrderHistory>? orderHistory = null,
|
||||
Options? options = null)
|
||||
: this(
|
||||
previous.Map,
|
||||
|
@ -96,6 +112,7 @@ public class World
|
|||
units ?? previous.Units,
|
||||
retreatingUnits ?? previous.RetreatingUnits,
|
||||
orderHistory ?? previous.OrderHistory,
|
||||
previous.Timelines,
|
||||
options ?? previous.Options)
|
||||
{
|
||||
}
|
||||
|
@ -105,12 +122,14 @@ public class World
|
|||
/// </summary>
|
||||
public static World WithMap(Map map)
|
||||
{
|
||||
TimelineFactory timelines = new();
|
||||
return new World(
|
||||
map,
|
||||
new([Season.MakeRoot()]),
|
||||
new([new(past: null, Season.FIRST_TURN, timelines.NextTimeline())]),
|
||||
new([]),
|
||||
new([]),
|
||||
new(new Dictionary<Season, OrderHistory>()),
|
||||
new(new Dictionary<string, OrderHistory>()),
|
||||
timelines,
|
||||
new Options());
|
||||
}
|
||||
|
||||
|
@ -124,7 +143,7 @@ public class World
|
|||
IEnumerable<Season>? seasons = null,
|
||||
IEnumerable<Unit>? units = null,
|
||||
IEnumerable<RetreatingUnit>? retreats = null,
|
||||
IEnumerable<KeyValuePair<Season, OrderHistory>>? orders = null)
|
||||
IEnumerable<KeyValuePair<string, OrderHistory>>? orders = null)
|
||||
=> new(
|
||||
previous: this,
|
||||
seasons: seasons == null
|
||||
|
@ -200,28 +219,17 @@ public class World
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a season immediately after this one in the same timeline.
|
||||
/// Create a continuation of this season if it has no futures, otherwise ceate a fork.
|
||||
/// </summary>
|
||||
public World ContinueSeason(string season)
|
||||
=> Update(seasons: Seasons.Append(SeasonLookup[season].MakeNext()));
|
||||
|
||||
/// <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()));
|
||||
public Season ContinueOrFork(Season season)
|
||||
=> GetFutures(season).Any()
|
||||
? new(season.Designation, season.Turn + 1, Timelines.NextTimeline())
|
||||
: new(season.Designation, season.Turn + 1, season.Timeline);
|
||||
|
||||
/// <summary>
|
||||
/// A standard Diplomacy game setup.
|
||||
/// </summary>
|
||||
public static World Standard => World
|
||||
.WithStandardMap()
|
||||
.AddStandardUnits();
|
||||
public static World Standard => WithStandardMap().AddStandardUnits();
|
||||
|
||||
/// <summary>
|
||||
/// Get a season by coordinate. Throws if the season is not found.
|
||||
|
@ -288,11 +296,10 @@ public class World
|
|||
/// <summary>
|
||||
/// Returns a unit in a province. Throws if there are duplicate units.
|
||||
/// </summary>
|
||||
public Unit GetUnitAt(string provinceName, (string timeline, int turn)? seasonCoord = null)
|
||||
public Unit GetUnitAt(string provinceName, Season? season = null)
|
||||
{
|
||||
Province province = Map.GetProvince(provinceName);
|
||||
seasonCoord ??= (this.RootSeason.Timeline, this.RootSeason.Turn);
|
||||
Season season = GetSeason(seasonCoord.Value.timeline, seasonCoord.Value.turn);
|
||||
season ??= RootSeason;
|
||||
Unit? foundUnit = this.Units.SingleOrDefault(
|
||||
u => u!.Province == province && u.Season == season,
|
||||
null)
|
||||
|
|
|
@ -9,9 +9,15 @@ internal class Program
|
|||
static void Main(string[] args)
|
||||
{
|
||||
var parser = Parser.Default;
|
||||
var parseResult = parser.ParseArguments<AdjudicateOptions>(args);
|
||||
var parseResult = parser.ParseArguments(
|
||||
args,
|
||||
typeof(AdjudicateOptions),
|
||||
typeof(ImageOptions),
|
||||
typeof(ReplOptions));
|
||||
|
||||
parseResult
|
||||
.WithParsed(AdjudicateOptions.Execute);
|
||||
.WithParsed<AdjudicateOptions>(AdjudicateOptions.Execute)
|
||||
.WithParsed<ImageOptions>(ImageOptions.Execute)
|
||||
.WithParsed<ReplOptions>(ReplOptions.Execute);
|
||||
}
|
||||
}
|
|
@ -43,14 +43,14 @@ public class TimeTravelTest
|
|||
|
||||
// Confirm that there is a unit in Tyr b1 originating from Mun a1
|
||||
Season fork = world.GetSeason("b1");
|
||||
Unit originalUnit = world.GetUnitAt("Mun", s0.Coord);
|
||||
Unit aMun0 = world.GetUnitAt("Mun", s1.Coord);
|
||||
Unit aTyr = world.GetUnitAt("Tyr", fork.Coord);
|
||||
Unit originalUnit = world.GetUnitAt("Mun", s0);
|
||||
Unit aMun0 = world.GetUnitAt("Mun", s1);
|
||||
Unit aTyr = world.GetUnitAt("Tyr", fork);
|
||||
Assert.That(aTyr.Past, Is.EqualTo(mun1.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
|
||||
Unit aMun1 = world.GetUnitAt("Mun", fork.Coord);
|
||||
Unit aMun1 = world.GetUnitAt("Mun", fork);
|
||||
Assert.That(aMun1.Past, Is.EqualTo(originalUnit));
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ public class TimeTravelTest
|
|||
// Confirm that an alternate future is created.
|
||||
World world = setup.UpdateWorld();
|
||||
Season fork = world.GetSeason("b1");
|
||||
Unit tyr1 = world.GetUnitAt("Tyr", fork.Coord);
|
||||
Unit tyr1 = world.GetUnitAt("Tyr", fork);
|
||||
Assert.That(
|
||||
tyr1.Past,
|
||||
Is.EqualTo(mun0.Order.Unit),
|
||||
|
|
|
@ -206,7 +206,7 @@ public class MovementAdjudicatorTest
|
|||
Assert.That(s2.Turn, Is.EqualTo(s1.Turn + 1));
|
||||
|
||||
// Confirm the unit was created in the future
|
||||
Unit u2 = updated.GetUnitAt("Mun", s2.Coord);
|
||||
Unit u2 = updated.GetUnitAt("Mun", s2);
|
||||
Assert.That(updated.Units.Count, Is.EqualTo(2));
|
||||
Assert.That(u2, Is.Not.EqualTo(mun1.Order.Unit));
|
||||
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit));
|
||||
|
@ -228,7 +228,7 @@ public class MovementAdjudicatorTest
|
|||
// Update the world again
|
||||
updated = setup.UpdateWorld();
|
||||
Season s3 = updated.GetSeason(s2.Timeline, s2.Turn + 1);
|
||||
Unit u3 = updated.GetUnitAt("Mun", s3.Coord);
|
||||
Unit u3 = updated.GetUnitAt("Mun", s3);
|
||||
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));
|
||||
|
||||
// Confirm the unit was created in the future
|
||||
Unit u2 = updated.GetUnitAt("Tyr", s2.Coord);
|
||||
Unit u2 = updated.GetUnitAt("Tyr", s2);
|
||||
Assert.That(updated.Units.Count, Is.EqualTo(2));
|
||||
Assert.That(u2, Is.Not.EqualTo(mun1.Order.Unit));
|
||||
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit));
|
||||
|
@ -278,7 +278,7 @@ public class MovementAdjudicatorTest
|
|||
// Update the world again
|
||||
updated = setup.UpdateWorld();
|
||||
Season s3 = updated.GetSeason(s2.Timeline, s2.Turn + 1);
|
||||
Unit u3 = updated.GetUnitAt("Mun", s3.Coord);
|
||||
Unit u3 = updated.GetUnitAt("Mun", s3);
|
||||
Assert.That(u3.Past, Is.EqualTo(u2));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,30 +9,22 @@ public class SeasonTests
|
|||
[Test]
|
||||
public void TimelineForking()
|
||||
{
|
||||
World world = World
|
||||
.WithMap(Map.Test)
|
||||
.ContinueSeason("a0")
|
||||
.ContinueSeason("a1")
|
||||
.ContinueSeason("a2")
|
||||
.ForkSeason("a1")
|
||||
.ContinueSeason("b2")
|
||||
.ForkSeason("a1")
|
||||
.ForkSeason("a2");
|
||||
World world = World.WithMap(Map.Test);
|
||||
Season a0 = world.GetSeason("a0");
|
||||
world = world
|
||||
.ContinueOrFork(a0, out Season a1)
|
||||
.ContinueOrFork(a1, out Season a2)
|
||||
.ContinueOrFork(a2, out Season a3)
|
||||
.ContinueOrFork(a1, out Season b2)
|
||||
.ContinueOrFork(b2, out Season b3)
|
||||
.ContinueOrFork(a1, out Season c2)
|
||||
.ContinueOrFork(a2, out Season d3);
|
||||
|
||||
Assert.That(
|
||||
world.Seasons.Select(season => season.ToString()),
|
||||
Is.EquivalentTo(new List<string> { "a0", "a1", "a2", "a3", "b2", "b3", "c2", "d3" }),
|
||||
"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(a1.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");
|
||||
Assert.That(a2.Timeline, Is.EqualTo("a"), "Unexpected trunk timeline");
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
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));
|
||||
}
|
||||
}
|
|
@ -17,12 +17,10 @@ public class UnitTests
|
|||
Season a0 = world.RootSeason;
|
||||
Unit u1 = Unit.Build(Mun, a0, pw1, UnitType.Army);
|
||||
|
||||
world = world.ContinueSeason(a0);
|
||||
Season a1 = world.GetSeason("a1");
|
||||
world = world.ContinueOrFork(a0, out Season a1);
|
||||
Unit u2 = u1.Next(Boh, a1);
|
||||
|
||||
world = world.ContinueSeason(a1);
|
||||
Season a2 = world.GetSeason("a2");
|
||||
_ = world.ContinueOrFork(a1, out Season a2);
|
||||
Unit u3 = u2.Next(Tyr, a2);
|
||||
|
||||
Assert.That(u3.Past, Is.EqualTo(u2), "Missing unit past");
|
||||
|
|
Loading…
Reference in New Issue