不败君

前端萌新&初级后端攻城狮

PHP实现礼品奖池抽奖

PHP实现礼品奖池抽奖

2020-04-03 18:10:42

围观(1555)

很多 APP / 小程序 为了增强用户交互或者激励用户都会开发一套抽奖.

博主最近就遇到了这样的抽奖需求.

设置多个奖品 如:

A奖品 中奖概率百分之十 总量十个
B奖品 中奖概率百分之五 总量五个
C奖品 中奖概率百分之十五 总量十五个
D奖品 中奖概率百分之十 总量十个
F奖品 中奖概率百分之五 总量五个
G奖品 中奖概率百分之二十 总量二十个
H奖品 中奖概率百分之五 总量五个
I奖品 中奖概率百分之五 总量五个

将这些奖品信息写入数组:

$list_awards = [
    ['id' => 1, 'name' => '奖品1', 'probability' => 10, 'count' => 10],
    ['id' => 2, 'name' => '奖品2', 'probability' => 5, 'count' => 5],
    ['id' => 3, 'name' => '奖品3', 'probability' => 15, 'count' => 15],
    ['id' => 4, 'name' => '奖品4', 'probability' => 10, 'count' => 10],
    ['id' => 5, 'name' => '奖品5', 'probability' => 5, 'count' => 5],
    ['id' => 6, 'name' => '奖品6', 'probability' => 20, 'count' => 20],
    ['id' => 7, 'name' => '奖品7', 'probability' => 5, 'count' => 5],
    ['id' => 8, 'name' => '奖品8', 'probability' => 5, 'count' => 5],
];

直接上代码:

$map_list_awards = [];
foreach ($list_awards as $key => $value) {
    $map_list_awards[$value['id']] = $value;
}

// 存放奖品
$new_list_awards = [];

foreach ($map_list_awards as $row_awards) {
    // 判断余量 / 总量 当余量大于等于一时 即奖池中有足够的库存才进行抽奖
    if ($row_awards['count'] >= 1) {
        for ($i = 1; $i <= $row_awards['probability']; $i++) {
            // 按照概率存入数组 比如奖品中奖改成为五 则会入库五个
            $new_list_awards[] = $row_awards;
        }
    }
}

// 计算当前剩余的百分比
$probability = 100 - array_sum(array_column($list_awards, 'probability'));

// 将剩下的百分比 作为空数组存入
for ($i = 0; $i < $probability; $i++) {
    $new_list_awards[] = [];
}

// 生成随机数 因为数组是从索引 0 开始 所以长度一百的数组 索引只到 99
$select_index = mt_rand(0, 99);

