# 大富翁 · 摇色子 — 项目文档

本文描述**业务玩法**与**服务端抽奖/结算机制**，便于产品、运营与二次开发对齐实现。接口路径、鉴权与联调细节见根目录 [`API对接文档.md`](API对接文档.md)。

---

## 1. 项目概述

- **形态**：平台玩家使用「平台币」参与摇五颗标准六面骰（点数各 1–6），结果对应棋盘/奖励配置；后台可配置档位权重、奖池、杀分策略与展示文案（含中英文）。
- **服务端**：PHP [Webman](https://www.workerman.net/webman)（`server/`），玩家与平台接口在 `app/api`；骰子业务模型在 `app/dice`。
- **管理端**：前端工程 `saiadmin-artd/`（与 SaiAdmin 插件体系配套）。

---

## 2. 核心概念

| 概念 | 说明 |
| --- | --- |
| **平台币 `coin`** | 玩家钱包余额；付费开局、购买套餐、中奖结算均围绕该字段。 |
| **注数 `ante`** | 倍数因子，须存在于表 `dice_ante_config` 的 `mult` 中；接口 `/api/game/anteConfig` 返回可选注数。 |
| **单注费用** | 付费抽奖时，开局前扣除 **`ante × 1`** 平台币（代码常量 `UNIT_COST = 1`，即「单注 1 币」口径）。 |
| **方向 `direction`** | 开局参数：`0` 与 `1` 对应两套奖励数据（顺时针/逆时针或「无 / 中奖」分支，由前端与配置表共同约定）；服务端在 **档位确定后**，按当前方向从 `DiceReward` 缓存结构中取该档位下的条目再按权重抽取。 |
| **档位 T1–T5** | 中奖层级；先抽档位，再在该档位 + 当前方向下按 `weight` 抽一条奖励配置。 |
| **`grid_number`（5–30）** | 与「五颗骰子点数之和」一致：最小 5（全 1），最大 30（全 6）；用于关联奖励行与后续生成 `roll_array`。 |
| **`real_ev`** | 奖励配置中的期望调节项；**普通中奖**结算为 **`real_ev × ante`**（付费局在开局已扣 `ante×1`，净效果依 `real_ev` 而定）。 |

---

## 3. 玩法流程（玩家视角）

1. **登录 / 进游戏**  
   平台侧通过 `/api/v1/getGameUrl` 或玩家侧 `/api/user/Login` 换取 token，打开前端页面。

2. **（可选）购买「抽奖券」套餐**  
   `POST /api/game/buyLotteryTickets`，`count` 仅支持 `1`、`5`、`10`：  
   - 1：1 币 → 1 次付费计数 + 0 次赠送  
   - 5：5 币 → 5 次付费 + **1 次赠送**（共 6 次计入总次数）  
   - 10：10 币 → 10 次付费 + **3 次赠送**（共 13 次）  

   会更新玩家身上的 `total_ticket_count` / `paid_ticket_count` / `free_ticket_count`，并记钱包与券流水。

3. **开局抽奖**  
   `POST /api/game/playStart`，需传 **`direction`（0 或 1）** 与 **`ante`（正整数，且须在底注配置中）**。  

4. **付费 vs 免费**  
   - **免费抽奖**：当 `free_ticket_count > 0` 时，本局视为免费类型：不扣 `ante×1`，但会消耗 **1 次** `free_ticket_count`。  
   - **付费抽奖**：不依赖「券张数是否大于 0」；只要非免费局，开局前扣 **`ante × 1`**。  

   > **重要**：当前实现已**不再用「抽奖券张数」作为能否开局的条件**；`buyLotteryTickets` 更新的是统计与赠送次数，**真正开局仍看余额、注数、免费次数等规则**（见下节）。

5. **免费注数锁定**  
   若上一局因命中 **T5** 赠送了免费次数，服务端会缓存「免费局须与触发时相同的 `ante`」，不一致则拒绝并提示修改注数。

---

## 4. 抽奖与结算机制（服务端逻辑）

以下对应 `PlayStartLogic` 与 `LotteryService`，便于理解「先抽什么、再算什么钱」。

### 4.1 前置校验

- 用户存在；`ante` 合法。  
- **最低余额**：`coin ≥ abs(min_real_ev) × ante`（`min_real_ev` 来自全表 `DiceRewardConfig` 缓存），防止极端负 EV 下余额不足以覆盖风险口径。  
- 付费局：`coin ≥ ante × 1`。

### 4.2 使用哪套「档位权重」：默认奖池 vs 杀分奖池

配置表 `dice_lottery_pool_config` 至少要有 **`name = default`**；可选 **`name = killScore`**。

- **`default` 彩金池**维护累计盈利字段 **`profit_amount`**（见 4.5）。  
- 记：`safety_line` = 安全线，`kill_enabled` = 是否开启杀分。

**是否按「奖池档位权重」抽档位（`usePoolWeights`）**：

| 情形 | 档位权重来源 |
| --- | --- |
| **免费局** | 使用 **killScore** 奖池的 T1–T5 权重；若无 `killScore` 则退回 `default`。 |
| **付费局** 且 **杀分开启** 且 **`profit_amount ≥ safety_line`** 且 **存在 killScore** | 使用 **killScore** 的档位权重（杀分模式）。 |
| **其他付费局** | 使用 **玩家**身上的 `t1_weight`～`t5_weight`（`DicePlayer` 字段，与 `LotteryService::drawTierByPlayerWeights` 一致）。 |

档位抽出 **T1–T5** 后，从 `DiceReward` 缓存中取出 **`[该档位][direction]`** 下的所有奖励行，再按行 **`weight`** 做加权随机（仅 `weight > 0` 参与；全为 0 会重试档位，最多约 10 次）。

### 4.3 杀分模式下的特殊处理

当使用 **killScore / 免费局** 等与杀分一致的权重路径时：

- 在奖励抽取阶段会 **排除 `grid_number` 为 5 和 30 的配置**（这两点数和只能对应「全 1」「全 6」豹子，无法做成非豹子展示）。  
- **不会触发豹子大奖**（见 4.4）：若摇到豹子点数组，只生成 **非豹子** 的五骰组合，不发放豹子附加奖金。

### 4.4 普通奖与「豹子 / BIGWIN」

- 若本次抽中的 `grid_number` **不是**「豹子集合」`{5,10,15,20,25,30}`：按点数和生成 5 个 1–6 的骰子（和为 `grid_number`），**普通奖金** = **`real_ev × ante`**（付费局已预先扣除 `ante×1`）。  

- 若点数和落在豹子集合：  
  - **`grid_number` 为 5 或 30**：若**非**杀分路径，**必定**按豹子结算（五颗相同点数）。  
  - **10 / 15 / 20 / 25**：读取 `DiceRewardConfig` 中 **`tier = BIGWIN`** 且对应该 `grid_number` 的配置，用其 **`weight`（0–10000，10000=100%）** 随机决定是否视为真豹子；否则生成**非豹子**但点数和不变的骰子组合。  
  - **真豹子**时：奖金按 **`big_win_real_ev × ante`** 发放（`big_win_real_ev` 来自 BIGWIN 配置；若未配则用代码兜底常量）；并**不计入**当次普通 `reward_win` 那条配置（与「中豹子不走普通奖」逻辑一致，详见代码注释）。

杀分路径下：**不触发**豹子奖，仅展示非豹子组合。

### 4.5 T5「再来一次」

若命中奖励属于 **T5** 档位（且未走「仅豹子清掉普通奖」的特殊分支）：在事务内为玩家 **`free_ticket_count + 1`**，并写入券流水备注；同时写入 Redis：**下一局免费抽奖必须使用本局相同 `ante`**。

### 4.6 彩金池盈利累计

在 **`default`** 那条池子上更新 **`profit_amount`**：

- **付费局**：本局贡献 `+= (本局总中奖 win_coin) - (本局付费 paid_amount)`，其中 `paid_amount = ante × 1`。  
- **免费局**：`+= win_coin`（无票价成本，`paid_amount = 0`）。

该累计值与 **`safety_line`、 `kill_enabled`** 共同决定下一局付费是否进入 **killScore** 档位权重（见 4.2）。

> 注意：仓库中部分数据库迁移脚本对 `profit_amount` 的注释可能仍沿用旧口径。当前行为应以 `PlayStartLogic` 中对 `profit_amount` 的实际累加逻辑为准。

---

## 5. 数据与配置要点（实现侧）

- **`DiceReward`**：按档位、方向组织好的多语言/展示与 `grid_number`、`weight`、`real_ev` 等，供开局加权抽取。  
- **`DiceRewardConfig`**：含 **BIGWIN** 档及普通档；`getCachedMinRealEv()` 等用于全局限定。  
- **`dice_lottery_pool_config`**：`default` / `killScore` 的 T1–T5 权重及杀分相关开关、安全线、累计盈利。  
- **对局表 `DicePlayRecord`**：记录 `lottery_config_id`、`lottery_type`（付费/免费）、`ante`、`paid_amount`、`roll_array`、`reward_tier`、各类中奖拆分字段等，供后台与平台对账。

---

## 6. 接口与文档索引

| 文档 | 内容 |
| --- | --- |
| [`API对接文档.md`](API对接文档.md) | 平台 `/api/v1/*`（`api-key` + `auth-token`）、玩家 `/api/*`（`token`）、统一返回码、联调建议。 |
| `server/docs/` | 性能、权重测试、出点分析等专项说明（按需阅读）。 |

**与玩法直接相关的玩家接口示例**：

- `GET /api/game/config` — 前端文案与分组配置  
- `GET /api/game/anteConfig` — 可选注数  
- `GET /api/game/lotteryPool` — 彩金池展示列表（不含 BIGWIN 档）  
- `POST /api/game/buyLotteryTickets` — 购买套餐（更新次数统计）  
- `POST /api/game/playStart` — 开局一局（`direction`、`ante`）

---

## 7. 修订说明

- 本文档依据 `server/app/api/logic/PlayStartLogic.php`、`GameLogic.php`、`LotteryService.php` 及 `GameController` 当前实现整理；若业务规则变更，请以代码与数据库迁移为准并同步更新本节与 [`API对接文档.md`](API对接文档.md)。
