10 连抽保底的概率模型

hfdn0751 3年前
   <p>网游里有很多抽卡、开箱子之类的赌性玩法,在最开始,游戏设计者实现的时候,仅仅给这些抽取概率简单的设置了一个值。比如抽卡抽出橙卡的概率是 10% ,那么就是说,玩家每次抽一张卡,有 90% 的可能是白卡,10% 的可能是橙卡。</p>    <p>但大 R 玩家是大爷,需要小心伺候。如果感受不好,人跑了就亏大了。概率这个东西靠改进伪随机数发生器是解决不了体验问题的,大爷要是连抽 20 张都出不来橙卡,那是要怒删游戏的。</p>    <p>连抽 20 张 10% 概率橙卡一张都抽不到的机会多不?一张抽不中的概率是 0.9 ,20 张都抽不中的概率是 0.9 ^20 = 12.2% 。这可不算小数字啊。平均 8 个大 R 就会碰到一次,一下子赶跑了 1/8 的金主,这个责任小策划可担当不起。</p>    <p>所以、一般网游都会用各种规则来避免玩家出现连抽不中的尴尬。例如,我忘记是谁发明的 10 连抽规则:如果你购买一个大包连抽 10 次,我在规则上就保证里面一定至少有一张橙卡。实现它应该并不困难,按常规概率生成 10 张的卡包,如果里面没有橙卡,那么我加一张即可。</p>    <p>但如果我想把 10 抽保底的规则惠及日常抽卡的玩家该怎么做呢?</p>    <p>就是说,我希望任何玩家任何时候,接连抽了 10 张卡,我都想保证这 10 张卡里至少有一张橙卡。</p>    <p>首先,要说明的一点:如果你同时想保证橙卡掉落率是 10% ,也就是在极大范围内,比如系统投放了一万张卡片中,其中要有一千张橙卡。那么同时保证每 10 张卡里有至少一张橙卡的话,结果一定是完全不随机的,也就是必须每抽 9 张白卡,必出一张橙卡。</p>    <p>所以、如果即想要随机(出橙卡的概率稳定),又想有 10 张出一张的保底,那么橙卡投放量是一定超过 1/10 的。</p>    <p>我们之前的游戏用了个很粗暴的方案:记录玩家已经连续几次没有抽中,如果连续次数超过 9 ,就必给他一张橙卡。为什么我说这个方案粗暴,因为它其实破坏了抽卡的自然体验。虽然玩家的确更高兴了,但是概率却很不自然。不自然的方案(其实是生硬的打了个补丁)实现起来还容易出错,我们前段时间就因为实现 bug 多发放了很多稀有物品,这个 bug 就不展开说了。</p>    <p>下面来看看,为什么这么做不自然。</p>    <p>假设橙卡的掉率是 10% ,那么你在获得一张橙卡后,再抽下一张橙卡的概率就是 0.1 。下一张是白卡,再下一张是橙卡的概率是 0.9 * 0.1 ,下两张是白卡,第三张是橙卡的概率是 0.9^2 * 0.1 ……</p>    <p>后续有 10 张及 10 张以上的概率总共有多少呢?我算了一下,大约是 35% 左右。</p>    <p>我们把抽到两张橙卡之间会抽取到的白卡张数排成一个数列的话,这个数列的值的范围是 0 到正无穷。是的,非洲酋长可能永远抽不到橙卡。当然这只是理论值。</p>    <p>如果你读过大学,学的是理工科,没有逃课的话,就应该知道,这个数列是大致符合 指数分布 的。指数分布正是用来表示独立随机事件发生的时间间隔的。</p>    <p>当我们把这个数列中大于 9 的数字都强行改成 9 ,那么 9 的出现频率就陡然跳变,这是极不自然的。(分布不平滑)</p>    <p>从一致分布的随机数,转换为指数分布的随机数非常简单。</p>    <p>让我们回答前面的问题,如果我希望获得一个大约每 10 张卡里出一张橙卡的随机数列,除了每次 random 一个 [0,10) 的整数,判断证书是不是 0 以外,还有一个方法。那就是每次抽到一个橙卡后,都从一个指数分布的随机数列中取一个值出来,作为接下来会抽取到白卡的张数。按这个张数去发放白卡,等计数器减到 0 ,就发一张橙卡给玩家。这个白卡张数的数值范围是 [0, inf) 。</p>    <p>用 lua 实现的话,大概是这样的:</p>    <p>math.floor(math.log(1-math.random()) * (-rate)) 其中 rate = 10 。</p>    <p>好了,如果我们想加上 10 张保底,又想让间隔大致符合指数分布怎么办?简单:</p>    <pre>  function erand(rate)      while true do          local p = math.floor(math.log(1-math.random()) * (-rate))          if p < rate then              return p          end      end  end</pre>    <p>让产生出来的数字小于 10 的时候重来一次就好了。如果你担心这里死循环(实际并不会),也可以加上循环上限:</p>    <pre>  function erand(rate)      for i = 1, 100 do  -- 100 可以随便写          local p = math.floor(math.log(1-math.random()) * (-rate))          if p < rate then              return p          end      end      return rate-1  end</pre>    <p>当然,一旦加上了 10 张保底,单张出橙卡的概率就大大增加了,增加到多少呢?大约是 21%。如果你希望保持 10% 左右的投放率,那么保底张数大约应该设置在 23 张左右。</p>    <p>ps. 今天在公司群里讨论这个问题时,雷先生提了这么一个问题,说是可以用来做数值策划的面试题:</p>    <p>已知橙卡的抽取率是 10% ,抽一次卡是 1 块钱;而 10 连抽的包可以帮你按同样概率连抽 10 次,但如果没有抽到橙卡的话,系统会补偿一张橙卡给你,换掉 10 张白卡中的一张。</p>    <p>假设白色一文不值,只有橙卡值钱。</p>    <p>那么请问:这个 10 连抽的包到底价值多少?</p>    <p> </p>    <p>来自:http://blog.codingnow.com/2017/01/exponential_distribution.html</p>    <p> </p>