Github Gist

//创建者: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}次");
		}
	}
}