using System.Collections.ObjectModel;
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders;
namespace MultiversalDiplomacyTests;
///
/// A fluent interface for defining adjudication test cases.
///
public class TestCaseBuilder
{
///
/// Context for choosing a season to define orders for.
///
public interface ISeasonContext
{
///
/// Choose a new season to define orders for.
///
public ISeasonContext this[(int turn, int timeline) seasonCoord] { get; }
///
/// Get the context for defining the orders for a power.
///
public IPowerContext this[string powerName] { get; }
///
/// Save a reference to this season.
///
public ISeasonContext GetReference(out Season season);
}
///
/// Context for defining orders given by a power.
///
public interface IPowerContext
{
///
/// Choose a new season to define orders for.
///
public ISeasonContext this[(int turn, int timeline) seasonCoord] { get; }
///
/// Get the context for defining the orders for another power.
///
public IPowerContext this[string powerName] { get; }
///
/// Define an order for an army in a province.
///
public IUnitContext Army(string provinceName, string? powerName = null);
///
/// Define an order for a fleet in a province, optionally on a specific coast.
///
public IUnitContext Fleet(string provinceName, string? coast = null, string? powerName = null);
}
///
/// Context for defining an order given to a unit.
///
public interface IUnitContext
{
///
/// Ensure the unit exists, but don't create an order for it.
///
public IPowerContext Exists();
///
/// Give the unit a hold order.
///
public IOrderDefinedContext Holds();
///
/// Give the unit a move order.
///
public IOrderDefinedContext MovesTo(string provinceName, string? coast = null);
///
/// Give the unit a convoy order.
///
public IConvoyContext Convoys { get; }
///
/// Give the unit a support order.
///
public ISupportContext Supports { get; }
}
///
/// Context for defining a convoy order.
///
public interface IConvoyContext
{
///
/// Make the convoy order target an army.
///
public IConvoyDestinationContext Army(string provinceName, string? powerName = null);
///
/// Make the convoy order target a fleet.
///
public IConvoyDestinationContext Fleet(
string provinceName,
string? coast = null,
string? powerName = null);
}
///
/// Context for defining the destination of a convoy order.
///
public interface IConvoyDestinationContext
{
///
/// Define the destination of the convoy order.
///
public IOrderDefinedContext To(string provinceName);
}
///
/// Context for defining a support order.
///
public interface ISupportContext
{
///
/// Make the support order target an army.
///
public ISupportTypeContext Army(string provinceName, string? powerName = null);
///
/// Make the support order target a fleet.
///
public ISupportTypeContext Fleet(
string provinceName,
string? coast = null,
string? powerName = null);
}
///
/// Context for defining the type of support order.
///
public interface ISupportTypeContext
{
///
/// Give the unit an order to support the target's hold order.
///
public IOrderDefinedContext Hold();
///
/// Give the unit an order to support the target's move order.
///
public IOrderDefinedContext MoveTo(string provinceName, string? coast = null);
}
///
/// Context for additional operations on a defined order or defining another order. This
/// context mimics the with additional functionality related to
/// the order that was just defined.
///
public interface IOrderDefinedContext where OrderType : Order
{
///
/// Choose a new season to define orders for.
///
public ISeasonContext this[(int turn, int timeline) seasonCoord] { get; }
///
/// Get the context for defining the orders for another power.
///
public IPowerContext this[string powerName] { get; }
///
/// Define an order for a new army in a province.
///
public IUnitContext Army(string provinceName, string? powerName = null);
///
/// Define an order for a new fleet in a province, optionally on a specific coast.
///
public IUnitContext Fleet(string provinceName, string? coast = null, string? powerName = null);
///
/// Save a reference to the order just defined.
///
public IOrderDefinedContext GetReference(out OrderReference order);
}
public World World { get; private set; }
public ReadOnlyCollection Orders { get; }
private List OrderList;
public List? ValidationResults { get; private set; }
public List? AdjudicationResults { get; private set; }
///
/// Create a test case builder that will operate on a world.
///
public TestCaseBuilder(World world)
{
this.World = world;
this.OrderList = new();
this.Orders = new(this.OrderList);
this.ValidationResults = null;
this.AdjudicationResults = null;
}
///
/// Get the context for defining the orders for a power. Defaults to the root season.
///
public IPowerContext this[string powerName] => this[(0, 0)][powerName];
///
/// Get the context for defining the orders for a season.
///
public ISeasonContext this[(int turn, int timeline) seasonCoord]
=> new SeasonContext(this, this.World.GetSeason(seasonCoord.turn, seasonCoord.timeline));
///
/// Get a unit matching a description. If no such unit exists, one is created and added to the
/// .
///
///
/// The unit type to create if the unit does not exist.
/// Per DATC 4.C.1-2, mismatching unit designations should not invalidate an order, which
/// effectively makes the designations superfluous. To support this, the test case builder
/// returns a unit that matches the power, location, and season even if the unit found is not
/// of this type.
///
private Unit GetOrBuildUnit(
Power power,
Location location,
Season season,
UnitType type)
{
foreach (Unit unit in this.World.Units)
{
if (unit.Power == power
&& unit.Location.Province == location.Province
&& unit.Season == season)
{
return unit;
}
}
// Not found
Unit newUnit = Unit.Build(location, season, power, type);
this.World = this.World.Update(units: this.World.Units.Append(newUnit));
return newUnit;
}
public List ValidateOrders(IPhaseAdjudicator adjudicator)
{
this.ValidationResults = adjudicator.ValidateOrders(this.World, this.Orders.ToList());
return this.ValidationResults;
}
public List AdjudicateOrders(IPhaseAdjudicator adjudicator)
{
if (this.ValidationResults == null)
{
throw new InvalidOperationException("Cannot adjudicate before validation");
}
List orders = this.ValidationResults
.Where(validation => validation.Valid)
.Select(validation => validation.Order)
.ToList();
this.AdjudicationResults = adjudicator.AdjudicateOrders(this.World, orders);
return this.AdjudicationResults;
}
public World UpdateWorld(IPhaseAdjudicator adjudicator)
{
if (this.AdjudicationResults == null)
{
throw new InvalidOperationException("Cannot update before adjudication");
}
this.World = adjudicator.UpdateWorld(this.World, this.AdjudicationResults);
return this.World;
}
private class SeasonContext : ISeasonContext
{
public TestCaseBuilder Builder;
public Season Season;
public SeasonContext(TestCaseBuilder Builder, Season season)
{
this.Builder = Builder;
this.Season = season;
}
public ISeasonContext this[(int turn, int timeline) seasonCoord]
=> this.Builder[(seasonCoord.turn, seasonCoord.timeline)];
public IPowerContext this[string powerName]
=> new PowerContext(this, this.Builder.World.GetPower(powerName));
public ISeasonContext GetReference(out Season season)
{
season = this.Season;
return this;
}
}
private class PowerContext : IPowerContext
{
public TestCaseBuilder Builder;
public SeasonContext SeasonContext;
public Power Power;
public PowerContext(SeasonContext seasonContext, Power Power)
{
this.Builder = seasonContext.Builder;
this.SeasonContext = seasonContext;
this.Power = Power;
}
public ISeasonContext this[(int turn, int timeline) seasonCoord]
=> this.SeasonContext[seasonCoord];
public IPowerContext this[string powerName]
=> this.SeasonContext[powerName];
public IUnitContext Army(string provinceName, string? powerName = null)
{
Power power = powerName == null
? this.Power
: this.Builder.World.GetPower(powerName);
Location location = this.Builder.World.GetLand(provinceName);
Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Army);
return new UnitContext(this, unit);
}
public IUnitContext Fleet(string provinceName, string? coast = null, string? powerName = null)
{
Power power = powerName == null
? this.Power
: this.Builder.World.GetPower(powerName);
Location location = this.Builder.World.GetWater(provinceName, coast);
Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Fleet);
return new UnitContext(this, unit);
}
}
private class UnitContext : IUnitContext
{
public TestCaseBuilder Builder;
public SeasonContext SeasonContext;
public PowerContext PowerContext;
public Unit Unit;
public UnitContext(PowerContext powerContext, Unit unit)
{
this.Builder = powerContext.Builder;
this.SeasonContext = powerContext.SeasonContext;
this.PowerContext = powerContext;
this.Unit = unit;
}
public IPowerContext Exists()
=> this.PowerContext;
public IOrderDefinedContext Holds()
{
HoldOrder order = new HoldOrder(this.PowerContext.Power, this.Unit);
this.Builder.OrderList.Add(order);
return new OrderDefinedContext(this, order);
}
public IOrderDefinedContext MovesTo(string provinceName, string? coast = null)
{
Location destination = this.Unit.Type == UnitType.Army
? this.Builder.World.GetLand(provinceName)
: this.Builder.World.GetWater(provinceName, coast);
MoveOrder moveOrder = new MoveOrder(
this.PowerContext.Power,
this.Unit,
this.SeasonContext.Season,
destination);
this.Builder.OrderList.Add(moveOrder);
return new OrderDefinedContext(this, moveOrder);
}
public IConvoyContext Convoys
=> new ConvoyContext(this);
public ISupportContext Supports
=> new SupportContext(this);
}
private class ConvoyContext : IConvoyContext
{
public TestCaseBuilder Builder;
public SeasonContext SeasonContext;
public PowerContext PowerContext;
public UnitContext UnitContext;
public ConvoyContext(UnitContext unitContext)
{
this.Builder = unitContext.Builder;
this.SeasonContext = unitContext.SeasonContext;
this.PowerContext = unitContext.PowerContext;
this.UnitContext = unitContext;
}
public IConvoyDestinationContext Army(string provinceName, string? powerName = null)
{
Power power = powerName == null
? this.PowerContext.Power
: this.Builder.World.GetPower(powerName);
Location location = this.Builder.World.GetLand(provinceName);
Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Army);
return new ConvoyDestinationContext(this, unit);
}
public IConvoyDestinationContext Fleet(
string provinceName,
string? coast = null,
string? powerName = null)
{
Power power = powerName == null
? this.PowerContext.Power
: this.Builder.World.GetPower(powerName);
Location location = this.Builder.World.GetWater(provinceName, coast);
Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Fleet);
return new ConvoyDestinationContext(this, unit);
}
}
private class ConvoyDestinationContext : IConvoyDestinationContext
{
public TestCaseBuilder Builder;
public SeasonContext SeasonContext;
public PowerContext PowerContext;
public UnitContext UnitContext;
public Unit Target;
public ConvoyDestinationContext(ConvoyContext convoyContext, Unit target)
{
this.Builder = convoyContext.Builder;
this.SeasonContext = convoyContext.SeasonContext;
this.PowerContext = convoyContext.PowerContext;
this.UnitContext = convoyContext.UnitContext;
this.Target = target;
}
public IOrderDefinedContext To(string provinceName)
{
Location location = this.Builder.World.GetLand(provinceName);
ConvoyOrder order = new ConvoyOrder(
this.PowerContext.Power,
this.UnitContext.Unit,
this.Target,
this.SeasonContext.Season,
location);
this.Builder.OrderList.Add(order);
return new OrderDefinedContext(this.UnitContext, order);
}
}
private class SupportContext : ISupportContext
{
public TestCaseBuilder Builder;
public SeasonContext SeasonContext;
public PowerContext PowerContext;
public UnitContext UnitContext;
public SupportContext(UnitContext unitContext)
{
this.Builder = unitContext.Builder;
this.SeasonContext = unitContext.SeasonContext;
this.PowerContext = unitContext.PowerContext;
this.UnitContext = unitContext;
}
public ISupportTypeContext Army(string provinceName, string? powerName = null)
{
Power power = powerName == null
? this.PowerContext.Power
: this.Builder.World.GetPower(powerName);
Location location = this.Builder.World.GetLand(provinceName);
Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Army);
return new SupportTypeContext(this, unit);
}
public ISupportTypeContext Fleet(
string provinceName,
string? coast = null,
string? powerName = null)
{
Power power = powerName == null
? this.PowerContext.Power
: this.Builder.World.GetPower(powerName);
Location location = this.Builder.World.GetWater(provinceName, coast);
Unit unit = this.Builder.GetOrBuildUnit(
power, location, this.SeasonContext.Season, UnitType.Fleet);
return new SupportTypeContext(this, unit);
}
}
private class SupportTypeContext : ISupportTypeContext
{
public TestCaseBuilder Builder;
public SeasonContext SeasonContext;
public PowerContext PowerContext;
public UnitContext UnitContext;
public Unit Target;
public SupportTypeContext(SupportContext supportContext, Unit target)
{
this.Builder = supportContext.Builder;
this.SeasonContext = supportContext.SeasonContext;
this.PowerContext = supportContext.PowerContext;
this.UnitContext = supportContext.UnitContext;
this.Target = target;
}
public IOrderDefinedContext Hold()
{
SupportHoldOrder order = new SupportHoldOrder(
this.PowerContext.Power,
this.UnitContext.Unit,
this.Target);
this.Builder.OrderList.Add(order);
return new OrderDefinedContext(this.UnitContext, order);
}
public IOrderDefinedContext MoveTo(string provinceName, string? coast = null)
{
Location destination = this.Target.Type == UnitType.Army
? this.Builder.World.GetLand(provinceName)
: this.Builder.World.GetWater(provinceName, coast);
SupportMoveOrder order = new SupportMoveOrder(
this.PowerContext.Power,
this.UnitContext.Unit,
this.Target,
this.SeasonContext.Season,
destination);
this.Builder.OrderList.Add(order);
return new OrderDefinedContext(this.UnitContext, order);
}
}
private class OrderDefinedContext : IOrderDefinedContext where OrderType : Order
{
public TestCaseBuilder Builder;
public SeasonContext SeasonContext;
public PowerContext PowerContext;
public UnitContext UnitContext;
public OrderType Order;
public OrderDefinedContext(UnitContext unitContext, OrderType order)
{
this.Builder = unitContext.Builder;
this.SeasonContext = unitContext.SeasonContext;
this.PowerContext = unitContext.PowerContext;
this.UnitContext = unitContext;
this.Order = order;
}
public ISeasonContext this[(int turn, int timeline) seasonCoord]
=> this.SeasonContext[seasonCoord];
public IPowerContext this[string powerName]
=> this.SeasonContext[powerName];
public IUnitContext Army(string provinceName, string? powerName = null)
=> this.PowerContext.Army(provinceName, powerName);
public IUnitContext Fleet(string provinceName, string? coast = null, string? powerName = null)
=> this.PowerContext.Fleet(provinceName, coast, powerName);
public IOrderDefinedContext GetReference(out OrderReference order)
{
order = new OrderReference(this.Builder, this.Order);
return this;
}
}
}