二七王:前端 CardMark,手牌标记推导与花色统计
三种标记互斥、优先级 tractor > zheng > zhu,据主花色本地推导。 拖拉机识别与服务端 decompose_trump 同思路:分对子后扫极大连续段, 主牌一组、副牌按花色分组。反面用例覆盖不连续、单对、跨花色三种不成拖的情形。 countByFlower 供选主面板的张数与对数,协议明确服务端不下发。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
///////////////////////////////////////////////////////////////
|
||||
////////// EQW_CardMark: 手牌牌面标记与花色统计(纯逻辑)///////
|
||||
///////////////////////////////////////////////////////////////
|
||||
// 清单 §1.6 / T-8:牌面三种标记,前端据主花色本地推导,服务端不下发。
|
||||
// 'tractor' 红色圆标「拖」= 该牌属于一组拖拉机
|
||||
// 'zheng' 橙色五角星 = 正2 / 正7(选定花色的 2 和 7)
|
||||
// 'zhu' 蓝色五角星 = 其余主牌(双王、副2、副7、主花色普通牌)
|
||||
// 三者【互斥】、每张牌至多一个,优先级 tractor > zheng > zhu。
|
||||
//
|
||||
// 牌编码区间(shared/cards.js 的 id_to_code 产物):
|
||||
// 9553/9554 王 / 8000-9000 正7 / 7000-8000 副7 / 3000-4000 正2
|
||||
// 2000-3000 副2 / 1000-2000 主花色普通牌 / 100-500 副牌普通牌
|
||||
// ⇒ 主牌 ⟺ code > 1000
|
||||
//
|
||||
// 拖拉机只在【同一牌组】内成立:全部主牌算一组(副2/副7/王与主花色牌可相连),
|
||||
// 副牌按各自花色分组。识别方式与服务端 decompose_trump 同思路:
|
||||
// 先分出对子,再用 is_continuous 扫极大连续段,长度 ≥2 的段内所有牌标 tractor。
|
||||
var EQW_CardMark = EQW_CardMark || {
|
||||
|
||||
MARK_TRACTOR: 'tractor',
|
||||
MARK_ZHENG: 'zheng',
|
||||
MARK_ZHU: 'zhu',
|
||||
|
||||
//整手牌的标记表:{ 牌id: 标记 };没有标记的牌不出现在结果里
|
||||
marksOfHand: function (cards, mainflower) {
|
||||
var marks = {};
|
||||
if (!cards || cards.length === 0) { return marks; }
|
||||
|
||||
var mf = mainflower || 0;
|
||||
var S = youle_erqiwang_shared_cards;
|
||||
var i, code;
|
||||
|
||||
//1) 分组:主牌一组(键 'Z'),副牌按花色分组(键为花色号)
|
||||
var groups = {};
|
||||
for (i = 0; i < cards.length; i++) {
|
||||
code = S.id_to_code(mf, cards[i]);
|
||||
var key = (code > 1000) ? 'Z' : String(S.id_to_flower(cards[i]));
|
||||
if (!groups[key]) { groups[key] = []; }
|
||||
groups[key].push(cards[i]);
|
||||
}
|
||||
|
||||
//2) 每组内找拖拉机
|
||||
for (var g in groups) {
|
||||
if (!groups.hasOwnProperty(g)) { continue; }
|
||||
var ordered = S.order_cards(mf, groups[g].concat());
|
||||
var pairs = S.get_pairlist(mf, ordered);
|
||||
var j = 0;
|
||||
while (j < pairs.length) {
|
||||
var run = [pairs[j]];
|
||||
var k = j;
|
||||
while (k + 1 < pairs.length &&
|
||||
S.is_continuous(S.id_to_code(mf, pairs[k][0]), S.id_to_code(mf, pairs[k + 1][0]))) {
|
||||
run.push(pairs[k + 1]);
|
||||
k++;
|
||||
}
|
||||
if (run.length >= 2) { //≥2 个连续对子才是拖拉机
|
||||
for (var r = 0; r < run.length; r++) {
|
||||
marks[run[r][0]] = 'tractor';
|
||||
marks[run[r][1]] = 'tractor';
|
||||
}
|
||||
}
|
||||
j = k + 1;
|
||||
}
|
||||
}
|
||||
|
||||
//3) 其余牌按 zheng / zhu 标注(拖拉机优先,已标的不覆盖)
|
||||
for (i = 0; i < cards.length; i++) {
|
||||
if (marks[cards[i]]) { continue; }
|
||||
code = S.id_to_code(mf, cards[i]);
|
||||
if ((code > 3000 && code < 4000) || (code > 8000 && code < 9000)) {
|
||||
marks[cards[i]] = 'zheng'; //正2 / 正7
|
||||
} else if (code > 1000) {
|
||||
marks[cards[i]] = 'zhu'; //其余主牌
|
||||
}
|
||||
}
|
||||
|
||||
return marks;
|
||||
},
|
||||
|
||||
//单张牌的标记;cards 是它所在的整手牌(拖拉机需要上下文才能判定)
|
||||
markOf: function (cardId, cards, mainflower) {
|
||||
var marks = this.marksOfHand(cards, mainflower);
|
||||
return marks[cardId] || null;
|
||||
},
|
||||
|
||||
//选主面板用:各花色的张数与对子数(清单 §1.5,服务端不下发、前端自算)
|
||||
//按【牌面花色】统计,与主牌无关;王不属于任何花色,不计入
|
||||
countByFlower: function (cards) {
|
||||
var result = { 1: { count: 0, pairs: 0 }, 2: { count: 0, pairs: 0 },
|
||||
3: { count: 0, pairs: 0 }, 4: { count: 0, pairs: 0 } };
|
||||
if (!cards || cards.length === 0) { return result; }
|
||||
|
||||
var S = youle_erqiwang_shared_cards;
|
||||
var byFlowerNumber = {}; //{花色: {点数: 张数}}
|
||||
var i, f, n;
|
||||
|
||||
for (i = 0; i < cards.length; i++) {
|
||||
f = S.id_to_flower(cards[i]);
|
||||
if (f === 5) { continue; } //王
|
||||
n = S.id_to_number(cards[i]);
|
||||
if (!byFlowerNumber[f]) { byFlowerNumber[f] = {}; }
|
||||
byFlowerNumber[f][n] = (byFlowerNumber[f][n] || 0) + 1;
|
||||
result[f].count++;
|
||||
}
|
||||
|
||||
for (f = 1; f <= 4; f++) {
|
||||
var nums = byFlowerNumber[f];
|
||||
if (!nums) { continue; }
|
||||
for (n in nums) {
|
||||
if (!nums.hasOwnProperty(n)) { continue; }
|
||||
result[f].pairs += Math.floor(nums[n] / 2);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user