# 麻将消一消 · 关卡难度分说明

> 配套《使用说明.md》的独立文档：难度分**是怎么算的**，以及**每一项为什么要这么设计**。
> 版本日期：2026-08-18（v3：加槽位压力维度 minSlots）。公式实现见 `generator.js` 的 `evaluateDifficulty()`。

---

## 一、难度分是什么

每个成品关卡的难度分是**生成时自动评估的一个整数**，直接写进文件名：
`level_001_T2_114.json` 里的 **114** 就是这关的难度分。

- 越大越难
- 理论范围 10~170，实际多在 60~150（v3 实测）
- 程序侧**按文件名排序**即可得到难度升序，用来排关卡梯度（前易后难）

---

## 二、公式（v3 · 2026-08-18）

```
难度分 = (1 − 可点率) × 40
       + 最高层序号 × 8
       + min(有效钩子数 / 8, 1) × 30
       + min(有效暗钩数 / 8, 1) × 20
       + min((槽位压力 − 2) / 4, 1) × 30
       + 10
```

| 术语 | 含义 |
|---|---|
| 可点率 | 开局可直接点击的牌数 ÷ 总牌数 |
| 最高层序号 | 牌堆最高层序号 = **显示层数 − 1**（6 层塔 = 序号 5） |
| 有效钩子 | 钩子数经"可见伴侣加权"。钩子=同一对牌中至少一张被埋。可见伴侣加权：埋的那张同色有 N 张可见，权重 = 1/(N+1)。同色全埋=1.0；同色有 1 张可见=0.5；同色有 2+ 张可见≤0.33 |
| 有效暗钩 | 暗钩数经可见伴侣加权。暗钩=钩子对里深埋的那张恰好是暗牌 |
| 槽位压力 | 最优打法下卡槽最多同时占几个花色位（7 格上限）。值越高容错率越低 |
| 钩子项 | **每 1 个有效钩子 +3.75 分，8 个拿满 30** |
| 暗钩子项 | **每 1 个有效暗钩 +2.5 分，8 个拿满 20** |
| 槽位压力项 | **minSlots=2 起 0 分，每 +1 槽 +7.5 分，6 槽拿满 30** |

> **v1 → v2 变更（2026-08-16）：** v1 用「钩子占比封顶 1」，1 个钩子和满盘钩同分。v2 改按数量计分（分母 5）。
>
> **v2 → v3 变更（2026-08-18）：**
> - 钩子/暗钩分母 5 → 8，让 5~8 之间的关卡也能拉开差距
> - 新增槽位压力项（30 分），解决"钩子拉满也不难"的问题——捕捉"消除顺序不对就死局"的动态难度
>
> **v1/v2/v3 分数不可混排**，换公式后需整批重新生成。

---

## 三、每一项为什么这么设计（核心）

### 1. 可点率项 `(1 − 可点率) × 40` —— 权重最高，40 分

**玩家对一关的第一感受，就是"开局能不能动"。**
开局可点的牌越少，玩家第一步就得先找路、翻牌、记位置——这种"无从下手"的卡感，是劝退感的最大来源；反过来可点率接近 1，就是满桌都能点的白给关。

- 为什么用 `1 − 可点率`：让「难 = 分高」（可点率低 → 分数高），与排序方向一致。
- 为什么权重给到 40（五项里最高）：这是最直接影响卡关感的维度，值得在总分里占最大头。

### 2. 层数项 `最高层序号 × 8` —— 基础成本，每层 8 分

**层数决定"要翻多少层才能见底"。** 塔堆得越高，整局要消的对越多、翻牌次数越多，时长和注意力成本近似线性上升。

- 为什么 × 8：给「每多一层」一个基础成本权重。6 层塔该项 40 分封顶，3 层塔只有 16 分。
- 这一项同时承担两种职责：
  - **跨模板**：多层模板天然比纯底子模板分高，保证不同形状之间有可比性；
  - **同模板内**：生成器对同一模板会随机堆出 3~6 层不等的塔，这一项也把「这局堆得高不高」量化进去。

