求解器(ui/LayoutSolver.js): - 按方向校验 anchor:竖排只收 top/bottom/center、横排只收 left/right/center, 未知 direction 一并拒绝。框架 AlignmentUtils 的 switch 对写错方向的 anchor 落到 default(居中),笔误会被静默吞掉——ROOM_CATEGORY_COLUMN 就是这么把前两行摆到画布外的。 - grid 校验合并 ctx 后的 cols/rows 必须是数字:缺 rows 时 capacity=NaN 让超载守卫失效、 循环一次不跑,静默返回空数组(一个矩形都不产出)。 - line/fan/grid 要求 anchorX/anchorY 必须是数字(框架的 anchorY||0 会把缺失变成 0); point 要求 x/y 必须是数字。 - apply() 精灵数与矩形数不等时抛错,不再静默截断:截断会让多余精灵停在上一手牌的旧坐标上。 - 统一运行时注入:ctx 里的 INJECT_KEYS(target/x/y/w/h/anchorX/anchorY/rows/ itemWidth/itemHeight)覆盖配置同名字段,优先级 ctx > bySeat > base。 - line + items 竖排显式拒绝(只实现了水平)。 配置: - ROOM_CATEGORY_COLUMN anchor 由 'left' 改为 'top'(竖排语义)。 - ROOM_OPTION_OVERFLOW_GRID 补 rows(runtime)、槽尺寸与 anchor 沿用 ROOM_OPTION_ROW, cols 直接引用 ROOM_OPTION_MAX_PER_ROW;ROOM_OPTION_ROW 补上注释里已声明的 itemHeight。 - 所有依赖运行时注入的节点加 runtime 声明,所有 attach 节点加 targetKind (sprite / layout / platform),显式区分「忘了写」与「故意延后到运行时」。 守卫(tests/test_constants.js): - 求解冒烟改为只对 runtime 声明过的键注入假值,没声明却缺字段的照常抛错变红。 - 补矩形数量断言(line/fan/grid 按项数、point/attach 恒 1)——此前只查数组与坐标类型, 空数组照样通过,「少画了几张牌」对守卫完全不可见。 - attach.target 改为只在 targetKind 指定的那一个命名空间里校验存在。 - 补 runtime 声明自身的合法性检查与布局节点总数(62)钉死。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
101 lines
4.8 KiB
JavaScript
101 lines
4.8 KiB
JavaScript
///////////////////////////////////////////////////////////////
|
||
////////// EQW_Layout: 牌区配置(清单 §6.7)////////////////////
|
||
///////////////////////////////////////////////////////////////
|
||
// 【纯数据文件】无函数、无副作用;依赖 LayoutConstants.js 先加载
|
||
// (引用其 EQW_Layout.CARD_SIZE,属 EQW_Layout 内部自引用)。
|
||
var EQW_Layout = EQW_Layout || {};
|
||
|
||
//===== 自己手牌区(清单 §6.7)=====
|
||
|
||
//自己手牌 · 单排:36 张时 spacing ≈ -78(露 32px),与参考图实测 33px 吻合
|
||
EQW_Layout.HAND_FAN_SINGLE = {
|
||
kind: 'fan', direction: 'horizontal',
|
||
anchorX: 640, anchorY: 455, maxWidth: 1215,
|
||
itemWidth: EQW_Layout.CARD_SIZE.L.w, itemHeight: EQW_Layout.CARD_SIZE.L.h,
|
||
spacingMax: 0, spacingMin: -78, anchor: 'center', overlapFrom: 'left'
|
||
};
|
||
|
||
//自己手牌 · 双排(埋牌)。分行策略是数据标记 splitStrategy,由调用方据此分行,本文件不写函数:
|
||
//floorTop = 上排 floor(n/2)、下排 ceil(n/2)(36 张 = 18/18;参考图 17/19 属填充画法,不作依据,见清单 §0.5)。
|
||
//spacingMax / anchor / overlapFrom 清单双排行未重复给出,沿用单排同参数(同一批牌、同一压缩规则)
|
||
EQW_Layout.HAND_TWO_ROW = {
|
||
splitStrategy: 'floorTop',
|
||
ROW_TOP: {
|
||
kind: 'fan', direction: 'horizontal',
|
||
anchorX: 644, anchorY: 285, maxWidth: 928,
|
||
itemWidth: EQW_Layout.CARD_SIZE.L.w, itemHeight: EQW_Layout.CARD_SIZE.L.h,
|
||
spacingMax: 0, spacingMin: -65, anchor: 'center', overlapFrom: 'left'
|
||
},
|
||
ROW_BOTTOM: {
|
||
kind: 'fan', direction: 'horizontal',
|
||
anchorX: 644, anchorY: 455, maxWidth: 928,
|
||
itemWidth: EQW_Layout.CARD_SIZE.L.w, itemHeight: EQW_Layout.CARD_SIZE.L.h,
|
||
spacingMax: 0, spacingMin: -65, anchor: 'center', overlapFrom: 'left'
|
||
}
|
||
};
|
||
|
||
//手牌 · 选中上浮偏移量定义在 EQW_Anim.HAND_SELECT_OFFSET_Y(AnimConstants.js),此处不重复(SSOT)
|
||
|
||
//底牌区(8 张,未揭示为牌背、揭示后翻正面,同一批精灵)
|
||
EQW_Layout.BOTTOM_CARDS = {
|
||
kind: 'line', direction: 'horizontal',
|
||
anchorX: 655, anchorY: 110,
|
||
itemWidth: EQW_Layout.CARD_SIZE.L.w, itemHeight: EQW_Layout.CARD_SIZE.L.h,
|
||
spacing: -36, anchor: 'center'
|
||
};
|
||
|
||
//埋牌底牌区(结算,8 张,尺寸档 S)
|
||
EQW_Layout.BURY_CARDS = {
|
||
kind: 'line', direction: 'horizontal',
|
||
anchorX: 640, anchorY: 163,
|
||
itemWidth: EQW_Layout.CARD_SIZE.S.w, itemHeight: EQW_Layout.CARD_SIZE.S.h,
|
||
spacing: 2, anchor: 'center'
|
||
};
|
||
|
||
//===== 已出牌区(清单 §6.2 示例 / §6.7)=====
|
||
|
||
//三家已出牌区:三家共用 base,LEFT 与 RIGHT 左右镜像(overlapFrom 相反)
|
||
EQW_Layout.PLAY_AREA = {
|
||
kind: 'fan', direction: 'horizontal',
|
||
itemWidth: EQW_Layout.CARD_SIZE.M.w, itemHeight: EQW_Layout.CARD_SIZE.M.h,
|
||
maxWidth: 170, spacingMax: -35, spacingMin: -62,
|
||
bySeat: {
|
||
SELF: { anchorX: 650, anchorY: 265, anchor: 'center', overlapFrom: 'left' },
|
||
RIGHT: { anchorX: 1022, anchorY: 120, anchor: 'center', overlapFrom: 'left' },
|
||
LEFT: { anchorX: 258, anchorY: 120, anchor: 'center', overlapFrom: 'right' }
|
||
}
|
||
};
|
||
|
||
//已出牌 · 牌型标签:贴该区第一张牌。首槽是固定精灵(PLAY_SELF_1 / PLAY_LEFT_1 / PLAY_RIGHT_1),非动态目标。
|
||
//资源 LABEL_CARDTYPE 76×24(清单 §3)
|
||
EQW_Layout.PLAY_TYPE_LABEL = {
|
||
kind: 'attach', targetKind: 'sprite', corner: 'bottomLeft', offsetX: 0, offsetY: -24, w: 76, h: 24,
|
||
bySeat: {
|
||
SELF: { target: 'PLAY_SELF_1' },
|
||
LEFT: { target: 'PLAY_LEFT_1' },
|
||
RIGHT: { target: 'PLAY_RIGHT_1' }
|
||
}
|
||
};
|
||
|
||
//已出牌 · 牌角『庄』/『大』标:贴的是本轮打出的哪张具体牌,运行时才能确定,不是固定精灵键。
|
||
//本节点只是模板(corner/offset),调用方在确定具体是哪张牌之后,以此为基础补上 target 再喂给
|
||
//EQW_LayoutSolver.solve——这是有意的动态目标模式,不在配置里预置 target(清单未给尺寸;
|
||
//两种资源分别是 MARK_BANKER_CORNER 28×28 / MARK_JOKER_BIG 34×34,按实际显示的是哪个徽标取值)
|
||
EQW_Layout.PLAY_CARD_CORNER_MARK = {
|
||
kind: 'attach', targetKind: 'sprite', corner: 'topRight', offsetX: -28, offsetY: 0,
|
||
runtime: ['target', 'w', 'h']
|
||
};
|
||
|
||
//冲关牌(叠加层):精灵复制场景(§5.6a),spacingMin 放宽到 -88 以容纳 20+ 张的极端情况。
|
||
//anchor / direction 清单未重复给出,沿用其余 fan 配置的通用约定(center / horizontal)
|
||
EQW_Layout.CHONGGUAN_FAN = {
|
||
kind: 'fan', direction: 'horizontal', anchor: 'center',
|
||
itemWidth: EQW_Layout.CARD_SIZE.L.w, itemHeight: EQW_Layout.CARD_SIZE.L.h,
|
||
maxWidth: 420, spacingMax: -40, spacingMin: -88,
|
||
bySeat: {
|
||
LEFT: { anchorX: 260, anchorY: 120 },
|
||
RIGHT: { anchorX: 1035, anchorY: 120 },
|
||
SELF: { anchorX: 640, anchorY: 400 }
|
||
}
|
||
};
|