| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- <?php
- namespace app\api\library\mlbb;
- /**
- * 抽奖服务
- * 规则:
- * 1. 总共12次抽奖机会;四个礼包+5个代金卷,共9个奖品;
- * 2. 前四次抽奖,必定包含3个不同的礼包和1个代金卷;
- * 3. 礼包在12次抽奖中不可重复获得;
- * 4. 抽奖概率:1元代金卷12%,其他所有奖品(包括礼包和其他代金卷)均为11%。
- */
- class LotteryService
- {
- /** @var array 原始奖品池 */
- private $prizePool = [];
- public function __construct(array $prizePool)
- {
- $this->prizePool = $prizePool;
- }
- /**
- * 单次抽奖
- * @param array $userHistory 用户已抽奖历史
- * @return array 中奖奖品
- */
- public function draw(array $userHistory): array
- {
- $drawCount = count($userHistory) + 1; // 当前是第几抽
- $pool = $this->preparePool($userHistory, $drawCount);
- $result = $this->weightedDraw($pool);
- return $result;
- }
- /**
- * 根据历史和规则准备奖品池
- */
- private function preparePool(array $userHistory, int $drawCount): array
- {
- $pool = [];
- // 已中过礼包
- $gotPacks = array_column(
- array_filter($userHistory, function ($h) {
- return $h['prize_type'] == 1;
- }),
- 'prize_mark'
- );
- foreach ($this->prizePool as $item) {
- // 礼包已中过,权重清零
- if ($item['type'] === 'pack' && in_array($item['prize_mark'], $gotPacks)) {
- $item['weight'] = 0;
- }
- $pool[$item['prize_mark']] = $item;
- }
- // 规则:前 4 抽
- if ($drawCount <= 4) {
- $packCount = count(array_filter($userHistory, function ($h) {
- return $h['prize_type'] == 1;
- }));
- $couponCount = count(array_filter($userHistory, function ($h) {
- return $h['prize_type'] == 2;
- }));
- // 前 3 次必须保证能抽到礼包
- if ($packCount < 3 && $drawCount <= 4) {
- // 仅保留礼包
- $pool = array_filter($pool, function ($p) {
- return $p['type'] === 'pack' && $p['weight'] > 0;
- });
- }
- // 第 4 次必须保证出代金券
- if ($drawCount == 4 && $couponCount < 1) {
- $pool = array_filter($pool, function ($p) {
- return $p['type'] === 'coupon' && $p['weight'] > 0;
- });
- }
- }
- return $pool;
- }
- /**
- * 加权随机抽取
- */
- private function weightedDraw(array $pool): array
- {
- $total = array_sum(array_column($pool, 'weight'));
- if ($total <= 0) {
- throw new \Exception("奖品池权重总和为 0,无法抽取");
- }
- $r = mt_rand() / mt_getrandmax() * $total;
- $acc = 0.0;
- foreach ($pool as $item) {
- if ($item['weight'] <= 0) continue;
- $acc += $item['weight'];
- if ($r <= $acc) {
- return $item;
- }
- }
- // 兜底
- foreach (array_reverse($pool, true) as $item) {
- if ($item['weight'] > 0) return $item;
- }
- throw new \Exception("未能选出奖品");
- }
- }
- // // 奖品池(使用整数权重)
- // $prizePool = [
- // ['prize_mark' => 1, 'prize_name' => '殿堂礼包', 'type' => 'pack', 'weight' => 11],
- // ['prize_mark' => 2, 'prize_name' => '勇者晋级礼包', 'type' => 'pack', 'weight' => 11],
- // ['prize_mark' => 3, 'prize_name' => '勇者冒险礼包', 'type' => 'pack', 'weight' => 11],
- // ['prize_mark' => 4, 'prize_name' => '勇者荣耀礼包', 'type' => 'pack', 'weight' => 11],
- // ['prize_mark' => 5, 'prize_name' => '1元代金券', 'type' => 'coupon', 'weight' => 12],
- // ['prize_mark' => 6, 'prize_name' => '25元代金券', 'type' => 'coupon', 'weight' => 11],
- // ['prize_mark' => 7, 'prize_name' => '40元代金券', 'type' => 'coupon', 'weight' => 11],
- // ['prize_mark' => 8, 'prize_name' => '65元代金券', 'type' => 'coupon', 'weight' => 11],
- // ['prize_mark' => 9, 'prize_name' => '100元代金券','type' => 'coupon', 'weight' => 11],
- // ];
- //
- // $lottery = new LotteryService($prizePool);
- //
- // $totalRounds = 10000; // 模拟 1 万轮
- // $counts = [];
- //
- // for ($round = 0; $round < $totalRounds; $round++) {
- // $userHistory = [];
- //
- // // 每轮 12 抽
- // for ($i = 0; $i < 12; $i++) {
- // $result = $lottery->draw($userHistory);
- // $mark = $result['prize_mark'];
- //
- // if (!isset($counts[$mark])) {
- // $counts[$mark] = 0;
- // }
- // $counts[$mark]++;
- //
- // // 记录历史
- // $userHistory[] = [
- // 'prize_mark' => $mark,
- // 'prize_type' => $result['type'] === 'pack' ? 1 : 2,
- // ];
- // }
- // }
- //
- // // 输出统计结果
- // $totalDraws = $totalRounds * 12;
- // echo "=== 模拟 {$totalRounds} 轮(共 {$totalDraws} 次抽奖)结果 ===\n";
- // foreach ($prizePool as $p) {
- // $mark = $p['prize_mark'];
- // $count = $counts[$mark] ?? 0;
- // $ratio = $count / $totalDraws * 100;
- // echo "{$p['prize_name']} : {$count} 次, 占比 " . number_format($ratio, 2) . "%\n";
- // }
|