diff --git a/server/games/erqiwang/docs/design/design.md b/server/games/erqiwang/docs/design/design.md index bc7540b..58226bc 100644 --- a/server/games/erqiwang/docs/design/design.md +++ b/server/games/erqiwang/docs/design/design.md @@ -40,7 +40,7 @@ | --- | --- | | 主牌 / 副牌 | 庄家选定的花色为"主牌",其余三个花色为"副牌";主牌大于副牌 | | 固定主牌 | 不论选择哪个花色为主,大王、小王、以及所有花色的"2""7"恒为主牌 | -| 正2 / 正7 | 选定花色的"2""7",大于其余花色的"2""7" | +| 正2 / 正7 | 选定花色的"2""7",大于其余花色的"2""7"(口语里也叫"主2""主7",本文档统一用"正2""正7")| | 副2 / 副7 | 非选定花色的"2""7" | | 暗牌 | 发牌时留在桌面的 8 张牌;庄家确认后可摸起查看,摸起后就不再是"暗牌",而是并入庄家手牌 | | 底牌 | 庄家"埋牌"时从手牌中选出重新扣下的 8 张牌;"暗牌"与"底牌"是两批不同的牌,不通用、不互换 | @@ -162,14 +162,30 @@ ### 5.3 拖拉机定义与相邻关系 -同一花色内点数相邻的两个(或以上)对子构成"拖拉机",如 K K Q Q 为两连对(两托),Q Q J J K K 为三连对(三托)。7 不可以和 6、8 组成拖拉机;6 和 8 可以组成拖拉机。 +**拖拉机 = 在各自的大小序列里「位置相邻」的两个(或以上)对子**,如 K K Q Q 为两连对(两托),K K Q Q J J 为三连对(三托)。副牌与主牌各有自己的序列,相邻规则不同,分开说: -拖拉机的相邻顺序(从大到小): +#### 副牌拖拉机(非主花色) + +**必须同一花色**,点数在下面这条序列里相邻: ``` -大王 - 小王 - 正7 - 副7 - 正2 - 副2 - A - K - Q - J - 10 - 9 - 8 - 6 - 5 +A - K - Q - J - 10 - 9 - 8 - 6 - 5 ``` +序列里没有 7 和 2(它们是固定主牌,见第 3 节),所以 **7 不与 6、8 相连,而 6 与 8 相连**(中间隔的 7 已被抽走);同理 4、3 不在牌堆里(见第 2 节),5 是副牌序列的最小一档。**跨花色不构成连对**——♥K 对 + ♣Q 对不是拖拉机。 + +#### 主牌拖拉机(主花色 + 固定主牌) + +主牌只有**一条**完整的大小序列(即第 3 节的主牌顺序): + +``` +大王 - 小王 - 正7 - 副7 - 正2 - 副2 - 主A - 主K - 主Q - 主J - 主10 - 主9 - 主8 - 主6 - 主5 +``` + +只要在这条序列里**位置相邻**的对子,就构成拖拉机,**不限花色**。因此「正7 对 + 副7 对」「副7 对 + 正2 对」「副2 对 + 主A 对」「大王对 + 小王对」都是合法的两连对,尽管两个对子的花色不同。序列末段(主A 到 主5)都是**选定花色**的普通牌,其中同样是 6 与 8 相连、7 不入此段。 + +> **同一档位的两个对子不相邻**:「副7」在序列里只占**一个**位置,所以 ♥7 对 + ♣7 对是**两个平级的对子、不是拖拉机**;副2 同理。这与第 3 节「成对只认同花色同点数的两副」是同一条原则的两面——先按同花色同点数成对,再看这些对子在序列里的位置是否相邻。 + ### 5.4 甩牌详细规则 #### 5.4.1 基础定义 diff --git a/server/games/erqiwang/test/test_arith.js b/server/games/erqiwang/test/test_arith.js index 10aff53..c0cbd44 100644 --- a/server/games/erqiwang/test/test_arith.js +++ b/server/games/erqiwang/test/test_arith.js @@ -192,6 +192,43 @@ t.eq('连对 边界 上述四张组不出拖拉机', t.eq('连对 正 正7对+副7对 构成两连对', A.get_tuolaji_list(MF, A.get_pairlist(MF, A.order_cards(MF, [id(1, 1, 7), id(2, 1, 7), id(1, 2, 7), id(2, 2, 7)])), 302).length, 1); +// ============ §5.3 拖拉机:副牌须同花色 / 主牌按链相邻·不限花色 ============ +// 这一层此前只测到 is_continuous(两张牌相不相邻),没测到「这几个对子能不能真的组成拖拉机」。 +// design §5.3 改写后明确:副牌**必须同一花色**;主牌只要在主牌序列里**位置相邻**即可、**不限花色**; +// 同一档位的两个对子(如 ♥7对 + ♣7对)平级、不相邻。 +const tljOf = cards => { + const s = A.order_cards(MF, cards.slice()); + const ps = A.get_pairlist(MF, s); + return ps.length >= 2 && A.get_tuolaji_list(MF, ps, 300 + ps.length).length > 0; +}; +// —— 主牌链:跨花色也成连对 —— +t.eq('§5.3 主链 正7对+副7对(跨花色) 成连对', tljOf([id(1, 1, 7), id(2, 1, 7), id(1, 2, 7), id(2, 2, 7)]), true); +t.eq('§5.3 主链 副7对+正2对(跨花色) 成连对', tljOf([id(1, 2, 7), id(2, 2, 7), id(1, 1, 2), id(2, 1, 2)]), true); +t.eq('§5.3 主链 正2对+副2对(跨花色) 成连对', tljOf([id(1, 1, 2), id(2, 1, 2), id(1, 3, 2), id(2, 3, 2)]), true); +t.eq('§5.3 主链 副2对+主A对(跨花色) 成连对', tljOf([id(1, 2, 2), id(2, 2, 2), id(1, 1, 1), id(2, 1, 1)]), true); +t.eq('§5.3 主链 大王对+小王对 成连对', tljOf([big(1), big(2), small(1), small(2)]), true); +t.eq('§5.3 主链 小王对+正7对 成连对', tljOf([small(1), small(2), id(1, 1, 7), id(2, 1, 7)]), true); +t.eq('§5.3 主链 三连(副7对+正2对+副2对) 成立', tljOf([id(1, 2, 7), id(2, 2, 7), id(1, 1, 2), id(2, 1, 2), id(1, 3, 2), id(2, 3, 2)]), true); +t.eq('§5.3 主链 主8对+主6对 成连对(7被抽走)', tljOf([id(1, 1, 8), id(2, 1, 8), id(1, 1, 6), id(2, 1, 6)]), true); +// —— 同一档位的两个对子平级、不相邻 —— +t.eq('§5.3 同档 ♥7对+♣7对 不成连对', tljOf([id(1, 3, 7), id(2, 3, 7), id(1, 2, 7), id(2, 2, 7)]), false); +t.eq('§5.3 同档 ♥2对+♣2对 不成连对', tljOf([id(1, 3, 2), id(2, 3, 2), id(1, 2, 2), id(2, 2, 2)]), false); +// —— 副牌:必须同花色 —— +t.eq('§5.3 副牌 ♥K对+♥Q对 同花色 成连对', tljOf([id(1, 3, 13), id(2, 3, 13), id(1, 3, 12), id(2, 3, 12)]), true); +t.eq('§5.3 副牌 ♥K对+♣Q对 跨花色 不成连对', tljOf([id(1, 3, 13), id(2, 3, 13), id(1, 2, 12), id(2, 2, 12)]), false); +t.eq('§5.3 副牌 ♥8对+♥6对 成连对(7是主牌被抽走)', tljOf([id(1, 3, 8), id(2, 3, 8), id(1, 3, 6), id(2, 3, 6)]), true); +t.eq('§5.3 副牌 ♥8对+♣6对 跨花色 不成连对', tljOf([id(1, 3, 8), id(2, 3, 8), id(1, 2, 6), id(2, 2, 6)]), false); +t.eq('§5.3 副牌 ♥6对+♥5对 成连对(5为最小档)', tljOf([id(1, 3, 6), id(2, 3, 6), id(1, 3, 5), id(2, 3, 5)]), true); +t.eq('§5.3 副牌 ♥9对+♥K对 不相邻 不成连对', tljOf([id(1, 3, 9), id(2, 3, 9), id(1, 3, 13), id(2, 3, 13)]), false); +// —— §2 牌堆里没有 3 和 4,所以副牌序列的最小一档就是 5 —— +// (规则复核时有人把副牌链写成 "…-6-5-3",那需要牌堆保留 3;但 92 = 28×3 + 8 只有在 +// 3、4 都剔除时才成立。这里就地钉住"牌堆里 3 和 4 各 0 张",避免链的末端再被写错。) +const _P = require('../class.paiju.js'); +const _deck = _P.new({ paiju_list: [] }, 0).cards.filter(c => c.dealowner !== -1); +t.eq('§2 参与发牌的牌里 3 和 4 各 0 张', + [3, 4].map(n => _deck.filter(c => c.number === n).length), [0, 0]); +t.eq('§2 参与发牌共 92 张 = 28×3 + 暗牌8', [_deck.length, 28 * 3 + 8], [92, 92]); + // ============ §6.3 扣底触发的反面:赢末轮的牌不全是主牌就不扣底 ============ t.eq('扣底 反 副牌两连对赢末轮 → 0', mult([id(1, 2, 13), id(2, 2, 13), id(1, 2, 12), id(2, 2, 12)]), 0); t.eq('扣底 反 混合出牌(主K+副K)赢末轮 → 0', mult([id(1, 1, 13), id(1, 2, 13)]), 0);