DemandDef gains optional ConsumptionPerTurn and SustainTurns. When ConsumptionPerTurn > 0 the demand maintains a buffer filled by deliveries and drained each turn. Shortage fires the first turn the buffer can't cover consumption; it clears when the buffer refills. SustainedTurns counts consecutive non-shortage turns, and IsSatisfied flips to true once it meets SustainTurns — so the victory condition becomes "no shortage for N consecutive turns" as soon as a mission opts in. Classic demands (ConsumptionPerTurn = 0) behave exactly as before. TurnExecutor runs the consumption sub-phase after transfers. Two new events (DemandShortageStarted / DemandShortageCleared) let the presentation surface the state later. BoardSnapshot + CampaignLoader carry the new fields; no existing mission opts in yet, so campaign_01.json is unaffected.
99 lines
3.2 KiB
C#
99 lines
3.2 KiB
C#
using Chessistics.Engine.Commands;
|
|
using Chessistics.Engine.Events;
|
|
using Chessistics.Engine.Model;
|
|
using Chessistics.Tests.Helpers;
|
|
using Xunit;
|
|
|
|
namespace Chessistics.Tests.Simulation;
|
|
|
|
public class RecurringDemandTests
|
|
{
|
|
private SimHelper BuildRecurringLevel()
|
|
{
|
|
var level = new LevelDef
|
|
{
|
|
Width = 3,
|
|
Height = 1,
|
|
Productions = new List<ProductionDef>
|
|
{
|
|
new(new Coords(0, 0), "Scierie", CargoType.Wood, 1)
|
|
},
|
|
Demands = new List<DemandDef>
|
|
{
|
|
new(new Coords(2, 0), "Ville", CargoType.Wood, Amount: 1,
|
|
Deadline: 0, ConsumptionPerTurn: 1, SustainTurns: 3)
|
|
},
|
|
Walls = new List<Coords>(),
|
|
Stock = new List<PieceStock>
|
|
{
|
|
new(PieceKind.Rook, 1)
|
|
}
|
|
};
|
|
return SimHelper.FromLevel(level);
|
|
}
|
|
|
|
[Fact]
|
|
public void RecurringDemand_WithoutSupply_EntersShortage()
|
|
{
|
|
var sim = BuildRecurringLevel();
|
|
// No piece placed — demand never fed, but doesn't consume anything
|
|
// it doesn't have. The first turn should already flag shortage.
|
|
var events = sim.Step();
|
|
Assert.Contains(events, e => e is DemandShortageStartedEvent);
|
|
|
|
// SustainedTurns should stay 0 while in shortage
|
|
var demand = sim.Snapshot.Demands[0];
|
|
Assert.False(demand.IsSatisfied);
|
|
}
|
|
|
|
[Fact]
|
|
public void RecurringDemand_BufferFeedsConsumption()
|
|
{
|
|
// Direct unit-style test: start a recurring demand with a filled
|
|
// buffer and confirm consumption + sustain counter tick correctly.
|
|
var demand = new DemandState(
|
|
new DemandDef(new Coords(0, 0), "V", CargoType.Wood, Amount: 0,
|
|
Deadline: 0, ConsumptionPerTurn: 1, SustainTurns: 3))
|
|
{
|
|
Buffer = 5
|
|
};
|
|
|
|
// Simulate 3 turns of consumption with no deliveries: should not
|
|
// shortage (buffer covers), SustainedTurns accrues.
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
var consumed = System.Math.Min(demand.Buffer, demand.Definition.ConsumptionPerTurn);
|
|
demand.Buffer -= consumed;
|
|
if (demand.Buffer <= 0)
|
|
{
|
|
demand.InShortage = true;
|
|
demand.SustainedTurns = 0;
|
|
}
|
|
else
|
|
{
|
|
demand.InShortage = false;
|
|
demand.SustainedTurns++;
|
|
}
|
|
}
|
|
|
|
Assert.False(demand.InShortage);
|
|
Assert.Equal(3, demand.SustainedTurns);
|
|
Assert.True(demand.IsSatisfied);
|
|
}
|
|
|
|
[Fact]
|
|
public void ClassicDemand_NotRecurring_BehavesAsBefore()
|
|
{
|
|
var level = new BoardBuilder(3, 1)
|
|
.WithProduction(0, 0, "S", CargoType.Wood)
|
|
.WithDemand(2, 0, "D", CargoType.Wood, 2, 20)
|
|
.WithStock(PieceKind.Rook, 1)
|
|
.Build();
|
|
var sim = SimHelper.FromLevel(level);
|
|
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
|
|
|
|
var events = sim.StepN(20);
|
|
Assert.DoesNotContain(events, e => e is DemandShortageStartedEvent);
|
|
Assert.Contains(events, e => e is MissionCompleteEvent);
|
|
}
|
|
}
|