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] != null) .OrderBy(p => p.Position.Col).ThenBy(p => p.Position.Row) .ToList(); foreach (var prod in productions) { var cargoType = state.ProductionBuffers[prod.Position]!.Value; // Find adjacent pieces without cargo that accept this cargo type var receivers = GetAdjacentPiecesWithoutCargo(state, prod.Position, participated, cargoType: cargoType); if (receivers.Count == 0) continue; var receiver = receivers[0]; receiver.Cargo = cargoType; state.ProductionBuffers[prod.Position] = null; participated.Add(receiver.Id); productionGave.Add(prod.Position); events.Add(new CargoTransferredEvent( prod.Position, receiver.CurrentCell, cargoType, GivingPieceId: null, ReceivingPieceId: receiver.Id)); } } 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) .ThenBy(p => MinDistanceToProduction(p.CurrentCell, state, p.Cargo)) .ThenBy(p => p.PlacementOrder) .ToList(); foreach (var giver in givers) { if (participated.Contains(giver.Id)) continue; var cargoType = giver.Cargo!.Value; // Priority 1: deliver to adjacent demand var adjacentDemand = GetAdjacentCompatibleDemand(state, giver.CurrentCell, cargoType); if (adjacentDemand != null) { giver.Cargo = null; adjacentDemand.ReceivedCount++; participated.Add(giver.Id); events.Add(new CargoTransferredEvent( giver.CurrentCell, adjacentDemand.Position, cargoType, GivingPieceId: giver.Id, ReceivingPieceId: null)); events.Add(new DemandProgressEvent( adjacentDemand.Position, adjacentDemand.Name, adjacentDemand.ReceivedCount, adjacentDemand.Required)); continue; } // Priority 2: transfer to adjacent piece without cargo // Prefer receivers farther from production (push cargo forward in chain) var receivers = GetAdjacentPiecesWithoutCargo(state, giver.CurrentCell, participated, forwardDirection: true, 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( giver.CurrentCell, receiver.CurrentCell, cargoType, GivingPieceId: giver.Id, ReceivingPieceId: receiver.Id)); } } private static List GetAdjacentPiecesWithoutCargo( BoardState state, Coords position, HashSet participated, bool forwardDirection = false, CargoType? cargoType = null) { var adjacent = position.GetAdjacent4(state.Width, state.Height); var query = 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); // For piece-to-piece transfers, prefer receivers farther from production // (pushes cargo forward through relay chains instead of backward). // For production pickups, prefer receivers closer to production. var sorted = forwardDirection ? query.ThenByDescending(p => MinDistanceToProduction(p.CurrentCell, state, cargoType)) : query.ThenBy(p => MinDistanceToProduction(p.CurrentCell, state, cargoType)); return sorted.ThenBy(p => p.PlacementOrder).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.IsSatisfied && d.Cargo == cargoType && adjacent.Contains(d.Position)) .FirstOrDefault(); } private static int MinDistanceToProduction(Coords cell, BoardState state, CargoType? cargoType = null) { var productions = cargoType != null ? state.Productions.Where(kv => kv.Value.Cargo == cargoType).Select(kv => kv.Key) : state.Productions.Keys; var prodList = productions.ToList(); if (prodList.Count == 0) return int.MaxValue; return prodList.Min(p => cell.ManhattanDistance(p)); } }