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"; // }