### 3. 钩子项 `min(有效钩子数 / 8, 1) × 30` —— 局中记忆成本，第三高权重

**钩子 = 某花色的一对牌中至少有一张被埋（层差 ≥ 2 / 上压 / 左右夹）。** 被埋的那张必须等玩家一路翻掉上面所有挡牌才能见到。

- 它制造的是**贯穿整局的记忆负担**：玩家全程记得"还有个 X 没出现"，要给它留槽位、留对，憋到最后一刻才消得掉。
- **可见伴侣加权**：埋的那张同色**越容易看到**，就越**不容易忘**，权重越低。例如同色有 3 张可见时埋 1 张，权重 = 1/(3+1) = 0.25——玩家很容易注意到"还有 X 没消"。
- **分母 8**：80~110 张常规关卡加权钩子数观测 0~8（中位 2），8 个封顶正好覆盖。
- **v1 的教训**：v1 用"钩子占比 ÷ 总牌数 × 100、封顶 1"，百来张牌时哪怕只有 1 个钩子占比就超过 1，直接拿满 30——钩子项退化成"有/无"开关。
- **v3 的改进**：从"按总对数加权"改为"按可见伴侣加权"——同色露在外面的越多，埋的那张越不容易忘。实测用户截图场景：raw 钩 95% → 加权钩 60%，分数更符合玩家直觉。

### 4. 暗钩子项 `min(有效暗钩数 / 8, 1) × 20` —— 暗牌版的不确定性加成

**暗钩子 = 钩子对里深埋那张是暗牌。** 玩家不仅要记住"还有个 X"，还要承担"翻开来才知道是不是 X"的不确定。

- 为什么权重**低于**钩子（20 < 30）：暗牌只增加认知/记忆成本，**不改变消除序列与可解性**（100% 可解由倒推法保证）。它是"体验加深"而不是"规则变难"，所以权重压一档。
- 生成器分配暗牌时会**优先把暗牌埋在钩子深埋位**（`assignDarkTiles` 先遍历钩子再随机补足 10%~20% 名额），所以暗牌版里暗钩子数 ≈ 钩子数，两项合计约"每钩 +6.25 分"。

### 5. 槽位压力项 `min((minSlots − 2) / 4, 1) × 30` —— 动态难度，第二高权重（v3 新增）

**槽位压力 = 最优打法下卡槽最多同时占几个花色位。** 这是第一个**中盘指标**——之前的维度都是开局快照，这一项捕捉"消除顺序不对就死局"的动态难度。

**为什么需要它**：原版麻将的难点不只是"开局能不能点"，更在于"点错顺序就卡死"。比如：
```
你点了 A1 进卡槽 → 想配对 A2，但 A2 被 B 压住
→ 想消 B，但 B2 被 C 压住 → A、B、C 都卡在槽里，容错率暴跌
```
这种"连环依赖"让槽位被"存着等另一半"的牌堆满——minSlots 直接量化这个压力。

- **minSlots=2**：容错极高，怎么玩都不容易死（0 分）
- **minSlots=4**：中等压力（15 分）
- **minSlots=6**：容错极低，必须严格按正确顺序消除（30 分满分）
- 为什么权重 30（第二高）：这是"会不会死局"的核心指标，比钩子/暗钩更直接决定通关难度。
- 分母 4：实测 minSlots 观测 1~5，6 已是极端高难。

**怎么算**：贪心模拟——优先配对（不增槽）、其次消现成对（短暂过槽即消）、最后选"底下压牌最多"的单张进槽（尽早解锁）。记录 slot.size 峰值。

**怎么生成高槽位压力关卡**：倒推法加 `slotPressure`（0~1）参数。倒推前中期故意把一张最高层 + 一张最低层配对，正向玩时高层早露、低层深埋，玩家被迫存槽。默认 0.5。

### 6. 保底 `+ 10`

