还在抱怨王者荣耀水晶难抽?PHP一文带你搞懂游戏中的抽奖算法

一、初始化奖品

  • id 奖品的id
  • pid 奖品的自定义id
  • type 奖品类型,1、虚拟奖品 2、实物奖品 3、礼包码 待扩充
  • name 奖品名称
  • total 奖品总数
  • chance 获奖概率/抽奖基数10000
  • daynum 每日数量限制
  • pay 充值限制
    1 <?php
    2 $prize = [
    3     ['id' => 1, 'pid' => 11, 'type' => 1, 'name' => '典藏英雄', 'total' => 20, 'chance' => 1000, 'daynum' => 10, 'pay' => 2000 ],
    4     ['id' => 2, 'pid' => 12, 'type' => 1, 'name' => '史诗皮肤', 'total' => 40, 'chance' => 1000, 'daynum' => 10, 'pay' => 4000 ],
    5     ['id' => 3, 'pid' => 13, 'type' => 1, 'name' => '钻石奖励', 'total' => 80, 'chance' => 1000, 'daynum' => 10, 'pay' => 4000 ],
    6     ['id' => 4, 'pid' => 14, 'type' => 1, 'name' => '荣耀水晶', 'total' => 20, 'chance' => 1000, 'daynum' => 10, 'pay' => 8000 ]
    7 ];
    •  奖品详情应该从数据库中读出来
    • 奖品详情应该加入缓存,避免数据库的压力

    二、谢谢参与

    1 <?php
    2 $thanks_prize = [
    3     'id' => 0,
    4     'pid' => 0,
    5     'type' => 1,
    6     'name' => '谢谢参与'
    7 ];
    •  为填充剩余概率的奖品

    三、过滤抽奖、如充值条件

    1 <?php
    2 $pay_total = 7000;
    3 foreach ($prize as $key => $value) {
    4     if($value['pay'] > $pay_total) unset($prize[$key]);
    5 }
    •  初步过滤一些必要因素,比如充值,角色创建时间等

    四、重组概率

     1 <?php
     2 $now_chance = array_sum(array_column($prize, 'chance'));
     3 $remain_chance = 10000 - $now_chance;
     4 $prize[] = ['id' => 0, 'pid' => 0, 'type' => 1, 'name' => '谢谢参与', 'total' => 0, 'chance' => $remain_chance, 'daynum' => 0, 'pay' => 0];
     5  
     6 $award = [];
     7 $num = 0;
     8 foreach ($prize as $_v) {
     9     $num += $_v['chance'];
    10     $award[] = ['id' => $_v['id'], 'pid' => $_v['pid'], 'type' => $_v['type'], 'name' => $_v['name'], 'total' => $_v['total'], 'chance' => $num, 'daynum' => $_v['daynum'], 'pay' => $_v['pay']];
    11 }
    •  初步过滤后,重构新的抽奖信息,加入谢谢参与
    • 第二步重组概率

    五、进行抽奖

     1 <?php
     2 $rand = mt_rand(1, 10000);
     3 $result = [];
     4 foreach ($award as $_k => $_v) {
     5     if ($_k == 0) {
     6         if ($rand > 0 && $rand <= $_v['chance']) {
     7             $result = $_v;
     8             break;
     9         }
    10     } else {
    11         if ($rand > $award[$_k - 1]['chance'] && $rand <= $_v['chance']) {
    12             $result = $_v;
    13             break;
    14         }
    15     }
    16 }
    • 开始抽奖,并返回抽中的结果

    六、过滤回调

     1 <?php
     2 //此处应该查询数据库,查看该奖品已经抽中的数量
     3 $yet_num = 50;
     4 if($result['pid'] != 0 && $yet_num > $result['total']) {
     5     $result = $thanks_prize;
     6 }
     7  
     8 //此处应该查询数据库,查看该奖品今日已经抽中的数量
     9 $yet_today_num = 50;
    10 if($result['pid'] != 0 && $yet_today_num > $result['daynum']) {
    11     $result = $thanks_prize;
    12 }
    • 二次过滤,奖品总数的限制以及奖品的每日限制等 

    七、最终抽奖结果

     1 <?php
     2 //删除敏感字段
     3 unset($result['total'],$result['chance'],$result['daynum'],$result['pay']);
     4  
     5 //返回最终抽奖结果
     6 echo json_encode([
     7     'prize' => $award,
     8     'rand' => $rand,
     9     'result' => $result
    10 ]);

    八、抽奖封装成类

      1 <?php
      2 /**
      3  * Created by PhpStorm.
      4  * User: autofelix
      5  * Date: 2020/10/30
      6  * Time: 13:14
      7  * Desc: 抽奖算法
      8  */
      9  
     10 class Lottery
     11 {
     12     /**
     13      * 概率基数
     14      * @var int
     15      */
     16     private $total_chance = 10000;
     17  
     18     /**
     19      * 谢谢参与奖励
     20      * @var array
     21      */
     22     private $thanks_prize = [
     23         'id' => 0,
     24         'pid' => 0,
     25         'type' => 1,
     26         'name' => '谢谢参与'
     27     ];
     28  
     29     /**
     30      * 奖池
     31      * @var array
     32      */
     33     private $prize = [
     34         ['id' => 1, 'pid' => 11, 'type' => 1, 'name' => '典藏英雄', 'total' => 20, 'chance' => 1000, 'daynum' => 10, 'pay' => 2000 ],
     35         ['id' => 2, 'pid' => 12, 'type' => 1, 'name' => '史诗皮肤', 'total' => 40, 'chance' => 1000, 'daynum' => 10, 'pay' => 4000 ],
     36         ['id' => 3, 'pid' => 13, 'type' => 1, 'name' => '钻石奖励', 'total' => 80, 'chance' => 1000, 'daynum' => 10, 'pay' => 4000 ],
     37         ['id' => 4, 'pid' => 14, 'type' => 1, 'name' => '荣耀水晶', 'total' => 20, 'chance' => 1000, 'daynum' => 10, 'pay' => 8000 ]
     38     ];
     39  
     40     /**
     41      * Lottery constructor.
     42      */
     43     public function __construct()
     44     {
     45     }
     46  
     47     /**
     48      * @return int
     49      */
     50     private function get_user_pay()
     51     {
     52         //这里应该调用接口,返回用户正确的充值信息
     53         return 3000;
     54     }
     55  
     56     /**
     57      * 重构奖池、重组概率
     58      * @return array
     59      */
     60     private function init_lottery_pond()
     61     {
     62         $award = [];
     63  
     64         //充值限制
     65         $user_pay = $this->get_user_pay();
     66         foreach ($this->prize as $key => $value) {
     67             if($value['pay'] <= $user_pay) unset($this->prize[$key]);
     68         }
     69  
     70         //加入谢谢惠顾
     71         $now_chance = array_sum(array_column($this->prize, 'chance'));
     72         $remain_chance = $this->total_chance - $now_chance;
     73         $this->prize[] = ['id' => 0, 'pid' => 0, 'type' => 1, 'name' => '谢谢参与', 'total' => 0, 'chance' => $remain_chance, 'daynum' => 0, 'pay' => 0];
     74  
     75         //重组概率
     76         $num = 0;
     77         foreach ($this->prize as $_v) {
     78             $num += $_v['chance'];
     79             $award[] = ['id' => $_v['id'], 'pid' => $_v['pid'], 'type' => $_v['type'], 'name' => $_v['name'], 'total' => $_v['total'], 'chance' => $num, 'daynum' => $_v['daynum'], 'pay' => $_v['pay']];
     80         }
     81  
     82         return $award;
     83     }
     84  
     85     /**
     86      * 获取抽奖结果
     87      * @return array
     88      */
     89     public function get_prize()
     90     {
     91         $award = $this->init_lottery_pond();
     92         $rand = mt_rand(1, $this->total_chance);
     93         $result = [];
     94         foreach ($award as $_k => $_v) {
     95             if ($_k == 0) {
     96                 if ($rand > 0 && $rand <= $_v['chance']) {
     97                     $result = $_v;
     98                     break;
     99                 }
    100             } else {
    101                 if ($rand > $award[$_k - 1]['chance'] && $rand <= $_v['chance']) {
    102                     $result = $_v;
    103                     break;
    104                 }
    105             }
    106         }
    107  
    108         $result = $this->filter($result);
    109         return $result;
    110     }
    111  
    112     /**
    113      * 抽奖过滤回调函数
    114      * @param $result
    115      * @return array
    116      */
    117     public function filter($result)
    118     {
    119         //奖品总数限制,此处应该查数据库
    120         $yet_num = 50;
    121         if($result['pid'] != 0 && $yet_num > $result['total']) {
    122             $result = $this->thanks_prize;
    123         }
    124  
    125         //奖品每日数量限制,此处应该查数据库
    126         $yet_today_num = 50;
    127         if($result['pid'] != 0 && $yet_today_num > $result['daynum']) {
    128             $result = $this->thanks_prize;
    129         }
    130  
    131         //不暴露敏感信息
    132         unset($result['total'], $result['chance'], $result['daynum'], $result['pay'] );
    133         return $result;
    134     }
    135  
    136     private function __clone()
    137     {
    138     }
    139 }
    140  
    141 echo json_encode((new Lottery())->get_prize());

     

上一篇:华为应用内支付无法拉起


下一篇:在核心交易分析中完成如下指标的计算