C#实现PRD暴击率算法

关于什么是PRD算法这里就不赘述了,本文只涉及PRD的实现与分析。

PRD公式

P(N) = C*N

C是概率增量
N是自上次暴击之后的第几次攻击(每次暴击后重置为1,一个意思)
P(N)是当前攻击的暴击概率

我们希望给一个期望暴击概率P,然后获取对应的概率增量C。

实现

step1. 根据 C 求平均暴击概率Ptested

根据我们打机的经验,如果好几🔪都不暴击,那么暴击概率会逐步提高,直到某一🔪必定暴击。
也就是说存在一个 N m a x Nmax Nmax,当 N > = N m a x N>=Nmax N>=Nmax时, P ( N m a x ) = 1 P(Nmax)=1 P(Nmax)=1
可得出 N m a x = C e i l ( 1 / C ) Nmax = Ceil(1/C) Nmax=Ceil(1/C)

现在假设攻击Nmax次,其中只有1次暴击。设n表示第几次攻击触发暴击,可得概率分布如下:

n123n
P C C C 2 C ( 1 − C ) 2C(1-C) 2C(1C) 3 C ( 1 − 3 C + 2 C 2 ) 3C(1-3C+2C^2) 3C(13C+2C2) M i n ( 1 , n C ) ( 1 − p 1 − . . . − p n − 1 ) = p n Min(1,nC)(1-p1-...-p_{n-1})=pn Min(1,nC)(1p1...pn1)=pn

第n次触发暴击的概率等于 P(N)乘前n-1次攻击不发生暴击的概率。
也就是说, p 1 = P ( 1 ) , p 2 = P ( 1 ) ( 1 − p 1 ) p1 = P(1), p2=P(1)(1-p1) p1=P(1),p2=P(1)(1p1),依次类推。
我们都知道,离散型随机变量的期望公式(难绷,我忘了)E(n) = 1*p1+2*p2+...+n*pn。对E(n)取倒数得到平均暴击概率,具体见下:

public double PFromC(double c)
{
    double dCurrP = 0d;//第n次攻击发生暴击的概率等于 前n-1次攻击不暴击与P(n)的乘积
    double dPreSuccessP = 0d; //前n-1次攻击中发生暴击的概率
    double dPE = 0; //触发暴击所需的平均攻击次数
    int nMaxFail = (int)Math.Ceiling(1d / c); //Nmax
    for (int i = 1; i <= nMaxFail; i++)
    {
        dCurrP = Math.Min(1d, i * c) * (1 - dPreSuccessP); 
        dPreSuccessP += dCurrP; 
        dPE += i * dCurrP;
    }
    return 1d / dPE; //返回平均暴击概率Ptested
}
step2. 二分法求C
public double CFromP(double p)
{
    double dUp = p; //C在0-p范围内,C<p降低连续暴击的可能性
    double dLow = 0d;
    double dMid = p;
    double dPLast = 1d; //保证逻辑至少执行依次
    while (true)
    {
        dMid = (dUp + dLow) / 2d;//取当前最大暴击概率范围内的中值为增量C
        double dPtested = PFromC(dMid);//测试当前
        if (Math.Abs(dPtested - dPLast) <= 0.00005d) break;// 如果平均值变化不大,说明二分的区域已经很小了,此时可以退出循环。
        if (dPtested > p) dUp = dMid; //当前平均值大于期望值,向右查找
        else dLow = dMid;
        dPLast = dPtested; //保存每次二分求得的平均值,见上break处
    }
    return dMid;
}

完整代码

public class PRD
{
    //期望概率
    public double p;

    //概率增量
    public double c;

    public double PFromC(double c)
    {
        double dCurrP = 0d;//第n次攻击发生暴击的概率等于 前n-1次攻击不暴击与P(n)的乘积
        double dPreSuccessP = 0d; //前n-1次攻击中发生暴击的概率
        double dPE = 0; //触发暴击所需的平均攻击次数
        int nMaxFail = (int)Math.Ceiling(1d / c); //Nmax
        for (int i = 1; i <= nMaxFail; i++)
        {
            dCurrP = Math.Min(1d, i * c) * (1 - dPreSuccessP); 
            dPreSuccessP += dCurrP; 
            dPE += i * dCurrP;
        }
        return 1d / dPE; //返回平均暴击概率Ptested
    }

    public double CFromP(double p)
    {
        double dUp = p; //C在0-p范围内,C<p降低连续暴击的可能性
        double dLow = 0d;
        double dMid = p;
        double dPLast = 1d; //保证逻辑至少执行依次
        while (true)
        {
            dMid = (dUp + dLow) / 2d;//取当前最大暴击概率范围内的中值为增量C
            double dPtested = PFromC(dMid);//测试当前
            if (Math.Abs(dPtested - dPLast) <= 0.00005d) break;// 如果平均值变化不大,说明二分的区域已经很小了,此时可以退出循环。
            if (dPtested > p) dUp = dMid; //当前平均值大于期望值,向右查找
            else dLow = dMid;
            dPLast = dPtested; //保存每次二分求得的平均值,见上break处
        }
        return dMid;
    }

    //当前攻击的次数
    private int attackCount = 1;

    //返回是否暴击,如果没暴击则攻击次数加1,如果暴击了,则攻击次数重置为1。
    public bool IsCriticalHit()
    {
        if (new Random().Next(0, 101) <= (int)(c * 100d) * attackCount)
        {
            attackCount = 1;
            return true;
        }
        else
        {
            attackCount++;
            return false;
        }
    }
}

class Program2_3
{
    public static void Main2_3()
    {
        PRD prd = new PRD();
        //设置暴击率为0.5
        prd.p = 0.5d;
        Console.WriteLine("暴击率为" + (int)(prd.p * 100d) + "%");

        //计算概率增量
        prd.c = prd.CFromP(prd.p);
        //记录最大连续不暴击次数和最大连续暴击次数
        int maxCriticalCount = 0, maxUnCriticalCount = 0, count = 0;
        int criticalCounter = 0, uncriticalCounter = 0;
        for (int j = 0; j < 100000; j++)
        {
            if (prd.IsCriticalHit())
            {
                maxUnCriticalCount = Math.Max(uncriticalCounter, maxUnCriticalCount);
                uncriticalCounter = 0;
                criticalCounter++;
            }
            else
            {
                maxCriticalCount = Math.Max(maxCriticalCount, criticalCounter);
                criticalCounter = 0;
                uncriticalCounter++;
            }
        }

        Console.WriteLine(maxCriticalCount + " " + maxUnCriticalCount);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值