再简单的关也不至于 0 分。避免文件名里出现"0 分"这种观感，也避免排序时被当成无效数据。10 分是"一关最基础的存在价值"。

---

## 四、为什么用这些指标，而不是别的

设计时比过的候选：

| 候选指标 | 为什么没用 |
|---|---|
| 总牌数 / 对数 | 同一模板内固定不变，无法区分关与关的难度 |
| 消除总步数 | 本质就是对数，同上，固定 |
| 模拟整局后的平均可选项 | 更精确，但计算成本高、难向非技术人员解释；且倒推法已保证可解 |
| 仅用钩子数 | 不能反映开局体验，且缺少"消除顺序"维度 |
| 依赖深度 | 和"层数"高度重复，都在说"埋得深" |
| 陷阱牌比例 | 概念好但计算量大（每个开局选择都要跑一遍解算器） |

**结论**：用「开局可点率（瞬间）× 层数（规模）× 钩子/暗钩子（局中记忆）× 槽位压力（中盘容错）」四个互补维度，兼顾"好算、可解释、能实测校准"。

---

## 五、这套权重是怎么定出来的

1. **先定骨架**（Day 12/13）：可点率 ×40 + 层数 ×8 + 钩子 ×30 + 保底 10。去掉上限后实测原始分 67~117。
2. **暗牌玩法加入后**（Day 15）：加暗钩子项 ×20，暗牌版平均 103 → 123.7，两版明显分层。
3. **钩子改数量梯度**（Day 17）：v1 占比封顶让钩子项二值化，改为每钩 +6 / 暗钩 +4、5 个封顶后，同批关卡拉开到 86~121 分。
4. **封顶 5→8**（Day 22）：让 5~8 个钩子之间也能拉开差距。
5. **加槽位压力**（Day 23，本次）：用户反馈"钩子拉满也不难"——发现缺少"消除顺序"维度。加 minSlots 项 ×30，实测 slotPressure=0.5 时 minSlots 平均 1.9（最高 5），minSlots=5 的关分数达 152。**v1/v2/v3 分数不可混排。**

---

## 六、已知特点与局限（诚实说明）

- **可点率只拍"开局一瞬间"**：局中变化不归它管——但槽位压力项部分弥补了这一点。
- **分数是系列内相对量**：同一模板排序可靠；跨模板的绝对值不能直接横比。
- **暗牌版与普通版的分不可直接横比**：暗牌版天然高一截。
- **v1/v2/v3 的分不可混排**：见第二节变更说明。
- **花色种类数（typeCount）已统计但未进公式**：若后续需要，可加"一对一花色占比"小项。
- **贪心模拟非完美最优**：computeMinSlots 用贪心而非完整 DFS，可能略低估真实最优解的槽位压力。但实测贪心结果稳定、区分度高，足够用于难度分级。

---

## 七、想调的话怎么调

权重集中在 `generator.js` 的 `evaluateDifficulty()`：

| 参数 | 位置 | 含义 |
|---|---|---|
| 40 / 8 / 30 / 20 / 30 / 10 | 公式内 | 可点率 / 层数 / 钩子 / 暗钩 / 槽位 / 保底 的权重 |
| `/ 8`（钩子/暗钩） | 公式内 | 梯度分母，"8 个拿满" |
| `/ 4`（槽位） | 公式内 | 槽位梯度，"6 槽拿满" |
| `slotPressure` | backwardFill 参数 | 0~1，生成时控制叠层配对偏好（默认 0.5） |
| `DARK_WEIGHT` | 文件顶部 | 暗钩子权重（默认 20） |
| `DARK_RATIO_MIN/MAX` | 文件顶部 | 暗牌数量占总牌 10%~20% |

建议流程：
1. 先跑一批，看汇总统计，判断整体档位；
2. 要拉开某个维度，改对应权重或分母，**成套改**——改完新旧对比一批再定；
3. 改完重新生成 + 对照统计，用试玩计时数据验证"分数 vs 通关时间"的相关性。
