using UnityEngine;
using System;
namespace SLSUtilities.General
{
///
/// 伪随机分布 (Pseudo-Random Distribution, PRD) 计算器。
/// 给定目标概率 P,算法会得出一个常数 C。
/// 第一次判定的概率为 C,第二次为 2C,第 N 次为 NC。
/// 在判定成功后,概率回归为 C。
///
[Serializable]
public class StableProbability
{
[SerializeField, Range(0, 1)]
private float _probability;
///
/// 设定的目标概率 (0.0 ~ 1.0)。
/// 每次更改时将自动重新计算常量 C。
///
public float Probability
{
get => _probability;
set
{
float clamped = Mathf.Clamp01(value);
if (Mathf.Abs(_probability - clamped) > 0.0001f)
{
_probability = clamped;
C = GetCFromP(_probability);
//Reset();
}
}
}
public float C { get; private set; }
public int N { get; private set; } = 1;
public StableProbability()
{
_probability = 0f;
C = 0f;
N = 1;
}
public StableProbability(float probability)
{
_probability = Mathf.Clamp01(probability);
C = GetCFromP(_probability);
N = 1;
}
///
/// 进行一次概率判定。
///
/// 如果为 true,则在失败时不增加 N,从而保持每次判定的实际概率不变;如果为 false,则在失败时增加 N,使得每次判定的实际概率逐渐接近 1。
/// 判定是否成功。如果成功,累积次数归 1;如果失败,次数递增。
public bool Roll(bool retainRealProbability = false)
{
if (_probability <= 0f) return false;
if (_probability >= 1f) return true;
float currentProb = N * C;
if (UnityEngine.Random.value <= currentProb)
{
N = 1;
return true;
}
else
{
if (!retainRealProbability) N++;
return false;
}
}
///
/// 重置当前的失败次数积累。
///
public void Reset()
{
N = 1;
}
///
/// 强制重新计算自身的C值(通常用于反序列化之后)
///
public void RecalculateC()
{
C = GetCFromP(_probability);
}
///
/// 基于目标概率 P 计算常量 C。(基于二分查找算法)
///
public static float GetCFromP(float p)
{
if (p <= 0f) return 0f;
if (p >= 1f) return 1f;
float low = 0f;
// C 的绝对上限不能超过 P 本身
float high = p;
float mid = 0f;
// 20次迭代二分查找精度足够
for (int i = 0; i < 20; i++)
{
mid = (low + high) / 2f;
float pTested = SimulatePFromC(mid);
if (Mathf.Abs(pTested - p) < 0.0001f)
break;
if (pTested < p)
low = mid;
else
high = mid;
}
return mid;
}
///
/// 给定常量 C,计算出整体期望表现出的概率 P。
///
private static float SimulatePFromC(float C)
{
if (C <= 0f) return 0f;
float expectedTrials = 0f;
float waitProbability = 1f; // 到目前为止仍然未触发的概率
int maxN = Mathf.CeilToInt(1f / C);
for (int n = 1; n <= maxN; n++)
{
float pSuccess = Mathf.Min(n * C, 1f);
float pSuccessOnN = pSuccess * waitProbability;
waitProbability *= (1f - pSuccess);
expectedTrials += n * pSuccessOnN;
}
return 1f / expectedTrials;
}
}
}