Files
erqiwang_youle/client/js/01_SubGame/codes/config/Layout_Result.js
T
joywayerandClaude Opus 5 8dba099717 二七王:HIST_FAN/MING_FAN maxWidth 由 490 改判定为 480,让 spacingMin 真正生效
上一版把 maxWidth 收窄到 490 只是让 RIGHT 座位恰好贴住画布边界(零余量),且
spacingMin:-96 在 28 张时永远不触底、成了死参数。裁定改为 480:
(480-110)/27-110≈-96.30 超过 spacingMin,spacing 被截断为 -96,总宽变为
28×110+27×(-96)=488,与清单原注释「每张露14px、总宽488」精确对齐——500 是笔误,
488 才是当初想要的值。

求解器验证(count=28):LEFT 16→504、RIGHT 791→1279、SELF 396→884,三座位均落在
0-1280 内且有余量。同步订正 Layout_Result.js 注释与清单 §6.9c 文字/数值。

client/tests/run.js 与 server/games/erqiwang/test/run.js 均全绿,精灵总数(380)/
布局节点数(83) 未变。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 09:08:48 +08:00

270 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
///////////////////////////////////////////////////////////////
////////// EQW_Layout: 结算区配置(清单 §6.9 / §5.7a / §5.7b)///
///////////////////////////////////////////////////////////////
// 小局结算 + 冲关遮罩 + 大局总结算 + 出牌历史/亮牌 + 明牌,共五块。
// 【纯数据文件】无函数、无副作用;依赖 LayoutConstants.js 先加载
// (引用其 EQW_Layout.NUM_STYLE.* / CARD_SIZE.*,属 EQW_Layout 内部自引用)。
var EQW_Layout = EQW_Layout || {};
EQW_Layout.RESULT_BOTTOM_PANEL = { kind: 'point', x: 430, y: 140, w: 420, h: 148 };
//底牌说明文字:贴面板底部,纯文字,清单未给宽高
EQW_Layout.RESULT_BOTTOM_TEXT = {
kind: 'attach', targetKind: 'layout', target: 'RESULT_BOTTOM_PANEL',
hAlign: 'center', vAlign: 'bottom', offsetY: -14,
runtime: ['w', 'h']
};
//三家分数光晕
EQW_Layout.RESULT_GLOW = {
kind: 'point', w: 225, h: 100,
bySeat: {
LEFT: { x: 175, y: 150 },
RIGHT: { x: 865, y: 150 },
SELF: { x: 490, y: 340 }
}
};
//三家总分大字(多帧图数字精灵,非文字精灵,2026-08-27):贴对应光晕居中。
//target 直接指向光晕精灵键(固定,非动态);高度恒为单字高,取 NUM_STYLE.RESULT_WIN.charHeight
//(与 NUM_STYLE.RESULT_LOSE 相同,SSOT);宽度随位数变化,留到运行时注入
//节点名去掉 _TEXT 后缀改为 _NUM:与 CALL_BTN_SCORE_NUM 同一理由,_TEXT 会让人误以为是文字精灵
EQW_Layout.RESULT_SCORE_NUM = {
kind: 'attach', targetKind: 'sprite', hAlign: 'center', vAlign: 'middle',
h: EQW_Layout.NUM_STYLE.RESULT_WIN.charHeight, runtime: ['w'],
bySeat: {
LEFT: { target: 'RESULT_GLOW_LEFT' },
RIGHT: { target: 'RESULT_GLOW_RIGHT' },
SELF: { target: 'RESULT_GLOW_SELF' }
}
};
//三家『牌局分』:贴对应光晕左下
EQW_Layout.RESULT_JF_TEXT = {
kind: 'attach', targetKind: 'sprite', hAlign: 'left', vAlign: 'bottom', offsetX: -15, offsetY: 24,
runtime: ['w', 'h'],
bySeat: {
LEFT: { target: 'RESULT_GLOW_LEFT' },
RIGHT: { target: 'RESULT_GLOW_RIGHT' },
SELF: { target: 'RESULT_GLOW_SELF' }
}
};
//三家『冲关分』:贴对应光晕右下
EQW_Layout.RESULT_AW_TEXT = {
kind: 'attach', targetKind: 'sprite', hAlign: 'right', vAlign: 'bottom', offsetX: 15, offsetY: 24,
runtime: ['w', 'h'],
bySeat: {
LEFT: { target: 'RESULT_GLOW_LEFT' },
RIGHT: { target: 'RESULT_GLOW_RIGHT' },
SELF: { target: 'RESULT_GLOW_SELF' }
}
};
EQW_Layout.RESULT_BTN_CG = { kind: 'point', x: 535, y: 515, w: 183, h: 63 };
EQW_Layout.RESULT_BTN_NEXT = { kind: 'point', x: 835, y: 518, w: 180, h: 65 };
//冲关遮罩:透明度也是配置项
EQW_Layout.CG_MASK = { kind: 'point', x: 0, y: 0, w: 1280, h: 720, opacity: 0.72 };
//===== 大局总结算 / 解散结算(AccountView,群组 241)=====
//
// 【坐标来源 · 2026-08-27 补齐】清单 §6.9 没有本面板的配置表,以下数值全部是
// docs_dev/uiref/大局结算.png 的【目视实测估值】(±5px),面板/标题栏/规则栏/按钮的 w/h
// 取清单 §3.5 / §3.3 的资源尺寸(那是权威值)。设计稿到位后只改本节。
// 之所以现在就落成配置:不补的话 C/D/E 阶段写渲染代码时会就地硬编码坐标(零裸值红线)。
//
// 【玩家栏是精灵复制】三条栏结构相同、条数随人数变,故不逐行写死,而是给
//「容器 ACC_ROW_CONTAINER + 行节点 ACC_ROW(行高即 itemHeight)+ 行内各模板贴附到行矩形」。
// 约定:ACC_ROW 及贴附到它的 ACC_TPL_* / ACC_ROW_TEXT_GRID 求解出的坐标都是
//【相对 ACC_ROW_CONTAINER 的】——SpriteCopyUtils 复制出的精灵是容器的子精灵,
// x/y 本就相对容器,求解结果可直接喂给 SpriteCopyUtils.create(与 §5.6a 冲关牌同一用法)。
// 行数由数据给出(ctx.count),不进配置(清单 §6.1)。
//面板底(1100×540,资源 PANEL_ACCOUNT)
EQW_Layout.ACC_PANEL_BG = { kind: 'point', x: 80, y: 88, w: 1100, h: 540 };
//标题栏:封住面板上沿(1100×56,资源 BAR_ACCOUNT_TITLE)
EQW_Layout.ACC_TITLE_BAR = {
kind: 'attach', targetKind: 'layout', target: 'ACC_PANEL_BG',
corner: 'topLeft', offsetX: 0, offsetY: 0, w: 1100, h: 56
};
//标题文字「二七王-3人 房号… 共…局 …」:贴标题栏内部左侧居中。
//文案随房间信息变长变短,真实宽高要等文案渲染出来才知道 → 留到运行时注入
EQW_Layout.ACC_TITLE_TEXT = {
kind: 'attach', targetKind: 'layout', target: 'ACC_TITLE_BAR',
hAlign: 'left', vAlign: 'middle', offsetX: 30,
runtime: ['w', 'h']
};
//关闭按钮:贴标题栏右上角(36×36,资源 BTN_ACC_CLOSE)
EQW_Layout.ACC_BTN_CLOSE = {
kind: 'attach', targetKind: 'layout', target: 'ACC_TITLE_BAR',
corner: 'topRight', offsetX: -20, offsetY: 10, w: 36, h: 36
};
//规则栏:封住面板下沿(1100×62,资源 BAR_ACCOUNT_RULE)
EQW_Layout.ACC_RULE_BAR = {
kind: 'attach', targetKind: 'layout', target: 'ACC_PANEL_BG',
corner: 'bottomLeft', offsetX: 0, offsetY: 0, w: 1100, h: 62
};
//规则文字「规则:可查牌、傍王、爬坡算子」:贴规则栏内部左侧居中,文案据 roomtype 拼出、长度不定
EQW_Layout.ACC_RULE_TEXT = {
kind: 'attach', targetKind: 'layout', target: 'ACC_RULE_BAR',
hAlign: 'left', vAlign: 'middle', offsetX: 30,
runtime: ['w', 'h']
};
//玩家栏容器(ACC_ROW_CONTAINER 精灵的位置):标题栏下沿 144 到规则栏上沿 566 之间
EQW_Layout.ACC_ROW_CONTAINER = { kind: 'point', x: 80, y: 144, w: 1100, h: 422 };
//玩家栏(行):竖排等距,itemHeight 即【行高/行距】。坐标相对容器,行数由 ctx.count 给出
EQW_Layout.ACC_ROW = {
kind: 'line', direction: 'vertical', anchorX: 0, anchorY: 14,
itemWidth: 1100, itemHeight: 132, spacing: 0, anchor: 'top'
};
//行内 · 名次徽章:贴该行内部左侧居中(44×44,资源 BADGE_RANK,帧=名次1–3)
EQW_Layout.ACC_TPL_RANK = {
kind: 'attach', targetKind: 'layout', target: 'ACC_ROW',
hAlign: 'left', vAlign: 'middle', offsetX: 28, w: 44, h: 44
};
//行内 · 头像:贴该行内部左侧居中,名次徽章右侧
EQW_Layout.ACC_TPL_AVATAR = {
kind: 'attach', targetKind: 'layout', target: 'ACC_ROW',
hAlign: 'left', vAlign: 'middle', offsetX: 92, w: 80, h: 80
};
//行内 · 文字栅格(ACC_TPL_TEXT 复制出的 6 条文字的槽位):4 列 × 2 行,行优先——
// 列1: 昵称 / ID:{playerid} 列2: 基础分{n} / 总得分{n}
// 列3: 冲关分{n}(参考图小字写「算奖分」,清单 §1.9 定案字段为 grade_cg_total)
// 列4: 傍王分{n}
// 第 2 行只有前两列有文字,第 3/4 列留空(求解仍返回 8 个槽位,调用方按数据取前 6 个)。
// anchorX 相对容器(= 参考图列1 绝对 x272 − 容器 x80);anchorY 随所在行浮动 → 运行时注入
// 【估值精度说明】本节点用等距列模型(itemWidth:176 spacingX:0 → 相对容器 192/368/544/720),
// 而参考图「大局结算.png」目视实测的列 x 约为 272/455/634/800(列距 183/179/166,并不等距)——
// 与等距模型偏差约 10px,比本文件头部通用声明的「±5px」更大,待设计稿复核(本次未改数值,仅订正估值说明)
EQW_Layout.ACC_ROW_TEXT_GRID = {
kind: 'grid', anchorX: 192, cols: 4, rows: 2,
itemWidth: 176, itemHeight: 34, spacingX: 0, spacingY: 10,
anchor: 'left', fillOrder: 'row',
runtime: ['anchorY']
};
//行内 · 右侧总分大字(多帧图数字精灵):贴该行内部右侧居中。
//高度恒为单字高,取 NUM_STYLE.RESULT_WIN.charHeight(与 RESULT_LOSE 相同,SSOT);
//宽度随位数变化(setNumberImage 按【字符数 × charWidth】自动改精灵宽),留到运行时注入
EQW_Layout.ACC_TPL_SCORE_NUM = {
kind: 'attach', targetKind: 'layout', target: 'ACC_ROW',
hAlign: 'right', vAlign: 'middle', offsetX: -25,
h: EQW_Layout.NUM_STYLE.RESULT_WIN.charHeight, runtime: ['w']
};
//行内 · 行间分隔线:贴该行【下沿】(vAlign bottom 的语义是"目标底边之下",见 AlignmentUtils)。
//1060×2,资源 LINE_DIVIDER;水平居中于 1100 宽的行,左右各留 20
EQW_Layout.ACC_TPL_DIVIDER = {
kind: 'attach', targetKind: 'layout', target: 'ACC_ROW',
hAlign: 'center', vAlign: 'bottom', offsetX: 0, offsetY: 0, w: 1060, h: 2
};
//面板【外】底部 4 个功能按钮(再来一局/分享好友/复制战绩/退出房间,各 190×60):
//绝对坐标(不在玩家栏容器内)。参考图里这排不是居中于屏幕、而是整体偏右,故用 anchor:left 定起点
EQW_Layout.ACC_FUNC_BUTTONS = {
kind: 'line', direction: 'horizontal', anchorX: 345, anchorY: 640,
itemWidth: 190, itemHeight: 60, spacing: 25, anchor: 'left'
};
//===== 出牌历史 / 上一轮 / 亮牌(HistoryView,群组 242)=====
//
// 【坐标来源 · 2026-08-27 补齐】清单 §5.7b 说本 View 与 MingPaiView、ChongGuanOverlayView
// 三者【同一种版式】(半透黑遮罩 + 每家一排重叠大牌),参考图同为
// docs_dev/uiref/冲关牌型显示.png;故以下节点照 CHONGGUAN_FAN / CG_MASK 同构给出,
// 三家落点沿用清单 §6.7 的冲关牌锚点(LEFT 260,120 / RIGHT 1035,120 / SELF 640,400)。
// 与冲关牌的唯一差别是【张数上限】:出牌历史摊平后可达 28 张以上,420 的 maxWidth 装不下。
// 2026-08-27 订正:原估值 maxWidth:500 是算错的——_solveFan 的间距公式是
// spacing = max(spacingMin, min(spacingMax, (maxWidth-itemWidth)/(count-1) - itemWidth))
// 代入 maxWidth 500 / itemWidth 110(CARD_SIZE.L.w)/ count 28:
// (500-110)/27 - 110 = -95.56,没有触到 spacingMin:-96 的下限,故实际 spacing 是 -95.56,
// 而 fan 的总宽恒等于 maxWidth(AlignmentUtils.distributeHorizontally 的 totalWidth 公式代入后
// 可化简为 maxWidth,只要 spacing 没被下限截断)——不是旧注释算的「488」。
// RIGHT 锚点 anchorX:1035、anchor:center 时,总宽 500 使整排右边缘落在 1035+250=1285,
// 超出 1280 设计画布 5px(LEFT/SELF 因锚点更靠左,仍在画布内,故此前没暴露)。
// 2026-08-27 再订正(裁定 maxWidth:490 → 480):490 虽能让 RIGHT 恰好贴住 1280,但零余量太脆弱——
// 这些锚点本身是参考图目视实测(±5px 误差),且 490 时 spacingMin:-96 永远不触底、是个死参数,
// 与「每张最窄只露 14px」这条约束名存实亡;旧注释原本写的「每张露 14px、总宽 488」正是
// spacingMin 触底后的结果,说明 488 才是当初想要的值,maxWidth:500 是与注释不符的笔误。
// 故改为 maxWidth:480:代入公式 (480-110)/27-110 = -96.30,**触及** spacingMin:-96 下限,
// 实际 spacing 被截断为 -96(不再等于 raw),总宽 = 28×110 + 27×(-96) = 3080-2592 = 488(不是 480,
// 因为 spacing 被下限截断后总宽不再恒等于 maxWidth)。RIGHT 边缘 = 1035+488/2 = 1279,留 1px 余量;
// LEFT 边缘 = 260+244 = 504,SELF 边缘 = 640+244 = 884,均有余量。spacingMin:-96 就此真正生效
// (不再是死参数)。每张露 14px(itemWidth+spacing = 110-96 = 14,与旧注释「14px」精确一致)。
// maxWidth:480 与 spacingMin:-96 仍是【估值,待设计稿复核】——清单未给本 View 的排布参数。
// 【已知限制,留给后来者】每张仅露 14px、视觉偏窄,待设计稿复核是否改为分两排显示,
// 或改用 CARD_SIZE.M(更小的牌面)——这是本次估值阶段接受的限制,不是本次要解决的问题。
//
// 【牌用精灵复制】求解结果直接作为 SpriteCopyUtils.create 的 x/y(相对容器),
// 与 §5.6a 冲关牌同一用法:三个容器精灵摆在原点、尺寸铺满屏,故容器内坐标 = 屏幕坐标。
//出牌历史 · 遮罩(兼点击关闭热区;【亮牌】用途下不注册点击,见 Sprites_Result.js 说明)
EQW_Layout.HIST_MASK = { kind: 'point', x: 0, y: 0, w: 1280, h: 720, opacity: 0.72 };
//出牌历史 · 三个牌容器(HIST_BOX_LEFT/RIGHT/SELF):同摆在原点、铺满屏,
//使 HIST_FAN 求出的屏幕坐标可直接当作容器内坐标使用
EQW_Layout.HIST_BOX = { kind: 'point', x: 0, y: 0, w: 1280, h: 720 };
//出牌历史 · 每家一排牌(张数不定,重叠压缩)
EQW_Layout.HIST_FAN = {
kind: 'fan', direction: 'horizontal', anchor: 'center',
itemWidth: EQW_Layout.CARD_SIZE.L.w, itemHeight: EQW_Layout.CARD_SIZE.L.h,
maxWidth: 480, spacingMax: -40, spacingMin: -96,
bySeat: {
LEFT: { anchorX: 260, anchorY: 120 },
RIGHT: { anchorX: 1035, anchorY: 120 },
SELF: { anchorX: 640, anchorY: 400 }
}
};
//出牌历史 · 三家座位标签(昵称/张数):各排牌下方;文案长度不定,w/h 运行时注入
EQW_Layout.HIST_LABEL = {
kind: 'point',
runtime: ['w', 'h'],
bySeat: {
LEFT: { x: 200, y: 318 },
RIGHT: { x: 975, y: 318 },
SELF: { x: 580, y: 598 }
}
};
//===== 明牌面板(MingPaiView,群组 243)=====
//
// 版式与 HistoryView 完全相同,差别只在【家数】:明牌只看另两家,没有自己那排,
// 故 bySeat 只有 LEFT / RIGHT 两个变体(精灵键为 MING_BOX_A / _B,A 落 LEFT 位、B 落 RIGHT 位)。
// 坐标来源同上:冲关牌型显示.png 目视实测 + §6.7 冲关牌锚点,【估值,待设计稿复核】。
EQW_Layout.MING_MASK = { kind: 'point', x: 0, y: 0, w: 1280, h: 720, opacity: 0.72 };
EQW_Layout.MING_BOX = { kind: 'point', x: 0, y: 0, w: 1280, h: 720 };
EQW_Layout.MING_FAN = {
kind: 'fan', direction: 'horizontal', anchor: 'center',
itemWidth: EQW_Layout.CARD_SIZE.L.w, itemHeight: EQW_Layout.CARD_SIZE.L.h,
maxWidth: 480, spacingMax: -40, spacingMin: -96,
bySeat: {
LEFT: { anchorX: 260, anchorY: 120 },
RIGHT: { anchorX: 1035, anchorY: 120 }
}
};
EQW_Layout.MING_LABEL = {
kind: 'point',
runtime: ['w', 'h'],
bySeat: {
LEFT: { x: 200, y: 318 },
RIGHT: { x: 975, y: 318 }
}
};