// 手牌三种标记推导 + 选主面板的花色统计(清单 §1.6 / §1.5) const { load } = require('./_load'); const t = require('./_assert')(); load('client/js/01_SubGame/codes/shared/cards.js'); load('client/js/01_SubGame/codes/core/CardOrder.js'); load('client/js/01_SubGame/codes/core/CardMark.js'); const id = (d, f, n) => (d - 1) * 54 + (f - 1) * 13 + (n - 1); const big = d => (d - 1) * 54 + 53; const small = d => (d - 1) * 54 + 52; const MF = 1; // 主花色 = 方块 // ---- 正2 / 正7(主花色的 2 和 7)标 zheng ---- t.eq('正2 标 zheng', EQW_CardMark.markOf(id(1, MF, 2), [id(1, MF, 2)], MF), 'zheng'); t.eq('正7 标 zheng', EQW_CardMark.markOf(id(1, MF, 7), [id(1, MF, 7)], MF), 'zheng'); // ---- 其他主牌标 zhu:双王、副2、副7、主花色普通牌 ---- t.eq('大王 标 zhu', EQW_CardMark.markOf(big(1), [big(1)], MF), 'zhu'); t.eq('小王 标 zhu', EQW_CardMark.markOf(small(1), [small(1)], MF), 'zhu'); t.eq('副2 标 zhu', EQW_CardMark.markOf(id(1, 2, 2), [id(1, 2, 2)], MF), 'zhu'); t.eq('副7 标 zhu', EQW_CardMark.markOf(id(1, 3, 7), [id(1, 3, 7)], MF), 'zhu'); t.eq('主花色A 标 zhu', EQW_CardMark.markOf(id(1, MF, 1), [id(1, MF, 1)], MF), 'zhu'); // ---- 副牌无标记 ---- t.eq('副牌普通牌无标记', EQW_CardMark.markOf(id(1, 2, 5), [id(1, 2, 5)], MF), null); // ---- 拖拉机:同花色两个连续对子 → 四张全标 tractor ---- // 副牌花色2 的 9对 + 10对(编码相差1,连续) const tractorHand = [id(1, 2, 9), id(2, 2, 9), id(1, 2, 10), id(2, 2, 10)]; const m1 = EQW_CardMark.marksOfHand(tractorHand, MF); t.eq('连续两对全标 tractor', tractorHand.map(c => m1[c]), ['tractor', 'tractor', 'tractor', 'tractor']); // ---- 反面:不连续的两个对子不成拖拉机(副牌无标记)---- const notTractor = [id(1, 2, 9), id(2, 2, 9), id(1, 2, 12), id(2, 2, 12)]; const m2 = EQW_CardMark.marksOfHand(notTractor, MF); t.eq('不连续两对不成拖', notTractor.map(c => m2[c] || null), [null, null, null, null]); // ---- 反面:单个对子不是拖拉机 ---- const onePair = [id(1, 2, 9), id(2, 2, 9)]; const m3 = EQW_CardMark.marksOfHand(onePair, MF); t.eq('单对不成拖', onePair.map(c => m3[c] || null), [null, null]); // ---- 反面:跨花色的对子不成拖(花色2的9对 + 花色3的10对)---- const crossFlower = [id(1, 2, 9), id(2, 2, 9), id(1, 3, 10), id(2, 3, 10)]; const m4 = EQW_CardMark.marksOfHand(crossFlower, MF); t.eq('跨花色不成拖', crossFlower.map(c => m4[c] || null), [null, null, null, null]); // ---- 优先级:属于拖拉机的正2,标 tractor 而非 zheng ---- // 主牌序里 正2 与 副2 相连(is_continuous: 正2、负2) const zhengInTractor = [id(1, MF, 2), id(2, MF, 2), id(1, 2, 2), id(2, 2, 2)]; const m5 = EQW_CardMark.marksOfHand(zhengInTractor, MF); t.eq('拖拉机优先于 zheng', m5[id(1, MF, 2)], 'tractor'); t.eq('拖拉机优先于 zhu', m5[id(1, 2, 2)], 'tractor'); // ---- 互斥:任何一张牌至多一个标记(marksOfHand 的值域)---- const mixed = [big(1), small(1), id(1, MF, 2), id(1, MF, 7), id(1, 2, 5), id(1, 2, 9), id(2, 2, 9), id(1, 2, 10), id(2, 2, 10)]; const m6 = EQW_CardMark.marksOfHand(mixed, MF); const validMarks = Object.keys(m6).every(k => ['tractor', 'zheng', 'zhu'].indexOf(m6[k]) >= 0); t.eq('标记值域合法', validMarks, true); // ---- markOf 与 marksOfHand 结果一致 ---- const consistent = mixed.every(c => (m6[c] || null) === EQW_CardMark.markOf(c, mixed, MF)); t.eq('markOf 与 marksOfHand 一致', consistent, true); // ---- 选主前(mainflower=0):只有固定主牌成主,主花色普通牌无标记 ---- t.eq('选主前 方块A 无标记', EQW_CardMark.markOf(id(1, 1, 1), [id(1, 1, 1)], 0), null); t.eq('选主前 2 仍是主', EQW_CardMark.markOf(id(1, 1, 2), [id(1, 1, 2)], 0), 'zhu'); t.eq('选主前 王 仍是主', EQW_CardMark.markOf(big(1), [big(1)], 0), 'zhu'); // ---- countByFlower:选主面板的张数与对数 ---- const forCount = [ id(1, 1, 5), id(2, 1, 5), // 方块5 一对 id(1, 1, 9), // 方块9 单张 id(1, 2, 3), id(1, 2, 6), id(2, 2, 6), // 梅花:一张3 + 一对6 big(1), small(1) // 王不计入任何花色 ]; const cnt = EQW_CardMark.countByFlower(forCount); t.eq('方块张数', cnt[1].count, 3); t.eq('方块对数', cnt[1].pairs, 1); t.eq('梅花张数', cnt[2].count, 3); t.eq('梅花对数', cnt[2].pairs, 1); t.eq('红心张数', cnt[3].count, 0); t.eq('黑桃对数', cnt[4].pairs, 0); // ---- 边界 ---- t.eq('空手牌 marks', EQW_CardMark.marksOfHand([], MF), {}); t.eq('空手牌 count', EQW_CardMark.countByFlower([]), { 1: { count: 0, pairs: 0 }, 2: { count: 0, pairs: 0 }, 3: { count: 0, pairs: 0 }, 4: { count: 0, pairs: 0 } }); process.exit(t.done('cardmark') ? 0 : 1);