@my-games/sudoku-react 0.0.1 → 0.1.0

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/dist/index.js CHANGED
@@ -1,14 +1,379 @@
1
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
2
+ import { Sudoku } from "@my-games/sudoku-core";
1
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
+ // src/useGame.ts
5
+ function useGame(options = {}) {
6
+ const { difficulty, storage, saveKey, bestKey, freshStart, puzzle } = options;
7
+ const game = useMemo(() => new Sudoku({
8
+ difficulty,
9
+ storage,
10
+ saveKey,
11
+ bestKey,
12
+ freshStart,
13
+ puzzle
14
+ }), [
15
+ difficulty,
16
+ saveKey,
17
+ bestKey
18
+ ]);
19
+ const gameRef = useRef(game);
20
+ gameRef.current = game;
21
+ const [state, setState] = useState(() => game.getState());
22
+ useEffect(() => {
23
+ setState(game.getState());
24
+ return game.subscribe(setState);
25
+ }, [game]);
26
+ return {
27
+ state,
28
+ game,
29
+ select: (row, col) => gameRef.current.select(row, col),
30
+ input: (digit) => gameRef.current.input(digit),
31
+ erase: () => gameRef.current.erase(),
32
+ toggleNote: (digit) => gameRef.current.toggleNote(digit),
33
+ hint: () => gameRef.current.hint(),
34
+ newGame: (diff) => gameRef.current.newGame(diff),
35
+ undo: () => gameRef.current.undo(),
36
+ canUndo: () => gameRef.current.canUndo()
37
+ };
38
+ }
2
39
  // src/GameSudoku.tsx
