實現一個高效、精確的加權隨機抽獎函式,支援動態獎品池與機率配置,回傳中獎結果與未中獎獎品列表供前端展示。
<?php
function get_rand(array $proArr) {
$proSum = array_sum($proArr); // 總權重
foreach ($proArr as $key => $proCur) {
$randNum = mt_rand(1, $proSum);
if ($randNum <= $proCur) {
return $key; // 中獎索引
}
$proSum -= $proCur; // 權重遞減
}
return ''; // 理論不會執行到這裡
}
?>
原理:將機率空間視為連續區間,隨機落點落在哪個獎品區間即中獎。每次未中獎縮減總權重,保證最終必中獎。
<?php
// 獎品池配置:id、名稱、權重(v)
$prize_arr = [
1 => ['id'=>1, 'prize'=>'平板電腦', 'v'=>1],
2 => ['id'=>2, 'prize'=>'數碼相機', 'v'=>5],
3 => ['id'=>3, 'prize'=>'音箱設備', 'v'=>10],
4 => ['id'=>4, 'prize'=>'4G優盤', 'v'=>12],
5 => ['id'=>5, 'prize'=>'10Q幣', 'v'=>22],
6 => ['id'=>6, 'prize'=>'下次沒準就能中哦', 'v'=>50],
];
// 建立權重陣列
$weights = [];
foreach ($prize_arr as $item) {
$weights[$item['id']] = $item['v'];
}
// 抽獎
$winId = get_rand($weights);
$winPrize = $prize_arr[$winId]['prize'];
// 移除已中獎項目,打亂剩餘順序供前端展示
unset($prize_arr[$winId]);
shuffle($prize_arr);
$otherPrizes = array_column($prize_arr, 'prize');
// 回傳 JSON
echo json_encode([
'status' => 1,
'win' => $winPrize,
'others' => $otherPrizes
]);
?>
<button id="drawBtn">開始抽獎</button>
<div id="result"></div>
<script>
$('#drawBtn').click(function() {
$.post('lottery.php', function(res) {
if (res.status) {
$('#result').html('恭喜中獎:' + res.win + '
其他獎品:' + res.others.join(', '));
}
}, 'json');
});
</script>
<?php
// 簡易防刷:同 IP 每日限 5 次
$key = 'lottery:' . $_SERVER['REMOTE_ADDR'] . ':' . date('Ymd');
$count = $redis->incr($key);
if ($count === 1) $redis->expire($key, 86400);
if ($count > 5) exit(json_encode(['status'=>0, 'msg'=>'今日次數已用完']));
// 記錄中獎日誌
$log = [
'uid' => $userId,
'prize' => $winPrize,
'ip' => $_SERVER['REMOTE_ADDR'],
'time' => time()
];
$redis->lpush('lottery:log', json_encode($log));
?>