dsh-plugin-feihualing 1.1.2 → 1.1.3

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.2
5
+ version: 1.1.3
6
6
  description: >-
7
7
  飞花令游戏插件(简易 / 古法严格双模式)。
8
8
  支持关键词指令(开始飞花令、结束飞花令、换字、提示、认输、暂停、继续飞花令)、
package/lib/client.js CHANGED
@@ -11,7 +11,6 @@ window.__ModuleLoader__.load({
11
11
  var __getOwnPropNames = Object.getOwnPropertyNames;
12
12
  var __getProtoOf = Object.getPrototypeOf;
13
13
  var __hasOwnProp = Object.prototype.hasOwnProperty;
14
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
15
14
  var __copyProps = (to, from, except, desc) => {
16
15
  if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
17
16
  key = keys[i];
@@ -29,6 +28,7 @@ window.__ModuleLoader__.load({
29
28
  //#endregion
30
29
  let react = require("react");
31
30
  react = __toESM(react, 1);
31
+ let react_jsx_runtime = require("react/jsx-runtime");
32
32
  //#region src/client/styles.ts
33
33
  /**
34
34
  * 客户端半边样式:一次性注入 <style>,全部 fhl-* 类以 data-plugin 根类名限定作用域。
@@ -865,39 +865,6 @@ body[data-ds-dark-theme] .fhl-root {
865
865
  };
866
866
  }
867
867
  //#endregion
868
- //#region node_modules/.pnpm/react@19.2.8/node_modules/react/cjs/react-jsx-runtime.production.js
869
- /**
870
- * @license React
871
- * react-jsx-runtime.production.js
872
- *
873
- * Copyright (c) Meta Platforms, Inc. and affiliates.
874
- *
875
- * This source code is licensed under the MIT license found in the
876
- * LICENSE file in the root directory of this source tree.
877
- */
878
- var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((exports) => {
879
- var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element");
880
- function jsxProd(type, config, maybeKey) {
881
- var key = null;
882
- void 0 !== maybeKey && (key = "" + maybeKey);
883
- void 0 !== config.key && (key = "" + config.key);
884
- if ("key" in config) {
885
- maybeKey = {};
886
- for (var propName in config) "key" !== propName && (maybeKey[propName] = config[propName]);
887
- } else maybeKey = config;
888
- config = maybeKey.ref;
889
- return {
890
- $$typeof: REACT_ELEMENT_TYPE,
891
- type,
892
- key,
893
- ref: void 0 !== config ? config : null,
894
- props: maybeKey
895
- };
896
- }
897
- exports.jsx = jsxProd;
898
- exports.jsxs = jsxProd;
899
- }));
900
- //#endregion
901
868
  //#region src/client/panel.tsx
902
869
  /**
903
870
  * 浮层游戏面板(完整对战 UI)。
@@ -905,9 +872,6 @@ body[data-ds-dark-theme] .fhl-root {
905
872
  * 关闭面板只隐藏,不丢对局;隐藏期间倒计时暂停(视同暂停)。
906
873
  * @module dsh-plugin-feihualing/client/panel
907
874
  */
908
- var import_jsx_runtime = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
909
- module.exports = require_react_jsx_runtime_production();
910
- })))();
911
875
  /** 会话短标识(展示用)。 */
912
876
  function shortSession(sessionId) {
913
877
  return sessionId.length > 12 ? `${sessionId.slice(0, 10)}…` : sessionId;
@@ -929,15 +893,15 @@ body[data-ds-dark-theme] .fhl-root {
929
893
  /** 开局设置行(未开局或对局结束后显示)。 */
930
894
  function SetupView(props) {
931
895
  const { api } = props;
932
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(react.default.Fragment, { children: [
933
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
896
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.default.Fragment, { children: [
897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
934
898
  className: "fhl-row",
935
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
899
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
936
900
  className: "fhl-seg-label",
937
901
  children: "模式"
938
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
902
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
939
903
  className: "fhl-seg",
940
- children: ["simple", "ancient"].map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
904
+ children: ["simple", "ancient"].map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
941
905
  type: "button",
942
906
  className: `fhl-seg-item${api.mode === m ? " is-active" : ""}`,
943
907
  onClick: () => api.setMode(m),
@@ -945,18 +909,18 @@ body[data-ds-dark-theme] .fhl-root {
945
909
  }, m))
946
910
  })]
947
911
  }),
948
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
912
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
949
913
  className: "fhl-row",
950
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
914
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
951
915
  className: "fhl-seg-label",
952
916
  children: "难度"
953
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
917
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
954
918
  className: "fhl-seg",
955
919
  children: [
956
920
  "easy",
957
921
  "medium",
958
922
  "hard"
959
- ].map((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
923
+ ].map((d) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
960
924
  type: "button",
961
925
  className: `fhl-seg-item${api.difficulty === d ? " is-active" : ""}`,
962
926
  onClick: () => api.setDifficulty(d),
@@ -965,7 +929,7 @@ body[data-ds-dark-theme] .fhl-root {
965
929
  }, d))
966
930
  })]
