はじめに
オンライン抽選システムを作る機会があり、くじ引きロジックを実装することになりました。
正直、確率制御のアルゴリズムは得意分野ではなく、どう実装すべきか悩んでいたのですが、
普段業務で使っているClaudeCodeに実装させてみた、累積確率方式という実装パターンを提案してもらい、スムーズに実装できました。
今回はその抽選ロジックを紹介します 📝
🎯 要件
- S賞(5%)、A賞(15%)、B賞(30%)、C賞(50%) のようなランク別確率
- 同じランク内に複数の景品があり、当選時は等確率で選択
- 1回の購入で複数回抽選可能
例: 「A賞」が当たった場合、A賞に紐づく「iPad」「Switch」「AirPods」のいずれかがランダムで選ばれる
💡 実装: 累積確率方式
ステップ1: 累積確率でランクを決定
// 0~100の乱数を生成
const random = Math.random() * 100
let cumulativeProbability = 0
let selectedRank = null
for (const rankSetting of raffle.rankSettings) {
cumulativeProbability += rankSetting.probability
if (random <= cumulativeProbability) {
selectedRank = rankSetting.rank
break
}
}
仕組み:
- 0〜100の乱数を取得 (例: 42.3)
- S賞(5%) → 累積: 5
- A賞(15%) → 累積: 20
- B賞(30%) → 累積: 50 ← 42.3はここに該当
- C賞(50%) → 累積: 100
累積確率で範囲を定義することで、設定された確率通りにランクを決定できます 🎲
ステップ2: 景品の選択
const prizesInRank = raffle.prizes.filter((p) => p.rank === selectedRank)
if (prizesInRank.length > 0) {
const randomPrizeIndex = Math.floor(Math.random() * prizesInRank.length)
selectedPrize = prizesInRank[randomPrizeIndex]
}
ランク内の景品を配列から等確率で選択するシンプルな実装です 🎯
🤔 他の実装案との比較
最初は「S賞なら1〜5、A賞なら6〜20...」のように固定範囲で分ける方法も考えましたが、累積確率方式の方が優れている点が多いです。
- 確率設定の変更に柔軟
- 合計確率の検証が容易
- コードがシンプルで可読性が高い
この実装パターンを知らなかったので、AIの提案は非常に参考になりました。
✨ 動作検証
10000回抽選を実行した結果 ↓
| ランク | 設定確率 | 実測値 |
|---|---|---|
| S賞 | 5% | 4.8% |
| A賞 | 15% | 15.3% |
| B賞 | 30% | 29.7% |
| C賞 | 50% | 50.2% |
期待通りの確率分布が得られました ✅
🙏 おわりに
実務経験を重ねても、専門外の分野ではベストプラクティスを知らないことがあります。
AIに壁打ちして即座にフィードバックをもらえる開発体験は、生産性を大きく向上させてくれます 🚀
これからも、AIを開発パートナーとして活用していきたいと思います 🤝