using Chessistics.Engine.Events; using Chessistics.Engine.Model; namespace Chessistics.Engine.Rules; public static class TransferResolver { public static List ResolveTransfers(BoardState state) { var events = new List(); var participated = new HashSet(); // piece IDs that already gave or received var productionGave = new HashSet(); // productions that already gave // Phase A: Productions give to adjacent pieces ResolveProductionTransfers(state, events, participated, productionGave); // Phase B: Pieces give to demands or other pieces ResolvePieceTransfers(state, events, participated); return events; } private static void ResolveProductionTransfers( BoardState state, List events, HashSet participated, HashSet productionGave) { // Sort productions deterministically (by position) var productions = state.Productions.Values .Where(p => state.ProductionBuffers[p.Position] > 0) .OrderBy(p => p.Position.Col).ThenBy(p => p.Position.Row) .ToList(); foreach (var prod in productions) { var cargoType = prod.Cargo; // Find adjacent pieces without cargo that accept this cargo type var receivers = GetAdjacentPiecesWithoutCargo(state, prod.Position, participated, cargoType: cargoType); foreach (var receiver in receivers) { if (state.ProductionBuffers[prod.Position] <= 0) break; receiver.Cargo = cargoType; state.ProductionBuffers[prod.Position]--; participated.Add(receiver.Id); events.Add(new CargoTransferredEvent( state.TurnNumber, prod.Position, receiver.CurrentCell, cargoType, GivingPieceId: null, ReceivingPieceId: receiver.Id)); } if (state.ProductionBuffers[prod.Position] < prod.Amount) productionGave.Add(prod.Position); } } private static void ResolvePieceTransfers( BoardState state, List events, HashSet participated) { // Get all pieces with cargo that haven't participated, sorted by giver priority var givers = state.Pieces .Where(p => p.Cargo != null && !participated.Contains(p.Id)) .OrderByDescending(p => p.SocialStatus) .ThenByDescending(p => p.Level) .ToList(); foreach (var giver in givers) { if (participated.Contains(giver.Id)) continue; var cargoType = giver.Cargo!.Value; // Priority 1: deliver to adjacent demand (always accepts matching cargo, even when satisfied) var adjacentDemand = GetAdjacentCompatibleDemand(state, giver.CurrentCell, cargoType); if (adjacentDemand != null) { giver.Cargo = null; adjacentDemand.ReceivedCount++; participated.Add(giver.Id); events.Add(new CargoTransferredEvent( state.TurnNumber, giver.CurrentCell, adjacentDemand.Position, cargoType, GivingPieceId: giver.Id, ReceivingPieceId: null)); events.Add(new DemandProgressEvent( state.TurnNumber, adjacentDemand.Position, adjacentDemand.Name, adjacentDemand.ReceivedCount, adjacentDemand.Required)); continue; } // Priority 2: transfer to adjacent piece without cargo var receivers = GetAdjacentPiecesWithoutCargo(state, giver.CurrentCell, participated, cargoType: cargoType); if (receivers.Count == 0) continue; var receiver = receivers[0]; receiver.Cargo = cargoType; giver.Cargo = null; participated.Add(giver.Id); participated.Add(receiver.Id); events.Add(new CargoTransferredEvent( state.TurnNumber, giver.CurrentCell, receiver.CurrentCell, cargoType, GivingPieceId: giver.Id, ReceivingPieceId: receiver.Id)); } } private static List GetAdjacentPiecesWithoutCargo( BoardState state, Coords position, HashSet participated, CargoType? cargoType = null) { var adjacent = position.GetAdjacent4(state.Width, state.Height); return state.Pieces .Where(p => p.Cargo == null && !participated.Contains(p.Id) && adjacent.Contains(p.CurrentCell) && (p.CargoFilter == null || cargoType == null || p.CargoFilter == cargoType)) .OrderByDescending(p => p.SocialStatus) .ThenByDescending(p => p.Level) .ThenBy(p => ClockwiseOrder(p.CurrentCell, position, state.TurnNumber)) .ToList(); } private static DemandState? GetAdjacentCompatibleDemand( BoardState state, Coords position, CargoType cargoType) { var adjacent = position.GetAdjacent4(state.Width, state.Height); return state.Demands.Values .Where(d => d.Cargo == cargoType && adjacent.Contains(d.Position)) .FirstOrDefault(); } /// /// Returns a sort key (0-3) based on cardinal direction from center to piece. /// In y-up coordinates, clockwise from 0° (right): /// right(1,0)=0, up(0,1)=1, left(-1,0)=2, down(0,-1)=3 /// On even turns, start from right (0°). On odd turns, start from left (180°). /// private static int ClockwiseOrder(Coords pieceCell, Coords center, int turnNumber) { int dx = pieceCell.Col - center.Col; int dy = pieceCell.Row - center.Row; int baseOrder = (dx, dy) switch { (1, 0) => 0, // right (0, 1) => 1, // up (y-up) (-1, 0) => 2, // left (0, -1) => 3, // down (y-up) _ => 4 // non-adjacent, shouldn't happen }; // Odd turns: rotate by 2 (start from left instead of right) if (turnNumber % 2 == 1) baseOrder = (baseOrder + 2) % 4; return baseOrder; } }