967
931
  }),
968
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
932
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
969
933
  className: "fhl-muted",
970
934
  style: {
971
935
  fontSize: 12,
@@ -978,13 +942,13 @@ body[data-ds-dark-theme] .fhl-root {
978
942
  DIFF_HINT[api.difficulty]
979
943
  ]
980
944
  }),
981
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
945
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
982
946
  type: "button",
983
947
  className: "fhl-btn fhl-btn-primary",
984
948
  onClick: api.start,
985
949
  children: "🎴 开始对局"
986
950
  }),
987
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
951
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
988
952
  className: "fhl-muted",
989
953
  style: {
990
954
  fontSize: 11,
@@ -1004,14 +968,14 @@ body[data-ds-dark-theme] .fhl-root {
1004
968
  function PlayView(props) {
1005
969
  const { api } = props;
1006
970
  const match = api.match;
1007
- if (match === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(react.default.Fragment, {});
971
+ if (match === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, {});
1008
972
  const timeLow = api.secondsLeft <= 5;
1009
973
  const canAct = match.phase === "playing";
1010
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(react.default.Fragment, { children: [
1011
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
974
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.default.Fragment, { children: [
975
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1012
976
  className: "fhl-stage",
1013
977
  children: [
1014
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
978
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1015
979
  className: "fhl-stage-round",
1016
980
  children: [
1017
981
  "第 ",
@@ -1021,11 +985,11 @@ body[data-ds-dark-theme] .fhl-root {
1021
985
  " 题"
1022
986
  ]
1023
987
  }),
1024
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
988
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1025
989
  className: `fhl-timer${timeLow && canAct ? " is-low" : ""}`,
1026
990
  children: [api.secondsLeft, "s"]
1027
991
  }),
1028
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1029
993
  className: "fhl-stage-tag",
1030
994
  children: [
1031
995
  MODE_LABEL[match.mode],
@@ -1035,7 +999,7 @@ body[data-ds-dark-theme] .fhl-root {
1035
999
  })
1036
1000
  ]
1037
1001
  }),
1038
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1039
1003
  className: "fhl-lingzi",
1040
1004
  children: [
1041
1005
  "「",
@@ -1043,7 +1007,7 @@ body[data-ds-dark-theme] .fhl-root {
1043
1007
  "」"
1044
1008
  ]
1045
1009
  }),
1046
- match.mode === "ancient" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1010
+ match.mode === "ancient" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1047
1011
  className: "fhl-muted",
1048
1012
  style: {
1049
1013
  fontSize: 11,
@@ -1052,50 +1016,50 @@ body[data-ds-dark-theme] .fhl-root {
1052
1016
  },
1053
1017
  children: [
1054
1018
  "古法:本轮令字须位于第 ",
1055
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", {
1019
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
1056
1020
  style: { color: "var(--fhl-accent)" },
1057
1021
  children: match.requiredPosition
1058
1022
  }),
1059
1023
  " 字"
1060
1024
  ]
1061
1025
  }),
1062
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1026
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1063
1027
  className: "fhl-card fhl-card-bot",
1064
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1028
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1065
1029
  className: "fhl-card-role",
1066
1030
  children: "🤖 对手"
1067
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1031
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1068
1032
  className: "fhl-card-line",
1069
1033
  children: match.challenge
1070
1034
  })]
1071
1035
  }),
1072
- match.lastVerdict !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1036
+ match.lastVerdict !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1073
1037
  className: `fhl-verdict${api.secondsLeft === 0 || !canAct ? "" : ""}`,
