# 直播间抽奖系统 - 随机离散方案

## 概述

此方案将原来的"连续区间权重"改为"随机离散位置"，解决大奖连续出现或长时间不出的问题。

## 核心变化

### 原方案（连续区间）
```
总权重: 99000

冰箱贴 (65800): [0 - 65799]     ← 占据大片连续区间
高达徽章 (28000): [65800 - 93799]
兰博基尼 (3000): [93800 - 96799]
...
大奖 (100): [98900 - 98999]     ← 仅占100个连续数字

问题：如果随机数生成有偏，整个区间都会受影响
```

### 新方案（随机离散）
```
1. 创建位置池 [0 - 98999]
2. 使用Fisher-Yates算法打乱位置顺序
3. 按权重分配位置给每个奖品

冰箱贴获得65800个**分散**的位置
大奖获得100个**分散**的位置

优势：随机数生成即使有偏，也不会导致某个奖品连续出现
```

## 实现原理

### 1. 随机位置生成

```go
// Fisher-Yates洗牌算法
positions := [0, 1, 2, 3, ..., 98999]
for i := len(positions)-1; i > 0; i-- {
    j = random(0, i)
    swap(positions[i], positions[j])
}
```

### 2. 位置分配

```go
prizePosMap = {
    冰箱贴ID: [打乱后的位置...],  // 65800个
    徽章ID: [打乱后的位置...],     // 28000个
    ...
}
```

### 3. 抽奖查找

```go
randValue = crypto/rand(0, totalWeight)

// 二分查找
for each prize in prizePosMap:
    if randValue in prize.positions:
        return prize
```

## 代码结构

```
internal/service/livestream/
├── livestream.go              # 主服务文件（已集成随机离散）
├── discrete_random.go         # 随机离散核心逻辑
├── discrete_random_test.go    # 单元测试
└── draw_integration_test.go  # 集成测试
```

## 使用方法

### 1. 配置活动奖品

```go
// 原有接口，无需改动
s.CreatePrizes(ctx, activityID, prizes)

// 系统会自动生成随机离散位置
```

### 2. 执行抽奖

```go
// Draw() 方法已自动使用随机离散
result, err := s.Draw(ctx, input)
```

### 3. 调试日志

```
随机离散位置已生成
  activity_id: 1
  total_weight: 99000
  prize_count: 11
```

## 测试验证

### 运行测试

```bash
cd /Users/win/aicode/bindbox/bindbox_game

# 单元测试
go test -v ./internal/service/livestream/ -run TestDiscreteRandomDistribution

# 集成测试
go test -v ./internal/service/livestream/ -run TestDrawWithDiscreteIntegration

# 性能测试
go test -bench=BenchmarkDiscreteGeneration ./internal/service/livestream/
```

### 预期输出

```
========== 随机离散抽奖统计结果 ==========
模拟次数: 1000

Nu高达               权重:   100 理论:  0.10% 实际:  0.10% 偏差: +0.00% 次数:    1
NT高达               权重:   100 理论:  0.10% 实际:  0.10% 偏差: +0.00% 次数:    1
魔礼青               权重:   100 理论:  0.10% 实际:  0.10% 偏差: +0.00% 次数:    1
维达尔               权重:   100 理论:  0.10% 实际:  0.20% 偏差: +0.10% 次数:    2
玉衡星6号            权重:   400 理论:  0.40% 实际:  0.40% 偏差: +0.00% 次数:    4
马克兔               权重:   600 理论:  0.61% 实际:  0.70% 偏差: +0.09% 次数:    7
00高达               权重:   700 理论:  0.71% 实际:  0.60% 偏差: -0.11% 次数:    6
SD随机款             权重:  1100 理论:  1.11% 实际:  1.10% 偏差: -0.01% 次数:   11
兰博基尼             权重:  3000 理论:  3.03% 实际:  2.90% 偏差: -0.13% 次数:   29
高达徽章             权重: 28000 理论: 28.28% 实际: 28.20% 偏差: -0.08% 次数:  282
冰箱贴               权重: 65800 理论: 66.47% 实际: 66.50% 偏差: +0.03% 次数:  665
```

## 性能分析

| 操作 | 时间复杂度 | 说明 |
|------|-----------|------|
| 生成位置 | O(n) | 一次生成，缓存复用 |
| 查找奖品 | O(m × log(k)) | m=奖品数量, k=平均位置数 |
| 内存占用 | O(totalWeight × 4字节) | 99000个位置≈390KB |

## 注意事项

1. **缓存失效**：奖品配置变更后，缓存会自动重新生成
2. **并发安全**：使用RWMutex保护缓存读写
3. **位置唯一**：Fisher-Yates算法保证每个位置只属于一个奖品
4. **密码学安全**：使用crypto/rand保证随机性

## 对比分析

| 特性 | 连续区间 | 随机离散 |
|------|----------|----------|
| 实现复杂度 | ⭐ 简单 | ⭐⭐⭐ 复杂 |
| 概率精度 | ✅ 准确 | ✅ 准确 |
| 分布均匀性 | ⚠️ 可能偏斜 | ✅ 均匀 |
| 防连续中奖 | ❌ 无 | ✅ 天然防聚集 |
| 缓存需求 | 无 | 需要 |
| 内存占用 | 低 | 中等（~400KB） |

## 结论

随机离散方案更适合需要**分布均匀、防连续中奖**的场景，虽然实现稍复杂，但能显著提升用户体验。