dsh-plugin-feihualing 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cordis.yml CHANGED
@@ -2,7 +2,7 @@
2
2
  # dsh-plugin-feihualing 插件元配置
3
3
  # ============================================================
4
4
  name: dsh-plugin-feihualing
5
- version: 1.1.1
5
+ version: 1.1.2
6
6
  description: >-
7
7
  飞花令游戏插件(简易 / 古法严格双模式)。
8
8
  支持关键词指令(开始飞花令、结束飞花令、换字、提示、认输、暂停、继续飞花令)、
package/lib/client.js CHANGED
@@ -484,7 +484,6 @@ body[data-ds-dark-theme] .fhl-root {
484
484
  "人闲桂花落",
485
485
  "举杯邀明月,对影成三人",
486
486
  "路上行人欲断魂",
487
- "独在异乡为异客,每逢佳节倍思亲",
488
487
  "人面不知何处去",
489
488
  "但愿人长久",
490
489
  "前不见古人,后不见来者",
@@ -503,7 +502,6 @@ body[data-ds-dark-theme] .fhl-root {
503
502
  "酒入愁肠,化作相思泪",
504
503
  "今朝有酒今朝醉",
505
504
  "白日放歌须纵酒",
506
- "人生得意须尽欢,莫使金樽空对月",
507
505
  "明月几时有,把酒问青天"
508
506
  ]),
