using System.Collections.Generic;
using UnityEngine;
using System.Linq; // for OrderByDescending
namespace Vampirefall.DecisionSystem
{
public class DecisionEngine<T>
{
private readonly List<IScorer<T>> _scorers = new List<IScorer<T>>();
private readonly List<IFilter<T>> _filters = new List<IFilter<T>>();
// --- 配置方法 ---
public DecisionEngine<T> AddScorer(IScorer<T> scorer)
{
_scorers.Add(scorer);
return this; // 链式调用
}
public DecisionEngine<T> AddFilter(IFilter<T> filter)
{
_filters.Add(filter);
return this;
}
// --- 核心逻辑 A: 选出最优解 (Best Choice) ---
// 适用于:AI索敌、自动拾取
public T SelectBest(IEnumerable<T> candidates, DecisionContext ctx)
{
T bestCandidate = default;
float maxScore = float.MinValue;
bool foundAny = false;
foreach (var candidate in candidates)
{
// 1. 过滤 (Hard Filter)
if (!PassesFilters(candidate, ctx)) continue;
// 2. 评分 (Scoring)
float currentScore = 0f;
for (int i = 0; i < _scorers.Count; i++)
{
currentScore += _scorers[i].Evaluate(candidate, ctx);
}
// 3. 比较 (Comparison)
if (currentScore > maxScore)
{
maxScore = currentScore;
bestCandidate = candidate;
foundAny = true;
}
}
return foundAny ? bestCandidate : default;
}
// --- 核心逻辑 B: 加权随机 (Weighted Random) ---
// 适用于:掉落、抽卡
public T SelectRandom(IEnumerable<T> candidates, DecisionContext ctx)
{
// 临时列表用于存储通过过滤的候选项及其权重
// 注意:生产环境应使用 ListPool 避免 GC
List<T> validCandidates = new List<T>();
List<float> weights = new List<float>();
float totalWeight = 0f;
foreach (var candidate in candidates)
{
if (!PassesFilters(candidate, ctx)) continue;
float weight = 0f;
for (int i = 0; i < _scorers.Count; i++)
{
weight += _scorers[i].Evaluate(candidate, ctx);
}
// 权重必须非负
if (weight <= 0) continue;
validCandidates.Add(candidate);
weights.Add(weight);
totalWeight += weight;
}
if (validCandidates.Count == 0) return default;
// 轮盘赌算法 (Roulette Wheel Selection)
float randomValue = Random.Range(0f, totalWeight);
float runningTotal = 0f;
for (int i = 0; i < weights.Count; i++)
{
runningTotal += weights[i];
if (randomValue <= runningTotal)
{
return validCandidates[i];
}
}
// Fallback for floating point inaccuracies or if randomValue is exactly totalWeight
return validCandidates.LastOrDefault();
}
private bool PassesFilters(T candidate, DecisionContext ctx)
{
for (int i = 0; i < _filters.Count; i++)
{
if (!_filters[i].IsValid(candidate, ctx)) return false;
}
return true;
}
}
}