1074
1038
  children: match.lastVerdict
1075
1039
  }),
1076
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
1040
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
1077
1041
  className: "fhl-input-row",
1078
1042
  onSubmit: (ev) => {
1079
1043
  ev.preventDefault();
1080
1044
  api.submit();
1081
1045
  },
1082
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
1046
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1083
1047
  className: "fhl-input",
1084
1048
  value: api.draft,
1085
1049
  onChange: (ev) => api.setDraft(ev.target.value),
1086
1050
  placeholder: `说一句含「${match.lingzi}」的诗句…`,
1087
1051
  disabled: !canAct,
1088
1052
  autoComplete: "off"
1089
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1053
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1090
1054
  type: "submit",
1091
1055
  className: "fhl-btn fhl-btn-primary",
1092
1056
  disabled: !canAct,
1093
1057
  children: "对诗"
1094
1058
  })]
1095
1059
  }),
1096
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1060
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1097
1061
  className: "fhl-actions",
1098
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
1062
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1099
1063
  type: "button",
1100
1064
  className: "fhl-btn",
1101
1065
  onClick: api.requestHint,
@@ -1106,7 +1070,7 @@ body[data-ds-dark-theme] .fhl-root {
1106
1070
  match.hintsLeft,
1107
1071
  ")"
1108
1072
  ]
1109
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1073
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1110
1074
  type: "button",
1111
1075
  className: "fhl-btn fhl-btn-danger",
1112
1076
  onClick: api.concede,
@@ -1114,28 +1078,28 @@ body[data-ds-dark-theme] .fhl-root {
1114
1078
  children: "认输"
1115
1079
  })]
1116
1080
  }),
1117
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1081
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1118
1082
  className: "fhl-stats",
1119
1083
  children: [
1120
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1084
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1121
1085
  className: "fhl-stat",
1122
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: match.score }), " 得分"]
1086
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: match.score }), " 得分"]
1123
1087
  }),
1124
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1088
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1125
1089
  className: "fhl-stat",
1126
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: match.combo }), " 连击"]
1090
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: match.combo }), " 连击"]
1127
1091
  }),
1128
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1092
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1129
1093
  className: "fhl-stat",
1130
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("b", { children: [
1094
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("b", { children: [
1131
1095
  match.solved,
1132
1096
  "/",
1133
1097
  7
1134
1098
  ] }), " 答对"]
1135
1099
  }),
1136
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1100
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1137
1101
  className: `fhl-stat${match.failures >= 2 ? " is-warn" : ""}`,
1138
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("b", { children: [
1102
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("b", { children: [
1139
1103
  match.failures,
1140
1104
  "/",
1141
1105
  3
@@ -1149,21 +1113,21 @@ body[data-ds-dark-theme] .fhl-root {
1149
1113
  function EndView(props) {
1150
1114
  const { api } = props;
1151
1115
  const match = api.match;
1152
- if (match === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(react.default.Fragment, {});
1116
+ if (match === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, {});
1153
1117
  const won = match.phase === "won";
1154
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(react.default.Fragment, { children: [
1155
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1118
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.default.Fragment, { children: [
1119
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1156
1120
  className: `fhl-result${won ? " is-won" : " is-lost"}`,
1157
1121
  children: [
1158
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1122
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1159
1123
  className: "fhl-result-icon",
1160
1124
  children: won ? "🏆" : "😢"
1161
1125
  }),
1162
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1126
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1163
1127
  className: "fhl-result-title",
1164
1128
  children: won ? "通关!" : "本局惜败"
1165
1129
  }),
1166
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1130
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1167
1131
  className: "fhl-muted",
1168
1132
  style: {
1169
1133
  fontSize: 12,
@@ -1173,44 +1137,44 @@ body[data-ds-dark-theme] .fhl-root {
1173
1137
  })
1174
1138
  ]
1175
1139
  }),
1176
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1140
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1177
1141
  className: "fhl-stats",
1178
1142
  children: [
1179
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1143
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1180
1144
  className: "fhl-stat",
1181
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: match.score }), " 得分"]
1145
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: match.score }), " 得分"]
1182
1146
  }),
1183
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1147
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1184
1148
  className: "fhl-stat",
1185
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: match.solved }), " 答对"]
1149
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: match.solved }), " 答对"]
1186
1150
  }),
