Chessistics/chessistics-tests/Rules/TransferResolverTests.cs

374 lines
15 KiB
C#
Raw Normal View History

using Chessistics.Engine.Events;
using Chessistics.Engine.Model;
using Chessistics.Engine.Rules;
using Chessistics.Tests.Helpers;
using Xunit;
namespace Chessistics.Tests.Rules;
public class TransferResolverTests
{
[Fact]
public void Production_GivesToAdjacentEmptyPiece()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(3, 0, "D", CargoType.Wood, 1, 99)
.WithStock(PieceKind.Rook, 3)
.BuildState();
// Place a piece at (1,0) — adjacent to production at (0,0)
var piece = new PieceState(1, PieceKind.Rook, new Coords(1, 0), new Coords(2, 0), 0);
piece.CurrentCell = new Coords(1, 0);
board.Pieces.Add(piece);
// Fill production buffer
board.ProductionBuffers[new Coords(0, 0)] = 1;
var events = TransferResolver.ResolveTransfers(board);
Assert.Contains(events, e => e is CargoTransferredEvent ct
&& ct.From == new Coords(0, 0)
&& ct.To == new Coords(1, 0)
&& ct.Type == CargoType.Wood);
Assert.Equal(CargoType.Wood, piece.Cargo);
Assert.Equal(0, board.ProductionBuffers[new Coords(0, 0)]);
}
[Fact]
public void Production_DoesNotGiveToPieceWithCargo()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(3, 0, "D", CargoType.Wood, 1, 99)
.WithStock(PieceKind.Rook, 3)
.BuildState();
var piece = new PieceState(1, PieceKind.Rook, new Coords(1, 0), new Coords(2, 0), 0);
piece.CurrentCell = new Coords(1, 0);
piece.Cargo = CargoType.Wood; // already carrying
board.Pieces.Add(piece);
board.ProductionBuffers[new Coords(0, 0)] = 1;
var events = TransferResolver.ResolveTransfers(board);
// No transfer from production — piece already has cargo
Assert.DoesNotContain(events, e => e is CargoTransferredEvent ct && ct.From == new Coords(0, 0));
Assert.Equal(1, board.ProductionBuffers[new Coords(0, 0)]);
}
[Fact]
public void Piece_TransfersToAdjacentEmptyPiece()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(3, 0, "D", CargoType.Wood, 1, 99)
.WithStock(PieceKind.Rook, 3)
.BuildState();
var giver = new PieceState(1, PieceKind.Rook, new Coords(1, 0), new Coords(2, 0), 0);
giver.CurrentCell = new Coords(1, 0);
giver.Cargo = CargoType.Wood;
var receiver = new PieceState(2, PieceKind.Rook, new Coords(2, 0), new Coords(3, 0), 1);
receiver.CurrentCell = new Coords(2, 0);
board.Pieces.AddRange([giver, receiver]);
var events = TransferResolver.ResolveTransfers(board);
Assert.Contains(events, e => e is CargoTransferredEvent ct
&& ct.GivingPieceId == 1 && ct.ReceivingPieceId == 2);
Assert.Null(giver.Cargo);
Assert.Equal(CargoType.Wood, receiver.Cargo);
}
[Fact]
public void Piece_DeliversToDemand()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(3, 0, "D", CargoType.Wood, 3, 99)
.WithStock(PieceKind.Rook, 3)
.BuildState();
var piece = new PieceState(1, PieceKind.Rook, new Coords(2, 0), new Coords(3, 0), 0);
piece.CurrentCell = new Coords(2, 0); // adjacent to demand at (3,0)
piece.Cargo = CargoType.Wood;
board.Pieces.Add(piece);
var events = TransferResolver.ResolveTransfers(board);
Assert.Contains(events, e => e is CargoTransferredEvent ct
&& ct.To == new Coords(3, 0) && ct.GivingPieceId == 1);
Assert.Contains(events, e => e is DemandProgressEvent dp
&& dp.Current == 1 && dp.Required == 3);
Assert.Null(piece.Cargo);
}
[Fact]
public void Piece_DoesNotDeliverWrongType()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(3, 0, "D", CargoType.Wood, 3, 99) // wants Wood
.WithStock(PieceKind.Rook, 3)
.BuildState();
var piece = new PieceState(1, PieceKind.Rook, new Coords(2, 0), new Coords(3, 0), 0);
piece.CurrentCell = new Coords(2, 0);
piece.Cargo = CargoType.Stone; // carrying Stone, demand wants Wood
board.Pieces.Add(piece);
var events = TransferResolver.ResolveTransfers(board);
Assert.DoesNotContain(events, e => e is CargoTransferredEvent ct && ct.To == new Coords(3, 0));
Assert.Equal(CargoType.Stone, piece.Cargo); // still holding
}
[Fact]
public void HigherStatus_ReceivesFirst()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(3, 0, "D", CargoType.Wood, 1, 99)
.WithStock(PieceKind.Rook, 3)
.WithStock(PieceKind.Knight, 3)
.BuildState();
// Giver piece at (1,0) with cargo
var giver = new PieceState(1, PieceKind.Rook, new Coords(1, 0), new Coords(2, 0), 0);
giver.CurrentCell = new Coords(1, 0);
giver.Cargo = CargoType.Wood;
// Two receivers: Rook (status 5) and Knight (status 3) both adjacent
var rookReceiver = new PieceState(2, PieceKind.Rook, new Coords(1, 1), new Coords(2, 1), 1);
rookReceiver.CurrentCell = new Coords(1, 1); // adjacent to (1,0)
var knightReceiver = new PieceState(3, PieceKind.Knight, new Coords(2, 0), new Coords(0, 1), 2);
knightReceiver.CurrentCell = new Coords(2, 0); // adjacent to (1,0)
board.Pieces.AddRange([giver, rookReceiver, knightReceiver]);
var events = TransferResolver.ResolveTransfers(board);
// Rook (status 5) should receive before Knight (status 3)
var transfer = events.OfType<CargoTransferredEvent>().First(e => e.GivingPieceId == 1);
Assert.Equal(2, transfer.ReceivingPieceId); // rook receives
}
[Fact]
public void HigherStatus_GivesFirst()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(3, 0, "D", CargoType.Wood, 1, 99)
.WithStock(PieceKind.Rook, 3)
.WithStock(PieceKind.Knight, 3)
.BuildState();
// Two givers: Rook (5) at (1,0) and Knight (3) at (1,1), both with cargo
var rook = new PieceState(1, PieceKind.Rook, new Coords(1, 0), new Coords(2, 0), 0);
rook.CurrentCell = new Coords(1, 0);
rook.Cargo = CargoType.Wood;
var knight = new PieceState(2, PieceKind.Knight, new Coords(1, 1), new Coords(3, 2), 1);
knight.CurrentCell = new Coords(1, 1);
knight.Cargo = CargoType.Wood;
// One receiver adjacent to both: at (2,0) — adjacent to rook at (1,0) but not to knight at (1,1)
// Let's make receiver at (1,2) — adjacent to knight (1,1) only
// Actually: receiver at (2,1) — adjacent to nothing. Let me think...
// Receiver needs to be adjacent to both. (1,0) and (1,1) share neighbor (2,0)? No, (2,0) is adjacent to (1,0) only.
// Shared neighbor: none directly... Let's change: both givers adjacent to same receiver
// Put receiver at (0, 0) — but that's production. Put receiver at (0, 1):
// (0,1) is adjacent to (1,1) [knight] and (0,0) [production], not to (1,0) [rook]
// Let's use a simpler setup:
// Rook with cargo at (2,0), Knight with cargo at (2,2), receiver at (2,1) — adjacent to both
rook.CurrentCell = new Coords(2, 0);
knight.CurrentCell = new Coords(2, 2);
var receiver = new PieceState(3, PieceKind.Rook, new Coords(2, 1), new Coords(3, 1), 2);
receiver.CurrentCell = new Coords(2, 1);
board.Pieces.AddRange([rook, knight, receiver]);
var events = TransferResolver.ResolveTransfers(board);
// Rook (status 5) should give first to the receiver
var transfer = events.OfType<CargoTransferredEvent>().First();
Assert.Equal(1, transfer.GivingPieceId); // rook gives first
}
[Fact]
public void TieBreaker_ClockwiseDirection_EvenTurn()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(3, 0, "D", CargoType.Wood, 1, 99)
.WithStock(PieceKind.Knight, 5)
.BuildState();
// Giver at (1,1) with cargo, two receivers: right(2,1) and up(1,2)
// On even turn (TurnNumber=0), clockwise from right: right=0 < up=1
var giver = new PieceState(1, PieceKind.Rook, new Coords(1, 1), new Coords(2, 1), 0);
giver.CurrentCell = new Coords(1, 1);
giver.Cargo = CargoType.Wood;
var receiverRight = new PieceState(2, PieceKind.Rook, new Coords(2, 1), new Coords(3, 1), 1);
receiverRight.CurrentCell = new Coords(2, 1); // right of giver
var receiverUp = new PieceState(3, PieceKind.Rook, new Coords(1, 2), new Coords(1, 3), 2);
receiverUp.CurrentCell = new Coords(1, 2); // up of giver
board.Pieces.AddRange([giver, receiverRight, receiverUp]);
board.TurnNumber = 2; // even turn
var events = TransferResolver.ResolveTransfers(board);
var transfer = events.OfType<CargoTransferredEvent>().First();
// On even turn, right(0) has priority over up(1)
Assert.Equal(2, transfer.ReceivingPieceId); // receiverRight
}
[Fact]
public void TieBreaker_ClockwiseDirection_OddTurn()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(3, 0, "D", CargoType.Wood, 1, 99)
.WithStock(PieceKind.Knight, 5)
.BuildState();
// Same setup but on odd turn: left=0 < down=1 < right=2 < up=3
var giver = new PieceState(1, PieceKind.Rook, new Coords(1, 1), new Coords(2, 1), 0);
giver.CurrentCell = new Coords(1, 1);
giver.Cargo = CargoType.Wood;
var receiverRight = new PieceState(2, PieceKind.Rook, new Coords(2, 1), new Coords(3, 1), 1);
receiverRight.CurrentCell = new Coords(2, 1); // right of giver
var receiverLeft = new PieceState(3, PieceKind.Rook, new Coords(0, 1), new Coords(0, 2), 2);
receiverLeft.CurrentCell = new Coords(0, 1); // left of giver
board.Pieces.AddRange([giver, receiverRight, receiverLeft]);
board.TurnNumber = 1; // odd turn
var events = TransferResolver.ResolveTransfers(board);
var transfer = events.OfType<CargoTransferredEvent>().First();
// On odd turn, left(0) has priority over right(2)
Assert.Equal(3, transfer.ReceivingPieceId); // receiverLeft
}
[Fact]
public void Cargo_MovesOneHopPerTurn()
{
var board = new BoardBuilder(5, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(4, 0, "D", CargoType.Wood, 1, 99)
.WithStock(PieceKind.Rook, 5)
.BuildState();
// Chain of 3 pieces: A(0,0→1,0), B(1,0→2,0), C(2,0→3,0)
// All currently at their start cells with A having cargo
var a = new PieceState(1, PieceKind.Rook, new Coords(0, 0), new Coords(1, 0), 0);
a.CurrentCell = new Coords(1, 0); // at end cell, adjacent to B
a.Cargo = CargoType.Wood;
var b = new PieceState(2, PieceKind.Rook, new Coords(1, 0), new Coords(2, 0), 1);
b.CurrentCell = new Coords(2, 0); // at end cell, adjacent to C
var c = new PieceState(3, PieceKind.Rook, new Coords(2, 0), new Coords(3, 0), 2);
c.CurrentCell = new Coords(3, 0); // at end cell
board.Pieces.AddRange([a, b, c]);
var events = TransferResolver.ResolveTransfers(board);
// A gives to B (adjacent: (1,0)→(2,0))
Assert.Contains(events, e => e is CargoTransferredEvent ct && ct.GivingPieceId == 1 && ct.ReceivingPieceId == 2);
// B should NOT give to C in the same turn (B just received, participated)
Assert.DoesNotContain(events, e => e is CargoTransferredEvent ct && ct.GivingPieceId == 2 && ct.ReceivingPieceId == 3);
}
[Fact]
public void NoCrossTransfer_NonAdjacent()
{
var board = new BoardBuilder(5, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(4, 0, "D", CargoType.Wood, 1, 99)
.WithStock(PieceKind.Rook, 5)
.BuildState();
var giver = new PieceState(1, PieceKind.Rook, new Coords(0, 0), new Coords(1, 0), 0);
giver.CurrentCell = new Coords(0, 0);
giver.Cargo = CargoType.Wood;
var farPiece = new PieceState(2, PieceKind.Rook, new Coords(2, 0), new Coords(3, 0), 1);
farPiece.CurrentCell = new Coords(2, 0); // 2 cells away, not adjacent
board.Pieces.AddRange([giver, farPiece]);
var events = TransferResolver.ResolveTransfers(board);
// No piece-to-piece transfer — they're not adjacent
Assert.DoesNotContain(events, e => e is CargoTransferredEvent ct && ct.GivingPieceId == 1 && ct.ReceivingPieceId == 2);
}
[Fact]
public void Production_Amount3_FeedsMultiplePieces()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood, amount: 3)
.WithDemand(3, 0, "D", CargoType.Wood, 10, 99)
.WithStock(PieceKind.Rook, 5)
.BuildState();
// Three pieces adjacent to production at (0,0): (1,0), (0,1)
var p1 = new PieceState(1, PieceKind.Rook, new Coords(1, 0), new Coords(2, 0), 0);
p1.CurrentCell = new Coords(1, 0);
var p2 = new PieceState(2, PieceKind.Rook, new Coords(0, 1), new Coords(0, 2), 1);
p2.CurrentCell = new Coords(0, 1);
board.Pieces.AddRange([p1, p2]);
board.ProductionBuffers[new Coords(0, 0)] = 3; // amount=3
var events = TransferResolver.ResolveTransfers(board);
// Both pieces should receive cargo (buffer had 3, 2 pieces adjacent)
Assert.Equal(CargoType.Wood, p1.Cargo);
Assert.Equal(CargoType.Wood, p2.Cargo);
Assert.Equal(1, board.ProductionBuffers[new Coords(0, 0)]); // 3 - 2 = 1 remaining
}
[Fact]
public void DemandPriority_OverPieceReceiver()
{
var board = new BoardBuilder(4, 4)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(2, 0, "D", CargoType.Wood, 3, 99)
.WithStock(PieceKind.Rook, 5)
.BuildState();
// Piece with cargo at (1,0) adjacent to both demand at (2,0) and empty piece at (1,1)
var giver = new PieceState(1, PieceKind.Rook, new Coords(1, 0), new Coords(2, 0), 0);
giver.CurrentCell = new Coords(1, 0);
giver.Cargo = CargoType.Wood;
var receiver = new PieceState(2, PieceKind.Rook, new Coords(1, 1), new Coords(2, 1), 1);
receiver.CurrentCell = new Coords(1, 1); // adjacent to giver
board.Pieces.AddRange([giver, receiver]);
var events = TransferResolver.ResolveTransfers(board);
// Should deliver to demand, not to piece
Assert.Contains(events, e => e is CargoTransferredEvent ct && ct.To == new Coords(2, 0));
Assert.DoesNotContain(events, e => e is CargoTransferredEvent ct && ct.ReceivingPieceId == 2);
}
}