Files
Cielonos/Assets/Scripts/SLSUtilities/General/StableProbability.cs
SoulliesOfficial f26f9fd374 爆更
2026-03-20 12:07:44 -04:00

146 lines
4.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using System;
namespace SLSUtilities.General
{
/// <summary>
/// 伪随机分布 (Pseudo-Random Distribution, PRD) 计算器。
/// 给定目标概率 P算法会得出一个常数 C。
/// 第一次判定的概率为 C第二次为 2C第 N 次为 NC。
/// 在判定成功后,概率回归为 C。
/// </summary>
[Serializable]
public class StableProbability
{
[SerializeField, Range(0, 1)]
private float _probability;
/// <summary>
/// 设定的目标概率 (0.0 ~ 1.0)。
/// 每次更改时将自动重新计算常量 C。
/// </summary>
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;
}
/// <summary>
/// 进行一次概率判定。
/// </summary>
/// <param name="retainRealProbability">如果为 true则在失败时不增加 N从而保持每次判定的实际概率不变如果为 false则在失败时增加 N使得每次判定的实际概率逐渐接近 1。</param>
/// <returns>判定是否成功。如果成功,累积次数归 1如果失败次数递增。</returns>
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;
}
}
/// <summary>
/// 重置当前的失败次数积累。
/// </summary>
public void Reset()
{
N = 1;
}
/// <summary>
/// 强制重新计算自身的C值通常用于反序列化之后
/// </summary>
public void RecalculateC()
{
C = GetCFromP(_probability);
}
/// <summary>
/// 基于目标概率 P 计算常量 C。(基于二分查找算法)
/// </summary>
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;
}
/// <summary>
/// 给定常量 C计算出整体期望表现出的概率 P。
/// </summary>
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;
}
}
}