509
507
  夜: Object.freeze([
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["React","React","engine.TIME_LIMIT","engine.timeoutRound","engine.createMatch","engine.submitPlayerLine","engine.useHint","engine.concedeMatch","React"],"sources":["../src/client/styles.ts","../src/client/constants.ts","../src/shared/rules.ts","../src/shared/poems.ts","../src/client/game/engine.ts","../node_modules/.pnpm/react@19.2.8/node_modules/react/cjs/react-jsx-runtime.production.js","../node_modules/.pnpm/react@19.2.8/node_modules/react/jsx-runtime.js","../src/client/panel.tsx","../src/client/game/stats.ts","../src/client/game/useGame.ts","../src/client/header-button.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * 客户端半边样式:一次性注入 <style>,全部 fhl-* 类以 data-plugin 根类名限定作用域。\n * 主题跟随 DSH:深色模式由 body[data-ds-dark-theme] 标识(官方约定),\n * 浅色/深色各写一套语义配色;不依赖任何宿主私有类名。\n * @module dsh-plugin-feihualing/client/styles\n */\n\nconst CSS = `\n[data-plugin=\"dsh-plugin-feihualing\"].fhl-root, .fhl-root {\n --fhl-bg: #ffffff;\n --fhl-bg-elev: #f4f4f5;\n --fhl-fg: #27272a;\n --fhl-fg-muted: #71717a;\n --fhl-accent: #8b5cf6;\n --fhl-accent-soft: rgba(139, 92, 246, 0.12);\n --fhl-border: rgba(0, 0, 0, 0.1);\n --fhl-ok: #16a34a;\n --fhl-ok-soft: rgba(22, 163, 74, 0.12);\n --fhl-bad: #dc2626;\n --fhl-bad-soft: rgba(220, 38, 38, 0.12);\n font-family: var(--dsh-content-font, ui-sans-serif, system-ui, \"PingFang SC\", \"Microsoft YaHei\", sans-serif);\n box-sizing: border-box;\n}\nbody[data-ds-dark-theme] .fhl-root {\n --fhl-bg: #18181b;\n --fhl-bg-elev: #27272a;\n --fhl-fg: #e4e4e7;\n --fhl-fg-muted: #a1a1aa;\n --fhl-accent: #a78bfa;\n --fhl-accent-soft: rgba(167, 139, 250, 0.16);\n --fhl-border: rgba(255, 255, 255, 0.12);\n --fhl-ok: #4ade80;\n --fhl-ok-soft: rgba(74, 222, 128, 0.16);\n --fhl-bad: #f87171;\n --fhl-bad-soft: rgba(248, 113, 113, 0.16);\n}\n.fhl-root *, .fhl-root *::before, .fhl-root *::after { box-sizing: border-box; }\n\n/* 会话头操作行的小按钮 */\n.fhl-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n height: 24px;\n padding: 0 6px;\n border: 1px solid transparent;\n border-radius: 6px;\n background: transparent;\n color: var(--fhl-fg-muted);\n font-size: 14px;\n line-height: 1;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.fhl-toggle:hover { background: var(--fhl-accent-soft); color: var(--fhl-fg); }\n.fhl-toggle[data-active=\"true\"] { background: var(--fhl-accent-soft); color: var(--fhl-accent); }\n\n/* 浮层游戏面板(portal 到 body 的独立 surface,自带作用域) */\n.fhl-panel {\n position: fixed;\n top: 64px;\n right: 16px;\n width: 340px;\n max-width: calc(100vw - 24px);\n max-height: calc(100vh - 88px);\n overflow: auto;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px;\n border-radius: 14px;\n background: var(--fhl-bg);\n color: var(--fhl-fg);\n border: 1px solid var(--fhl-border);\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);\n}\n.fhl-panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.fhl-panel-title { font-weight: 700; font-size: 14px; letter-spacing: 0.02em; }\n.fhl-panel-close {\n border: none;\n background: transparent;\n color: var(--fhl-fg-muted);\n cursor: pointer;\n font-size: 14px;\n line-height: 1;\n padding: 4px;\n border-radius: 6px;\n}\n.fhl-panel-close:hover { background: var(--fhl-bad-soft); color: var(--fhl-bad); }\n.fhl-muted { color: var(--fhl-fg-muted); }\n.fhl-tag { font-size: 11px; color: var(--fhl-fg-muted); border: 1px solid var(--fhl-border); padding: 1px 6px; border-radius: 999px; }\n\n/* ---- 对战 UI ---- */\n.fhl-row { display: flex; align-items: center; gap: 8px; }\n.fhl-seg-label { font-size: 12px; color: var(--fhl-fg-muted); flex: none; width: 34px; }\n.fhl-seg { display: flex; gap: 4px; flex: 1; }\n.fhl-seg-item {\n flex: 1;\n padding: 5px 0;\n font-size: 12px;\n border: 1px solid var(--fhl-border);\n border-radius: 8px;\n background: transparent;\n color: var(--fhl-fg-muted);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n.fhl-seg-item:hover { border-color: var(--fhl-accent); color: var(--fhl-fg); }\n.fhl-seg-item.is-active { background: var(--fhl-accent-soft); border-color: var(--fhl-accent); color: var(--fhl-accent); font-weight: 600; }\n\n.fhl-stage { display: flex; align-items: center; gap: 8px; }\n.fhl-stage-round { font-size: 12px; color: var(--fhl-fg-muted); }\n.fhl-timer {\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 700;\n color: var(--fhl-fg);\n padding: 2px 8px;\n border-radius: 999px;\n background: var(--fhl-bg-elev);\n}\n.fhl-timer.is-low { color: var(--fhl-bad); background: var(--fhl-bad-soft); animation: fhl-blink 1s ease-in-out infinite; }\n@keyframes fhl-blink { 50% { opacity: 0.45; } }\n.fhl-stage-tag { margin-left: auto; font-size: 11px; color: var(--fhl-fg-muted); }\n\n.fhl-lingzi {\n text-align: center;\n font-size: 34px;\n font-weight: 800;\n line-height: 1.1;\n letter-spacing: 0.08em;\n color: var(--fhl-accent);\n}\n\n.fhl-card {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 10px 12px;\n border-radius: 12px;\n background: var(--fhl-bg-elev);\n border: 1px solid var(--fhl-border);\n}\n.fhl-card-role { font-size: 11px; color: var(--fhl-fg-muted); }\n.fhl-card-line { font-size: 15px; line-height: 1.5; }\n\n.fhl-verdict { font-size: 12px; color: var(--fhl-fg-muted); min-height: 16px; }\n\n.fhl-input-row { display: flex; gap: 6px; }\n.fhl-input {\n flex: 1;\n min-width: 0;\n padding: 7px 10px;\n font-size: 13px;\n border-radius: 9px;\n border: 1px solid var(--fhl-border);\n background: var(--fhl-bg);\n color: var(--fhl-fg);\n outline: none;\n transition: border-color 0.15s ease;\n}\n.fhl-input:focus { border-color: var(--fhl-accent); }\n.fhl-input:disabled { opacity: 0.5; }\n\n.fhl-actions { display: flex; gap: 6px; }\n.fhl-btn {\n flex: 1;\n padding: 7px 10px;\n font-size: 13px;\n border: 1px solid var(--fhl-border);\n border-radius: 9px;\n background: var(--fhl-bg-elev);\n color: var(--fhl-fg);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n.fhl-btn:hover:not(:disabled) { border-color: var(--fhl-accent); }\n.fhl-btn:disabled { opacity: 0.45; cursor: default; }\n.fhl-btn-primary { background: var(--fhl-accent-soft); border-color: var(--fhl-accent); color: var(--fhl-accent); font-weight: 600; }\n.fhl-btn-danger { color: var(--fhl-bad); }\n.fhl-btn-danger:hover:not(:disabled) { border-color: var(--fhl-bad); background: var(--fhl-bad-soft); }\n\n.fhl-stats {\n display: flex;\n gap: 6px;\n justify-content: space-between;\n padding-top: 2px;\n border-top: 1px dashed var(--fhl-border);\n}\n.fhl-stat { font-size: 11px; color: var(--fhl-fg-muted); display: inline-flex; gap: 3px; align-items: baseline; }\n.fhl-stat b { color: var(--fhl-fg); font-size: 13px; }\n.fhl-stat.is-warn b { color: var(--fhl-bad); }\n\n.fhl-result { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 10px 0 4px; }\n.fhl-result-icon { font-size: 40px; }\n.fhl-result-title { font-size: 18px; font-weight: 800; }\n.fhl-result.is-won .fhl-result-title { color: var(--fhl-ok); }\n.fhl-result.is-lost .fhl-result-title { color: var(--fhl-bad); }\n\n.fhl-record {\n display: flex;\n justify-content: center;\n gap: 10px;\n font-size: 11px;\n color: var(--fhl-fg-muted);\n}\n`\n\n/** 注入样式表;返回 disposer(卸载时移除 style 节点)。 */\nexport function injectStyles(): () => void {\n const style = document.createElement('style')\n style.setAttribute('data-plugin', 'dsh-plugin-feihualing')\n style.textContent = CSS\n document.head.appendChild(style)\n return () => {\n if (style.parentNode) style.parentNode.removeChild(style)\n }\n}\n","/**\n * 客户端半边共享常量。\n * NAMESPACE 用作插槽注册 id,须与包名(package.json name / cordis.patch.yml)一致。\n * @module dsh-plugin-feihualing/client/constants\n */\n\n/** 插件命名空间(与 cordis.patch.yml 的 id、Loader 行 name 保持一致)。 */\nexport const NAMESPACE = 'dsh-plugin-feihualing'\n\n/** 面板在会话标题操作行中的渲染顺序(靠后,避免挤占内置预设/任务按钮)。 */\nexport const HEADER_ORDER = 90\n","/**\n * 共享判定规则(Host 与 Client 浏览器两半共同使用)。\n * 纯函数、零平台依赖:不允许 import node:* / DOM —— 保证「对话判定」与\n * 「面板即时判定」对同一输入给出完全一致的结果。\n * @module dsh-plugin-feihualing/shared/rules\n */\n\n/** 游戏模式:simple 简易 / ancient 古法严格。 */\nexport type GameMode = 'simple' | 'ancient'\n\n/** 诗句尝试的最小/最大长度(规范化后字符数)。 */\nexport const MIN_LINE_CHARS = 4\nexport const MAX_LINE_CHARS = 48\n\n/** 古法令字位置循环上限(第 1 字 → 第 7 字 → 第 1 字)。 */\nexport const MAX_ANCIENT_POSITION = 7\n\n/**\n * 规范化一行文本:去除空白与常见中英文标点,仅保留汉字/字母/数字。\n * 用于关键词匹配与诗句去重比较。\n */\nexport function normalizeLine(raw: string): string {\n return raw.replace(/[\\s,。!?、;:\"\"''()《》〈〉【】\\[\\]·,.!?;:'\"()\\-—…]+/gu, '')\n}\n\n/** 去掉文本首尾空白(用于展示时压缩用户输入的换行/空格)。 */\nexport function trimDisplay(raw: string): string {\n return raw.trim().replace(/\\s+/gu, ' ').slice(0, MAX_LINE_CHARS)\n}\n\n/** 一次诗句判定的输入(与 GameState 中参与判定的字段一一对应)。 */\nexport interface JudgeInput {\n rawText: string\n lingzi: string\n mode: GameMode\n /** 古法本轮要求令字位置(1 起);simple 模式忽略。 */\n requiredPosition: number\n /** 已使用诗句的规范化集合(去重依据)。 */\n usedNormalized: readonly string[]\n}\n\n/** 判定结果分类。 */\nexport type JudgeKind =\n | 'notPoem' // 规范化后不含令字 → 视为普通聊天,不判定\n | 'length' // 长度不符(4-48 字之外)\n | 'duplicate' // 与已使用诗句重复\n | 'badPosition' // 古法模式位置不符\n | 'valid'\n\n/** 古法位置错误的细分原因(badPosition 时有效)。 */\nexport type BadPositionReason = 'tooShort' | 'mismatch'\n\nexport interface JudgeResult {\n kind: JudgeKind\n /** 规范化文本(valid 时用于登记去重与位置推进)。 */\n normalized: string\n /** 展示用文本(压缩空白、截断)。 */\n display: string\n /** 规范化后长度。 */\n length: number\n /** badPosition 的细分原因;其余 kind 时为 undefined。 */\n badPosReason?: BadPositionReason\n /** badPosition 时本轮要求的位置;其余 kind 时为 undefined。 */\n requiredPosition?: number\n}\n\n/**\n * 诗句判定核心(纯函数,Host/Client 共用)。\n * 判定顺序与对话版完全一致:含令字门槛 → 长度 → 去重 → 古法位置。\n */\nexport function judgePoemAttempt(input: JudgeInput): JudgeResult {\n const normalized = normalizeLine(input.rawText)\n const display = trimDisplay(input.rawText)\n // 不含令字的消息不视为诗句尝试(普通聊天直接忽略)\n if (!normalized.includes(input.lingzi)) {\n return { kind: 'notPoem', normalized, display, length: normalized.length }\n }\n if (normalized.length < MIN_LINE_CHARS || normalized.length > MAX_LINE_CHARS) {\n return { kind: 'length', normalized, display, length: normalized.length }\n }\n if (input.usedNormalized.includes(normalized)) {\n return { kind: 'duplicate', normalized, display, length: normalized.length }\n }\n if (input.mode === 'ancient') {\n const pos = input.requiredPosition\n if (normalized.length < pos) {\n return { kind: 'badPosition', badPosReason: 'tooShort', requiredPosition: pos, normalized, display, length: normalized.length }\n }\n if (normalized[pos - 1] !== input.lingzi) {\n return { kind: 'badPosition', badPosReason: 'mismatch', requiredPosition: pos, normalized, display, length: normalized.length }\n }\n }\n return { kind: 'valid', normalized, display, length: normalized.length }\n}\n","/**\n * 共享诗句库(Host 与 Client 两半共同使用;纯数据,零平台依赖)。\n * 用途:Host —— 提示(hint)与令字抽取;Client —— 内置 AI 对手出句与提示。\n * 判定本身不依赖本库(只查“含令字 + 未重复 + 古法位置”),因此诗句库的\n * 覆盖面只影响“对手出句 / 提示”的花样,不影响规则正确性。\n * 全部为公共领域经典诗句(唐/宋/元/清为主,个别汉魏、民歌与古乐府)。\n * @module dsh-plugin-feihualing/shared/poems\n */\n\n/** 令字 → 含该令字的经典诗句(规范化前原文,展示与去重用;每句必含令字)。 */\nexport const POEM_BANK: Readonly<Record<string, readonly string[]>> = Object.freeze({\n 花: Object.freeze([\n '花间一壶酒',\n '感时花溅泪',\n '人面桃花相映红',\n '霜叶红于二月花',\n '花落知多少',\n '春城无处不飞花',\n '不是花中偏爱菊',\n '黄四娘家花满蹊',\n '竹外桃花三两枝',\n '映日荷花别样红',\n '花重锦官城',\n '乱花渐欲迷人眼',\n ]),\n 月: Object.freeze([\n '床前明月光',\n '举头望明月',\n '海上生明月',\n '月落乌啼霜满天',\n '月是故乡明',\n '明月几时有',\n '二十四桥明月夜',\n '月上柳梢头',\n '露似真珠月似弓',\n '月黑雁飞高',\n '深林人不知,明月来相照',\n '明月松间照',\n '江清月近人',\n '海上明月共潮生',\n '杨柳岸,晓风残月',\n '月有阴晴圆缺',\n ]),\n 风: Object.freeze([\n '夜来风雨声',\n '春风又绿江南岸',\n '风吹草低见牛羊',\n '二月春风似剪刀',\n '春风不度玉门关',\n '长风破浪会有时',\n '随风潜入夜',\n '野火烧不尽,春风吹又生',\n '大风起兮云飞扬',\n '风萧萧兮易水寒',\n '昨夜西风凋碧树',\n '吹面不寒杨柳风',\n '古道西风瘦马',\n '北风卷地白草折',\n ]),\n 雪: Object.freeze([\n '窗含西岭千秋雪',\n '独钓寒江雪',\n '柴门闻犬吠,风雪夜归人',\n '北风吹雁雪纷纷',\n '雪上空留马行处',\n '北风卷地白草折,胡天八月即飞雪',\n '遥知不是雪,为有暗香来',\n '欲渡黄河冰塞川,将登太行雪满山',\n '燕山雪花大如席',\n '大雪满弓刀',\n '青海长云暗雪山',\n ]),\n 山: Object.freeze([\n '白日依山尽',\n '山重水复疑无路',\n '轻舟已过万重山',\n '空山不见人',\n '千山鸟飞绝',\n '会当凌绝顶,一览众山小',\n '相看两不厌,只有敬亭山',\n '远上寒山石径斜',\n '两岸青山相对出',\n '国破山河在',\n '青山遮不住',\n '山外青山楼外楼',\n '只在此山中,云深不知处',\n ]),\n 水: Object.freeze([\n '抽刀断水水更流',\n '桃花潭水深千尺',\n '一江春水向东流',\n '行到水穷处',\n '春来江水绿如蓝',\n '白毛浮绿水',\n '水光潋滟晴方好',\n '竹外桃花三两枝,春江水暖鸭先知',\n '曾经沧海难为水',\n '山重水复疑无路,柳暗花明又一村',\n '问君能有几多愁,恰似一江春水向东流',\n '流水落花春去也',\n ]),\n 云: Object.freeze([\n '黄河远上白云间',\n '只在此山中,云深不知处',\n '白云生处有人家',\n '行到水穷处,坐看云起时',\n '众鸟高飞尽,孤云独去闲',\n '黑云压城城欲摧',\n '白云千载空悠悠',\n '朝辞白帝彩云间',\n '云想衣裳花想容',\n '荡胸生层云',\n '青海长云暗雪山',\n '浮云游子意',\n ]),\n 雨: Object.freeze([\n '清明时节雨纷纷',\n '好雨知时节',\n '天街小雨润如酥',\n '巴山夜雨涨秋池',\n '山色空蒙雨亦奇',\n '夜阑卧听风吹雨',\n '渭城朝雨浥轻尘',\n '寒雨连江夜入吴',\n '斜风细雨不须归',\n '沾衣欲湿杏花雨',\n '空山新雨后',\n ]),\n 春: Object.freeze([\n '春眠不觉晓',\n '春来江水绿如蓝',\n '春城无处不飞花',\n '春蚕到死丝方尽',\n '野火烧不尽,春风吹又生',\n '春色满园关不住',\n '等闲识得东风面,万紫千红总是春',\n '春潮带雨晚来急',\n '谁言寸草心,报得三春晖',\n '国破山河在,城春草木深',\n '春江潮水连海平',\n ]),\n 秋: Object.freeze([\n '空山新雨后,天气晚来秋',\n '湖光秋月两相和',\n '自古逢秋悲寂寥',\n '春种一粒粟,秋收万颗子',\n '银烛秋光冷画屏',\n '解落三秋叶,能开二月花',\n '万里悲秋常作客',\n '塞下秋来风景异',\n '洛阳城里见秋风',\n '秋风萧瑟天气凉',\n ]),\n 人: Object.freeze([\n '人生得意须尽欢',\n '人生自古谁无死',\n '人闲桂花落',\n '举杯邀明月,对影成三人',\n '路上行人欲断魂',\n '独在异乡为异客,每逢佳节倍思亲',\n '人面不知何处去',\n '但愿人长久',\n '前不见古人,后不见来者',\n '故人西辞黄鹤楼',\n '莫愁前路无知己,天下谁人不识君',\n '劝君更尽一杯酒,西出阳关无故人',\n '遥知兄弟登高处,遍插茱萸少一人',\n ]),\n 酒: Object.freeze([\n '花间一壶酒',\n '葡萄美酒夜光杯',\n '借问酒家何处有',\n '劝君更尽一杯酒',\n '对酒当歌',\n '金樽清酒斗十千',\n '酒入愁肠,化作相思泪',\n '今朝有酒今朝醉',\n '白日放歌须纵酒',\n '人生得意须尽欢,莫使金樽空对月',\n '明月几时有,把酒问青天',\n ]),\n 夜: Object.freeze([\n '夜来风雨声',\n '忽如一夜春风来',\n '巴山夜雨涨秋池',\n '夜发清溪向三峡',\n '姑苏城外寒山寺,夜半钟声到客船',\n '东风夜放花千树',\n '夜阑卧听风吹雨',\n '可怜九月初三夜',\n '随风直到夜郎西',\n '烟笼寒水月笼沙,夜泊秦淮近酒家',\n '夜深千帐灯',\n '月黑雁飞高,单于夜遁逃',\n ]),\n 江: Object.freeze([\n '孤帆远影碧空尽,唯见长江天际流',\n '春来江水绿如蓝',\n '春风又绿江南岸',\n '一江春水向东流',\n '天门中断楚江开',\n '寒雨连江夜入吴',\n '竹外桃花三两枝,春江水暖鸭先知',\n '月落乌啼霜满天,江枫渔火对愁眠',\n '江畔何人初见月,江月何年初照人',\n '朝辞白帝彩云间,千里江陵一日还',\n '不尽长江滚滚流',\n '杨柳青青江水平',\n ]),\n 日: Object.freeze([\n '白日依山尽',\n '日照香炉生紫烟',\n '千里江陵一日还',\n '映日荷花别样红',\n '锄禾日当午',\n '日出江花红胜火',\n '海日生残夜',\n '大漠孤烟直,长河落日圆',\n '日暮乡关何处是',\n '浮云游子意,落日故人情',\n '白日放歌须纵酒',\n '闲云潭影日悠悠',\n ]),\n 星: Object.freeze([\n '星垂平野阔',\n '迢迢牵牛星',\n '危楼高百尺,手可摘星辰',\n '微微风簇浪,散作满河星',\n '七八个星天外',\n '天阶夜色凉如水,卧看牵牛织女星',\n '昨夜星辰昨夜风',\n '星汉灿烂,若出其里',\n ]),\n 天: Object.freeze([\n '天街小雨润如酥',\n '黄河之水天上来',\n '飞流直下三千尺,疑是银河落九天',\n '天生我材必有用',\n '天门中断楚江开',\n '江天一色无纤尘',\n '天阶夜色凉如水',\n '天苍苍,野茫茫',\n '接天莲叶无穷碧',\n '蜀道之难,难于上青天',\n '天若有情天亦老',\n ]),\n 柳: Object.freeze([\n '羌笛何须怨杨柳',\n '客舍青青柳色新',\n '两个黄鹂鸣翠柳',\n '柳暗花明又一村',\n '月上柳梢头',\n '春城无处不飞花,寒食东风御柳斜',\n '此夜曲中闻折柳',\n '杨柳岸,晓风残月',\n '枝上柳绵吹又少',\n '最是一年春好处,绝胜烟柳满皇都',\n '芙蓉如面柳如眉',\n '杨柳青青江水平',\n ]),\n 雁: Object.freeze([\n '雁字回时,月满西楼',\n '塞下秋来风景异,衡阳雁去无留意',\n '北风吹雁雪纷纷',\n '征蓬出汉塞,归雁入胡天',\n '月黑雁飞高',\n '雁过也,正伤心,却是旧时相识',\n '秋风起兮白云飞,草木黄落兮雁南归',\n '乡书何处达,归雁洛阳边',\n '何处秋风至,萧萧送雁群',\n ]),\n 烟: Object.freeze([\n '日照香炉生紫烟',\n '大漠孤烟直',\n '烟笼寒水月笼沙',\n '草长莺飞二月天,拂堤杨柳醉春烟',\n '一蓑烟雨任平生',\n '南朝四百八十寺,多少楼台烟雨中',\n '故人西辞黄鹤楼,烟花三月下扬州',\n '日暮乡关何处是,烟波江上使人愁',\n '绿杨烟外晓寒轻',\n '最是一年春好处,绝胜烟柳满皇都',\n ]),\n 楼: Object.freeze([\n '故人西辞黄鹤楼',\n '山外青山楼外楼',\n '小楼昨夜又东风',\n '危楼高百尺',\n '欲穷千里目,更上一层楼',\n '无言独上西楼',\n '近水楼台先得月',\n '昨夜西风凋碧树,独上高楼',\n '楼船夜雪瓜洲渡',\n '雁字回时,月满西楼',\n '多少楼台烟雨中',\n '明月楼高休独倚',\n ]),\n 梦: Object.freeze([\n '夜阑卧听风吹雨,铁马冰河入梦来',\n '梦里不知身是客',\n '人生如梦,一尊还酹江月',\n '庄生晓梦迷蝴蝶',\n '故人入我梦,明我长相忆',\n '夜来幽梦忽还乡',\n '梦回吹角连营',\n '因思杜陵梦,凫雁满回塘',\n ]),\n})\n\n/** 开局/换字时随机抽取的令字池(即诗句库的键)。 */\nexport const LINGZI_POOL: readonly string[] = Object.freeze(Object.keys(POEM_BANK))\n\n/** 常见(好接)令字:简易/中等难度可选池。 */\nconst COMMON_LINGZI: ReadonlySet<string> = new Set([\n '花', '月', '风', '雪', '山', '水', '云', '雨', '春', '秋', '人', '酒', '夜', '江', '日', '星', '天',\n])\n\n/** 简易(常见)令字池。 */\nexport const EASY_LINGZI: readonly string[] = Object.freeze(\n LINGZI_POOL.filter((z) => COMMON_LINGZI.has(z)),\n)\n\n/** 困难(偏门)令字池。 */\nexport const HARD_LINGZI: readonly string[] = Object.freeze(\n LINGZI_POOL.filter((z) => !COMMON_LINGZI.has(z)),\n)\n","/**\n * 内置 AI 对手 · 即时对战引擎(纯逻辑,零平台依赖、不依赖 React)。\n * 与 Host 共用 shared/rules.ts 的判定与 shared/poems.ts 的诗库:\n * - 判定:judgePoemAttempt —— 面板即时判定与对话判定规则完全一致;\n * - 双边去重:对手(bot)出句与玩家诗句都登记进 used 集合,杜绝重复;\n * - 对手出句:从诗库按难度取未使用句;库句不足即判玩家获胜。\n * @module dsh-plugin-feihualing/client/game/engine\n */\n\nimport { judgePoemAttempt, MAX_ANCIENT_POSITION, normalizeLine } from '../../shared/rules.ts'\nimport type { GameMode } from '../../shared/rules.ts'\nimport { EASY_LINGZI, HARD_LINGZI, LINGZI_POOL, POEM_BANK } from '../../shared/poems.ts'\n\n/** 对手难度。 */\nexport type Difficulty = 'easy' | 'medium' | 'hard'\n\n/** 对局阶段。 */\nexport type MatchPhase = 'playing' | 'won' | 'lost'\n\n/** 本局上限题数(每键诗库 ≥8 句,扣除双方占用后仍充足)。 */\nexport const MAX_ROUNDS = 7\n\n/** 失败(超时)上限,达到即判负。 */\nexport const FAIL_LIMIT = 3\n\n/** 每局提示次数。 */\nexport const HINTS_PER_MATCH = 3\n\n/** 每题限时(秒),按难度递减。 */\nexport const TIME_LIMIT: Readonly<Record<Difficulty, number>> = {\n easy: 30,\n medium: 20,\n hard: 15,\n}\n\n/** 基础得分:ancient 更严格故更高。 */\nconst SCORE_BASE: Readonly<Record<GameMode, number>> = { simple: 10, ancient: 15 }\n\n/** 连击加成(每段连续 +2)。 */\nconst COMBO_BONUS = 2\n\n/** 使用提示后本题通过的基础分。 */\nconst HINT_SCORE = 5\n\n/** 对局快照。 */\nexport interface MatchState {\n phase: MatchPhase\n mode: GameMode\n difficulty: Difficulty\n /** 当前令字。 */\n lingzi: string\n /** 当前题号(0 起;玩家每答对一题推进)。 */\n roundIndex: number\n /** 玩家已成功题数(roundIndex 与失败推进的解耦计数)。 */\n solved: number\n /** 失败次数(超时)。 */\n failures: number\n /** 累计得分。 */\n score: number\n /** 当前连击数(连续答对;0 表示断连)。 */\n combo: number\n /** 双方已用诗句(展示原文)。 */\n usedRaw: string[]\n /** 双方已用诗句(规范化,双边去重依据)。 */\n usedNormalized: string[]\n /** 对手当前出的题(玩家要接的句子)。 */\n challenge: string\n /** 本回合是否使用过提示(使用后本题通过只计 HINT_SCORE 且断连击)。 */\n hintUsedThisRound: boolean\n /** 剩余提示次数。 */\n hintsLeft: number\n /** 最近一次判定/事件文案(面板状态条展示)。 */\n lastVerdict: string\n /** 古法模式本轮要求位置(1 起);simple 恒 1。 */\n requiredPosition: number\n startedAt: number\n}\n\n/** 单局设置。 */\nexport interface MatchOptions {\n mode: GameMode\n difficulty: Difficulty\n}\n\n/** 玩家提交的裁决结果。 */\nexport interface SubmitOutcome {\n state: MatchState\n /** 是否本题通过(得分推进)。 */\n ok: boolean\n}\n\n// ---------- 内部小工具 ----------\n\nfunction pick<T>(list: readonly T[]): T {\n return list[Math.floor(Math.random() * list.length)]\n}\n\nfunction clone(state: MatchState): MatchState {\n return {\n ...state,\n usedRaw: [...state.usedRaw],\n usedNormalized: [...state.usedNormalized],\n }\n}\n\n/** 按难度抽令字:hard 用偏门字池,其余用常见字池。 */\nfunction pickLingzi(difficulty: Difficulty): string {\n const pool = difficulty === 'hard' ? HARD_LINGZI : EASY_LINGZI\n return pick(pool.length > 0 ? pool : LINGZI_POOL)\n}\n\n/** 诗库中该令字的全部句子。 */\nfunction bankOf(lingzi: string): readonly string[] {\n return POEM_BANK[lingzi] ?? []\n}\n\n/**\n * 从诗库挑一句未使用的句子(对手出句/提示候选)。\n * @returns 命中返回展示文本,库句被双方用完返回 undefined。\n */\nfunction pickUnused(state: MatchState): string | undefined {\n const candidates = bankOf(state.lingzi).filter(\n (line) => !state.usedNormalized.includes(normalizeLine(line)),\n )\n if (candidates.length === 0) return undefined\n return pick(candidates)\n}\n\n// ---------- 公开 API ----------\n\n/** 创建一局(抽令字 + 对手先手出题)。 */\nexport function createMatch(options: MatchOptions): MatchState {\n const lingzi = pickLingzi(options.difficulty)\n const base: MatchState = {\n phase: 'playing',\n mode: options.mode,\n difficulty: options.difficulty,\n lingzi,\n roundIndex: 0,\n solved: 0,\n failures: 0,\n score: 0,\n combo: 0,\n usedRaw: [],\n usedNormalized: [],\n challenge: '',\n hintUsedThisRound: false,\n hintsLeft: HINTS_PER_MATCH,\n lastVerdict: '',\n requiredPosition: 1,\n startedAt: Date.now(),\n }\n const opener = pickUnused(base)\n if (opener === undefined) {\n // 库为空(理论上不会发生):以空白开局仍可玩\n return { ...base, lastVerdict: `令字「${lingzi}」,请出句` }\n }\n return {\n ...base,\n challenge: opener,\n usedRaw: [opener],\n usedNormalized: [normalizeForUsed(opener)],\n lastVerdict: `令字「${lingzi}」· 对手出题,请对诗`,\n }\n}\n\n/** 仅规范化用于登记的辅助(与判定同源)。 */\nfunction normalizeForUsed(raw: string): string {\n return normalizeLine(raw)\n}\n\n/** 生成\"通过\"时的结算文案与计分(advance = 是否推进题号)。 */\nfunction applySolved(state: MatchState, display: string): MatchState {\n const comboBefore = state.combo\n const hintPenalty = state.hintUsedThisRound\n const gain = hintPenalty\n ? HINT_SCORE\n : SCORE_BASE[state.mode] + comboBefore * COMBO_BONUS\n const next = clone(state)\n next.solved += 1\n next.combo = hintPenalty ? 0 : comboBefore + 1\n next.score += gain\n next.usedRaw.push(display)\n next.usedNormalized.push(normalizeForUsed(display))\n if (next.mode === 'ancient') {\n next.requiredPosition = (next.requiredPosition % MAX_ANCIENT_POSITION) + 1\n }\n next.roundIndex += 1\n next.hintUsedThisRound = false\n const streak = next.combo\n next.lastVerdict = `答对!+${gain}${streak >= 2 ? `(连击 ×${streak})` : ''} · ${next.score} 分`\n return next\n}\n\n/** 推进到下一题(对手换句);无句可出或已到上限即玩家获胜。 */\nfunction advance(state: MatchState, why: string): MatchState {\n if (state.roundIndex >= MAX_ROUNDS) {\n return { ...clone(state), phase: 'won', lastVerdict: `通关!连对 ${state.solved} 题,共 ${state.score} 分(${why})` }\n }\n const nextLine = pickUnused(state)\n if (nextLine === undefined) {\n const won = clone(state)\n won.phase = 'won'\n won.lastVerdict = `对手诗穷,你赢了!共 ${state.score} 分(${why})`\n return won\n }\n const next = clone(state)\n next.challenge = nextLine\n next.usedRaw.push(nextLine)\n next.usedNormalized.push(normalizeForUsed(nextLine))\n next.lastVerdict = `${state.lastVerdict} —— 对手换题`\n return next\n}\n\n/** 玩家提交诗句。 */\nexport function submitPlayerLine(state: MatchState, rawText: string): SubmitOutcome {\n if (state.phase !== 'playing') return { state, ok: false }\n const result = judgePoemAttempt({\n rawText,\n lingzi: state.lingzi,\n mode: state.mode,\n requiredPosition: state.requiredPosition,\n usedNormalized: state.usedNormalized,\n })\n if (result.kind === 'notPoem') {\n return { state: { ...state, lastVerdict: `这句没有令字「${state.lingzi}」,请重新对诗` }, ok: false }\n }\n if (result.kind === 'length') {\n return { state: { ...state, lastVerdict: '诗句长度需在 4-48 字之间' }, ok: false }\n }\n if (result.kind === 'duplicate') {\n return { state: { ...state, lastVerdict: '这句用过了(双方都不能重复),换一句吧' }, ok: false }\n }\n if (result.kind === 'badPosition') {\n const pos = result.requiredPosition ?? state.requiredPosition\n const why = result.badPosReason === 'tooShort'\n ? `句子太短:古法要求令字位于第 ${pos} 字`\n : `位置不对:古法要求第 ${pos} 字是令字「${state.lingzi}」`\n return { state: { ...state, lastVerdict: why }, ok: false }\n }\n // 通过\n const solved = applySolved(state, result.display)\n const advanced = advance(solved, '答对推进')\n // 若本轮因对答通关/对手诗穷而 won,保留\n return { state: advanced, ok: true }\n}\n\n/** 超时(由面板倒计时触发):本题作废,换题;连续失败达上限判负。 */\nexport function timeoutRound(state: MatchState): MatchState {\n if (state.phase !== 'playing') return state\n const next = clone(state)\n next.failures += 1\n next.combo = 0\n next.hintUsedThisRound = false\n if (next.failures >= FAIL_LIMIT) {\n next.phase = 'lost'\n next.lastVerdict = `超时 ${FAIL_LIMIT} 次,本局惜败 · 共答对 ${next.solved} 题 / ${next.score} 分`\n return next\n }\n next.lastVerdict = `超时(第 ${next.failures}/${FAIL_LIMIT} 次)—— 换下一题,加油`\n return advance(next, '超时换题')\n}\n\n/** 认输/放弃。 */\nexport function concedeMatch(state: MatchState): MatchState {\n if (state.phase !== 'playing') return state\n const next = clone(state)\n next.phase = 'lost'\n next.lastVerdict = `你认输了 · 共答对 ${next.solved} 题 / ${next.score} 分`\n return next\n}\n\n/** 提示:消耗 1 次,给出库中一句未用参考诗(可直接跟对,但只计 HINT_SCORE 且断连击)。 */\nexport function useHint(state: MatchState): { state: MatchState; line?: string } {\n if (state.phase !== 'playing' || state.hintsLeft <= 0) return { state }\n const next = clone(state)\n next.hintsLeft -= 1\n next.hintUsedThisRound = true\n const line = pickUnused(next)\n next.lastVerdict = line\n ? `提示(剩 ${next.hintsLeft} 次):参考「${line}」—— 参考句直接对出只 +${HINT_SCORE} 分`\n : `提示(剩 ${next.hintsLeft} 次):想一句含「${state.lingzi}」的诗句吧`\n return { state: next, line }\n}\n\n/** 换令字重开本局(保留设置)。 */\nexport function restartMatch(options: MatchOptions): MatchState {\n return createMatch(options)\n}\n","/**\n * @license React\n * react-jsx-runtime.production.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\nvar REACT_ELEMENT_TYPE = Symbol.for(\"react.transitional.element\"),\n REACT_FRAGMENT_TYPE = Symbol.for(\"react.fragment\");\nfunction jsxProd(type, config, maybeKey) {\n var key = null;\n void 0 !== maybeKey && (key = \"\" + maybeKey);\n void 0 !== config.key && (key = \"\" + config.key);\n if (\"key\" in config) {\n maybeKey = {};\n for (var propName in config)\n \"key\" !== propName && (maybeKey[propName] = config[propName]);\n } else maybeKey = config;\n config = maybeKey.ref;\n return {\n $$typeof: REACT_ELEMENT_TYPE,\n type: type,\n key: key,\n ref: void 0 !== config ? config : null,\n props: maybeKey\n };\n}\nexports.Fragment = REACT_FRAGMENT_TYPE;\nexports.jsx = jsxProd;\nexports.jsxs = jsxProd;\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-jsx-runtime.production.js');\n} else {\n module.exports = require('./cjs/react-jsx-runtime.development.js');\n}\n","/**\n * 浮层游戏面板(完整对战 UI)。\n * 对局状态由 HeaderButton 层持有(useGamePanel),本组件只做展示与事件转发:\n * 关闭面板只隐藏,不丢对局;隐藏期间倒计时暂停(视同暂停)。\n * @module dsh-plugin-feihualing/client/panel\n */\n\nimport React from 'react'\nimport * as engine from './game/engine.ts'\nimport type { GamePanelApi } from './game/useGame.ts'\n\nexport interface GamePanelProps {\n /** 所属会话 id(session 级 header 按钮注入)。 */\n sessionId: string\n /** 关闭面板(仅隐藏,对局保留)。 */\n onClose: () => void\n /** 对局控制面(由父层 useGamePanel 提供)。 */\n api: GamePanelApi\n}\n\n/** 会话短标识(展示用)。 */\nfunction shortSession(sessionId: string): string {\n return sessionId.length > 12 ? `${sessionId.slice(0, 10)}…` : sessionId\n}\n\nconst MODE_LABEL: Record<string, string> = { simple: '简易', ancient: '古法' }\nconst DIFF_LABEL: Record<string, string> = { easy: '轻松', medium: '标准', hard: '困难' }\nconst DIFF_HINT: Record<string, string> = { easy: '常见令字 · 30s', medium: '常见令字 · 20s', hard: '冷门令字 · 15s' }\n\n/** 开局设置行(未开局或对局结束后显示)。 */\nfunction SetupView(props: { api: GamePanelApi }): React.ReactElement {\n const { api } = props\n return (\n <React.Fragment>\n <div className=\"fhl-row\">\n <span className=\"fhl-seg-label\">模式</span>\n <div className=\"fhl-seg\">\n {(['simple', 'ancient'] as const).map((m) => (\n <button\n key={m}\n type=\"button\"\n className={`fhl-seg-item${api.mode === m ? ' is-active' : ''}`}\n onClick={() => api.setMode(m)}\n >\n {MODE_LABEL[m]}\n </button>\n ))}\n </div>\n </div>\n <div className=\"fhl-row\">\n <span className=\"fhl-seg-label\">难度</span>\n <div className=\"fhl-seg\">\n {(['easy', 'medium', 'hard'] as const).map((d) => (\n <button\n key={d}\n type=\"button\"\n className={`fhl-seg-item${api.difficulty === d ? ' is-active' : ''}`}\n onClick={() => api.setDifficulty(d)}\n title={DIFF_HINT[d]}\n >\n {DIFF_LABEL[d]}\n </button>\n ))}\n </div>\n </div>\n <div className=\"fhl-muted\" style={{ fontSize: 12, textAlign: 'center', paddingBottom: 4 }}>\n {api.mode === 'ancient'\n ? '古法:令字须按位置循环(第 1 字 → 第 7 字)'\n : '简易:诗句任意位置含令字即可'}\n {' · '}\n {DIFF_HINT[api.difficulty]}\n </div>\n <button type=\"button\" className=\"fhl-btn fhl-btn-primary\" onClick={api.start}>\n 🎴 开始对局\n </button>\n <div className=\"fhl-muted\" style={{ fontSize: 11, textAlign: 'center' }}>\n 对手出题 · 你限时对诗 · 连对 {engine.MAX_ROUNDS} 题通关 · 超时 {engine.FAIL_LIMIT} 次惜败\n </div>\n </React.Fragment>\n )\n}\n\n/** 进行中对局视图。 */\nfunction PlayView(props: { api: GamePanelApi }): React.ReactElement {\n const { api } = props\n const match = api.match\n if (match === null) return <React.Fragment />\n const timeLow = api.secondsLeft <= 5\n const canAct = match.phase === 'playing'\n return (\n <React.Fragment>\n <div className=\"fhl-stage\">\n <span className=\"fhl-stage-round\">第 {match.roundIndex + 1}/{engine.MAX_ROUNDS} 题</span>\n <span className={`fhl-timer${timeLow && canAct ? ' is-low' : ''}`}>{api.secondsLeft}s</span>\n <span className=\"fhl-stage-tag\">{MODE_LABEL[match.mode]} · {DIFF_LABEL[match.difficulty]}</span>\n </div>\n <div className=\"fhl-lingzi\">「{match.lingzi}」</div>\n {match.mode === 'ancient' && (\n <div className=\"fhl-muted\" style={{ fontSize: 11, textAlign: 'center', marginTop: -4 }}>\n 古法:本轮令字须位于第 <b style={{ color: 'var(--fhl-accent)' }}>{match.requiredPosition}</b> 字\n </div>\n )}\n <div className=\"fhl-card fhl-card-bot\">\n <span className=\"fhl-card-role\">🤖 对手</span>\n <span className=\"fhl-card-line\">{match.challenge}</span>\n </div>\n {match.lastVerdict !== '' && (\n <div className={`fhl-verdict${api.secondsLeft === 0 || !canAct ? '' : ''}`}>{match.lastVerdict}</div>\n )}\n <form\n className=\"fhl-input-row\"\n onSubmit={(ev) => {\n ev.preventDefault()\n api.submit()\n }}\n >\n <input\n className=\"fhl-input\"\n value={api.draft}\n onChange={(ev) => api.setDraft(ev.target.value)}\n placeholder={`说一句含「${match.lingzi}」的诗句…`}\n disabled={!canAct}\n autoComplete=\"off\"\n />\n <button type=\"submit\" className=\"fhl-btn fhl-btn-primary\" disabled={!canAct}>对诗</button>\n </form>\n <div className=\"fhl-actions\">\n <button\n type=\"button\"\n className=\"fhl-btn\"\n onClick={api.requestHint}\n disabled={!canAct || match.hintsLeft <= 0}\n title=\"消耗提示次数,给出参考诗句(参考句直接跟对只得 5 分)\"\n >\n 💡 提示({match.hintsLeft})\n </button>\n <button type=\"button\" className=\"fhl-btn fhl-btn-danger\" onClick={api.concede} disabled={!canAct}>\n 认输\n </button>\n </div>\n <div className=\"fhl-stats\">\n <span className=\"fhl-stat\"><b>{match.score}</b> 得分</span>\n <span className=\"fhl-stat\"><b>{match.combo}</b> 连击</span>\n <span className=\"fhl-stat\"><b>{match.solved}/{engine.MAX_ROUNDS}</b> 答对</span>\n <span className={`fhl-stat${match.failures >= engine.FAIL_LIMIT - 1 ? ' is-warn' : ''}`}>\n <b>{match.failures}/{engine.FAIL_LIMIT}</b> 超时\n </span>\n </div>\n </React.Fragment>\n )\n}\n\n/** 结算视图。 */\nfunction EndView(props: { api: GamePanelApi }): React.ReactElement {\n const { api } = props\n const match = api.match\n if (match === null) return <React.Fragment />\n const won = match.phase === 'won'\n return (\n <React.Fragment>\n <div className={`fhl-result${won ? ' is-won' : ' is-lost'}`}>\n <div className=\"fhl-result-icon\">{won ? '🏆' : '😢'}</div>\n <div className=\"fhl-result-title\">{won ? '通关!' : '本局惜败'}</div>\n <div className=\"fhl-muted\" style={{ fontSize: 12, textAlign: 'center' }}>{match.lastVerdict}</div>\n </div>\n <div className=\"fhl-stats\">\n <span className=\"fhl-stat\"><b>{match.score}</b> 得分</span>\n <span className=\"fhl-stat\"><b>{match.solved}</b> 答对</span>\n <span className=\"fhl-stat\"><b>{match.failures}</b> 超时</span>\n </div>\n <div className=\"fhl-record\">\n <span>🏅 {api.stats.wins} 胜 / {api.stats.losses} 负</span>\n <span>🔥 连胜 {api.stats.streak}</span>\n <span>✨ 最佳 {api.stats.bestScore}</span>\n </div>\n <button type=\"button\" className=\"fhl-btn fhl-btn-primary\" onClick={api.start}>\n 🔄 再来一局\n </button>\n <div className=\"fhl-muted\" style={{ fontSize: 11, textAlign: 'center' }}>\n 令字「{match.lingzi}」· {MODE_LABEL[match.mode]} · {DIFF_LABEL[match.difficulty]}\n </div>\n </React.Fragment>\n )\n}\n\n/** 面板主体:按对局阶段切换视图。 */\nexport function GamePanel(props: GamePanelProps): React.ReactElement {\n const { sessionId, onClose, api } = props\n const phase = api.match === null ? 'setup' : api.match.phase\n return (\n <div className=\"fhl-root fhl-panel\" data-plugin=\"dsh-plugin-feihualing\">\n <div className=\"fhl-panel-header\">\n <span className=\"fhl-panel-title\">🎴 飞花令 · 即时对战</span>\n <span className=\"fhl-tag\" title={sessionId}>会话 {shortSession(sessionId)}</span>\n <button type=\"button\" className=\"fhl-panel-close\" onClick={onClose} title=\"收起(对局保留)\">✕</button>\n </div>\n {phase === 'setup' && <SetupView api={api} />}\n {phase === 'playing' && <PlayView api={api} />}\n {(phase === 'won' || phase === 'lost') && <EndView api={api} />}\n </div>\n )\n}\n","/**\n * 面板对战战绩(localStorage 持久化,本机会话无关、跨刷新保留)。\n * 纯浏览器模块;与 Host 对话模式状态互不干扰(面板是独立即时战)。\n * @module dsh-plugin-feihualing/client/game/stats\n */\n\nimport type { MatchState } from './engine.ts'\n\nexport interface GameStats {\n /** 累计胜局。 */\n wins: number\n /** 累计败局。 */\n losses: number\n /** 历史最佳得分。 */\n bestScore: number\n /** 当前连胜。 */\n streak: number\n /** 历史最长连胜。 */\n bestStreak: number\n /** 累计答对题数。 */\n solvedTotal: number\n}\n\nconst KEY = 'dsh-plugin-feihualing:stats'\n\nfunction empty(): GameStats {\n return { wins: 0, losses: 0, bestScore: 0, streak: 0, bestStreak: 0, solvedTotal: 0 }\n}\n\n/** 读取战绩(损坏/缺失返回空战绩)。 */\nexport function loadStats(): GameStats {\n try {\n const raw = localStorage.getItem(KEY)\n if (raw === null) return empty()\n const parsed = JSON.parse(raw) as Partial<GameStats>\n return {\n wins: typeof parsed.wins === 'number' ? parsed.wins : 0,\n losses: typeof parsed.losses === 'number' ? parsed.losses : 0,\n bestScore: typeof parsed.bestScore === 'number' ? parsed.bestScore : 0,\n streak: typeof parsed.streak === 'number' ? parsed.streak : 0,\n bestStreak: typeof parsed.bestStreak === 'number' ? parsed.bestStreak : 0,\n solvedTotal: typeof parsed.solvedTotal === 'number' ? parsed.solvedTotal : 0,\n }\n } catch {\n return empty()\n }\n}\n\n/** 结算一局并入账(返回并持久化新战绩)。 */\nexport function recordResult(stats: GameStats, match: MatchState): GameStats {\n const won = match.phase === 'won'\n const next: GameStats = {\n ...stats,\n wins: stats.wins + (won ? 1 : 0),\n losses: stats.losses + (won ? 0 : 1),\n bestScore: Math.max(stats.bestScore, match.score),\n streak: won ? stats.streak + 1 : 0,\n bestStreak: Math.max(stats.bestStreak, won ? stats.streak + 1 : stats.bestStreak),\n solvedTotal: stats.solvedTotal + match.solved,\n }\n try {\n localStorage.setItem(KEY, JSON.stringify(next))\n } catch {\n /* 存储失败(隐私模式等)不影响对局 */\n }\n return next\n}\n","/**\n * React 对局 hook:包装 engine 的纯逻辑,管理倒计时与面板动作。\n * 倒计时由 useGamePanel 内部控制(setInterval,无第三方库)。\n * @module dsh-plugin-feihualing/client/game/useGame\n */\n\nimport React from 'react'\nimport * as engine from './engine.ts'\nimport type { GameStats } from './stats.ts'\nimport { loadStats, recordResult } from './stats.ts'\nimport type { GameMode } from '../../shared/rules.ts'\n\n/** 对局控制面(供面板组件使用)。 */\nexport interface GamePanelApi {\n match: engine.MatchState | null\n secondsLeft: number\n mode: GameMode\n difficulty: engine.Difficulty\n draft: string\n stats: GameStats\n setMode(mode: GameMode): void\n setDifficulty(difficulty: engine.Difficulty): void\n setDraft(text: string): void\n start(): void\n submit(): void\n requestHint(): void\n concede(): void\n}\n\n/**\n * 一局对战面板状态。倒计时只在对局进行中且面板激活时跑;\n * 面板隐藏(active=false)视同暂停:计时停住、不超时,重开面板继续。\n * 答题推进/超时换题时按 (phase, roundIndex, challenge) 变化重置计时。\n * @param active 面板是否可见(决定倒计时是否运行)。\n */\nexport function useGamePanel(active: boolean): GamePanelApi {\n const [match, setMatch] = React.useState<engine.MatchState | null>(null)\n const [secondsLeft, setSecondsLeft] = React.useState(0)\n const [mode, setMode] = React.useState<GameMode>('simple')\n const [difficulty, setDifficulty] = React.useState<engine.Difficulty>('medium')\n const [draft, setDraft] = React.useState('')\n const [stats, setStats] = React.useState<GameStats>(() => loadStats())\n // 记录已入账的局(按 startedAt),防止同一局重复累计战绩\n const recordedStartedAt = React.useRef<number | null>(null)\n\n // 结算(won/lost)时入账一次战绩\n React.useEffect(() => {\n if (match === null || (match.phase !== 'won' && match.phase !== 'lost')) return\n if (recordedStartedAt.current === match.startedAt) return\n recordedStartedAt.current = match.startedAt\n setStats((cur) => recordResult(cur, match))\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [match])\n\n const roundKey = match === null\n ? ''\n : `${match.phase}:${match.roundIndex}:${match.challenge}`\n\n // 每一“题”(phase/题号/对手句变化)重置倒计时为满额限时\n React.useEffect(() => {\n if (match === null || match.phase !== 'playing') return\n setSecondsLeft(engine.TIME_LIMIT[match.difficulty])\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [roundKey])\n\n // 秒针:仅面板激活且对局进行中时递减;隐藏(active=false)即暂停并保留剩余时间\n React.useEffect(() => {\n if (!active || match === null || match.phase !== 'playing') return\n const timer = setInterval(() => {\n setSecondsLeft((s) => Math.max(0, s - 1))\n }, 1000)\n return () => clearInterval(timer)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [active, roundKey])\n\n // 倒计时归零:本题超时\n React.useEffect(() => {\n if (!active || match === null || match.phase !== 'playing') return\n if (secondsLeft > 0) return\n setMatch((cur) => (cur !== null && cur.phase === 'playing' ? engine.timeoutRound(cur) : cur))\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [secondsLeft, active])\n\n const start = React.useCallback(() => {\n setMatch(engine.createMatch({ mode, difficulty }))\n setDraft('')\n }, [mode, difficulty])\n\n const submit = React.useCallback(() => {\n const text = draft.trim()\n if (text.length === 0) return\n setMatch((cur) => {\n if (cur === null || cur.phase !== 'playing') return cur\n const outcome = engine.submitPlayerLine(cur, text)\n return outcome.state\n })\n setDraft('')\n }, [draft])\n\n const requestHint = React.useCallback(() => {\n setMatch((cur) => {\n if (cur === null || cur.phase !== 'playing') return cur\n return engine.useHint(cur).state\n })\n }, [])\n\n const concede = React.useCallback(() => {\n setMatch((cur) => (cur === null ? cur : engine.concedeMatch(cur)))\n }, [])\n\n return {\n match,\n secondsLeft,\n mode,\n difficulty,\n draft,\n stats,\n setMode,\n setDifficulty,\n setDraft,\n start,\n submit,\n requestHint,\n concede,\n }\n}\n","/**\n * 会话标题游戏开关按钮(conversation.session.header.actions 插槽,session 级 list)。\n * 点击后直接渲染浮层游戏面板(fixed 定位,无需 react-dom portal——\n * client 半边仅依赖 DSH 模块表确认提供的 react)。\n * 对局状态常驻本组件:关闭面板只隐藏,不丢局;隐藏期间倒计时暂停(见 useGame)。\n * @module dsh-plugin-feihualing/client/header-button\n */\n\nimport React from 'react'\nimport { HEADER_ORDER, NAMESPACE } from './constants.ts'\nimport type { LocalContext } from './types.ts'\nimport { GamePanel } from './panel.tsx'\nimport { useGamePanel } from './game/useGame.ts'\n\n/** 在会话头操作行注册游戏开关按钮。 */\nexport function registerHeaderButton(ctx: LocalContext): void {\n ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register(\n {\n name: 'conversation.session.header.actions',\n id: NAMESPACE,\n order: HEADER_ORDER,\n inject: (sessionId) => ({ sessionId }),\n },\n HeaderButton,\n ))\n}\n\n/** 会话头游戏开关按钮 + 浮层面板(open 时渲染,fixed 定位于视口右上)。 */\nfunction HeaderButton(props: { sessionId?: string }): React.ReactElement {\n const [open, setOpen] = React.useState(false)\n // 对局状态提升到按钮层:面板开合不影响对局,隐藏 = 暂停计时\n const api = useGamePanel(open)\n const sessionId = props.sessionId ?? ''\n const toggle = React.useCallback(() => setOpen((prev) => !prev), [])\n\n return (\n <React.Fragment>\n <button\n type=\"button\"\n className=\"fhl-toggle\"\n data-active={open ? 'true' : 'false'}\n onClick={toggle}\n title={open ? '收起飞花令(对局保留)' : '飞花令小游戏'}\n aria-label=\"飞花令小游戏\"\n >\n 🎴\n </button>\n {open && <GamePanel sessionId={sessionId} onClose={() => setOpen(false)} api={api} />}\n </React.Fragment>\n )\n}\n","/**\n * 客户端半边入口(dsh-plugin-feihualing/client)。\n * 唯一职责:注入样式并组装各 UI 面注册(当前:会话头游戏开关按钮 + 浮层面板)。\n * 只做纯插槽注册,不受 harness 的 WEB_SETTINGS_NAMESPACES 白名单影响,装完即用。\n * @module dsh-plugin-feihualing/client\n */\n\nimport type { LocalContext } from './types.ts'\nimport { injectStyles } from './styles.ts'\nimport { registerHeaderButton } from './header-button.tsx'\n\n/** 依赖的服务:slots 就绪后本插件才会加载。 */\nexport const inject = ['slots']\n\n/** 客户端插件主体。 */\nexport function apply(ctx: LocalContext): void {\n // 样式表随插件生命周期注入/移除(ctx.effect 由 cordis 客户端运行时提供)\n const effect = (ctx as { effect?: (execute: () => (() => void) | void, label?: string) => void }).effect\n const disposeStyles = injectStyles()\n if (effect) {\n effect(() => disposeStyles, 'dsh-plugin-feihualing: client styles')\n }\n registerHeaderButton(ctx)\n}\n"],"x_google_ignoreList":[5,6],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAOA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiNZ,SAAgB,eAA2B;GACzC,MAAM,QAAQ,SAAS,cAAc,OAAO;GAC5C,MAAM,aAAa,eAAe,uBAAuB;GACzD,MAAM,cAAc;GACpB,SAAS,KAAK,YAAY,KAAK;GAC/B,aAAa;IACX,IAAI,MAAM,YAAY,MAAM,WAAW,YAAY,KAAK;GAC1D;EACF;;;;;;;;;ECzNA,MAAa,YAAY;;;;;ECczB,SAAgB,cAAc,KAAqB;GACjD,OAAO,IAAI,QAAQ,iDAAiD,EAAE;EACxE;;EAGA,SAAgB,YAAY,KAAqB;GAC/C,OAAO,IAAI,KAAK,CAAC,CAAC,QAAQ,SAAS,GAAG,CAAC,CAAC,MAAM,GAAA,EAAiB;EACjE;;;;;EA0CA,SAAgB,iBAAiB,OAAgC;GAC/D,MAAM,aAAa,cAAc,MAAM,OAAO;GAC9C,MAAM,UAAU,YAAY,MAAM,OAAO;GAEzC,IAAI,CAAC,WAAW,SAAS,MAAM,MAAM,GACnC,OAAO;IAAE,MAAM;IAAW;IAAY;IAAS,QAAQ,WAAW;GAAO;GAE3E,IAAI,WAAW,SAAA,KAA2B,WAAW,SAAA,IACnD,OAAO;IAAE,MAAM;IAAU;IAAY;IAAS,QAAQ,WAAW;GAAO;GAE1E,IAAI,MAAM,eAAe,SAAS,UAAU,GAC1C,OAAO;IAAE,MAAM;IAAa;IAAY;IAAS,QAAQ,WAAW;GAAO;GAE7E,IAAI,MAAM,SAAS,WAAW;IAC5B,MAAM,MAAM,MAAM;IAClB,IAAI,WAAW,SAAS,KACtB,OAAO;KAAE,MAAM;KAAe,cAAc;KAAY,kBAAkB;KAAK;KAAY;KAAS,QAAQ,WAAW;IAAO;IAEhI,IAAI,WAAW,MAAM,OAAO,MAAM,QAChC,OAAO;KAAE,MAAM;KAAe,cAAc;KAAY,kBAAkB;KAAK;KAAY;KAAS,QAAQ,WAAW;IAAO;GAElI;GACA,OAAO;IAAE,MAAM;IAAS;IAAY;IAAS,QAAQ,WAAW;GAAO;EACzE;;;;;;;;;;;;ECnFA,MAAa,YAAyD,OAAO,OAAO;GAClF,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;EACH,CAAC;;EAGD,MAAa,cAAiC,OAAO,OAAO,OAAO,KAAK,SAAS,CAAC;;EAGlF,MAAM,gCAAqC,IAAI,IAAI;GACjD;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;EAClF,CAAC;;EAGD,MAAa,cAAiC,OAAO,OACnD,YAAY,QAAQ,MAAM,cAAc,IAAI,CAAC,CAAC,CAChD;;EAGA,MAAa,cAAiC,OAAO,OACnD,YAAY,QAAQ,MAAM,CAAC,cAAc,IAAI,CAAC,CAAC,CACjD;;ECxSA,MAAa,aAAmD;GAC9D,MAAM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"}
1
+ {"version":3,"file":"client.js","names":["React","React","engine.TIME_LIMIT","engine.timeoutRound","engine.createMatch","engine.submitPlayerLine","engine.useHint","engine.concedeMatch","React"],"sources":["../src/client/styles.ts","../src/client/constants.ts","../src/shared/rules.ts","../src/shared/poems.ts","../src/client/game/engine.ts","../node_modules/.pnpm/react@19.2.8/node_modules/react/cjs/react-jsx-runtime.production.js","../node_modules/.pnpm/react@19.2.8/node_modules/react/jsx-runtime.js","../src/client/panel.tsx","../src/client/game/stats.ts","../src/client/game/useGame.ts","../src/client/header-button.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * 客户端半边样式:一次性注入 <style>,全部 fhl-* 类以 data-plugin 根类名限定作用域。\n * 主题跟随 DSH:深色模式由 body[data-ds-dark-theme] 标识(官方约定),\n * 浅色/深色各写一套语义配色;不依赖任何宿主私有类名。\n * @module dsh-plugin-feihualing/client/styles\n */\n\nconst CSS = `\n[data-plugin=\"dsh-plugin-feihualing\"].fhl-root, .fhl-root {\n --fhl-bg: #ffffff;\n --fhl-bg-elev: #f4f4f5;\n --fhl-fg: #27272a;\n --fhl-fg-muted: #71717a;\n --fhl-accent: #8b5cf6;\n --fhl-accent-soft: rgba(139, 92, 246, 0.12);\n --fhl-border: rgba(0, 0, 0, 0.1);\n --fhl-ok: #16a34a;\n --fhl-ok-soft: rgba(22, 163, 74, 0.12);\n --fhl-bad: #dc2626;\n --fhl-bad-soft: rgba(220, 38, 38, 0.12);\n font-family: var(--dsh-content-font, ui-sans-serif, system-ui, \"PingFang SC\", \"Microsoft YaHei\", sans-serif);\n box-sizing: border-box;\n}\nbody[data-ds-dark-theme] .fhl-root {\n --fhl-bg: #18181b;\n --fhl-bg-elev: #27272a;\n --fhl-fg: #e4e4e7;\n --fhl-fg-muted: #a1a1aa;\n --fhl-accent: #a78bfa;\n --fhl-accent-soft: rgba(167, 139, 250, 0.16);\n --fhl-border: rgba(255, 255, 255, 0.12);\n --fhl-ok: #4ade80;\n --fhl-ok-soft: rgba(74, 222, 128, 0.16);\n --fhl-bad: #f87171;\n --fhl-bad-soft: rgba(248, 113, 113, 0.16);\n}\n.fhl-root *, .fhl-root *::before, .fhl-root *::after { box-sizing: border-box; }\n\n/* 会话头操作行的小按钮 */\n.fhl-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n height: 24px;\n padding: 0 6px;\n border: 1px solid transparent;\n border-radius: 6px;\n background: transparent;\n color: var(--fhl-fg-muted);\n font-size: 14px;\n line-height: 1;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.fhl-toggle:hover { background: var(--fhl-accent-soft); color: var(--fhl-fg); }\n.fhl-toggle[data-active=\"true\"] { background: var(--fhl-accent-soft); color: var(--fhl-accent); }\n\n/* 浮层游戏面板(portal 到 body 的独立 surface,自带作用域) */\n.fhl-panel {\n position: fixed;\n top: 64px;\n right: 16px;\n width: 340px;\n max-width: calc(100vw - 24px);\n max-height: calc(100vh - 88px);\n overflow: auto;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px;\n border-radius: 14px;\n background: var(--fhl-bg);\n color: var(--fhl-fg);\n border: 1px solid var(--fhl-border);\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);\n}\n.fhl-panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.fhl-panel-title { font-weight: 700; font-size: 14px; letter-spacing: 0.02em; }\n.fhl-panel-close {\n border: none;\n background: transparent;\n color: var(--fhl-fg-muted);\n cursor: pointer;\n font-size: 14px;\n line-height: 1;\n padding: 4px;\n border-radius: 6px;\n}\n.fhl-panel-close:hover { background: var(--fhl-bad-soft); color: var(--fhl-bad); }\n.fhl-muted { color: var(--fhl-fg-muted); }\n.fhl-tag { font-size: 11px; color: var(--fhl-fg-muted); border: 1px solid var(--fhl-border); padding: 1px 6px; border-radius: 999px; }\n\n/* ---- 对战 UI ---- */\n.fhl-row { display: flex; align-items: center; gap: 8px; }\n.fhl-seg-label { font-size: 12px; color: var(--fhl-fg-muted); flex: none; width: 34px; }\n.fhl-seg { display: flex; gap: 4px; flex: 1; }\n.fhl-seg-item {\n flex: 1;\n padding: 5px 0;\n font-size: 12px;\n border: 1px solid var(--fhl-border);\n border-radius: 8px;\n background: transparent;\n color: var(--fhl-fg-muted);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n.fhl-seg-item:hover { border-color: var(--fhl-accent); color: var(--fhl-fg); }\n.fhl-seg-item.is-active { background: var(--fhl-accent-soft); border-color: var(--fhl-accent); color: var(--fhl-accent); font-weight: 600; }\n\n.fhl-stage { display: flex; align-items: center; gap: 8px; }\n.fhl-stage-round { font-size: 12px; color: var(--fhl-fg-muted); }\n.fhl-timer {\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 700;\n color: var(--fhl-fg);\n padding: 2px 8px;\n border-radius: 999px;\n background: var(--fhl-bg-elev);\n}\n.fhl-timer.is-low { color: var(--fhl-bad); background: var(--fhl-bad-soft); animation: fhl-blink 1s ease-in-out infinite; }\n@keyframes fhl-blink { 50% { opacity: 0.45; } }\n.fhl-stage-tag { margin-left: auto; font-size: 11px; color: var(--fhl-fg-muted); }\n\n.fhl-lingzi {\n text-align: center;\n font-size: 34px;\n font-weight: 800;\n line-height: 1.1;\n letter-spacing: 0.08em;\n color: var(--fhl-accent);\n}\n\n.fhl-card {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 10px 12px;\n border-radius: 12px;\n background: var(--fhl-bg-elev);\n border: 1px solid var(--fhl-border);\n}\n.fhl-card-role { font-size: 11px; color: var(--fhl-fg-muted); }\n.fhl-card-line { font-size: 15px; line-height: 1.5; }\n\n.fhl-verdict { font-size: 12px; color: var(--fhl-fg-muted); min-height: 16px; }\n\n.fhl-input-row { display: flex; gap: 6px; }\n.fhl-input {\n flex: 1;\n min-width: 0;\n padding: 7px 10px;\n font-size: 13px;\n border-radius: 9px;\n border: 1px solid var(--fhl-border);\n background: var(--fhl-bg);\n color: var(--fhl-fg);\n outline: none;\n transition: border-color 0.15s ease;\n}\n.fhl-input:focus { border-color: var(--fhl-accent); }\n.fhl-input:disabled { opacity: 0.5; }\n\n.fhl-actions { display: flex; gap: 6px; }\n.fhl-btn {\n flex: 1;\n padding: 7px 10px;\n font-size: 13px;\n border: 1px solid var(--fhl-border);\n border-radius: 9px;\n background: var(--fhl-bg-elev);\n color: var(--fhl-fg);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n.fhl-btn:hover:not(:disabled) { border-color: var(--fhl-accent); }\n.fhl-btn:disabled { opacity: 0.45; cursor: default; }\n.fhl-btn-primary { background: var(--fhl-accent-soft); border-color: var(--fhl-accent); color: var(--fhl-accent); font-weight: 600; }\n.fhl-btn-danger { color: var(--fhl-bad); }\n.fhl-btn-danger:hover:not(:disabled) { border-color: var(--fhl-bad); background: var(--fhl-bad-soft); }\n\n.fhl-stats {\n display: flex;\n gap: 6px;\n justify-content: space-between;\n padding-top: 2px;\n border-top: 1px dashed var(--fhl-border);\n}\n.fhl-stat { font-size: 11px; color: var(--fhl-fg-muted); display: inline-flex; gap: 3px; align-items: baseline; }\n.fhl-stat b { color: var(--fhl-fg); font-size: 13px; }\n.fhl-stat.is-warn b { color: var(--fhl-bad); }\n\n.fhl-result { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 10px 0 4px; }\n.fhl-result-icon { font-size: 40px; }\n.fhl-result-title { font-size: 18px; font-weight: 800; }\n.fhl-result.is-won .fhl-result-title { color: var(--fhl-ok); }\n.fhl-result.is-lost .fhl-result-title { color: var(--fhl-bad); }\n\n.fhl-record {\n display: flex;\n justify-content: center;\n gap: 10px;\n font-size: 11px;\n color: var(--fhl-fg-muted);\n}\n`\n\n/** 注入样式表;返回 disposer(卸载时移除 style 节点)。 */\nexport function injectStyles(): () => void {\n const style = document.createElement('style')\n style.setAttribute('data-plugin', 'dsh-plugin-feihualing')\n style.textContent = CSS\n document.head.appendChild(style)\n return () => {\n if (style.parentNode) style.parentNode.removeChild(style)\n }\n}\n","/**\n * 客户端半边共享常量。\n * NAMESPACE 用作插槽注册 id,须与包名(package.json name / cordis.patch.yml)一致。\n * @module dsh-plugin-feihualing/client/constants\n */\n\n/** 插件命名空间(与 cordis.patch.yml 的 id、Loader 行 name 保持一致)。 */\nexport const NAMESPACE = 'dsh-plugin-feihualing'\n\n/** 面板在会话标题操作行中的渲染顺序(靠后,避免挤占内置预设/任务按钮)。 */\nexport const HEADER_ORDER = 90\n","/**\n * 共享判定规则(Host 与 Client 浏览器两半共同使用)。\n * 纯函数、零平台依赖:不允许 import node:* / DOM —— 保证「对话判定」与\n * 「面板即时判定」对同一输入给出完全一致的结果。\n * @module dsh-plugin-feihualing/shared/rules\n */\n\n/** 游戏模式:simple 简易 / ancient 古法严格。 */\nexport type GameMode = 'simple' | 'ancient'\n\n/** 诗句尝试的最小/最大长度(规范化后字符数)。 */\nexport const MIN_LINE_CHARS = 4\nexport const MAX_LINE_CHARS = 48\n\n/** 古法令字位置循环上限(第 1 字 → 第 7 字 → 第 1 字)。 */\nexport const MAX_ANCIENT_POSITION = 7\n\n/**\n * 规范化一行文本:去除空白与常见中英文标点,仅保留汉字/字母/数字。\n * 用于关键词匹配与诗句去重比较。\n */\nexport function normalizeLine(raw: string): string {\n return raw.replace(/[\\s,。!?、;:\"\"''()《》〈〉【】\\[\\]·,.!?;:'\"()\\-—…]+/gu, '')\n}\n\n/** 去掉文本首尾空白(用于展示时压缩用户输入的换行/空格)。 */\nexport function trimDisplay(raw: string): string {\n return raw.trim().replace(/\\s+/gu, ' ').slice(0, MAX_LINE_CHARS)\n}\n\n/** 一次诗句判定的输入(与 GameState 中参与判定的字段一一对应)。 */\nexport interface JudgeInput {\n rawText: string\n lingzi: string\n mode: GameMode\n /** 古法本轮要求令字位置(1 起);simple 模式忽略。 */\n requiredPosition: number\n /** 已使用诗句的规范化集合(去重依据)。 */\n usedNormalized: readonly string[]\n}\n\n/** 判定结果分类。 */\nexport type JudgeKind =\n | 'notPoem' // 规范化后不含令字 → 视为普通聊天,不判定\n | 'length' // 长度不符(4-48 字之外)\n | 'duplicate' // 与已使用诗句重复\n | 'badPosition' // 古法模式位置不符\n | 'valid'\n\n/** 古法位置错误的细分原因(badPosition 时有效)。 */\nexport type BadPositionReason = 'tooShort' | 'mismatch'\n\nexport interface JudgeResult {\n kind: JudgeKind\n /** 规范化文本(valid 时用于登记去重与位置推进)。 */\n normalized: string\n /** 展示用文本(压缩空白、截断)。 */\n display: string\n /** 规范化后长度。 */\n length: number\n /** badPosition 的细分原因;其余 kind 时为 undefined。 */\n badPosReason?: BadPositionReason\n /** badPosition 时本轮要求的位置;其余 kind 时为 undefined。 */\n requiredPosition?: number\n}\n\n/**\n * 诗句判定核心(纯函数,Host/Client 共用)。\n * 判定顺序与对话版完全一致:含令字门槛 → 长度 → 去重 → 古法位置。\n */\nexport function judgePoemAttempt(input: JudgeInput): JudgeResult {\n const normalized = normalizeLine(input.rawText)\n const display = trimDisplay(input.rawText)\n // 不含令字的消息不视为诗句尝试(普通聊天直接忽略)\n if (!normalized.includes(input.lingzi)) {\n return { kind: 'notPoem', normalized, display, length: normalized.length }\n }\n if (normalized.length < MIN_LINE_CHARS || normalized.length > MAX_LINE_CHARS) {\n return { kind: 'length', normalized, display, length: normalized.length }\n }\n if (input.usedNormalized.includes(normalized)) {\n return { kind: 'duplicate', normalized, display, length: normalized.length }\n }\n if (input.mode === 'ancient') {\n const pos = input.requiredPosition\n if (normalized.length < pos) {\n return { kind: 'badPosition', badPosReason: 'tooShort', requiredPosition: pos, normalized, display, length: normalized.length }\n }\n if (normalized[pos - 1] !== input.lingzi) {\n return { kind: 'badPosition', badPosReason: 'mismatch', requiredPosition: pos, normalized, display, length: normalized.length }\n }\n }\n return { kind: 'valid', normalized, display, length: normalized.length }\n}\n","/**\n * 共享诗句库(Host 与 Client 两半共同使用;纯数据,零平台依赖)。\n * 用途:Host —— 提示(hint)与令字抽取;Client —— 内置 AI 对手出句与提示。\n * 判定本身不依赖本库(只查“含令字 + 未重复 + 古法位置”),因此诗句库的\n * 覆盖面只影响“对手出句 / 提示”的花样,不影响规则正确性。\n * 全部为公共领域经典诗句(唐/宋/元/清为主,个别汉魏、民歌与古乐府)。\n * @module dsh-plugin-feihualing/shared/poems\n */\n\n/** 令字 → 含该令字的经典诗句(规范化前原文,展示与去重用;每句必含令字)。 */\nexport const POEM_BANK: Readonly<Record<string, readonly string[]>> = Object.freeze({\n 花: Object.freeze([\n '花间一壶酒',\n '感时花溅泪',\n '人面桃花相映红',\n '霜叶红于二月花',\n '花落知多少',\n '春城无处不飞花',\n '不是花中偏爱菊',\n '黄四娘家花满蹊',\n '竹外桃花三两枝',\n '映日荷花别样红',\n '花重锦官城',\n '乱花渐欲迷人眼',\n ]),\n 月: Object.freeze([\n '床前明月光',\n '举头望明月',\n '海上生明月',\n '月落乌啼霜满天',\n '月是故乡明',\n '明月几时有',\n '二十四桥明月夜',\n '月上柳梢头',\n '露似真珠月似弓',\n '月黑雁飞高',\n '深林人不知,明月来相照',\n '明月松间照',\n '江清月近人',\n '海上明月共潮生',\n '杨柳岸,晓风残月',\n '月有阴晴圆缺',\n ]),\n 风: Object.freeze([\n '夜来风雨声',\n '春风又绿江南岸',\n '风吹草低见牛羊',\n '二月春风似剪刀',\n '春风不度玉门关',\n '长风破浪会有时',\n '随风潜入夜',\n '野火烧不尽,春风吹又生',\n '大风起兮云飞扬',\n '风萧萧兮易水寒',\n '昨夜西风凋碧树',\n '吹面不寒杨柳风',\n '古道西风瘦马',\n '北风卷地白草折',\n ]),\n 雪: Object.freeze([\n '窗含西岭千秋雪',\n '独钓寒江雪',\n '柴门闻犬吠,风雪夜归人',\n '北风吹雁雪纷纷',\n '雪上空留马行处',\n '北风卷地白草折,胡天八月即飞雪',\n '遥知不是雪,为有暗香来',\n '欲渡黄河冰塞川,将登太行雪满山',\n '燕山雪花大如席',\n '大雪满弓刀',\n '青海长云暗雪山',\n ]),\n 山: Object.freeze([\n '白日依山尽',\n '山重水复疑无路',\n '轻舟已过万重山',\n '空山不见人',\n '千山鸟飞绝',\n '会当凌绝顶,一览众山小',\n '相看两不厌,只有敬亭山',\n '远上寒山石径斜',\n '两岸青山相对出',\n '国破山河在',\n '青山遮不住',\n '山外青山楼外楼',\n '只在此山中,云深不知处',\n ]),\n 水: Object.freeze([\n '抽刀断水水更流',\n '桃花潭水深千尺',\n '一江春水向东流',\n '行到水穷处',\n '春来江水绿如蓝',\n '白毛浮绿水',\n '水光潋滟晴方好',\n '竹外桃花三两枝,春江水暖鸭先知',\n '曾经沧海难为水',\n '山重水复疑无路,柳暗花明又一村',\n '问君能有几多愁,恰似一江春水向东流',\n '流水落花春去也',\n ]),\n 云: Object.freeze([\n '黄河远上白云间',\n '只在此山中,云深不知处',\n '白云生处有人家',\n '行到水穷处,坐看云起时',\n '众鸟高飞尽,孤云独去闲',\n '黑云压城城欲摧',\n '白云千载空悠悠',\n '朝辞白帝彩云间',\n '云想衣裳花想容',\n '荡胸生层云',\n '青海长云暗雪山',\n '浮云游子意',\n ]),\n 雨: Object.freeze([\n '清明时节雨纷纷',\n '好雨知时节',\n '天街小雨润如酥',\n '巴山夜雨涨秋池',\n '山色空蒙雨亦奇',\n '夜阑卧听风吹雨',\n '渭城朝雨浥轻尘',\n '寒雨连江夜入吴',\n '斜风细雨不须归',\n '沾衣欲湿杏花雨',\n '空山新雨后',\n ]),\n 春: Object.freeze([\n '春眠不觉晓',\n '春来江水绿如蓝',\n '春城无处不飞花',\n '春蚕到死丝方尽',\n '野火烧不尽,春风吹又生',\n '春色满园关不住',\n '等闲识得东风面,万紫千红总是春',\n '春潮带雨晚来急',\n '谁言寸草心,报得三春晖',\n '国破山河在,城春草木深',\n '春江潮水连海平',\n ]),\n 秋: Object.freeze([\n '空山新雨后,天气晚来秋',\n '湖光秋月两相和',\n '自古逢秋悲寂寥',\n '春种一粒粟,秋收万颗子',\n '银烛秋光冷画屏',\n '解落三秋叶,能开二月花',\n '万里悲秋常作客',\n '塞下秋来风景异',\n '洛阳城里见秋风',\n '秋风萧瑟天气凉',\n ]),\n 人: Object.freeze([\n '人生得意须尽欢',\n '人生自古谁无死',\n '人闲桂花落',\n '举杯邀明月,对影成三人',\n '路上行人欲断魂',\n '人面不知何处去',\n '但愿人长久',\n '前不见古人,后不见来者',\n '故人西辞黄鹤楼',\n '莫愁前路无知己,天下谁人不识君',\n '劝君更尽一杯酒,西出阳关无故人',\n '遥知兄弟登高处,遍插茱萸少一人',\n ]),\n 酒: Object.freeze([\n '花间一壶酒',\n '葡萄美酒夜光杯',\n '借问酒家何处有',\n '劝君更尽一杯酒',\n '对酒当歌',\n '金樽清酒斗十千',\n '酒入愁肠,化作相思泪',\n '今朝有酒今朝醉',\n '白日放歌须纵酒',\n '明月几时有,把酒问青天',\n ]),\n 夜: Object.freeze([\n '夜来风雨声',\n '忽如一夜春风来',\n '巴山夜雨涨秋池',\n '夜发清溪向三峡',\n '姑苏城外寒山寺,夜半钟声到客船',\n '东风夜放花千树',\n '夜阑卧听风吹雨',\n '可怜九月初三夜',\n '随风直到夜郎西',\n '烟笼寒水月笼沙,夜泊秦淮近酒家',\n '夜深千帐灯',\n '月黑雁飞高,单于夜遁逃',\n ]),\n 江: Object.freeze([\n '孤帆远影碧空尽,唯见长江天际流',\n '春来江水绿如蓝',\n '春风又绿江南岸',\n '一江春水向东流',\n '天门中断楚江开',\n '寒雨连江夜入吴',\n '竹外桃花三两枝,春江水暖鸭先知',\n '月落乌啼霜满天,江枫渔火对愁眠',\n '江畔何人初见月,江月何年初照人',\n '朝辞白帝彩云间,千里江陵一日还',\n '不尽长江滚滚流',\n '杨柳青青江水平',\n ]),\n 日: Object.freeze([\n '白日依山尽',\n '日照香炉生紫烟',\n '千里江陵一日还',\n '映日荷花别样红',\n '锄禾日当午',\n '日出江花红胜火',\n '海日生残夜',\n '大漠孤烟直,长河落日圆',\n '日暮乡关何处是',\n '浮云游子意,落日故人情',\n '白日放歌须纵酒',\n '闲云潭影日悠悠',\n ]),\n 星: Object.freeze([\n '星垂平野阔',\n '迢迢牵牛星',\n '危楼高百尺,手可摘星辰',\n '微微风簇浪,散作满河星',\n '七八个星天外',\n '天阶夜色凉如水,卧看牵牛织女星',\n '昨夜星辰昨夜风',\n '星汉灿烂,若出其里',\n ]),\n 天: Object.freeze([\n '天街小雨润如酥',\n '黄河之水天上来',\n '飞流直下三千尺,疑是银河落九天',\n '天生我材必有用',\n '天门中断楚江开',\n '江天一色无纤尘',\n '天阶夜色凉如水',\n '天苍苍,野茫茫',\n '接天莲叶无穷碧',\n '蜀道之难,难于上青天',\n '天若有情天亦老',\n ]),\n 柳: Object.freeze([\n '羌笛何须怨杨柳',\n '客舍青青柳色新',\n '两个黄鹂鸣翠柳',\n '柳暗花明又一村',\n '月上柳梢头',\n '春城无处不飞花,寒食东风御柳斜',\n '此夜曲中闻折柳',\n '杨柳岸,晓风残月',\n '枝上柳绵吹又少',\n '最是一年春好处,绝胜烟柳满皇都',\n '芙蓉如面柳如眉',\n '杨柳青青江水平',\n ]),\n 雁: Object.freeze([\n '雁字回时,月满西楼',\n '塞下秋来风景异,衡阳雁去无留意',\n '北风吹雁雪纷纷',\n '征蓬出汉塞,归雁入胡天',\n '月黑雁飞高',\n '雁过也,正伤心,却是旧时相识',\n '秋风起兮白云飞,草木黄落兮雁南归',\n '乡书何处达,归雁洛阳边',\n '何处秋风至,萧萧送雁群',\n ]),\n 烟: Object.freeze([\n '日照香炉生紫烟',\n '大漠孤烟直',\n '烟笼寒水月笼沙',\n '草长莺飞二月天,拂堤杨柳醉春烟',\n '一蓑烟雨任平生',\n '南朝四百八十寺,多少楼台烟雨中',\n '故人西辞黄鹤楼,烟花三月下扬州',\n '日暮乡关何处是,烟波江上使人愁',\n '绿杨烟外晓寒轻',\n '最是一年春好处,绝胜烟柳满皇都',\n ]),\n 楼: Object.freeze([\n '故人西辞黄鹤楼',\n '山外青山楼外楼',\n '小楼昨夜又东风',\n '危楼高百尺',\n '欲穷千里目,更上一层楼',\n '无言独上西楼',\n '近水楼台先得月',\n '昨夜西风凋碧树,独上高楼',\n '楼船夜雪瓜洲渡',\n '雁字回时,月满西楼',\n '多少楼台烟雨中',\n '明月楼高休独倚',\n ]),\n 梦: Object.freeze([\n '夜阑卧听风吹雨,铁马冰河入梦来',\n '梦里不知身是客',\n '人生如梦,一尊还酹江月',\n '庄生晓梦迷蝴蝶',\n '故人入我梦,明我长相忆',\n '夜来幽梦忽还乡',\n '梦回吹角连营',\n '因思杜陵梦,凫雁满回塘',\n ]),\n})\n\n/** 开局/换字时随机抽取的令字池(即诗句库的键)。 */\nexport const LINGZI_POOL: readonly string[] = Object.freeze(Object.keys(POEM_BANK))\n\n/** 常见(好接)令字:简易/中等难度可选池。 */\nconst COMMON_LINGZI: ReadonlySet<string> = new Set([\n '花', '月', '风', '雪', '山', '水', '云', '雨', '春', '秋', '人', '酒', '夜', '江', '日', '星', '天',\n])\n\n/** 简易(常见)令字池。 */\nexport const EASY_LINGZI: readonly string[] = Object.freeze(\n LINGZI_POOL.filter((z) => COMMON_LINGZI.has(z)),\n)\n\n/** 困难(偏门)令字池。 */\nexport const HARD_LINGZI: readonly string[] = Object.freeze(\n LINGZI_POOL.filter((z) => !COMMON_LINGZI.has(z)),\n)\n","/**\n * 内置 AI 对手 · 即时对战引擎(纯逻辑,零平台依赖、不依赖 React)。\n * 与 Host 共用 shared/rules.ts 的判定与 shared/poems.ts 的诗库:\n * - 判定:judgePoemAttempt —— 面板即时判定与对话判定规则完全一致;\n * - 双边去重:对手(bot)出句与玩家诗句都登记进 used 集合,杜绝重复;\n * - 对手出句:从诗库按难度取未使用句;库句不足即判玩家获胜。\n * @module dsh-plugin-feihualing/client/game/engine\n */\n\nimport { judgePoemAttempt, MAX_ANCIENT_POSITION, normalizeLine } from '../../shared/rules.ts'\nimport type { GameMode } from '../../shared/rules.ts'\nimport { EASY_LINGZI, HARD_LINGZI, LINGZI_POOL, POEM_BANK } from '../../shared/poems.ts'\n\n/** 对手难度。 */\nexport type Difficulty = 'easy' | 'medium' | 'hard'\n\n/** 对局阶段。 */\nexport type MatchPhase = 'playing' | 'won' | 'lost'\n\n/** 本局上限题数(每键诗库 ≥8 句,扣除双方占用后仍充足)。 */\nexport const MAX_ROUNDS = 7\n\n/** 失败(超时)上限,达到即判负。 */\nexport const FAIL_LIMIT = 3\n\n/** 每局提示次数。 */\nexport const HINTS_PER_MATCH = 3\n\n/** 每题限时(秒),按难度递减。 */\nexport const TIME_LIMIT: Readonly<Record<Difficulty, number>> = {\n easy: 30,\n medium: 20,\n hard: 15,\n}\n\n/** 基础得分:ancient 更严格故更高。 */\nconst SCORE_BASE: Readonly<Record<GameMode, number>> = { simple: 10, ancient: 15 }\n\n/** 连击加成(每段连续 +2)。 */\nconst COMBO_BONUS = 2\n\n/** 使用提示后本题通过的基础分。 */\nconst HINT_SCORE = 5\n\n/** 对局快照。 */\nexport interface MatchState {\n phase: MatchPhase\n mode: GameMode\n difficulty: Difficulty\n /** 当前令字。 */\n lingzi: string\n /** 当前题号(0 起;玩家每答对一题推进)。 */\n roundIndex: number\n /** 玩家已成功题数(roundIndex 与失败推进的解耦计数)。 */\n solved: number\n /** 失败次数(超时)。 */\n failures: number\n /** 累计得分。 */\n score: number\n /** 当前连击数(连续答对;0 表示断连)。 */\n combo: number\n /** 双方已用诗句(展示原文)。 */\n usedRaw: string[]\n /** 双方已用诗句(规范化,双边去重依据)。 */\n usedNormalized: string[]\n /** 对手当前出的题(玩家要接的句子)。 */\n challenge: string\n /** 本回合是否使用过提示(使用后本题通过只计 HINT_SCORE 且断连击)。 */\n hintUsedThisRound: boolean\n /** 剩余提示次数。 */\n hintsLeft: number\n /** 最近一次判定/事件文案(面板状态条展示)。 */\n lastVerdict: string\n /** 古法模式本轮要求位置(1 起);simple 恒 1。 */\n requiredPosition: number\n startedAt: number\n}\n\n/** 单局设置。 */\nexport interface MatchOptions {\n mode: GameMode\n difficulty: Difficulty\n}\n\n/** 玩家提交的裁决结果。 */\nexport interface SubmitOutcome {\n state: MatchState\n /** 是否本题通过(得分推进)。 */\n ok: boolean\n}\n\n// ---------- 内部小工具 ----------\n\nfunction pick<T>(list: readonly T[]): T {\n return list[Math.floor(Math.random() * list.length)]\n}\n\nfunction clone(state: MatchState): MatchState {\n return {\n ...state,\n usedRaw: [...state.usedRaw],\n usedNormalized: [...state.usedNormalized],\n }\n}\n\n/** 按难度抽令字:hard 用偏门字池,其余用常见字池。 */\nfunction pickLingzi(difficulty: Difficulty): string {\n const pool = difficulty === 'hard' ? HARD_LINGZI : EASY_LINGZI\n return pick(pool.length > 0 ? pool : LINGZI_POOL)\n}\n\n/** 诗库中该令字的全部句子。 */\nfunction bankOf(lingzi: string): readonly string[] {\n return POEM_BANK[lingzi] ?? []\n}\n\n/**\n * 从诗库挑一句未使用的句子(对手出句/提示候选)。\n * @returns 命中返回展示文本,库句被双方用完返回 undefined。\n */\nfunction pickUnused(state: MatchState): string | undefined {\n const candidates = bankOf(state.lingzi).filter(\n (line) => !state.usedNormalized.includes(normalizeLine(line)),\n )\n if (candidates.length === 0) return undefined\n return pick(candidates)\n}\n\n// ---------- 公开 API ----------\n\n/** 创建一局(抽令字 + 对手先手出题)。 */\nexport function createMatch(options: MatchOptions): MatchState {\n const lingzi = pickLingzi(options.difficulty)\n const base: MatchState = {\n phase: 'playing',\n mode: options.mode,\n difficulty: options.difficulty,\n lingzi,\n roundIndex: 0,\n solved: 0,\n failures: 0,\n score: 0,\n combo: 0,\n usedRaw: [],\n usedNormalized: [],\n challenge: '',\n hintUsedThisRound: false,\n hintsLeft: HINTS_PER_MATCH,\n lastVerdict: '',\n requiredPosition: 1,\n startedAt: Date.now(),\n }\n const opener = pickUnused(base)\n if (opener === undefined) {\n // 库为空(理论上不会发生):以空白开局仍可玩\n return { ...base, lastVerdict: `令字「${lingzi}」,请出句` }\n }\n return {\n ...base,\n challenge: opener,\n usedRaw: [opener],\n usedNormalized: [normalizeForUsed(opener)],\n lastVerdict: `令字「${lingzi}」· 对手出题,请对诗`,\n }\n}\n\n/** 仅规范化用于登记的辅助(与判定同源)。 */\nfunction normalizeForUsed(raw: string): string {\n return normalizeLine(raw)\n}\n\n/** 生成\"通过\"时的结算文案与计分(advance = 是否推进题号)。 */\nfunction applySolved(state: MatchState, display: string): MatchState {\n const comboBefore = state.combo\n const hintPenalty = state.hintUsedThisRound\n const gain = hintPenalty\n ? HINT_SCORE\n : SCORE_BASE[state.mode] + comboBefore * COMBO_BONUS\n const next = clone(state)\n next.solved += 1\n next.combo = hintPenalty ? 0 : comboBefore + 1\n next.score += gain\n next.usedRaw.push(display)\n next.usedNormalized.push(normalizeForUsed(display))\n if (next.mode === 'ancient') {\n next.requiredPosition = (next.requiredPosition % MAX_ANCIENT_POSITION) + 1\n }\n next.roundIndex += 1\n next.hintUsedThisRound = false\n const streak = next.combo\n next.lastVerdict = `答对!+${gain}${streak >= 2 ? `(连击 ×${streak})` : ''} · ${next.score} 分`\n return next\n}\n\n/** 推进到下一题(对手换句);无句可出或已到上限即玩家获胜。 */\nfunction advance(state: MatchState, why: string): MatchState {\n if (state.roundIndex >= MAX_ROUNDS) {\n return { ...clone(state), phase: 'won', lastVerdict: `通关!连对 ${state.solved} 题,共 ${state.score} 分(${why})` }\n }\n const nextLine = pickUnused(state)\n if (nextLine === undefined) {\n const won = clone(state)\n won.phase = 'won'\n won.lastVerdict = `对手诗穷,你赢了!共 ${state.score} 分(${why})`\n return won\n }\n const next = clone(state)\n next.challenge = nextLine\n next.usedRaw.push(nextLine)\n next.usedNormalized.push(normalizeForUsed(nextLine))\n next.lastVerdict = `${state.lastVerdict} —— 对手换题`\n return next\n}\n\n/** 玩家提交诗句。 */\nexport function submitPlayerLine(state: MatchState, rawText: string): SubmitOutcome {\n if (state.phase !== 'playing') return { state, ok: false }\n const result = judgePoemAttempt({\n rawText,\n lingzi: state.lingzi,\n mode: state.mode,\n requiredPosition: state.requiredPosition,\n usedNormalized: state.usedNormalized,\n })\n if (result.kind === 'notPoem') {\n return { state: { ...state, lastVerdict: `这句没有令字「${state.lingzi}」,请重新对诗` }, ok: false }\n }\n if (result.kind === 'length') {\n return { state: { ...state, lastVerdict: '诗句长度需在 4-48 字之间' }, ok: false }\n }\n if (result.kind === 'duplicate') {\n return { state: { ...state, lastVerdict: '这句用过了(双方都不能重复),换一句吧' }, ok: false }\n }\n if (result.kind === 'badPosition') {\n const pos = result.requiredPosition ?? state.requiredPosition\n const why = result.badPosReason === 'tooShort'\n ? `句子太短:古法要求令字位于第 ${pos} 字`\n : `位置不对:古法要求第 ${pos} 字是令字「${state.lingzi}」`\n return { state: { ...state, lastVerdict: why }, ok: false }\n }\n // 通过\n const solved = applySolved(state, result.display)\n const advanced = advance(solved, '答对推进')\n // 若本轮因对答通关/对手诗穷而 won,保留\n return { state: advanced, ok: true }\n}\n\n/** 超时(由面板倒计时触发):本题作废,换题;连续失败达上限判负。 */\nexport function timeoutRound(state: MatchState): MatchState {\n if (state.phase !== 'playing') return state\n const next = clone(state)\n next.failures += 1\n next.combo = 0\n next.hintUsedThisRound = false\n if (next.failures >= FAIL_LIMIT) {\n next.phase = 'lost'\n next.lastVerdict = `超时 ${FAIL_LIMIT} 次,本局惜败 · 共答对 ${next.solved} 题 / ${next.score} 分`\n return next\n }\n next.lastVerdict = `超时(第 ${next.failures}/${FAIL_LIMIT} 次)—— 换下一题,加油`\n return advance(next, '超时换题')\n}\n\n/** 认输/放弃。 */\nexport function concedeMatch(state: MatchState): MatchState {\n if (state.phase !== 'playing') return state\n const next = clone(state)\n next.phase = 'lost'\n next.lastVerdict = `你认输了 · 共答对 ${next.solved} 题 / ${next.score} 分`\n return next\n}\n\n/** 提示:消耗 1 次,给出库中一句未用参考诗(可直接跟对,但只计 HINT_SCORE 且断连击)。 */\nexport function useHint(state: MatchState): { state: MatchState; line?: string } {\n if (state.phase !== 'playing' || state.hintsLeft <= 0) return { state }\n const next = clone(state)\n next.hintsLeft -= 1\n next.hintUsedThisRound = true\n const line = pickUnused(next)\n next.lastVerdict = line\n ? `提示(剩 ${next.hintsLeft} 次):参考「${line}」—— 参考句直接对出只 +${HINT_SCORE} 分`\n : `提示(剩 ${next.hintsLeft} 次):想一句含「${state.lingzi}」的诗句吧`\n return { state: next, line }\n}\n\n/** 换令字重开本局(保留设置)。 */\nexport function restartMatch(options: MatchOptions): MatchState {\n return createMatch(options)\n}\n","/**\n * @license React\n * react-jsx-runtime.production.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\nvar REACT_ELEMENT_TYPE = Symbol.for(\"react.transitional.element\"),\n REACT_FRAGMENT_TYPE = Symbol.for(\"react.fragment\");\nfunction jsxProd(type, config, maybeKey) {\n var key = null;\n void 0 !== maybeKey && (key = \"\" + maybeKey);\n void 0 !== config.key && (key = \"\" + config.key);\n if (\"key\" in config) {\n maybeKey = {};\n for (var propName in config)\n \"key\" !== propName && (maybeKey[propName] = config[propName]);\n } else maybeKey = config;\n config = maybeKey.ref;\n return {\n $$typeof: REACT_ELEMENT_TYPE,\n type: type,\n key: key,\n ref: void 0 !== config ? config : null,\n props: maybeKey\n };\n}\nexports.Fragment = REACT_FRAGMENT_TYPE;\nexports.jsx = jsxProd;\nexports.jsxs = jsxProd;\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-jsx-runtime.production.js');\n} else {\n module.exports = require('./cjs/react-jsx-runtime.development.js');\n}\n","/**\n * 浮层游戏面板(完整对战 UI)。\n * 对局状态由 HeaderButton 层持有(useGamePanel),本组件只做展示与事件转发:\n * 关闭面板只隐藏,不丢对局;隐藏期间倒计时暂停(视同暂停)。\n * @module dsh-plugin-feihualing/client/panel\n */\n\nimport React from 'react'\nimport * as engine from './game/engine.ts'\nimport type { GamePanelApi } from './game/useGame.ts'\n\nexport interface GamePanelProps {\n /** 所属会话 id(session 级 header 按钮注入)。 */\n sessionId: string\n /** 关闭面板(仅隐藏,对局保留)。 */\n onClose: () => void\n /** 对局控制面(由父层 useGamePanel 提供)。 */\n api: GamePanelApi\n}\n\n/** 会话短标识(展示用)。 */\nfunction shortSession(sessionId: string): string {\n return sessionId.length > 12 ? `${sessionId.slice(0, 10)}…` : sessionId\n}\n\nconst MODE_LABEL: Record<string, string> = { simple: '简易', ancient: '古法' }\nconst DIFF_LABEL: Record<string, string> = { easy: '轻松', medium: '标准', hard: '困难' }\nconst DIFF_HINT: Record<string, string> = { easy: '常见令字 · 30s', medium: '常见令字 · 20s', hard: '冷门令字 · 15s' }\n\n/** 开局设置行(未开局或对局结束后显示)。 */\nfunction SetupView(props: { api: GamePanelApi }): React.ReactElement {\n const { api } = props\n return (\n <React.Fragment>\n <div className=\"fhl-row\">\n <span className=\"fhl-seg-label\">模式</span>\n <div className=\"fhl-seg\">\n {(['simple', 'ancient'] as const).map((m) => (\n <button\n key={m}\n type=\"button\"\n className={`fhl-seg-item${api.mode === m ? ' is-active' : ''}`}\n onClick={() => api.setMode(m)}\n >\n {MODE_LABEL[m]}\n </button>\n ))}\n </div>\n </div>\n <div className=\"fhl-row\">\n <span className=\"fhl-seg-label\">难度</span>\n <div className=\"fhl-seg\">\n {(['easy', 'medium', 'hard'] as const).map((d) => (\n <button\n key={d}\n type=\"button\"\n className={`fhl-seg-item${api.difficulty === d ? ' is-active' : ''}`}\n onClick={() => api.setDifficulty(d)}\n title={DIFF_HINT[d]}\n >\n {DIFF_LABEL[d]}\n </button>\n ))}\n </div>\n </div>\n <div className=\"fhl-muted\" style={{ fontSize: 12, textAlign: 'center', paddingBottom: 4 }}>\n {api.mode === 'ancient'\n ? '古法:令字须按位置循环(第 1 字 → 第 7 字)'\n : '简易:诗句任意位置含令字即可'}\n {' · '}\n {DIFF_HINT[api.difficulty]}\n </div>\n <button type=\"button\" className=\"fhl-btn fhl-btn-primary\" onClick={api.start}>\n 🎴 开始对局\n </button>\n <div className=\"fhl-muted\" style={{ fontSize: 11, textAlign: 'center' }}>\n 对手出题 · 你限时对诗 · 连对 {engine.MAX_ROUNDS} 题通关 · 超时 {engine.FAIL_LIMIT} 次惜败\n </div>\n </React.Fragment>\n )\n}\n\n/** 进行中对局视图。 */\nfunction PlayView(props: { api: GamePanelApi }): React.ReactElement {\n const { api } = props\n const match = api.match\n if (match === null) return <React.Fragment />\n const timeLow = api.secondsLeft <= 5\n const canAct = match.phase === 'playing'\n return (\n <React.Fragment>\n <div className=\"fhl-stage\">\n <span className=\"fhl-stage-round\">第 {match.roundIndex + 1}/{engine.MAX_ROUNDS} 题</span>\n <span className={`fhl-timer${timeLow && canAct ? ' is-low' : ''}`}>{api.secondsLeft}s</span>\n <span className=\"fhl-stage-tag\">{MODE_LABEL[match.mode]} · {DIFF_LABEL[match.difficulty]}</span>\n </div>\n <div className=\"fhl-lingzi\">「{match.lingzi}」</div>\n {match.mode === 'ancient' && (\n <div className=\"fhl-muted\" style={{ fontSize: 11, textAlign: 'center', marginTop: -4 }}>\n 古法:本轮令字须位于第 <b style={{ color: 'var(--fhl-accent)' }}>{match.requiredPosition}</b> 字\n </div>\n )}\n <div className=\"fhl-card fhl-card-bot\">\n <span className=\"fhl-card-role\">🤖 对手</span>\n <span className=\"fhl-card-line\">{match.challenge}</span>\n </div>\n {match.lastVerdict !== '' && (\n <div className={`fhl-verdict${api.secondsLeft === 0 || !canAct ? '' : ''}`}>{match.lastVerdict}</div>\n )}\n <form\n className=\"fhl-input-row\"\n onSubmit={(ev) => {\n ev.preventDefault()\n api.submit()\n }}\n >\n <input\n className=\"fhl-input\"\n value={api.draft}\n onChange={(ev) => api.setDraft(ev.target.value)}\n placeholder={`说一句含「${match.lingzi}」的诗句…`}\n disabled={!canAct}\n autoComplete=\"off\"\n />\n <button type=\"submit\" className=\"fhl-btn fhl-btn-primary\" disabled={!canAct}>对诗</button>\n </form>\n <div className=\"fhl-actions\">\n <button\n type=\"button\"\n className=\"fhl-btn\"\n onClick={api.requestHint}\n disabled={!canAct || match.hintsLeft <= 0}\n title=\"消耗提示次数,给出参考诗句(参考句直接跟对只得 5 分)\"\n >\n 💡 提示({match.hintsLeft})\n </button>\n <button type=\"button\" className=\"fhl-btn fhl-btn-danger\" onClick={api.concede} disabled={!canAct}>\n 认输\n </button>\n </div>\n <div className=\"fhl-stats\">\n <span className=\"fhl-stat\"><b>{match.score}</b> 得分</span>\n <span className=\"fhl-stat\"><b>{match.combo}</b> 连击</span>\n <span className=\"fhl-stat\"><b>{match.solved}/{engine.MAX_ROUNDS}</b> 答对</span>\n <span className={`fhl-stat${match.failures >= engine.FAIL_LIMIT - 1 ? ' is-warn' : ''}`}>\n <b>{match.failures}/{engine.FAIL_LIMIT}</b> 超时\n </span>\n </div>\n </React.Fragment>\n )\n}\n\n/** 结算视图。 */\nfunction EndView(props: { api: GamePanelApi }): React.ReactElement {\n const { api } = props\n const match = api.match\n if (match === null) return <React.Fragment />\n const won = match.phase === 'won'\n return (\n <React.Fragment>\n <div className={`fhl-result${won ? ' is-won' : ' is-lost'}`}>\n <div className=\"fhl-result-icon\">{won ? '🏆' : '😢'}</div>\n <div className=\"fhl-result-title\">{won ? '通关!' : '本局惜败'}</div>\n <div className=\"fhl-muted\" style={{ fontSize: 12, textAlign: 'center' }}>{match.lastVerdict}</div>\n </div>\n <div className=\"fhl-stats\">\n <span className=\"fhl-stat\"><b>{match.score}</b> 得分</span>\n <span className=\"fhl-stat\"><b>{match.solved}</b> 答对</span>\n <span className=\"fhl-stat\"><b>{match.failures}</b> 超时</span>\n </div>\n <div className=\"fhl-record\">\n <span>🏅 {api.stats.wins} 胜 / {api.stats.losses} 负</span>\n <span>🔥 连胜 {api.stats.streak}</span>\n <span>✨ 最佳 {api.stats.bestScore}</span>\n </div>\n <button type=\"button\" className=\"fhl-btn fhl-btn-primary\" onClick={api.start}>\n 🔄 再来一局\n </button>\n <div className=\"fhl-muted\" style={{ fontSize: 11, textAlign: 'center' }}>\n 令字「{match.lingzi}」· {MODE_LABEL[match.mode]} · {DIFF_LABEL[match.difficulty]}\n </div>\n </React.Fragment>\n )\n}\n\n/** 面板主体:按对局阶段切换视图。 */\nexport function GamePanel(props: GamePanelProps): React.ReactElement {\n const { sessionId, onClose, api } = props\n const phase = api.match === null ? 'setup' : api.match.phase\n return (\n <div className=\"fhl-root fhl-panel\" data-plugin=\"dsh-plugin-feihualing\">\n <div className=\"fhl-panel-header\">\n <span className=\"fhl-panel-title\">🎴 飞花令 · 即时对战</span>\n <span className=\"fhl-tag\" title={sessionId}>会话 {shortSession(sessionId)}</span>\n <button type=\"button\" className=\"fhl-panel-close\" onClick={onClose} title=\"收起(对局保留)\">✕</button>\n </div>\n {phase === 'setup' && <SetupView api={api} />}\n {phase === 'playing' && <PlayView api={api} />}\n {(phase === 'won' || phase === 'lost') && <EndView api={api} />}\n </div>\n )\n}\n","/**\n * 面板对战战绩(localStorage 持久化,本机会话无关、跨刷新保留)。\n * 纯浏览器模块;与 Host 对话模式状态互不干扰(面板是独立即时战)。\n * @module dsh-plugin-feihualing/client/game/stats\n */\n\nimport type { MatchState } from './engine.ts'\n\nexport interface GameStats {\n /** 累计胜局。 */\n wins: number\n /** 累计败局。 */\n losses: number\n /** 历史最佳得分。 */\n bestScore: number\n /** 当前连胜。 */\n streak: number\n /** 历史最长连胜。 */\n bestStreak: number\n /** 累计答对题数。 */\n solvedTotal: number\n}\n\nconst KEY = 'dsh-plugin-feihualing:stats'\n\nfunction empty(): GameStats {\n return { wins: 0, losses: 0, bestScore: 0, streak: 0, bestStreak: 0, solvedTotal: 0 }\n}\n\n/** 读取战绩(损坏/缺失返回空战绩)。 */\nexport function loadStats(): GameStats {\n try {\n const raw = localStorage.getItem(KEY)\n if (raw === null) return empty()\n const parsed = JSON.parse(raw) as Partial<GameStats>\n return {\n wins: typeof parsed.wins === 'number' ? parsed.wins : 0,\n losses: typeof parsed.losses === 'number' ? parsed.losses : 0,\n bestScore: typeof parsed.bestScore === 'number' ? parsed.bestScore : 0,\n streak: typeof parsed.streak === 'number' ? parsed.streak : 0,\n bestStreak: typeof parsed.bestStreak === 'number' ? parsed.bestStreak : 0,\n solvedTotal: typeof parsed.solvedTotal === 'number' ? parsed.solvedTotal : 0,\n }\n } catch {\n return empty()\n }\n}\n\n/** 结算一局并入账(返回并持久化新战绩)。 */\nexport function recordResult(stats: GameStats, match: MatchState): GameStats {\n const won = match.phase === 'won'\n const next: GameStats = {\n ...stats,\n wins: stats.wins + (won ? 1 : 0),\n losses: stats.losses + (won ? 0 : 1),\n bestScore: Math.max(stats.bestScore, match.score),\n streak: won ? stats.streak + 1 : 0,\n bestStreak: Math.max(stats.bestStreak, won ? stats.streak + 1 : stats.bestStreak),\n solvedTotal: stats.solvedTotal + match.solved,\n }\n try {\n localStorage.setItem(KEY, JSON.stringify(next))\n } catch {\n /* 存储失败(隐私模式等)不影响对局 */\n }\n return next\n}\n","/**\n * React 对局 hook:包装 engine 的纯逻辑,管理倒计时与面板动作。\n * 倒计时由 useGamePanel 内部控制(setInterval,无第三方库)。\n * @module dsh-plugin-feihualing/client/game/useGame\n */\n\nimport React from 'react'\nimport * as engine from './engine.ts'\nimport type { GameStats } from './stats.ts'\nimport { loadStats, recordResult } from './stats.ts'\nimport type { GameMode } from '../../shared/rules.ts'\n\n/** 对局控制面(供面板组件使用)。 */\nexport interface GamePanelApi {\n match: engine.MatchState | null\n secondsLeft: number\n mode: GameMode\n difficulty: engine.Difficulty\n draft: string\n stats: GameStats\n setMode(mode: GameMode): void\n setDifficulty(difficulty: engine.Difficulty): void\n setDraft(text: string): void\n start(): void\n submit(): void\n requestHint(): void\n concede(): void\n}\n\n/**\n * 一局对战面板状态。倒计时只在对局进行中且面板激活时跑;\n * 面板隐藏(active=false)视同暂停:计时停住、不超时,重开面板继续。\n * 答题推进/超时换题时按 (phase, roundIndex, challenge) 变化重置计时。\n * @param active 面板是否可见(决定倒计时是否运行)。\n */\nexport function useGamePanel(active: boolean): GamePanelApi {\n const [match, setMatch] = React.useState<engine.MatchState | null>(null)\n const [secondsLeft, setSecondsLeft] = React.useState(0)\n const [mode, setMode] = React.useState<GameMode>('simple')\n const [difficulty, setDifficulty] = React.useState<engine.Difficulty>('medium')\n const [draft, setDraft] = React.useState('')\n const [stats, setStats] = React.useState<GameStats>(() => loadStats())\n // 记录已入账的局(按 startedAt),防止同一局重复累计战绩\n const recordedStartedAt = React.useRef<number | null>(null)\n\n // 结算(won/lost)时入账一次战绩\n React.useEffect(() => {\n if (match === null || (match.phase !== 'won' && match.phase !== 'lost')) return\n if (recordedStartedAt.current === match.startedAt) return\n recordedStartedAt.current = match.startedAt\n setStats((cur) => recordResult(cur, match))\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [match])\n\n const roundKey = match === null\n ? ''\n : `${match.phase}:${match.roundIndex}:${match.challenge}`\n\n // 每一“题”(phase/题号/对手句变化)重置倒计时为满额限时\n React.useEffect(() => {\n if (match === null || match.phase !== 'playing') return\n setSecondsLeft(engine.TIME_LIMIT[match.difficulty])\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [roundKey])\n\n // 秒针:仅面板激活且对局进行中时递减;隐藏(active=false)即暂停并保留剩余时间\n React.useEffect(() => {\n if (!active || match === null || match.phase !== 'playing') return\n const timer = setInterval(() => {\n setSecondsLeft((s) => Math.max(0, s - 1))\n }, 1000)\n return () => clearInterval(timer)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [active, roundKey])\n\n // 倒计时归零:本题超时\n React.useEffect(() => {\n if (!active || match === null || match.phase !== 'playing') return\n if (secondsLeft > 0) return\n setMatch((cur) => (cur !== null && cur.phase === 'playing' ? engine.timeoutRound(cur) : cur))\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [secondsLeft, active])\n\n const start = React.useCallback(() => {\n setMatch(engine.createMatch({ mode, difficulty }))\n setDraft('')\n }, [mode, difficulty])\n\n const submit = React.useCallback(() => {\n const text = draft.trim()\n if (text.length === 0) return\n setMatch((cur) => {\n if (cur === null || cur.phase !== 'playing') return cur\n const outcome = engine.submitPlayerLine(cur, text)\n return outcome.state\n })\n setDraft('')\n }, [draft])\n\n const requestHint = React.useCallback(() => {\n setMatch((cur) => {\n if (cur === null || cur.phase !== 'playing') return cur\n return engine.useHint(cur).state\n })\n }, [])\n\n const concede = React.useCallback(() => {\n setMatch((cur) => (cur === null ? cur : engine.concedeMatch(cur)))\n }, [])\n\n return {\n match,\n secondsLeft,\n mode,\n difficulty,\n draft,\n stats,\n setMode,\n setDifficulty,\n setDraft,\n start,\n submit,\n requestHint,\n concede,\n }\n}\n","/**\n * 会话标题游戏开关按钮(conversation.session.header.actions 插槽,session 级 list)。\n * 点击后直接渲染浮层游戏面板(fixed 定位,无需 react-dom portal——\n * client 半边仅依赖 DSH 模块表确认提供的 react)。\n * 对局状态常驻本组件:关闭面板只隐藏,不丢局;隐藏期间倒计时暂停(见 useGame)。\n * @module dsh-plugin-feihualing/client/header-button\n */\n\nimport React from 'react'\nimport { HEADER_ORDER, NAMESPACE } from './constants.ts'\nimport type { LocalContext } from './types.ts'\nimport { GamePanel } from './panel.tsx'\nimport { useGamePanel } from './game/useGame.ts'\n\n/** 在会话头操作行注册游戏开关按钮。 */\nexport function registerHeaderButton(ctx: LocalContext): void {\n ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register(\n {\n name: 'conversation.session.header.actions',\n id: NAMESPACE,\n order: HEADER_ORDER,\n inject: (sessionId) => ({ sessionId }),\n },\n HeaderButton,\n ))\n}\n\n/** 会话头游戏开关按钮 + 浮层面板(open 时渲染,fixed 定位于视口右上)。 */\nfunction HeaderButton(props: { sessionId?: string }): React.ReactElement {\n const [open, setOpen] = React.useState(false)\n // 对局状态提升到按钮层:面板开合不影响对局,隐藏 = 暂停计时\n const api = useGamePanel(open)\n const sessionId = props.sessionId ?? ''\n const toggle = React.useCallback(() => setOpen((prev) => !prev), [])\n\n return (\n <React.Fragment>\n <button\n type=\"button\"\n className=\"fhl-toggle\"\n data-active={open ? 'true' : 'false'}\n onClick={toggle}\n title={open ? '收起飞花令(对局保留)' : '飞花令小游戏'}\n aria-label=\"飞花令小游戏\"\n >\n 🎴\n </button>\n {open && <GamePanel sessionId={sessionId} onClose={() => setOpen(false)} api={api} />}\n </React.Fragment>\n )\n}\n","/**\n * 客户端半边入口(dsh-plugin-feihualing/client)。\n * 唯一职责:注入样式并组装各 UI 面注册(当前:会话头游戏开关按钮 + 浮层面板)。\n * 只做纯插槽注册,不受 harness 的 WEB_SETTINGS_NAMESPACES 白名单影响,装完即用。\n * @module dsh-plugin-feihualing/client\n */\n\nimport type { LocalContext } from './types.ts'\nimport { injectStyles } from './styles.ts'\nimport { registerHeaderButton } from './header-button.tsx'\n\n/** 依赖的服务:slots 就绪后本插件才会加载。 */\nexport const inject = ['slots']\n\n/** 客户端插件主体。 */\nexport function apply(ctx: LocalContext): void {\n // 样式表随插件生命周期注入/移除(ctx.effect 由 cordis 客户端运行时提供)\n const effect = (ctx as { effect?: (execute: () => (() => void) | void, label?: string) => void }).effect\n const disposeStyles = injectStyles()\n if (effect) {\n effect(() => disposeStyles, 'dsh-plugin-feihualing: client styles')\n }\n registerHeaderButton(ctx)\n}\n"],"x_google_ignoreList":[5,6],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAOA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiNZ,SAAgB,eAA2B;GACzC,MAAM,QAAQ,SAAS,cAAc,OAAO;GAC5C,MAAM,aAAa,eAAe,uBAAuB;GACzD,MAAM,cAAc;GACpB,SAAS,KAAK,YAAY,KAAK;GAC/B,aAAa;IACX,IAAI,MAAM,YAAY,MAAM,WAAW,YAAY,KAAK;GAC1D;EACF;;;;;;;;;ECzNA,MAAa,YAAY;;;;;ECczB,SAAgB,cAAc,KAAqB;GACjD,OAAO,IAAI,QAAQ,iDAAiD,EAAE;EACxE;;EAGA,SAAgB,YAAY,KAAqB;GAC/C,OAAO,IAAI,KAAK,CAAC,CAAC,QAAQ,SAAS,GAAG,CAAC,CAAC,MAAM,GAAA,EAAiB;EACjE;;;;;EA0CA,SAAgB,iBAAiB,OAAgC;GAC/D,MAAM,aAAa,cAAc,MAAM,OAAO;GAC9C,MAAM,UAAU,YAAY,MAAM,OAAO;GAEzC,IAAI,CAAC,WAAW,SAAS,MAAM,MAAM,GACnC,OAAO;IAAE,MAAM;IAAW;IAAY;IAAS,QAAQ,WAAW;GAAO;GAE3E,IAAI,WAAW,SAAA,KAA2B,WAAW,SAAA,IACnD,OAAO;IAAE,MAAM;IAAU;IAAY;IAAS,QAAQ,WAAW;GAAO;GAE1E,IAAI,MAAM,eAAe,SAAS,UAAU,GAC1C,OAAO;IAAE,MAAM;IAAa;IAAY;IAAS,QAAQ,WAAW;GAAO;GAE7E,IAAI,MAAM,SAAS,WAAW;IAC5B,MAAM,MAAM,MAAM;IAClB,IAAI,WAAW,SAAS,KACtB,OAAO;KAAE,MAAM;KAAe,cAAc;KAAY,kBAAkB;KAAK;KAAY;KAAS,QAAQ,WAAW;IAAO;IAEhI,IAAI,WAAW,MAAM,OAAO,MAAM,QAChC,OAAO;KAAE,MAAM;KAAe,cAAc;KAAY,kBAAkB;KAAK;KAAY;KAAS,QAAQ,WAAW;IAAO;GAElI;GACA,OAAO;IAAE,MAAM;IAAS;IAAY;IAAS,QAAQ,WAAW;GAAO;EACzE;;;;;;;;;;;;ECnFA,MAAa,YAAyD,OAAO,OAAO;GAClF,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,GAAG,OAAO,OAAO;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;EACH,CAAC;;EAGD,MAAa,cAAiC,OAAO,OAAO,OAAO,KAAK,SAAS,CAAC;;EAGlF,MAAM,gCAAqC,IAAI,IAAI;GACjD;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;GAAK;EAClF,CAAC;;EAGD,MAAa,cAAiC,OAAO,OACnD,YAAY,QAAQ,MAAM,cAAc,IAAI,CAAC,CAAC,CAChD;;EAGA,MAAa,cAAiC,OAAO,OACnD,YAAY,QAAQ,MAAM,CAAC,cAAc,IAAI,CAAC,CAAC,CACjD;;ECtSA,MAAa,aAAmD;GAC9D,MAAM;GACN,QAAQ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"}
package/lib/index.js CHANGED
@@ -224,7 +224,6 @@ const POEM_BANK = Object.freeze({
224
224
  "人闲桂花落",
225
225
  "举杯邀明月,对影成三人",
226
226
  "路上行人欲断魂",
227
- "独在异乡为异客,每逢佳节倍思亲",
228
227
  "人面不知何处去",
229
228
  "但愿人长久",
230
229
  "前不见古人,后不见来者",
@@ -243,7 +242,6 @@ const POEM_BANK = Object.freeze({
243
242
  "酒入愁肠,化作相思泪",
244
243
  "今朝有酒今朝醉",
245
244
  "白日放歌须纵酒",
246
- "人生得意须尽欢,莫使金樽空对月",
247
245
  "明月几时有,把酒问青天"
248
246
  ]),
249
247
  夜: Object.freeze([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-feihualing",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "DeepSeek Harness 飞花令游戏插件:简易/古法严格双模式,按会话独立维护令字、得分、已用诗句与剩余提示次数;提供 feihualing_start / feihualing_status / feihualing_stop 三个工具与关键词指令,检测到 shell/文件写入类工具调用时自动暂停并保存现场。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",