Files
huajishe-tts/docs/double-up/00-rules.md
e2hang ddcb28472c feat(double-up): 双升游戏 + 单端口 start.sh 部署 + 文档统一更新
- 新增双升(DoubleUp)完整实现:引擎 double-up-core.ts / double-up.ts、牌桌 UI DoubleUpBoard.tsx、单测与 E2E
- 甩牌跟牌修复:validateFollow 新增 mixed 分支,甩牌后其他家可正常跟单张同花色牌
- start.sh:PORT=5173 单端口一键启动(Koa 同时 serve 页面+/api+/socket.io),浏览器直连无需 Vite 代理
- 玩家名 server 权威化(join 时写入 G.playerNames);LobbyRoomList 支持 fixedSeats(双升固定 4 人)
- 文档:新增 dev-log/14、更新 dev-log README 索引与进度、更新根 README 启动/部署说明
- .gitignore 忽略根目录 data/*.db 运行时 SQLite
2026-08-30 02:11:04 +08:00

214 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 双升(两副牌升级 / 拖拉机 / 80分玩法规则汇总
> 本文是给 CodeBuddy及后续实现双升游戏 `packages/engine/src/games/double-up.ts`)的玩法依据。
> 来源QQ游戏"升级"规则说明、百度百科"升级/拖拉机"词条、知乎升级(拖拉机)规则说明。
> 所有 **【已拍板】** 标记的规则为用户在 2026-08-27 确认的选型,实现时以此为准。
---
## 0. 一句话定义
四人两两结对,用**两副含大小王的扑克牌108张**,通过"亮主定花色(无人亮则翻牌兜底)→ 庄家扣底 → 轮流出牌捡分5/10/K→ 抠底翻番"的流程,按闲家得分决定庄家队伍是否升级,先打到 A 的一方获胜。
---
## 1. 牌具与发牌
- 牌数:**108 张**(两副 × 54 张,含大小王)。【已拍板 V1含大小王】
- 人数4 人,**固定 4 人**(双升不支持其他人数),对家为一队(南北 vs 东西)。
- 分牌分值:**5 = 5 分****10 = 10 分****K = 10 分**。两副牌总分 = 200 分。
### 1.1 发牌流程(逐张发 + 实时排序 + 可亮主)
发牌是一个**独立阶段deal**,与出牌严格分离。流程:
1. 洗牌后,**一张一张**发给 4 家(按座位顺序轮转)。每发出一张,收到牌的玩家手牌**立即按「花色分组(主牌在前、副牌按 ♠>♥>♦>♣)、同花色点数从大到小」重排**(由 `sortHand` 保证,玩家无需手动整理)。
2. 发到 **100 张**时停止(每人 25 张),**剩余 8 张作为底牌,不再发给任何人**
3. 发牌过程中deal 阶段内任意时刻)任何玩家可**亮主**(见 §3。亮出的牌**从手牌移到该玩家座位前的「亮主公开区」**(桌上所有人可见),被更高优先级反主时**自动归还其手牌**。
4. 发牌结束(满 100 张、剩 8 张底牌)**自动定主**:按亮主公开区里「当前最大优先级」的亮主记录定主花色+庄家(无人亮则**翻底牌第一张兜底**,见 §3 V3亮主区牌归还各自主人庄家收底直接进入 **bottom 阶段**。**不再有独立的「确认主花色」按钮/阶段**Request B
5. 主花色确认后进入 **bottom 阶段****庄家把 8 张底牌全部收入自己手牌**(临时 33 张),按主花色在前、副花色在后重新排序,再**挑 8 张扣出作为新底牌**。这 8 张扣底牌**只有庄家可见,其他人不可见**(结算抠底时才公开)。
6. 扣底完成后进入 play 阶段开始出牌。
> 工程实现注v2026-08-27 重构):发牌改为**真正的一张一张发**——房主端 UI 通过 `dealOne` move 以 ~150ms 间隔自动驱动(`DoubleUpBoard` 内 `useEffect` 监听 `phase==='deal' && me==='0'`),发满 100 张(`dealIndex>=100`)自动调用 `resolveTrump` 定主并转 `bottom`。亮主为**公开亮主区**`revealed[pid]``declareTrump` 把牌从手牌移入该区(所有人可见),更高优先级反主时先把被反者亮主区的牌**归还其手牌**再落新亮主;`resolveTrump`(发满 100 张自动触发,或旧 `confirmTrump` move 兜底)把所有亮主区牌归还各自主人、确定主花色/庄家、庄家收底。`playerView` 对所有人隐藏 `deck`(未发的整副牌)与 `bottom`(非庄家),但 `revealed` 公开。
> 阶段机:`waiting`(进房)→ `deal`(一张一张发+可亮主,发满自动定主)→ `bottom`(庄家收底+扣 8 张隐藏)→ `play`(出牌)→ `score`(结算)。`trump` 阶段已合并进 `deal` 末尾自动处理。
---
## 2. 级牌 / 主牌 / 副牌
- **级数**:双方从 **2 级**起步;庄家队伍的当前级别 = 本局级牌点数。序列2,3,4,5,6,7,8,9,10,J,Q,K,A。打到 A 且获胜即整场获胜。
- **级牌**:点数等于当前级数的一切牌(四种花色都有),无论是否主花色皆为主牌。
- **主牌**:大小王(无花色,恒为主)+ 级牌(四花色皆主)+ **主花色**所有牌(亮主决定)。
- **副牌**:除主牌之外其余三花色的牌。
- **2 是否常主**:【已拍板 V22 为常主】所有花色的 2 均为主牌,分"主2"主花色里的2与"副2"其他花色的2
主牌大小顺序2为常主从大→小
```
大王 > 小王 > 主级牌 > 副级牌 > 主2 > 副2 > A > K > Q > J > 9 > 8 > 7 > 6 > 5 > 4 > 3
```
副牌大小顺序(从大→小,花色间无大小):
```
A > K > Q > J > 9 > 8 > 7 > 6 > 5 > 4 > 3 > 2
```
> 同级牌"先出者大"(出牌时同点同花色以先落者为大)。
---
## 3. 亮主 / 反主(确定主花色)
**时机**:在 **deal 阶段(发牌过程中)** 即可亮主;亮出的牌从手牌移到该玩家座位前的「亮主公开区」(桌面可见,所有人都能看到),被反主时立即归还其手牌——不在手中、也不在暗处。发牌满 100 张时**自动定主**(亮主区最大优先级胜出,否则翻底牌第一张兜底),无需手动确认。
**抢亮优先级由低到高V5 四级抢亮,"2 张反 1 张"**
| 等级 | 亮出的牌 | 效果 |
| --- | --- | --- |
| 1 | 单张级牌 | 定该花色为主 |
| 2 | 一对级牌 | 反主(覆盖单张) |
| 3 | 一对小王 | 反主(覆盖级牌对,等同"反无主"方向) |
| 4 | 一对大王 | 反主(最高,打无主) |
- 后亮者用**更高优先级**的牌可**反主**改花色;同优先级不能再反(以先出现者为准)。
- "自保":直接亮对级牌同花色,除反无主外不能被反;"自反":本队再次用更高牌反。
- 一对小王/大王亮出 = **打无主**(无主花色,仅大小王+级牌+2 为主)。
- **庄家认定**:本局庄家 = 最后成功亮主(优先级最高)的玩家。第一局若无人亮主,庄家按轮庄默认值(初始 P0之后每局按轮庄顺序§8依次更替。
- **【已拍板 V3翻第四张兜底】** 仅当全程**无人亮主**时,发牌满 100 张后从底牌翻一张牌兜底定主:
- 翻到普通花色牌 → 该花色为主花色。
- 翻到大小王 → 打无主。
- 翻到级牌 → 该级牌的花色为主花色。
- 有人亮过主(含无主)则**不**翻牌,直接采用已亮主结果。
- 定主由 `resolveTrump` 在发满 100 张时自动完成(旧 `confirmTrump` move 作为手动兜底保留但未在 UI 暴露。定案后庄家收底牌§4
---
## 4. 扣底(庄家收底 + 扣 8 张隐藏)
- 主花色确认后,进入 bottom 阶段:**庄家先将 8 张底牌全部收入自己手牌**(手牌临时变为 25+8 = **33 张**),按主花色在前、副花色在后重新排序。
- 庄家从这 33 张中**挑 8 张扣出**,作为新底牌(`unshownBottom`)。这 8 张**只有庄家可见**其他人不可见不放在明面直到结算抠底§6才公开。
- 庄家扣底后手牌回到 25 张,进入 play 阶段。
- 扣底策略:藏分牌或藏大牌防被抠。
- 底牌分数归属在 §6 抠底时结算。
> 工程实现注:底牌 8 张在 playerView 中对非庄家返回空数组,确保 secrecy仅庄家 `myHand` 含其完整 33→25 牌。
---
## 5. 出牌规则
- **首家(首引)**:每局第一轮由**庄家**先出;之后每轮由上一轮赢家先出。
- 首家可出任意牌型:单张、对子、拖拉机(连对)、或"甩牌"。
- **跟牌义务**
- 首家出**单张**:有该花色必须跟单张(无该花色可毙/垫)。
- 首家出**对子**:有对子必须出对子(含拖拉机中的对)。
- 首家出**拖拉机**:有拖拉机必须出拖拉机;无拖拉机则必须出对子;无对子才能出别的。
- 首家出**副牌花色**而某家**无此门花色**:可出**主牌毙**(牌型须匹配:对子用主对、拖拉机用主拖拉机),或出**别的副牌垫**(垫牌永远最小)。
- ⚠️ 若手上有首出花色牌,**不能**直接用主牌毙,必须先跟该花色。
- **毙牌(主牌压副牌)**
- 仅当无首出花色时可出主牌;主牌天然大于副牌。
- 毙牌牌型须 ≥ 首出牌型(拖拉机数、对子数都不能少于首家),否则算垫牌。
- 多家毙牌时比毙牌中拖拉机/对子大小,大者"盖毙"。
- **甩牌规则**
- 甩出的所有牌必须**全大**(同花色无人能压)。
- 甩牌失败(他人有更大的)→ 强制出被压制牌型中的**最小牌**。
- **【已拍板 V4甩牌失败罚分】** 额外按被压制的牌型张数罚分:
- 单张被压:**罚 10 分**。
- 对子被压:**罚 20 分**。
- 拖拉机被压(长度 n**罚 20 × n 分**。
- 结算方向:**庄家甩牌失败 → 闲家得分**(罚分加给闲家);**闲家甩牌失败 → 闲家扣分**(从闲家总分中扣除)。
- 甩牌失败的具体最小牌型仍须按规则打出(软惩罚 + 罚分并存)。
- 一轮 4 家出齐(或对应牌型张数),最大者赢墩并收走本圈所有分牌。
> **UI 出牌交互v2026-08-27**:本人手牌可**自由多选**`DoubleUpBoard` 实时调用 `parsePlay` + `validateFollow` 解析当前选择并提示牌型(单张/对子/拖拉机/甩牌)与合法性(如"必须跟领出花色")。「出牌」按钮**仅当选择合法时**才点亮。首出可任意牌型;跟牌若持有领出花色/对子/拖拉机则必须匹配,断门才可毙/垫。这样**对子、拖拉机、甩牌均可在 UI 正常打出**(之前缺实时提示导致只能出单张)。
---
## 6. 拖拉机(连对)定义
- **定义**:同花色、点数**连续**、每点两张(对子)的联牌。必须全主或全副,主副不可混。
- 副牌例:`KKQQ``JJ99``554433`
- 主牌例:`一对小王+一对主级牌``主级牌对+副级牌对``主级牌对+主A对`
- **级牌相邻可连**:主花色级牌 7 可与红桃 66、红桃 88 连成拖拉机;副级牌 7 与副花色 7 可连(同为主)。
- **王的特殊**:大王+小王 可成对/连(在主牌序相邻)。
- **不算拖拉机**`554``544``5533``JJQ``两对副10`(不相邻)、`JJ1010``AA22`(常主时 AA 与 22 间隔 KQJ9…8不相邻
> 连续长度 n两连对 n=2三连对 n=3以此类推。
---
## 7. 抠底(底牌翻番)
- 最后一圈若**闲家方**赢,触发抠底:底牌中的分牌按倍数计入闲家得分。
- **倍数 = 2 的(最后一圈最大牌型张数)次方**。【已拍板 V6封顶 64 倍】(即 6 张 / 三连对封顶)。
- 单张抠底2¹ = 2 倍。
- 对子抠底(如 442² = 4 倍。
- 两连对(如 44552⁴ = 16 倍。
- 三连对(如 4445552⁶ = 64 倍(封顶)。
- 甩牌抠底:只按甩牌中的**最大牌型**算倍数(如甩 AKQJ109877按对子 77 算 4 倍;甩 AKQJ 按单张算 2 倍)。
- 最后一圈**庄家方**大 → 不抠底(底牌分归庄家,闲家不得分)。
> 底牌分 = 底牌中 5/10/K 的面值之和 × 倍数,加入闲家总分后参与 §8 升级判定。
---
## 8. 得分与升级判定
以**闲家(抓分方)本局总得分**计(含捡到的分牌 + 抠底翻番分 + 甩牌罚分加减),总分 200
| 闲家得分 | 结果 |
| --- | --- |
| 0 | 庄家方连升 **3 级** |
| 1 39不满40 | 庄家方升 **2 级** |
| 40 79 | 庄家方升 **1 级** |
| 80 119 | 轮换庄家(闲家上台,不升级) |
| 120 159 | 闲家方升 **1 级** |
| 160 199 | 闲家方连升 **2 级** |
| ≥200每多40 | 闲家方再加升相应级数 |
> 台阶累计逻辑0/40/80/120/160/200… 各对应 庄+3/庄+2/庄+1/闲+1/闲+2/闲+3…之后每 40 分再 +1 级。
**轮庄**
- 庄家升级(保庄):下一副由**对家**当庄家。
- 闲家上台:下一副由**本副庄家的下家**当庄家。
**胜负**:某队打到 **A** 且本局获胜(升级过 A 或闲家上台时已达 A→ 该队整场胜。
> A 级特殊回流(如被用 A 抠底则下局级牌回退到 J属进阶变体【待确认默认不实现】。
---
## 9. 已拍板的规则选型汇总
| # | 项目 | 选型 |
| --- | --- | --- |
| V1 | 是否含大小王 | 含108张 |
| V2 | 2 是否常主 | 是主2/副2 |
| V3 | 无人亮牌兜底 | 翻第四张兜底(有人亮则不打翻牌);翻到王=无主,翻到花色/级牌=该花色主 |
| V4 | 甩牌失败惩罚 | 罚分单张10/对子20/拖拉机20×n庄家失败闲家得、闲家失败闲家扣 |
| V6 | 抠底倍数上限 | 64 倍封顶 |
未拍板项(实现时采用主流默认,后续可调整):
- V5 亮主机制:四级抢亮(单级牌<对级牌<对小王<对大王)+ 自保/自反。
- V7 级牌起始:从 2 级起步。
- A 级回流变体:默认不实现。
- 翻牌兜底翻到"级牌"时是否允许再反主:默认不允许(翻牌为最终兜底,等同于定主)。
---
## 10. 与现有代码库的对接要点(实现提示,非规则)
- 新游戏文件:`packages/engine/src/games/double-up.ts`,导出 `TTSGame``name: 'DoubleUp'``engineName: 'DoubleUp'``minPlayers/maxPlayers: 4``setup` 发 108 张牌(含大小王),留 8 张底。
- `game.ui`:需声明主/副牌区、4 家手牌区、底牌区、已出牌区、当前级牌/主花色指示。因 4 人 + 拖拉机复杂度高,建议先实现**逻辑引擎**moves亮主/翻牌兜底/扣底/出牌/跟牌校验/收分/甩牌罚分/抠底/升级判定UI 可先用 `game.ui.actions` + 简化 Board或仿 `HoldemBoard` 写专用 `DoubleUpBoard`
- 必实现的核心校验(出牌合法性):跟牌花色义务、对子/拖拉机跟牌、毙牌牌型匹配、甩牌全大判定 + 罚分、主副大小比较、抠底倍数封顶64
- 需注册到 `packages/server/src/index.ts` 的两个数组(`Server({games})``GAMES_ENTRIES`),及 `apps/web/src/App.tsx``GAMES`maxSeats=4
- 建议配套:在 `packages/engine/src/games/double-up.test.ts` 写单元测试覆盖亮主/反主、跟牌校验、拖拉机判定、抠底倍数、升级判定表、甩牌罚分。
---
## 11. 参考来源
- QQ游戏"升级"规则说明minigameimg.qq.com/help/rule36.html
- 百度百科:升级/拖拉机baike.baidu.com/item/升级/3563150
- 知乎升级拖拉机规则zhuanlan.zhihu.com/p/2035707530240844260甩牌罚分精确算法来源
- 知乎:升级(拖拉机)游戏基本游戏规则zhuanlan.zhihu.com/p/101357920
- 知乎四人升级80分/拖拉机/双升游戏规则说明zhuanlan.zhihu.com/p/1900479633754948317