// §5.1/5.2 正常跟牌/毙牌/垫牌(can_followcard 纯函数)。期望值取自 design §5.2 require('./_shim'); const A = require('../class.arith.js'); const t = require('./_assert')(); const id = (d, f, n) => (d - 1) * 54 + (f - 1) * 13 + (n - 1); const MF = 1; // 主花色=方块(1) // 副花色2的牌(number 避开 2/7,否则是固定主) const K = d => id(d, 2, 13), Q = d => id(d, 2, 12), J = d => id(d, 2, 11), T = d => id(d, 2, 10), N9 = d => id(d, 2, 9), N8 = d => id(d, 2, 8); // 主牌(flower1) const zK = d => id(d, 1, 13), zQ = d => id(d, 1, 12), zJ = d => id(d, 1, 11), z9 = d => id(d, 1, 9); // 另一副花色3(用于垫牌) const oth = n => id(1, 3, n); // follow(inhand, cards, startcount, startflower, startcardtype) -> can_followcard 结果 const f = (inhand, cards, cnt, flw, typ) => A.can_followcard(MF, inhand, cards, cnt, flw, typ); const res = r => r.result; // 能否跟 const beat = r => (r.cardvalue || 0) > 0; // 是否压过(cardvalue>0) // ===================== 单张跟牌(首家 副K 单张,flower2, 101)===================== // §5.2: 跟同花色任意单张;缺门则任意补(毙牌用主牌可压、垫副牌不压) t.eq('单-跟同花色单张(合法)', res(f([N9(1), z9(1)], [N9(1)], 1, 2, 101)), true); t.eq('单-有同花色却出主牌(拒)', res(f([N9(1), zK(1)], [zK(1)], 1, 2, 101)), false); t.eq('单-缺门垫副牌(合法)', res(f([oth(9), zK(1)], [oth(9)], 1, 2, 101)), true); t.eq('单-缺门垫副牌不压', beat(f([oth(9), zK(1)], [oth(9)], 1, 2, 101)), false); t.eq('单-缺门毙牌主单(合法)', res(f([oth(9), zK(1)], [zK(1)], 1, 2, 101)), true); t.eq('单-缺门毙牌主单压过', beat(f([oth(9), zK(1)], [zK(1)], 1, 2, 101)), true); // ===================== 对子跟牌(首家 副K对,flower2, 201, 2张)===================== // §2: 有同花色对必出对;没对用两张同花色单;不足两张任意补;缺门毙牌必须主对(不能任意两张) t.eq('对-有同花色对必出对(合法)', res(f([N9(1), N9(2), N8(1)], [N9(1), N9(2)], 2, 2, 201)), true); t.eq('对-有对却出两散张(拒)', res(f([N9(1), N9(2), N8(1)], [N9(1), N8(1)], 2, 2, 201)), false); t.eq('对-无对出两同花色单(合法)', res(f([N9(1), N8(1), Q(1)], [N9(1), N8(1)], 2, 2, 201)), true); t.eq('对-缺门毙主对(合法且压)', beat(f([zK(1), zK(2), oth(9)], [zK(1), zK(2)], 2, 2, 201)), true); t.eq('对-缺门两散主顶对(可出但不压)', res(f([zK(1), zQ(1), oth(9)], [zK(1), zQ(1)], 2, 2, 201)), true); t.eq('对-缺门两散主顶对 不压', beat(f([zK(1), zQ(1), oth(9)], [zK(1), zQ(1)], 2, 2, 201)), false); // ===================== 拖拉机跟牌(首家 副KQ两连对,flower2, 302, 4张)===================== // §2: 有同花色拖拉机必出拖;无拖但有两对不连也必出两对 t.eq('拖-有同长拖必出拖(合法)', res(f([J(1), J(2), T(1), T(2), N8(1)], [J(1), J(2), T(1), T(2)], 4, 2, 302)), true); t.eq('拖-有拖却出4散(拒)', res(f([J(1), J(2), T(1), T(2), N8(1)], [J(1), T(1), N9(1), N8(1)], 4, 2, 302)), false); t.eq('拖-无拖两对不连必出两对(合法)', res(f([J(1), J(2), N9(1), N9(2), N8(1)], [J(1), J(2), N9(1), N9(2)], 4, 2, 302)), true); t.eq('拖-有两对却出散张(拒)', res(f([J(1), J(2), N9(1), N9(2), N8(1)], [J(1), N9(1), N8(1), Q(1)], 4, 2, 302)), false); t.eq('拖-缺门毙同长主拖(合法且压)', beat(f([zK(1), zK(2), zQ(1), zQ(2), oth(9)], [zK(1), zK(2), zQ(1), zQ(2)], 4, 2, 302)), true); const N6 = d => id(d, 2, 6); // ===================== 主牌首出的跟牌(followers 必须跟主牌)===================== t.eq('主单 有主必跟主', res(f([zQ(1), oth(9)], [zQ(1)], 1, 1, 101)), true); t.eq('主单 有主却垫副→拒', res(f([zQ(1), oth(9)], [oth(9)], 1, 1, 101)), false); t.eq('主单 缺主垫副(合法不压)', res(f([oth(9), oth(8)], [oth(9)], 1, 1, 101)), true); t.eq('主单 缺主垫副 不压', beat(f([oth(9), oth(8)], [oth(9)], 1, 1, 101)), false); t.eq('主对 有主对必出对', res(f([zJ(1), zJ(2), z9(1)], [zJ(1), zJ(2)], 2, 1, 201)), true); t.eq('主对 有主对却拆散→拒', res(f([zJ(1), zJ(2), z9(1)], [zJ(1), z9(1)], 2, 1, 201)), false); // ===================== 对子首出·同花色不足两张(必出仅有的1张+补)===================== t.eq('对 同花1张必出它+补1', res(f([N9(1), oth(5), oth(6)], [N9(1), oth(5)], 2, 2, 201)), true); t.eq('对 同花1张却不出它→拒', res(f([N9(1), oth(5), oth(6)], [oth(5), oth(6)], 2, 2, 201)), false); // ===================== 拖拉机首出·跟牌方各手牌分支 ===================== t.eq('拖 对子不够·必出1对+补', res(f([J(1), J(2), N9(1), N8(1), N6(1)], [J(1), J(2), N9(1), N8(1)], 4, 2, 302)), true); t.eq('拖 对子不够·不出对→拒', res(f([J(1), J(2), N9(1), N8(1), N6(1)], [N9(1), N8(1), N6(1), Q(1)], 4, 2, 302)), false); t.eq('拖 同花无对·补同花4张', res(f([K(1), Q(1), J(1), T(1), N9(1)], [K(1), Q(1), J(1), T(1)], 4, 2, 302)), true); // 多候选拖拉机(3连对里选1个2连拖)——回归 can_followcard 曾崩溃的 cantype=3xx 分支 t.eq('拖 多候选·出合法2连拖', res(f([K(1), K(2), Q(1), Q(2), J(1), J(2)], [K(1), K(2), Q(1), Q(2)], 4, 2, 302)), true); t.eq('拖 多候选·出不连两对→拒(D1回归)', res(f([K(1), K(2), Q(1), Q(2), J(1), J(2)], [K(1), K(2), J(1), J(2)], 4, 2, 302)), false); // 3连对首出(303) t.eq('拖 3连对·有3连拖必出', res(f([K(1), K(2), Q(1), Q(2), J(1), J(2), N8(1)], [K(1), K(2), Q(1), Q(2), J(1), J(2)], 6, 2, 303)), true); // 3连对首出、跟牌方有富余对子(4对)但含1个3连拖 → 必出该3连拖,拆散→拒 t.eq('拖 3连对·富余对子中出3连拖', res(f([K(1), K(2), Q(1), Q(2), J(1), J(2), N9(1), N9(2)], [K(1), K(2), Q(1), Q(2), J(1), J(2)], 6, 2, 303)), true); t.eq('拖 3连对·拆散3连拖(改出99)→拒', res(f([K(1), K(2), Q(1), Q(2), J(1), J(2), N9(1), N9(2)], [K(1), K(2), Q(1), Q(2), N9(1), N9(2)], 6, 2, 303)), false); // ===================== 手牌数 == 首出张数:全部必出 ===================== t.eq('全出 手牌恰1张(单张首出)', res(f([oth(9)], [oth(9)], 1, 2, 101)), true); t.eq('全出 手牌恰2张(对子首出)', res(f([oth(9), oth(8)], [oth(9), oth(8)], 2, 2, 201)), true); // ===================== 垫牌:缺门首家副花色,用其他副牌补,不争夺出牌权 ===================== // (单张垫牌上文已覆盖;此处补对子/拖拉机首家时的副牌垫) t.eq('垫 对子缺门出两副散(合法)', res(f([oth(9), oth(8), zK(1)], [oth(9), oth(8)], 2, 2, 201)), true); t.eq('垫 对子缺门出两副散 不压', beat(f([oth(9), oth(8), zK(1)], [oth(9), oth(8)], 2, 2, 201)), false); t.eq('垫 拖缺门出四副散(合法)', res(f([oth(9), oth(8), oth(6), oth(5), zK(1)], [oth(9), oth(8), oth(6), oth(5)], 4, 2, 302)), true); t.eq('垫 拖缺门出四副散 不压', beat(f([oth(9), oth(8), oth(6), oth(5), zK(1)], [oth(9), oth(8), oth(6), oth(5)], 4, 2, 302)), false); // ===================== 混合出牌:有该花色但不够,只能补主牌,等效垫牌不争权 ===================== // 首家副对子:手中仅1张该副花色 + 主牌(无其他副牌)→ 必出该副 + 补主,混合牌压不过 t.eq('混 对子·1副+补主(合法)', res(f([N9(1), zK(1), zQ(1)], [N9(1), zK(1)], 2, 2, 201)), true); t.eq('混 对子·1副+补主 不压(等效垫)', beat(f([N9(1), zK(1), zQ(1)], [N9(1), zK(1)], 2, 2, 201)), false); t.eq('混 对子·丢下必出的副牌只出两主→拒', res(f([N9(1), zK(1), zQ(1)], [zK(1), zQ(1)], 2, 2, 201)), false); // 首家副拖拉机:手中2张该副花色散张 + 主牌 → 必出2副 + 补2主,混合压不过 t.eq('混 拖·2副散+补2主(合法)', res(f([N9(1), N8(1), zK(1), zQ(1), zJ(1)], [N9(1), N8(1), zK(1), zQ(1)], 4, 2, 302)), true); t.eq('混 拖·2副散+补2主 不压', beat(f([N9(1), N8(1), zK(1), zQ(1), zJ(1)], [N9(1), N8(1), zK(1), zQ(1)], 4, 2, 302)), false); // 首家副拖拉机:手中1对该副花色(数量仍不足) + 主牌 → 有对也压不过(混合非纯主拖) t.eq('混 拖·副对(不足)+补2主 不压', beat(f([N9(1), N9(2), zK(1), zQ(1), zJ(1)], [N9(1), N9(2), zK(1), zQ(1)], 4, 2, 302)), false); process.exit(t.done('follow') ? 0 : 1);