2026-04-10 14:58:03 +02:00
|
|
|
using Chessistics.Engine.Events;
|
|
|
|
|
using Chessistics.Engine.Model;
|
|
|
|
|
|
|
|
|
|
namespace Chessistics.Engine.Rules;
|
|
|
|
|
|
|
|
|
|
public static class TransferResolver
|
|
|
|
|
{
|
|
|
|
|
public static List<IWorldEvent> ResolveTransfers(BoardState state)
|
|
|
|
|
{
|
|
|
|
|
var events = new List<IWorldEvent>();
|
|
|
|
|
var participated = new HashSet<int>(); // piece IDs that already gave or received
|
|
|
|
|
var productionGave = new HashSet<Coords>(); // 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<IWorldEvent> events,
|
|
|
|
|
HashSet<int> participated, HashSet<Coords> 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;
|
|
|
|
|
|
2026-04-10 15:35:37 +02:00
|
|
|
// Find adjacent pieces without cargo that accept this cargo type
|
|
|
|
|
var receivers = GetAdjacentPiecesWithoutCargo(state, prod.Position, participated,
|
|
|
|
|
cargoType: cargoType);
|
2026-04-10 14:58:03 +02:00
|
|
|
|
|
|
|
|
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<IWorldEvent> events, HashSet<int> 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)
|
2026-04-10 15:35:37 +02:00
|
|
|
.ThenBy(p => MinDistanceToProduction(p.CurrentCell, state, p.Cargo))
|
2026-04-10 14:58:03 +02:00
|
|
|
.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
|
2026-04-10 15:25:25 +02:00
|
|
|
// Prefer receivers farther from production (push cargo forward in chain)
|
|
|
|
|
var receivers = GetAdjacentPiecesWithoutCargo(state, giver.CurrentCell, participated,
|
2026-04-10 15:35:37 +02:00
|
|
|
forwardDirection: true, cargoType: cargoType);
|
2026-04-10 14:58:03 +02:00
|
|
|
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<PieceState> GetAdjacentPiecesWithoutCargo(
|
2026-04-10 15:25:25 +02:00
|
|
|
BoardState state, Coords position, HashSet<int> participated,
|
2026-04-10 15:35:37 +02:00
|
|
|
bool forwardDirection = false, CargoType? cargoType = null)
|
2026-04-10 14:58:03 +02:00
|
|
|
{
|
|
|
|
|
var adjacent = position.GetAdjacent4(state.Width, state.Height);
|
|
|
|
|
|
2026-04-10 15:25:25 +02:00
|
|
|
var query = state.Pieces
|
2026-04-10 14:58:03 +02:00
|
|
|
.Where(p => p.Cargo == null
|
|
|
|
|
&& !participated.Contains(p.Id)
|
2026-04-10 15:35:37 +02:00
|
|
|
&& adjacent.Contains(p.CurrentCell)
|
|
|
|
|
&& (p.CargoFilter == null || cargoType == null || p.CargoFilter == cargoType))
|
2026-04-10 15:25:25 +02:00
|
|
|
.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
|
2026-04-10 15:35:37 +02:00
|
|
|
? query.ThenByDescending(p => MinDistanceToProduction(p.CurrentCell, state, cargoType))
|
|
|
|
|
: query.ThenBy(p => MinDistanceToProduction(p.CurrentCell, state, cargoType));
|
2026-04-10 15:25:25 +02:00
|
|
|
|
|
|
|
|
return sorted.ThenBy(p => p.PlacementOrder).ToList();
|
2026-04-10 14:58:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 15:35:37 +02:00
|
|
|
private static int MinDistanceToProduction(Coords cell, BoardState state, CargoType? cargoType = null)
|
2026-04-10 14:58:03 +02:00
|
|
|
{
|
2026-04-10 15:35:37 +02:00
|
|
|
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));
|
2026-04-10 14:58:03 +02:00
|
|
|
}
|
|
|
|
|
}
|