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

> 配套《使用说明.md》的独立文档：难度分**是怎么算的**，以及**每一项为什么要这么设计**。
> 版本日期：2026-08-17（v2：钩子/暗钩子改为按数量计分）。公式实现见 `generate_fill_std.js` 的 `evaluateDifficulty()`（`generator.js` 同步维护）。

---

## 一、难度分是什么

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

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

---

## 二、公式（v2 · 2026-08-17）

```
难度分 = (1 − 可点率) × 40 + 最高层序号 × 8 + min(钩子数 / 5, 1) × 30 + min(暗钩子数 / 5, 1) × 20 + 10
```

| 术语 | 含义 |
|---|---|
| 可点率 | 开局可直接点击的牌数 ÷ 总牌数 |
| 最高层序号 | 牌堆最高层序号 = **显示层数 − 1**（6 层塔 = 序号 5） |
| 钩子 | 某个花色恰好只有 2 张、且两张**层差 ≥ 2** 的对 |
| 暗钩子 | 钩子对里「深埋那张」恰好是暗牌（只有暗牌版会出） |
| 钩子项 | **每 1 个钩子 +6 分，5 个拿满 30**（数量梯度制） |
| 暗钩子项 | **每 1 个暗钩子 +4 分，5 个拿满 20** |

> **v1 → v2 变更（重要）：** v1 用「钩子占比（钩子数÷总牌数×100）封顶 1」，导致百来张牌时哪怕只有 1 个钩子占比就超过 1、直接拿满 30 分——钩子项退化成"有/无"开关，1 个钩子和满盘钩同分。v2 改为按数量计分后，暗牌版里**每多 1 个钩子实际约 +10 分**（钩子 6 + 暗钩子 4），梯度 0/10/20/30/40/50。v1 生成的旧关卡分数普遍虚高 20~40 分，**与 v2 分数不可混排**，换公式后需整批重新生成。

---

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

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

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

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

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

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

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

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

**钩子 = 某花色只有 2 张、且两张层差 ≥ 2。** 深埋的那张必须等玩家一路翻掉上面所有挡牌才能见到。

- 它制造的是**贯穿整局的记忆负担**：玩家全程记得"还有个 X 没出现"，要给它留槽位、留对，憋到最后一刻才消得掉。这是策略感的主要来源，比开局一瞬间更持续，所以权重给了 30（第二高）。
- **数量梯度（v2）**：每 1 个钩子 +6 分，5 个拿满 30。分母 5 是实测校准值——80~110 张的常规关卡钩子数观测范围 0~5 个（中位 2），5 个封顶正好覆盖，再多对体验的边际差异也趋于饱和。
- **v1 的教训**：v1 用"钩子占比 ÷ 总牌数 × 100、封顶 1"，百来张牌时哪怕只有 1 个钩子占比就超过 1，直接拿满 30——钩子项退化成"有/无"开关，1 个钩子和满盘钩同分。v2 改按数量计分后，"几乎每种花色都是唯一一对且埋在深处"的满盘钩局才能拿到接近满分，只有 1 个钩子的局只拿 6 分。

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

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

- 为什么权重**低于**钩子（20 < 30）：暗牌只增加认知/记忆成本，**不改变消除序列与可解性**（100% 可解由倒推法保证）。它是"体验加深"而不是"规则变难"，所以权重压一档。
- **数量梯度（v2）**：每 1 个暗钩子 +4 分，5 个拿满 20，与钩子项同理。
- 生成器分配暗牌时会**优先把暗牌埋在钩子深埋位**（`assignDarkTiles` 先遍历钩子再随机补足 10%~20% 名额），所以暗牌版里暗钩子数 ≈ 钩子数，两项合计约"每钩 +10 分"——v1 时代（Day 15）暗牌版平均 103 -> 123.7 的分层效果，在 v2 下由数量梯度自然延续。

### 5. 保底 `+ 10`

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

---

## 四、为什么用"开局可点率"，而不是别的指标

设计时比过几个候选：

| 候选指标 | 为什么没用 |
|---|---|
| 总牌数 / 对数 | 同一模板内固定不变，无法区分关与关的难度 |
| 消除总步数 | 本质就是对数，同上，固定 |
| 模拟整局后的平均可选项 | 更精确，但计算成本高、难向非技术人员解释；且倒推法已保证可解，局中不会真死局 |
| 仅用钩子数 | 不能反映开局体验（v1 时代还因封顶失去数量区分度，v2 已修正） |

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

---

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

1. **先定骨架**（Day 12/13）：可点率 ×40 + 层数 ×8 + 钩子 ×30 + 保底 10。去掉上限后实测原始分 67~117 —— 骨架能把关拉开，但偏挤在 100 上下。
2. **暗牌玩法加入后**（Day 15）：普通公式不含暗牌，暗牌版需要单独体现"不确定性加成"，于是加暗钩子项 ×20，实测平均 103 -> 123.7，两版明显分层。
3. **钩子改数量梯度**（Day 17，2026-08-16 深夜校准）：实测发现 v1 的占比封顶让钩子项二值化（1 个钩子 = 满盘钩 = 满分 30），同一底座 1~5 个钩子的关卡全部挤在 121~127 分。改为每钩 +6 / 暗钩 +4、5 个封顶后，实测同批关卡拉开到 86~121 分，每个钩子约 10 分台阶。**v1 与 v2 分数不可混排，旧批次需重新生成。**
4. **现在的数字仍是"经验校准值"**：目标是让各维度的占比接近玩家感知——开局卡顿(40) > 局中记忆(30) > 暗牌不确定(20) > 层数规模(8/层) > 保底(10)。后续可用编辑器的**试玩计时**（通关耗时随关卡保存）做相关性验证：分数与实际通关时间相关性越高，公式越"诚实"。

---

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

- **可点率只拍"开局一瞬间"**：局中变化（翻一半后突然难找）不归它管——但那些情况由倒推法保证可解，不会真卡死。
- **分数是系列内相对量**：同一模板（同形状、同规模）排序可靠；跨模板的绝对值不能直接横比（不同牌数、不同层高）。排全量难度曲线时建议**按系列分组排序**。
- **暗牌版与普通版的分不可直接横比**：暗牌版天然高一截（每钩多 4 分），两条难度曲线要分开排。
- **v1 与 v2 的分不可混排**：见第二节变更说明。
- **花色种类数（typeCount）已统计但未进公式**：同样 30 对牌，摊在 25 种花色（重复多、好配）和摊在 30 种（几乎全是一对一精确匹配）难度不同，当前公式看不见这件事——钩子只捕捉了其中层差大的部分。若后续需要，可加"一对一花色占比"小项。

---

## 七、想调的话怎么调

权重数字集中在 `generate_fill_std.js` / `generator.js` 的 `evaluateDifficulty()` 里（40 / 8 / 30 / 20 / 10），钩子梯度分母是公式里的 `hooks / 5` 和 `darkHooks / 5`（想"5 个拿满"改成"8 个拿满"就改分母），暗牌相关参数是文件顶部的 `DARK_RATIO_MIN / MAX`（暗牌数量占总牌 10%~20%）和 `DARK_WEIGHT`（暗钩子权重 20）。

建议流程：

1. 先跑一批，看「难度: 最小 / 最大 / 平均」汇总，判断整体档位是否合适；
2. 要拉开某个维度，改对应权重或分母，**成套改**——改完新旧对比一批再定；
3. 改完重新生成 + 对照统计，不要单点微调；用试玩计时数据验证"分数 vs 通关时间"的相关性。
