Chessistics/chessistics-engine/Rules/TransferResolver.cs

226 lines
8.7 KiB
C#
Raw Permalink Normal View History

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 A2: Transformer outputs give to adjacent pieces (like productions)
ResolveTransformerOutputTransfers(state, events, participated);
// Phase B: Pieces give to demands, transformers, 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] > 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 ResolveTransformerOutputTransfers(
BoardState state, List<IWorldEvent> events, HashSet<int> participated)
{
var transformers = state.Transformers.Values
.Where(t => state.TransformerOutputBuffers[t.Position] > 0)
.OrderBy(t => t.Position.Col).ThenBy(t => t.Position.Row)
.ToList();
foreach (var transformer in transformers)
{
var cargoType = transformer.OutputCargo;
var receivers = GetAdjacentPiecesWithoutCargo(state, transformer.Position, participated,
cargoType: cargoType);
foreach (var receiver in receivers)
{
if (state.TransformerOutputBuffers[transformer.Position] <= 0) break;
receiver.Cargo = cargoType;
state.TransformerOutputBuffers[transformer.Position]--;
participated.Add(receiver.Id);
events.Add(new CargoTransferredEvent(
state.TurnNumber, transformer.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)
.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++;
if (adjacentDemand.IsRecurring)
adjacentDemand.Buffer++;
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: deliver to adjacent transformer (input side)
var adjacentTransformer = GetAdjacentCompatibleTransformer(state, giver.CurrentCell, cargoType);
if (adjacentTransformer != null)
{
giver.Cargo = null;
state.TransformerInputBuffers[adjacentTransformer.Position]++;
participated.Add(giver.Id);
events.Add(new CargoTransferredEvent(
state.TurnNumber, giver.CurrentCell, adjacentTransformer.Position, cargoType,
GivingPieceId: giver.Id, ReceivingPieceId: null));
continue;
}
// Priority 3: 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<PieceState> GetAdjacentPiecesWithoutCargo(
BoardState state, Coords position, HashSet<int> 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();
}
private static TransformerDef? GetAdjacentCompatibleTransformer(
BoardState state, Coords position, CargoType cargoType)
{
var adjacent = position.GetAdjacent4(state.Width, state.Height);
return state.Transformers.Values
.Where(t => t.InputCargo == cargoType
&& adjacent.Contains(t.Position))
.FirstOrDefault();
}
/// <summary>
/// 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°).
/// </summary>
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;
}
}