一个防Dota2的伪随机计算类
//创建者:Icarus
//手动滑稽,滑稽脸
//ヾ(•ω•`)o
//https://www.ykls.app
//2019年07月18日-18:13
//Assembly-CSharp
using System;
using System.Collections.Generic;
namespace CabinIcarus.LifeC.Utils
{
/// <summary>
/// https://dota2.gamepedia.com/Random_distribution
/// C计算: https://gaming.stackexchange.com/questions/161430/calculating-the-constant-c-in-dota-2-pseudo-random-distribution
/// http://archive.playdota.com/threads/article-pseudo-random-distribution.7993/
///
/// 防Dota2的伪随机计算
/// 1. 初始化,设置随机数种子
/// 2. 获取C数
/// 3. 计算命中
/// </summary>
public class DotaRandom
{
/// <summary>
/// 决定命中值
/// </summary>
public static int SurePercentage = 100;
/// <summary>
/// 伪随机种子
/// </summary>
public static int Seed { get; private set; } = 596;
private static System.Random _random
{
get
{
if (__random == null)
{
throw new Exception($"No Init. Please Call {nameof(DotaRandom)}.{nameof(DotaRandom.Init)}");
}
return __random;
}
set { __random = value; }
}
private static System.Random __random;
private static Dictionary<int, decimal> _CCache;
/// <summary>
/// 初始化随机器
/// </summary>
/// <param name="seed"></param>
public static void Init(int seed)
{
_random = new System.Random(seed);
_CCache = new Dictionary<int, decimal>();
}
/// <summary>
/// 计算命中并设置Count
/// </summary>
/// <param name="c">c值</param>
/// <param name="count">次数值,如果命中会被重置为1,否则递增</param>
/// <returns></returns>
public static bool RandomHitAndSetCount(in decimal c, ref int count)
{
if (count < 1)
{
count = 1;
}
var hit = RandomHit(c, count);
if (hit)
{
count = 1;
return true;
}
count++;
return false;
}
/// <summary>
/// 计算命中
/// </summary>
/// <param name="c">c值</param>
/// <param name="count">次数值</param>
/// <exception cref="ArgumentException"><see cref="count"/>参数 小于1时引发</exception>
/// <returns></returns>
public static bool RandomHit(in decimal c,in int count)
{
if (count < 1)
{
throw new ArgumentException("Less than 1",nameof(count));
}
var value = _random.Next(1, SurePercentage + 1);
var currentP = GetPseudo(c, count);
if (value <= (currentP * SurePercentage))
{
return true;
}
return false;
}
/// <summary>
/// 获取当前概率
/// </summary>
/// <param name="c">c值</param>
/// <param name="count">次数,最小为1</param>
/// <returns></returns>
private static decimal GetPseudo(in decimal c, in int count)
{
return c * count;
}
/// <summary>
/// 获取C值
/// </summary>
/// <param name="p">基础概率</param>
/// <returns></returns>
public static decimal GetC(int p)
{
if (!_CCache.TryGetValue(p, out var c))
{
c = CfromP((decimal) (p / (float) SurePercentage));
_CCache.Add(p, c);
}
return c;
}
/// <summary>
/// 获取 C 数
/// </summary>
/// <param name="p">基础概率</param>
/// <returns></returns>
private static decimal CfromP(decimal p)
{
decimal Cupper = p;
decimal Clower = 0m;
decimal Cmid;
decimal p1;
decimal p2 = 1m;
while (true)
{
Cmid = (Cupper + Clower) / 2m;
p1 = PfromC(Cmid);
if (Math.Abs(p1 - p2) <= 0m) break;
if (p1 > p)
{
Cupper = Cmid;
}
else
{
Clower = Cmid;
}
p2 = p1;
}
return Cmid;
}
private static decimal PfromC(decimal C)
{
decimal pProcOnN = 0m;
decimal pProcByN = 0m;
decimal sumNpProcOnN = 0m;
int maxFails = (int) Math.Ceiling(1m / C);
for (int N = 1; N <= maxFails; ++N)
{
pProcOnN = Math.Min(1m, N * C) * (1m - pProcByN);
pProcByN += pProcOnN;
sumNpProcOnN += N * pProcOnN;
}
return (1m / sumNpProcOnN);
}
}
}
使用例子
//创建者:Icarus
//手动滑稽,滑稽脸
//ヾ(•ω•`)o
//https://www.ykls.app
//2019年07月09日-21:53
//Assembly-CSharp
using CabinIcarus.LifeC.Utils;
using UnityEngine;
namespace CabinIcarus.LifeC.Test
{
public class RandomTest : MonoBehaviour
{
public int Percentage = 20;
public int RandomSeed = 20;
[SerializeField] private int _count;
private void Awake()
{
DotaRandom.Init(RandomSeed);
}
public int ForCount = 100;
[ContextMenu("System Random_N次计算")]
public void _percentage_100()
{
var c = DotaRandom.GetC(Percentage);
_count = 1;
var hitCount = 0;
for (var i = 0; i < ForCount; i++)
{
if (DotaRandom.RandomHit(c, ref _count))
{
++hitCount;
}
}
Debug.LogError($"{Percentage}%概率,{ForCount}次,命中了{hitCount}次");
}
}
}