diff --git a/client/js/01_SubGame/codes/core/SeatMap.js b/client/js/01_SubGame/codes/core/SeatMap.js new file mode 100644 index 0000000..7b7c559 --- /dev/null +++ b/client/js/01_SubGame/codes/core/SeatMap.js @@ -0,0 +1,31 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_SeatMap: 服务端座位 ↔ 屏幕显示位(纯逻辑)/////// +/////////////////////////////////////////////////////////////// +// 清单 §0.1:服务端座位是绝对序号 0/1/2;前端一律把自己放底部, +// 另两家按 design §4 的逆时针顺序分居左上、右上。 +// 底部(自己) = mySeat +// 右上(下家) = (mySeat + 1) % 3 ← 服务端 class.paiju.js:264 get_nextseat +// 左上(上家) = (mySeat + 2) % 3 +// 【全前端只此一处实现】布局配置的 bySeat 键、各界面的座位换算都用它。 +var EQW_SeatMap = EQW_SeatMap || { + + SELF: 'SELF', + LEFT: 'LEFT', + RIGHT: 'RIGHT', + + //服务端座位 → 显示位键 + toDisplay: function (seat, mySeat) { + var d = ((seat - mySeat) % 3 + 3) % 3; + if (d === 0) { return 'SELF'; } + if (d === 1) { return 'RIGHT'; } + return 'LEFT'; + }, + + //显示位键 → 服务端座位 + toSeat: function (displayKey, mySeat) { + if (displayKey === 'SELF') { return mySeat; } + if (displayKey === 'RIGHT') { return (mySeat + 1) % 3; } + if (displayKey === 'LEFT') { return (mySeat + 2) % 3; } + throw new Error('[EQW_SeatMap] 未知显示位: ' + displayKey); + } +}; diff --git a/client/tests/test_seatmap.js b/client/tests/test_seatmap.js new file mode 100644 index 0000000..a1396b1 --- /dev/null +++ b/client/tests/test_seatmap.js @@ -0,0 +1,44 @@ +// 服务端座位 ↔ 显示位(清单 §0.1) +const { load, throws } = require('./_load'); +const t = require('./_assert')(); + +load('client/js/01_SubGame/codes/core/SeatMap.js'); + +// ---- 3×3 全组合 ---- +// mySeat=0: 自己0、下家1在右上、上家2在左上 +t.eq('my0 seat0', EQW_SeatMap.toDisplay(0, 0), 'SELF'); +t.eq('my0 seat1', EQW_SeatMap.toDisplay(1, 0), 'RIGHT'); +t.eq('my0 seat2', EQW_SeatMap.toDisplay(2, 0), 'LEFT'); +t.eq('my1 seat1', EQW_SeatMap.toDisplay(1, 1), 'SELF'); +t.eq('my1 seat2', EQW_SeatMap.toDisplay(2, 1), 'RIGHT'); +t.eq('my1 seat0', EQW_SeatMap.toDisplay(0, 1), 'LEFT'); +t.eq('my2 seat2', EQW_SeatMap.toDisplay(2, 2), 'SELF'); +t.eq('my2 seat0', EQW_SeatMap.toDisplay(0, 2), 'RIGHT'); +t.eq('my2 seat1', EQW_SeatMap.toDisplay(1, 2), 'LEFT'); + +// ---- 反向 ---- +t.eq('my1 -> RIGHT 是座位2', EQW_SeatMap.toSeat('RIGHT', 1), 2); +t.eq('my2 -> RIGHT 是座位0', EQW_SeatMap.toSeat('RIGHT', 2), 0); +t.eq('my2 -> LEFT 是座位1', EQW_SeatMap.toSeat('LEFT', 2), 1); +t.eq('my0 -> SELF 是座位0', EQW_SeatMap.toSeat('SELF', 0), 0); + +// ---- 互逆:任意 mySeat/seat 组合 toSeat(toDisplay(s)) == s ---- +let ok = true; +for (let my = 0; my < 3; my++) { + for (let s = 0; s < 3; s++) { + if (EQW_SeatMap.toSeat(EQW_SeatMap.toDisplay(s, my), my) !== s) { ok = false; } + } +} +t.eq('toDisplay/toSeat 互逆', ok, true); + +// ---- 与服务端 get_nextseat 一致:下家恒在 RIGHT ---- +let nextOk = true; +for (let my = 0; my < 3; my++) { + if (EQW_SeatMap.toDisplay((my + 1) % 3, my) !== 'RIGHT') { nextOk = false; } +} +t.eq('下家(seat+1)%3 恒在右上', nextOk, true); + +// ---- 反面:非法显示位显式抛错,不静默返回 ---- +t.eq('非法显示位抛错', throws(() => EQW_SeatMap.toSeat('TOP', 0)), true); + +process.exit(t.done('seatmap') ? 0 : 1);