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, 2) .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)] = CargoType.Wood; 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.Null(board.ProductionBuffers[new Coords(0, 0)]); } [Fact] public void Production_DoesNotGiveToPieceWithCargo() { var board = new BoardBuilder(4, 4) .WithProduction(0, 0, "P", CargoType.Wood, 2) .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)] = CargoType.Wood; 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(CargoType.Wood, board.ProductionBuffers[new Coords(0, 0)]); } [Fact] public void Piece_TransfersToAdjacentEmptyPiece() { var board = new BoardBuilder(4, 4) .WithProduction(0, 0, "P", CargoType.Wood, 2) .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, 2) .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, 2) .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, 2) .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().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, 2) .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().First(); Assert.Equal(1, transfer.GivingPieceId); // rook gives first } [Fact] public void TieBreaker_PlacementOrder() { var board = new BoardBuilder(4, 4) .WithProduction(0, 0, "P", CargoType.Wood, 2) .WithDemand(3, 0, "D", CargoType.Wood, 1, 99) .WithStock(PieceKind.Knight, 5) .BuildState(); // Two knights (same status 3) with cargo, both adjacent to same empty receiver var knight1 = new PieceState(1, PieceKind.Knight, new Coords(2, 0), new Coords(0, 1), 0); // earlier knight1.CurrentCell = new Coords(2, 0); knight1.Cargo = CargoType.Wood; var knight2 = new PieceState(2, PieceKind.Knight, new Coords(2, 2), new Coords(0, 3), 1); // later knight2.CurrentCell = new Coords(2, 2); knight2.Cargo = CargoType.Wood; var receiver = new PieceState(3, PieceKind.Knight, new Coords(2, 1), new Coords(0, 2), 2); receiver.CurrentCell = new Coords(2, 1); // adjacent to both (2,0) and (2,2) board.Pieces.AddRange([knight1, knight2, receiver]); var events = TransferResolver.ResolveTransfers(board); var transfer = events.OfType().First(); // knight1 is closer to production at (0,0): dist = 2, knight2: dist = 4 // So knight1 gives first due to proximity (tiebreaker before placement order) Assert.Equal(1, transfer.GivingPieceId); } [Fact] public void Cargo_MovesOneHopPerTurn() { var board = new BoardBuilder(5, 4) .WithProduction(0, 0, "P", CargoType.Wood, 2) .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, 2) .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 DemandPriority_OverPieceReceiver() { var board = new BoardBuilder(4, 4) .WithProduction(0, 0, "P", CargoType.Wood, 2) .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); } }