1187
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1151
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1188
1152
  className: "fhl-stat",
1189
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: match.failures }), " 超时"]
1153
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: match.failures }), " 超时"]
1190
1154
  })
1191
1155
  ]
1192
1156
  }),
1193
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1157
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1194
1158
  className: "fhl-record",
1195
1159
  children: [
1196
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1160
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1197
1161
  "🏅 ",
1198
1162
  api.stats.wins,
1199
1163
  " 胜 / ",
1200
1164
  api.stats.losses,
1201
1165
  " 负"
1202
1166
  ] }),
1203
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["🔥 连胜 ", api.stats.streak] }),
1204
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["✨ 最佳 ", api.stats.bestScore] })
1167
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["🔥 连胜 ", api.stats.streak] }),
1168
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["✨ 最佳 ", api.stats.bestScore] })
1205
1169
  ]
1206
1170
  }),
1207
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1171
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1208
1172
  type: "button",
1209
1173
  className: "fhl-btn fhl-btn-primary",
1210
1174
  onClick: api.start,
1211
1175
  children: "🔄 再来一局"
1212
1176
  }),
1213
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1177
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1214
1178
  className: "fhl-muted",
1215
1179
  style: {
1216
1180
  fontSize: 11,
@@ -1231,23 +1195,23 @@ body[data-ds-dark-theme] .fhl-root {
1231
1195
  function GamePanel(props) {
1232
1196
  const { sessionId, onClose, api } = props;
1233
1197
  const phase = api.match === null ? "setup" : api.match.phase;
1234
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1198
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1235
1199
  className: "fhl-root fhl-panel",
1236
1200
  "data-plugin": "dsh-plugin-feihualing",
1237
1201
  children: [
1238
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1202
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1239
1203
  className: "fhl-panel-header",
1240
1204
  children: [
1241
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1205
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1242
1206
  className: "fhl-panel-title",
1243
1207
  children: "🎴 飞花令 · 即时对战"
1244
1208
  }),
1245
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1209
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1246
1210
  className: "fhl-tag",
1247
1211
  title: sessionId,
1248
1212
  children: ["会话 ", shortSession(sessionId)]
1249
1213
  }),
1250
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1214
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1251
1215
  type: "button",
1252
1216
  className: "fhl-panel-close",
1253
1217
  onClick: onClose,
@@ -1256,9 +1220,9 @@ body[data-ds-dark-theme] .fhl-root {
1256
1220
  })
1257
1221
  ]
1258
1222
  }),
1259
- phase === "setup" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SetupView, { api }),
1260
- phase === "playing" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlayView, { api }),
1261
- (phase === "won" || phase === "lost") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EndView, { api })
1223
+ phase === "setup" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SetupView, { api }),
1224
+ phase === "playing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlayView, { api }),
1225
+ (phase === "won" || phase === "lost") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EndView, { api })
1262
1226
  ]
1263
1227
  });
1264
1228
  }
@@ -1415,7 +1379,7 @@ body[data-ds-dark-theme] .fhl-root {
1415
1379
  const api = useGamePanel(open);
1416
1380
  const sessionId = props.sessionId ?? "";
1417
1381
  const toggle = react.default.useCallback(() => setOpen((prev) => !prev), []);
1418
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(react.default.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1382
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.default.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1419
1383
  type: "button",
1420
1384
  className: "fhl-toggle",
1421
1385
  "data-active": open ? "true" : "false",
@@ -1423,7 +1387,7 @@ body[data-ds-dark-theme] .fhl-root {
1423
1387
  title: open ? "收起飞花令(对局保留)" : "飞花令小游戏",
1424
1388
  "aria-label": "飞花令小游戏",
1425
1389
  children: "🎴"
1426
- }), open && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GamePanel, {
1390
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GamePanel, {
1427
1391
  sessionId,
1428
1392
  onClose: () => setOpen(false),
1429
1393
  api
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 酒: 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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"}
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","../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 * 浮层游戏面板(完整对战 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"],"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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-feihualing",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "DeepSeek Harness 飞花令游戏插件:简易/古法严格双模式,按会话独立维护令字、得分、已用诗句与剩余提示次数;提供 feihualing_start / feihualing_status / feihualing_stop 三个工具与关键词指令,检测到 shell/文件写入类工具调用时自动暂停并保存现场。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",