3
- function GameSudoku({ title = "Sudoku" }) {
4
- return /* @__PURE__ */ jsx("div", {
5
- className: "game-sudoku",
6
- children: /* @__PURE__ */ jsxs("p", { children: [title, " · React 占位组件(0.0.0)。真实棋盘随引擎后续提交补入。"] })
40
+ var PAD_DIGITS = [
41
+ 1,
42
+ 2,
43
+ 3,
44
+ 4,
45
+ 5,
46
+ 6,
47
+ 7,
48
+ 8,
49
+ 9
50
+ ];
51
+ var DIFFS = [
52
+ {
53
+ key: "easy",
54
+ label: "简单"
55
+ },
56
+ {
57
+ key: "medium",
58
+ label: "中等"
59
+ },
60
+ {
61
+ key: "hard",
62
+ label: "困难"
63
+ }
64
+ ];
65
+ /** 9 宫 × 9 格的全局坐标映射。 */
66
+ var BOXES = (() => {
67
+ const out = [];
68
+ for (let bi = 0; bi < 9; bi++) {
69
+ const cells = [];
70
+ for (let ci = 0; ci < 9; ci++) cells.push({
71
+ row: Math.floor(bi / 3) * 3 + Math.floor(ci / 3),
72
+ col: bi % 3 * 3 + ci % 3
73
+ });
74
+ out.push(cells);
75
+ }
76
+ return out;
77
+ })();
78
+ function formatTime(totalSeconds) {
79
+ const m = Math.floor(totalSeconds / 60);
80
+ const s = totalSeconds % 60;
81
+ return `${m}:${String(s).padStart(2, "0")}`;
82
+ }
83
+ function cellValueOf(state, row, col) {
84
+ return state.grid[row]?.[col] ?? 0;
85
+ }
86
+ function cellClass(state, row, col) {
87
+ const v = cellValueOf(state, row, col);
88
+ const given = state.givenMask[row]?.[col] ?? false;
89
+ const sel = state.selected;
90
+ const cls = ["game-sudoku-cell"];
91
+ if (given) cls.push("is-given");
92
+ else if (v !== 0) cls.push("is-user");
93
+ if (state.conflicts[row]?.[col]) cls.push("is-conflict");
94
+ if (sel && sel.row === row && sel.col === col) cls.push("is-selected");
95
+ else if (sel) {
96
+ const sameBox = Math.floor(sel.row / 3) === Math.floor(row / 3) && Math.floor(sel.col / 3) === Math.floor(col / 3);
97
+ if (sel.row === row || sel.col === col || sameBox) cls.push("is-peer");
98
+ const selVal = cellValueOf(state, sel.row, sel.col);
99
+ if (v !== 0 && v === selVal) cls.push("is-same");
100
+ }
101
+ return cls.join(" ");
102
+ }
103
+ function cellNotesOf(state, row, col) {
104
+ return cellValueOf(state, row, col) === 0 ? state.notes[row]?.[col] ?? [] : [];
105
+ }
106
+ function GameSudokuInner(props, ref) {
107
+ const { difficulty = "easy", storage, saveKey, bestKey, freshStart = false, autofocus = true, showPad = true, title = "数独", className, showHint = false, onDigit, onSolved } = props;
108
+ const { state, game, select, input, erase, toggleNote, hint, newGame, undo } = useGame({
109
+ difficulty,
110
+ storage,
111
+ saveKey,
112
+ bestKey,
113
+ freshStart
114
+ });
115
+ const boardRef = useRef(null);
116
+ const [noteMode, setNoteMode] = useState(false);
117
+ const [, setTick] = useState(0);
118
+ useEffect(() => {
119
+ if (state.status !== "playing") return;
120
+ const id = window.setInterval(() => setTick((t) => t + 1), 1e3);
121
+ return () => window.clearInterval(id);
122
+ }, [state.status]);
123
+ const applyDigit = useCallback((digit) => {
124
+ const changed = noteMode ? toggleNote(digit) : input(digit);
125
+ if (!noteMode) onDigit?.(digit, changed);
126
+ }, [
127
+ noteMode,
128
+ toggleNote,
129
+ input,
130
+ onDigit
131
+ ]);
132
+ const handleKey = useCallback((ev) => {
133
+ if (document.activeElement !== boardRef.current) return;
134
+ if (/^[1-9]$/.test(ev.key)) {
135
+ applyDigit(Number(ev.key));
136
+ ev.preventDefault();
137
+ return;
138
+ }
139
+ if (ev.key === "Backspace" || ev.key === "Delete") {
140
+ erase();
141
+ ev.preventDefault();
142
+ return;
143
+ }
144
+ if (ev.key === "n" || ev.key === "N") {
145
+ setNoteMode((v) => !v);
146
+ return;
147
+ }
148
+ const sel = state.selected;
149
+ if (!sel) return;
150
+ let { row, col } = sel;
151
+ switch (ev.key) {
152
+ case "ArrowUp":
153
+ row = (row + 8) % 9;
154
+ break;
155
+ case "ArrowDown":
156
+ row = (row + 1) % 9;
157
+ break;
158
+ case "ArrowLeft":
159
+ col = (col + 8) % 9;
160
+ break;
161
+ case "ArrowRight":
162
+ col = (col + 1) % 9;
163
+ break;
164
+ default: return;
165
+ }
166
+ ev.preventDefault();
167
+ select(row, col);
168
+ }, [
169
+ applyDigit,
170
+ erase,
171
+ select,
172
+ state.selected
173
+ ]);
174
+ useEffect(() => {
175
+ window.addEventListener("keydown", handleKey, { passive: false });
176
+ return () => window.removeEventListener("keydown", handleKey);
177
+ }, [handleKey]);
178
+ useEffect(() => {
179
+ if (autofocus) boardRef.current?.focus({ preventScroll: true });
180
+ }, [autofocus]);
181
+ const lastStatus = useRef(state.status);
182
+ useEffect(() => {
183
+ if (state.status === "won" && lastStatus.current !== "won") onSolved?.({
184
+ difficulty: state.difficulty,
185
+ seconds: state.elapsedSeconds
186
+ });
187
+ lastStatus.current = state.status;
188
+ }, [
189
+ state.status,
190
+ state.difficulty,
191
+ state.elapsedSeconds,
192
+ onSolved
193
+ ]);
194
+ useImperativeHandle(ref, () => ({
195
+ select,
196
+ input,
197
+ erase,
198
+ toggleNote,
199
+ hint,
200
+ newGame,
201
+ undo
202
+ }), [
203
+ select,
204
+ input,
205
+ erase,
206
+ toggleNote,
207
+ hint,
208
+ newGame,
209
+ undo
210
+ ]);
211
+ const won = state.status === "won";
212
+ const timeText = formatTime(won ? state.elapsedSeconds : game.getState().elapsedSeconds);
213
+ const bestText = state.bestSeconds === null ? "—" : formatTime(state.bestSeconds);
214
+ const renderCell = (row, col) => {
215
+ const value = cellValueOf(state, row, col);
216
+ const notes = cellNotesOf(state, row, col);
217
+ return /* @__PURE__ */ jsx("div", {
218
+ className: cellClass(state, row, col),
219
+ "data-row": row,
220
+ "data-col": col,
221
+ onClick: () => select(row, col),
222
+ children: value !== 0 ? value : /* @__PURE__ */ jsx("div", {
223
+ className: "cell-notes",
224
+ children: PAD_DIGITS.map((d) => /* @__PURE__ */ jsx("span", { children: notes.includes(d) ? d : "" }, d))
225
+ })
226
+ }, `${row}-${col}`);
227
+ };
228
+ return /* @__PURE__ */ jsxs("div", {
229
+ className: ["game-sudoku", className].filter(Boolean).join(" "),
230
+ children: [
231
+ /* @__PURE__ */ jsxs("header", {
232
+ className: "game-sudoku-header",
233
+ children: [/* @__PURE__ */ jsxs("h1", {
234
+ className: "game-sudoku-title",
235
+ children: [title, /* @__PURE__ */ jsx("span", {
236
+ className: "subtitle",
237
+ children: "@my-games/sudoku-react"
238
+ })]
239
+ }), /* @__PURE__ */ jsxs("div", {
240
+ className: "game-sudoku-stats",
241
+ children: [
242
+ /* @__PURE__ */ jsxs("div", {
243
+ className: "game-sudoku-stat",
244
+ children: [/* @__PURE__ */ jsx("span", {
245
+ className: "label",
246
+ children: "用时"
247
+ }), /* @__PURE__ */ jsx("span", {
248
+ className: "value",
249
+ children: timeText
250
+ })]
251
+ }),
252
+ /* @__PURE__ */ jsxs("div", {
253
+ className: "game-sudoku-stat",
254
+ children: [/* @__PURE__ */ jsx("span", {
255
+ className: "label",
256
+ children: "最佳"
257
+ }), /* @__PURE__ */ jsx("span", {
258
+ className: "value",
259
+ children: bestText
260
+ })]
261
+ }),
262
+ /* @__PURE__ */ jsxs("div", {
263
+ className: `game-sudoku-stat${state.mistakeCount > 0 ? " is-danger" : ""}`,
264
+ children: [/* @__PURE__ */ jsx("span", {
265
+ className: "label",
266
+ children: "错误"
267
+ }), /* @__PURE__ */ jsx("span", {
268
+ className: "value",
269
+ children: state.mistakeCount
270
+ })]
271
+ })
272
+ ]
273
+ })]
274
+ }),
275
+ /* @__PURE__ */ jsxs("div", {
276
+ className: "game-sudoku-toolbar",
277
+ children: [/* @__PURE__ */ jsx("div", {
278
+ className: "game-sudoku-difficulty",
279
+ role: "group",
280
+ "aria-label": "难度",
281
+ children: DIFFS.map((d) => /* @__PURE__ */ jsx("button", {
282
+ className: `game-sudoku-chip${state.difficulty === d.key ? " is-active" : ""}`,
283
+ type: "button",
284
+ onClick: () => newGame(d.key),
285
+ children: d.label
286
+ }, d.key))
287
+ }), /* @__PURE__ */ jsxs("div", {
288
+ className: "game-sudoku-actions",
289
+ children: [
290
+ /* @__PURE__ */ jsxs("button", {
291
+ className: `game-sudoku-button${noteMode ? " is-toggled" : ""}`,
292
+ type: "button",
293
+ disabled: won,
294
+ onClick: () => setNoteMode((v) => !v),
295
+ children: ["笔记 ", noteMode ? "开" : "关"]
296
+ }),
297
+ /* @__PURE__ */ jsx("button", {
298
+ className: "game-sudoku-button",
299
+ type: "button",
300
+ disabled: won || state.historySize === 0,
301
+ onClick: () => undo(),
302
+ children: "撤销"
303
+ }),
304
+ showHint ? /* @__PURE__ */ jsx("button", {
305
+ className: "game-sudoku-button",
306
+ type: "button",
307
+ disabled: won,
308
+ onClick: () => hint(),
309
+ children: "提示"
310
+ }) : null,
311
+ /* @__PURE__ */ jsx("button", {
312
+ className: "game-sudoku-button is-primary",
313
+ type: "button",
314
+ onClick: () => newGame(),
315
+ children: "新游戏"
316
+ })
317
+ ]
318
+ })]
319
+ }),
320
+ /* @__PURE__ */ jsxs("div", {
321
+ ref: boardRef,
322
+ className: "game-sudoku-board",
323
+ tabIndex: 0,
324
+ role: "application",
325
+ "aria-label": `数独棋盘 ${state.status}`,
326
+ style: { "--cols": 9 },
327
+ children: [BOXES.map((cells, bi) => /* @__PURE__ */ jsx("div", {
328
+ className: "game-sudoku-box",
329
+ children: cells.map(({ row, col }) => renderCell(row, col))
330
+ }, bi)), /* @__PURE__ */ jsxs("div", {
331
+ className: `game-sudoku-overlay${won ? " is-visible" : ""}`,
332
+ children: [
333
+ /* @__PURE__ */ jsx("h2", { children: "解开了!" }),
334
+ /* @__PURE__ */ jsxs("p", {
335
+ className: "meta",
336
+ children: [
337
+ "难度 ",
338
+ state.difficulty,
339
+ " · 用时 ",
340
+ timeText,
341
+ " · 提示 ",
342
+ state.hintCount,
343
+ " 次 · 错误",
344
+ " ",
345
+ state.mistakeCount,
346
+ " 次"
347
+ ]
348
+ }),
349
+ /* @__PURE__ */ jsx("div", {
350
+ className: "row",
351
+ children: /* @__PURE__ */ jsx("button", {
352
+ className: "game-sudoku-button is-primary",
353
+ type: "button",
354
+ onClick: () => newGame(),
355
+ children: "再来一局"
356
+ })
357
+ })
358
+ ]
359
+ })]
360
+ }),
361
+ showPad ? /* @__PURE__ */ jsx("div", {
362
+ className: "game-sudoku-pad",
363
+ role: "group",
364
+ "aria-label": "数字键盘",
365
+ children: PAD_DIGITS.map((d) => /* @__PURE__ */ jsx("button", {
366
+ type: "button",
367
+ disabled: won,
368
+ onClick: () => applyDigit(d),
369
+ children: d
370
+ }, d))
371
+ }) : null
372
+ ]
7
373
  });
8
374
  }
9
- // src/index.ts
10
- var SUDOKU_PACKAGE = "sudoku-react";
11
- var SUDOKU_STAGE = "0.0.0";
12
- export { GameSudoku, SUDOKU_PACKAGE, SUDOKU_STAGE };
375
+ var GameSudoku = forwardRef(GameSudokuInner);
376
+ GameSudoku.displayName = "GameSudoku";
377
+ export { GameSudoku, useGame };
13
378
 
14
379
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/GameSudoku.tsx","../src/index.ts"],"sourcesContent":["// 0.0.0 空骨架占位组件:仅 import core 样式以让 vite lib build 抽出非空\n// dist/index.css(verify:dist 守卫要求)。真实棋盘/输入/提示 UI 随引擎补入。\nimport '@my-games/sudoku-core/style.css';\n\nexport interface GameSudokuProps {\n title?: string;\n}\n\nexport function GameSudoku({ title = 'Sudoku' }: GameSudokuProps) {\n return (\n <div className=\"game-sudoku\">\n <p>{title} · React 占位组件(0.0.0)。真实棋盘随引擎后续提交补入。</p>\n </div>\n );\n}\n","export { GameSudoku, type GameSudokuProps } from './GameSudoku';\n\nexport const SUDOKU_PACKAGE = 'sudoku-react' as const;\nexport const SUDOKU_STAGE = '0.0.0' as const;\n"],"mappings":";;AAQA,SAAgB,WAAW,EAAE,QAAQ,YAA6B;CAChE,OACE,oBAAC,OAAD;EAAK,WAAU;EACb,UAAA,qBAAC,KAAD,EAAA,UAAA,CAAI,OAAM,qCAAsC,EAAA,CAAA;CAC7C,CAAA;AAET;;ACZA,IAAa,iBAAiB;AAC9B,IAAa,eAAe"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/useGame.ts","../src/GameSudoku.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport {\n Sudoku,\n type Difficulty,\n type Digit,\n type GameOptions,\n type GameState\n} from '@my-games/sudoku-core';\n\n/**\n * `useGame` —— React Hook 版引擎桥接(与 Vue 的 useGame 语义对齐)。\n *\n * - 引擎实例用 useMemo 按「难度 + 键名」缓存;这些 prop 变化 = 重开一局,视为预期语义。\n * - useState 订阅引擎快照,useEffect 挂/卸订阅防泄漏。\n * - useRef 保最新 game 实例给命令式 handler,避免闭包过期。\n */\nexport interface UseGameReturn {\n state: GameState;\n game: Sudoku;\n select: (row: number, col: number) => void;\n input: (digit: Digit) => boolean;\n erase: () => boolean;\n toggleNote: (digit: Digit) => boolean;\n hint: () => boolean;\n newGame: (difficulty?: Difficulty) => void;\n undo: () => boolean;\n canUndo: () => boolean;\n}\n\nexport function useGame(options: GameOptions = {}): UseGameReturn {\n const { difficulty, storage, saveKey, bestKey, freshStart, puzzle } = options;\n\n const game = useMemo(\n () => new Sudoku({ difficulty, storage, saveKey, bestKey, freshStart, puzzle }),\n // 故意只依赖这些稳定原始值;storage / puzzle / rng / clock 引用不稳定,重开一次即可。\n [difficulty, saveKey, bestKey]\n );\n\n const gameRef = useRef(game);\n gameRef.current = game;\n\n const [state, setState] = useState<GameState>(() => game.getState());\n\n useEffect(() => {\n setState(game.getState());\n const unsub = game.subscribe(setState);\n return unsub;\n }, [game]);\n\n return {\n state,\n game,\n select: (row, col) => gameRef.current.select(row, col),\n input: (digit) => gameRef.current.input(digit),\n erase: () => gameRef.current.erase(),\n toggleNote: (digit) => gameRef.current.toggleNote(digit),\n hint: () => gameRef.current.hint(),\n newGame: (diff) => gameRef.current.newGame(diff),\n undo: () => gameRef.current.undo(),\n canUndo: () => gameRef.current.canUndo()\n };\n}\n","import {\n type CSSProperties,\n type ReactNode,\n type Ref,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState\n} from 'react';\nimport '@my-games/sudoku-core/style.css';\nimport type { Difficulty, Digit, GameState, SaveStorage } from '@my-games/sudoku-core';\nimport { useGame } from './useGame';\n\n/**\n * GameSudoku —— React 18/19 组件,行为与 Vue 版对齐。\n * 键盘:数字 1-9 填数 / N 切笔记 / Backspace·Delete 擦除 / 方向键移动选中;\n * 严格判 `document.activeElement === board`,多实例并存只活动那个响应。\n */\n\nexport interface GameSudokuProps {\n difficulty?: Difficulty;\n storage?: SaveStorage | null;\n saveKey?: string;\n bestKey?: string;\n freshStart?: boolean;\n autofocus?: boolean;\n showPad?: boolean;\n title?: string;\n className?: string;\n showHint?: boolean;\n onDigit?: (digit: Digit, changed: boolean) => void;\n onSolved?: (payload: { difficulty: Difficulty; seconds: number }) => void;\n}\n\nexport interface GameSudokuHandle {\n select(row: number, col: number): void;\n input(digit: Digit): boolean;\n erase(): boolean;\n toggleNote(digit: Digit): boolean;\n hint(): boolean;\n newGame(difficulty?: Difficulty): void;\n undo(): boolean;\n}\n\nconst PAD_DIGITS: Digit[] = [1, 2, 3, 4, 5, 6, 7, 8, 9];\nconst DIFFS: Array<{ key: Difficulty; label: string }> = [\n { key: 'easy', label: '简单' },\n { key: 'medium', label: '中等' },\n { key: 'hard', label: '困难' }\n];\n\n/** 9 宫 × 9 格的全局坐标映射。 */\nconst BOXES: Array<Array<{ row: number; col: number }>> = (() => {\n const out: Array<Array<{ row: number; col: number }>> = [];\n for (let bi = 0; bi < 9; bi++) {\n const cells: Array<{ row: number; col: number }> = [];\n for (let ci = 0; ci < 9; ci++) {\n cells.push({\n row: Math.floor(bi / 3) * 3 + Math.floor(ci / 3),\n col: (bi % 3) * 3 + (ci % 3)\n });\n }\n out.push(cells);\n }\n return out;\n})();\n\nfunction formatTime(totalSeconds: number): string {\n const m = Math.floor(totalSeconds / 60);\n const s = totalSeconds % 60;\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n\nfunction cellValueOf(state: GameState, row: number, col: number): number {\n return state.grid[row]?.[col] ?? 0;\n}\n\nfunction cellClass(state: GameState, row: number, col: number): string {\n const v = cellValueOf(state, row, col);\n const given = state.givenMask[row]?.[col] ?? false;\n const sel = state.selected;\n const cls = ['game-sudoku-cell'];\n if (given) cls.push('is-given');\n else if (v !== 0) cls.push('is-user');\n if (state.conflicts[row]?.[col]) cls.push('is-conflict');\n if (sel && sel.row === row && sel.col === col) {\n cls.push('is-selected');\n } else if (sel) {\n const sameBox =\n Math.floor(sel.row / 3) === Math.floor(row / 3) &&\n Math.floor(sel.col / 3) === Math.floor(col / 3);\n if (sel.row === row || sel.col === col || sameBox) cls.push('is-peer');\n const selVal = cellValueOf(state, sel.row, sel.col);\n if (v !== 0 && v === selVal) cls.push('is-same');\n }\n return cls.join(' ');\n}\n\nfunction cellNotesOf(state: GameState, row: number, col: number): Digit[] {\n return cellValueOf(state, row, col) === 0 ? (state.notes[row]?.[col] ?? []) : [];\n}\n\nfunction GameSudokuInner(props: GameSudokuProps, ref: Ref<GameSudokuHandle>) {\n const {\n difficulty = 'easy',\n storage,\n saveKey,\n bestKey,\n freshStart = false,\n autofocus = true,\n showPad = true,\n title = '数独',\n className,\n showHint = false,\n onDigit,\n onSolved\n } = props;\n\n const { state, game, select, input, erase, toggleNote, hint, newGame, undo } = useGame({\n difficulty,\n storage,\n saveKey,\n bestKey,\n freshStart\n });\n\n const boardRef = useRef<HTMLDivElement>(null);\n const [noteMode, setNoteMode] = useState(false);\n // 用时每秒刷新:core 只在操作时推快照,tick 触发重绘后由 game.getState() 读实时值。\n const [, setTick] = useState(0);\n useEffect(() => {\n if (state.status !== 'playing') return;\n const id = window.setInterval(() => setTick((t) => t + 1), 1000);\n return () => window.clearInterval(id);\n }, [state.status]);\n\n const applyDigit = useCallback(\n (digit: Digit) => {\n const changed = noteMode ? toggleNote(digit) : input(digit);\n if (!noteMode) onDigit?.(digit, changed);\n },\n [noteMode, toggleNote, input, onDigit]\n );\n\n const handleKey = useCallback(\n (ev: KeyboardEvent) => {\n if (document.activeElement !== boardRef.current) return;\n if (/^[1-9]$/.test(ev.key)) {\n applyDigit(Number(ev.key) as Digit);\n ev.preventDefault();\n return;\n }\n if (ev.key === 'Backspace' || ev.key === 'Delete') {\n erase();\n ev.preventDefault();\n return;\n }\n if (ev.key === 'n' || ev.key === 'N') {\n setNoteMode((v) => !v);\n return;\n }\n const sel = state.selected;\n if (!sel) return;\n let { row, col } = sel;\n switch (ev.key) {\n case 'ArrowUp':\n row = (row + 8) % 9;\n break;\n case 'ArrowDown':\n row = (row + 1) % 9;\n break;\n case 'ArrowLeft':\n col = (col + 8) % 9;\n break;\n case 'ArrowRight':\n col = (col + 1) % 9;\n break;\n default:\n return;\n }\n ev.preventDefault();\n select(row, col);\n },\n [applyDigit, erase, select, state.selected]\n );\n\n useEffect(() => {\n window.addEventListener('keydown', handleKey, { passive: false });\n return () => window.removeEventListener('keydown', handleKey);\n }, [handleKey]);\n\n useEffect(() => {\n if (autofocus) boardRef.current?.focus({ preventScroll: true });\n }, [autofocus]);\n\n const lastStatus = useRef(state.status);\n useEffect(() => {\n if (state.status === 'won' && lastStatus.current !== 'won') {\n onSolved?.({ difficulty: state.difficulty, seconds: state.elapsedSeconds });\n }\n lastStatus.current = state.status;\n }, [state.status, state.difficulty, state.elapsedSeconds, onSolved]);\n\n useImperativeHandle(\n ref,\n () => ({ select, input, erase, toggleNote, hint, newGame, undo }),\n [select, input, erase, toggleNote, hint, newGame, undo]\n );\n\n const won = state.status === 'won';\n const timeText = formatTime(won ? state.elapsedSeconds : game.getState().elapsedSeconds);\n const bestText = state.bestSeconds === null ? '—' : formatTime(state.bestSeconds);\n\n const renderCell = (row: number, col: number): ReactNode => {\n const value = cellValueOf(state, row, col);\n const notes = cellNotesOf(state, row, col);\n return (\n <div\n key={`${row}-${col}`}\n className={cellClass(state, row, col)}\n data-row={row}\n data-col={col}\n onClick={() => select(row, col)}\n >\n {value !== 0 ? (\n value\n ) : (\n <div className=\"cell-notes\">\n {PAD_DIGITS.map((d) => (\n <span key={d}>{notes.includes(d) ? d : ''}</span>\n ))}\n </div>\n )}\n </div>\n );\n };\n\n return (\n <div className={['game-sudoku', className].filter(Boolean).join(' ')}>\n <header className=\"game-sudoku-header\">\n <h1 className=\"game-sudoku-title\">\n {title}\n <span className=\"subtitle\">@my-games/sudoku-react</span>\n </h1>\n <div className=\"game-sudoku-stats\">\n <div className=\"game-sudoku-stat\">\n <span className=\"label\">用时</span>\n <span className=\"value\">{timeText}</span>\n </div>\n <div className=\"game-sudoku-stat\">\n <span className=\"label\">最佳</span>\n <span className=\"value\">{bestText}</span>\n </div>\n <div className={`game-sudoku-stat${state.mistakeCount > 0 ? ' is-danger' : ''}`}>\n <span className=\"label\">错误</span>\n <span className=\"value\">{state.mistakeCount}</span>\n </div>\n </div>\n </header>\n\n <div className=\"game-sudoku-toolbar\">\n <div className=\"game-sudoku-difficulty\" role=\"group\" aria-label=\"难度\">\n {DIFFS.map((d) => (\n <button\n key={d.key}\n className={`game-sudoku-chip${state.difficulty === d.key ? ' is-active' : ''}`}\n type=\"button\"\n onClick={() => newGame(d.key)}\n >\n {d.label}\n </button>\n ))}\n </div>\n <div className=\"game-sudoku-actions\">\n <button\n className={`game-sudoku-button${noteMode ? ' is-toggled' : ''}`}\n type=\"button\"\n disabled={won}\n onClick={() => setNoteMode((v) => !v)}\n >\n 笔记 {noteMode ? '开' : '关'}\n </button>\n <button\n className=\"game-sudoku-button\"\n type=\"button\"\n disabled={won || state.historySize === 0}\n onClick={() => undo()}\n >\n 撤销\n </button>\n {showHint ? (\n <button\n className=\"game-sudoku-button\"\n type=\"button\"\n disabled={won}\n onClick={() => hint()}\n >\n 提示\n </button>\n ) : null}\n <button className=\"game-sudoku-button is-primary\" type=\"button\" onClick={() => newGame()}>\n 新游戏\n </button>\n </div>\n </div>\n\n <div\n ref={boardRef}\n className=\"game-sudoku-board\"\n tabIndex={0}\n role=\"application\"\n aria-label={`数独棋盘 ${state.status}`}\n style={{ '--cols': 9 } as CSSProperties}\n >\n {BOXES.map((cells, bi) => (\n <div key={bi} className=\"game-sudoku-box\">\n {cells.map(({ row, col }) => renderCell(row, col))}\n </div>\n ))}\n\n <div className={`game-sudoku-overlay${won ? ' is-visible' : ''}`}>\n <h2>解开了!</h2>\n <p className=\"meta\">\n 难度 {state.difficulty} · 用时 {timeText} · 提示 {state.hintCount} 次 · 错误{' '}\n {state.mistakeCount} 次\n </p>\n <div className=\"row\">\n <button className=\"game-sudoku-button is-primary\" type=\"button\" onClick={() => newGame()}>\n 再来一局\n </button>\n </div>\n </div>\n </div>\n\n {showPad ? (\n <div className=\"game-sudoku-pad\" role=\"group\" aria-label=\"数字键盘\">\n {PAD_DIGITS.map((d) => (\n <button key={d} type=\"button\" disabled={won} onClick={() => applyDigit(d)}>\n {d}\n </button>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n\nexport const GameSudoku = forwardRef(GameSudokuInner);\nGameSudoku.displayName = 'GameSudoku';\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { Sudoku, Difficulty, Digit, GameOptions, GameState } from '@my-games/sudoku-core';
2
+ /**
3
+ * `useGame` —— React Hook 版引擎桥接(与 Vue 的 useGame 语义对齐)。
4
+ *
5
+ * - 引擎实例用 useMemo 按「难度 + 键名」缓存;这些 prop 变化 = 重开一局,视为预期语义。
6
+ * - useState 订阅引擎快照,useEffect 挂/卸订阅防泄漏。
7
+ * - useRef 保最新 game 实例给命令式 handler,避免闭包过期。
8
+ */
9
+ export interface UseGameReturn {
10
+ state: GameState;
11
+ game: Sudoku;
12
+ select: (row: number, col: number) => void;
13
+ input: (digit: Digit) => boolean;
14
+ erase: () => boolean;
15
+ toggleNote: (digit: Digit) => boolean;
16
+ hint: () => boolean;
17
+ newGame: (difficulty?: Difficulty) => void;
18
+ undo: () => boolean;
19
+ canUndo: () => boolean;
20
+ }
21
+ export declare function useGame(options?: GameOptions): UseGameReturn;
22
+ //# sourceMappingURL=useGame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGame.d.ts","sourceRoot":"","sources":["../src/useGame.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EACN,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,SAAS,EACf,MAAM,uBAAuB,CAAC;AAE/B;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;IACjC,KAAK,EAAE,MAAM,OAAO,CAAC;IACrB,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;IACtC,IAAI,EAAE,MAAM,OAAO,CAAC;IACpB,OAAO,EAAE,CAAC,UAAU,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAC3C,IAAI,EAAE,MAAM,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC;CACxB;AAED,wBAAgB,OAAO,CAAC,OAAO,GAAE,WAAgB,GAAG,aAAa,CAgChE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@my-games/sudoku-react",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "React 18/19 component adapter for @my-games/sudoku-core — function component + hooks + forwardRef, mobile-ready. 数独的 React 组件适配器。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "react-dom": "^18.2.0 || ^19.0.0"
26
26
  },
27
27
  "dependencies": {
28
- "@my-games/sudoku-core": "0.0.1"
28
+ "@my-games/sudoku-core": "0.1.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/react": "^19.3.0",