if (empty($new_list_awards[$select_index])) {
    // 如果是空数组 自然没中奖
    echo "谢谢惠顾";
} else {

    // 存入文件 更加方便查看中奖记录, 也可以存入数据库
    $file = fopen('test.log', 'a+');
    fwrite($file, '奖品ID:' . $new_list_awards[$select_index]['id'] . '; 抽第' . $o . '次时中奖; 奖品几率' . $new_list_awards[$select_index]['probability'] . '; 奖品剩余:' . $new_list_awards[$select_index]['count'] . "
");
    fclose($file);

    echo '恭喜中奖了, 奖品:' . $new_list_awards[$select_index]['name'] . "
";

    // 中奖了当然要减少奖品余量
    $map_list_awards[$new_list_awards[$select_index]['id']]['count']--;
}

这个抽奖原理就是, 每个奖品设置中奖率, 全部奖品中奖率加起来是等于 100 (百分百), 根据这个特性, 创建了一个长度为 100 的数组, 比如设置的奖品总和几率是 70, 那么这个数组 1 到 70 的索引都有对应奖品的值, 然后在 1 到 100 之间生成随机数, 如果随机数是在 1 - 70 之间, 说明中奖了. 当然 中奖率还可以再调.


代码写好了, 马上进行测试, 使用 PHP CLI 执行文件测试, 测试条件: 抽奖十万次.

1.png

跑完后打开存储记录的文件, 显示信息如下:

奖品ID:3; 抽第0次时中奖; 奖品几率15; 奖品剩余:15
奖品ID:6; 抽第1次时中奖; 奖品几率20; 奖品剩余:20
奖品ID:6; 抽第2次时中奖; 奖品几率20; 奖品剩余:19
奖品ID:6; 抽第3次时中奖; 奖品几率20; 奖品剩余:18
奖品ID:1; 抽第4次时中奖; 奖品几率10; 奖品剩余:10
奖品ID:6; 抽第5次时中奖; 奖品几率20; 奖品剩余:17
奖品ID:4; 抽第6次时中奖; 奖品几率10; 奖品剩余:10
奖品ID:7; 抽第7次时中奖; 奖品几率5; 奖品剩余:5
奖品ID:5; 抽第8次时中奖; 奖品几率5; 奖品剩余:5
奖品ID:6; 抽第9次时中奖; 奖品几率20; 奖品剩余:16
奖品ID:6; 抽第10次时中奖; 奖品几率20; 奖品剩余:15
奖品ID:6; 抽第11次时中奖; 奖品几率20; 奖品剩余:14
奖品ID:2; 抽第13次时中奖; 奖品几率5; 奖品剩余:5
奖品ID:1; 抽第14次时中奖; 奖品几率10; 奖品剩余:9
奖品ID:3; 抽第16次时中奖; 奖品几率15; 奖品剩余:14
奖品ID:1; 抽第17次时中奖; 奖品几率10; 奖品剩余:8
奖品ID:4; 抽第19次时中奖; 奖品几率10; 奖品剩余:9
奖品ID:2; 抽第20次时中奖; 奖品几率5; 奖品剩余:4
奖品ID:6; 抽第21次时中奖; 奖品几率20; 奖品剩余:13
奖品ID:5; 抽第22次时中奖; 奖品几率5; 奖品剩余:4
奖品ID:6; 抽第23次时中奖; 奖品几率20; 奖品剩余:12
奖品ID:1; 抽第24次时中奖; 奖品几率10; 奖品剩余:7
奖品ID:7; 抽第25次时中奖; 奖品几率5; 奖品剩余:4
奖品ID:1; 抽第26次时中奖; 奖品几率10; 奖品剩余:6
奖品ID:3; 抽第28次时中奖; 奖品几率15; 奖品剩余:13
奖品ID:3; 抽第30次时中奖; 奖品几率15; 奖品剩余:12
奖品ID:4; 抽第31次时中奖; 奖品几率10; 奖品剩余:8
奖品ID:2; 抽第33次时中奖; 奖品几率5; 奖品剩余:3
奖品ID:6; 抽第35次时中奖; 奖品几率20; 奖品剩余:11
奖品ID:3; 抽第37次时中奖; 奖品几率15; 奖品剩余:11
奖品ID:3; 抽第39次时中奖; 奖品几率15; 奖品剩余:10
奖品ID:5; 抽第40次时中奖; 奖品几率5; 奖品剩余:3
奖品ID:1; 抽第41次时中奖; 奖品几率10; 奖品剩余:5
奖品ID:3; 抽第42次时中奖; 奖品几率15; 奖品剩余:9
奖品ID:3; 抽第44次时中奖; 奖品几率15; 奖品剩余:8
奖品ID:6; 抽第45次时中奖; 奖品几率20; 奖品剩余:10
奖品ID:6; 抽第46次时中奖; 奖品几率20; 奖品剩余:9
奖品ID:3; 抽第47次时中奖; 奖品几率15; 奖品剩余:7
奖品ID:4; 抽第49次时中奖; 奖品几率10; 奖品剩余:7
奖品ID:6; 抽第50次时中奖; 奖品几率20; 奖品剩余:8
奖品ID:3; 抽第51次时中奖; 奖品几率15; 奖品剩余:6
奖品ID:4; 抽第52次时中奖; 奖品几率10; 奖品剩余:6
奖品ID:7; 抽第54次时中奖; 奖品几率5; 奖品剩余:3
奖品ID:2; 抽第56次时中奖; 奖品几率5; 奖品剩余:2
奖品ID:2; 抽第58次时中奖; 奖品几率5; 奖品剩余:1
奖品ID:4; 抽第59次时中奖; 奖品几率10; 奖品剩余:5
奖品ID:4; 抽第60次时中奖; 奖品几率10; 奖品剩余:4
奖品ID:3; 抽第61次时中奖; 奖品几率15; 奖品剩余:5
奖品ID:6; 抽第62次时中奖; 奖品几率20; 奖品剩余:7
奖品ID:7; 抽第63次时中奖; 奖品几率5; 奖品剩余:2
奖品ID:6; 抽第64次时中奖; 奖品几率20; 奖品剩余:6
奖品ID:8; 抽第65次时中奖; 奖品几率5; 奖品剩余:5
奖品ID:6; 抽第66次时中奖; 奖品几率20; 奖品剩余:5
奖品ID:6; 抽第67次时中奖; 奖品几率20; 奖品剩余:4
奖品ID:7; 抽第68次时中奖; 奖品几率5; 奖品剩余:1
奖品ID:5; 抽第69次时中奖; 奖品几率5; 奖品剩余:2
奖品ID:3; 抽第71次时中奖; 奖品几率15; 奖品剩余:4
奖品ID:5; 抽第72次时中奖; 奖品几率5; 奖品剩余:1
奖品ID:1; 抽第73次时中奖; 奖品几率10; 奖品剩余:4
奖品ID:4; 抽第74次时中奖; 奖品几率10; 奖品剩余:3
奖品ID:3; 抽第75次时中奖; 奖品几率15; 奖品剩余:3
奖品ID:1; 抽第77次时中奖; 奖品几率10; 奖品剩余:3
奖品ID:3; 抽第78次时中奖; 奖品几率15; 奖品剩余:2
奖品ID:4; 抽第79次时中奖; 奖品几率10; 奖品剩余:2
奖品ID:1; 抽第81次时中奖; 奖品几率10; 奖品剩余:2
奖品ID:8; 抽第82次时中奖; 奖品几率5; 奖品剩余:4
奖品ID:6; 抽第83次时中奖; 奖品几率20; 奖品剩余:3
奖品ID:3; 抽第84次时中奖; 奖品几率15; 奖品剩余:1
奖品ID:4; 抽第86次时中奖; 奖品几率10; 奖品剩余:1
奖品ID:1; 抽第88次时中奖; 奖品几率10; 奖品剩余:1
奖品ID:6; 抽第92次时中奖; 奖品几率20; 奖品剩余:2
奖品ID:8; 抽第104次时中奖; 奖品几率5; 奖品剩余:3
奖品ID:6; 抽第113次时中奖; 奖品几率20; 奖品剩余:1
奖品ID:8; 抽第154次时中奖; 奖品几率5; 奖品剩余:2
奖品ID:8; 抽第158次时中奖; 奖品几率5; 奖品剩余:1

此时抽奖程序是正常的, 但是有个问题, 刚才测试抽的次数是 10W 次, 但是看记录 到158次 其实全部奖品都已经被抽完了. 第 158 次往后, 就只能全部都是谢谢惠顾了. 可以说是先到先得...

由于对后面进来的抽奖者来说并不公平, 解决方法就是, 将概率减小. 比如上面的代码存的数组长度只有 100 可以增大这个数组长度 就实现了减小记录.

如(直接贴完整代码 代码带循环十万次):

// 打开文件 准备好存储记录 因为是十万次抽奖测试 所以放在循环体外面 只打开一次 后面循环体内仅写入就好
$file = fopen('test.log', 'a+');

$list_awards = [
    ['id' => 1, 'name' => '奖品1', 'probability' => 10, 'count' => 10],
    ['id' => 2, 'name' => '奖品2', 'probability' => 5, 'count' => 5],
    ['id' => 3, 'name' => '奖品3', 'probability' => 15, 'count' => 15],
    ['id' => 4, 'name' => '奖品4', 'probability' => 10, 'count' => 10],
    ['id' => 5, 'name' => '奖品5', 'probability' => 5, 'count' => 5],
    ['id' => 6, 'name' => '奖品6', 'probability' => 20, 'count' => 20],
    ['id' => 7, 'name' => '奖品7', 'probability' => 5, 'count' => 5],
    ['id' => 8, 'name' => '奖品8', 'probability' => 5, 'count' => 5],
];

// Map 处理
$map_list_awards = [];
foreach ($list_awards as $key => $value) {
    $map_list_awards[$value['id']] = $value;
}

for ($o = 1; $o <= 100000; $o++) {

    $new_list_awards = [];   // 将奖品项全部放入新数组

    foreach ($map_list_awards as $row_awards) {
        if ($row_awards['count'] >= 1) {
            for ($i = 1; $i <= $row_awards['probability']; $i++) { 
                $new_list_awards[] = $row_awards;
            }
        }
    }

    // 这里原本几率是 100 现在乘以 1000 减少几率 让后面的人也能抽奖 当然 中奖就变难了
    $probability = (100 * 1000) - array_sum(array_column($list_awards, 'probability'));

    // 存入空数组 只要随机抽到空数组 说明没中奖
    for ($i = 0; $i < $probability; $i++) {
        $new_list_awards[] = [];
    }

    $select_index = mt_rand(0, 99999);  // 生成随机数 中奖就看这个了 原本仅仅是 99 减少了中奖几率

    if (empty($new_list_awards[$select_index])) {
        echo '谢谢惠顾';
    } else {

        // 减剩余奖品数量
        $map_list_awards[$new_list_awards[$select_index]['id']]['count'] = $map_list_awards[$new_list_awards[$select_index]['id']]['count'] - 1;

        fwrite($file, '奖品ID:' . $new_list_awards[$select_index]['id'] . '; 抽第' . $o . '次时中奖; 奖品几率' . $new_list_awards[$select_index]['probability'] . '; 奖品剩余:' . $new_list_awards[$select_index]['count'] . "
");

        echo '中奖了:' . $new_list_awards[$select_index]['name'];
    }
    echo '第' . $o . "次
";
}

fclose($file);

中奖几率重点就在这两句:

$probability = (100 * 1000) - array_sum(array_column($list_awards, 'probability'));

$select_index = mt_rand(0, 99999);

当然 这个数字越大, 抽奖所需的服务器性能要求也会提高. 一般建议 设置成 100 * 100 然后索引值最大就是 9999.

以这个几率配置博主跑了两次抽奖十万次的, 第一次是第 14252 次就把所有奖品抽完了, 第二次是第 14484 次就把全部奖品抽完了. 两次都是 1.4W 次左右, 说明还是比较稳的, 而且这一共才 75 个奖品(八个奖项的数量加起来), 如果一人抽一次 需要 1.5W 人才能把这奖品抽完, 中奖几率控制在这个数值还是很难中奖的.


实际项目中的效果:

1.png

本文地址 : bubaijun.com/page.php?id=171

版权声明 : 未经允许禁止转载!

评论:我要评论
发布评论:
Copyright © 不败君 粤ICP备18102917号-1

不败君

首 页 作 品 微 语