diff --git a/src/components/SudokuGame.tsx b/src/components/SudokuGame.tsx index b0f118a..fb2ac12 100644 --- a/src/components/SudokuGame.tsx +++ b/src/components/SudokuGame.tsx @@ -17,9 +17,16 @@ type SudokuRound = { board: GameBoard; regionMap: RegionMap; }; +type DraftCells = boolean[][]; +type BoardSnapshot = { + board: GameBoard; + draftCells: DraftCells; +}; const CELL_SIZE = 80; const CELL_FONT_SIZE = 36; +const DRAFT_SIZE = CELL_SIZE / 2; +const DRAFT_FONT_SIZE = CELL_FONT_SIZE / 2; const createSudokuRound = (difficulty: Difficulty, mode: GameMode): SudokuRound => { const { puzzle, regionMap } = generatePuzzleForMode(difficulty, mode); @@ -30,6 +37,8 @@ const createSudokuRound = (difficulty: Difficulty, mode: GameMode): SudokuRound }; const cloneGameBoard = (board: GameBoard): GameBoard => board.map(row => [...row]); +const cloneDraftCells = (draftCells: DraftCells): DraftCells => draftCells.map(row => [...row]); +const createEmptyDraftCells = (): DraftCells => Array.from({ length: 9 }, () => Array(9).fill(false)); const SudokuGame: React.FC = () => { // 游戏难度 @@ -40,8 +49,10 @@ const SudokuGame: React.FC = () => { const [round, setRound] = useState(() => createSudokuRound('hard', 'standard')); const { board, regionMap } = round; - const [undoStack, setUndoStack] = useState([]); - const [redoStack, setRedoStack] = useState([]); + const [draftCells, setDraftCells] = useState(() => createEmptyDraftCells()); + const [draftMode, setDraftMode] = useState(false); + const [undoStack, setUndoStack] = useState([]); + const [redoStack, setRedoStack] = useState([]); const setBoard = (nextBoard: GameBoard) => { setRound((currentRound) => ({ @@ -50,10 +61,17 @@ const SudokuGame: React.FC = () => { })); }; - const applyBoardChange = (nextBoard: GameBoard) => { - setUndoStack((previousStack) => [...previousStack, cloneGameBoard(board)]); + const applyBoardChange = (nextBoard: GameBoard, nextDraftCells: DraftCells) => { + setUndoStack((previousStack) => [ + ...previousStack, + { + board: cloneGameBoard(board), + draftCells: cloneDraftCells(draftCells), + }, + ]); setRedoStack([]); setBoard(nextBoard); + setDraftCells(nextDraftCells); }; const [fixedCells, setFixedCells] = useState(() => @@ -159,15 +177,18 @@ const SudokuGame: React.FC = () => { if (!selectedCell || isGameComplete) return; const [row, col] = selectedCell; if (fixedCells[row][col]) return; - if (board[row][col] === num) return; + if (board[row][col] === num && draftCells[row][col] === draftMode) return; const newBoard = board.map(row => [...row]); + const newDraftCells = cloneDraftCells(draftCells); newBoard[row][col] = num; - applyBoardChange(newBoard); + newDraftCells[row][col] = draftMode; + applyBoardChange(newBoard, newDraftCells); // 检查是否完成 setTimeout(() => { if (checkIsComplete(newBoard)) { + setDraftCells(createEmptyDraftCells()); setIsGameComplete(true); setIsTimerRunning(false); setShowCompleteDialog(true); @@ -210,26 +231,42 @@ const SudokuGame: React.FC = () => { if (fixedCells[row][col] || board[row][col] === 0) return; const newBoard = board.map(row => [...row]); + const newDraftCells = cloneDraftCells(draftCells); newBoard[row][col] = 0; - applyBoardChange(newBoard); + newDraftCells[row][col] = false; + applyBoardChange(newBoard, newDraftCells); }; const handleUndo = () => { if (isGameComplete || undoStack.length === 0) return; - const previousBoard = undoStack[undoStack.length - 1]; + const previousSnapshot = undoStack[undoStack.length - 1]; setUndoStack(undoStack.slice(0, -1)); - setRedoStack([cloneGameBoard(board), ...redoStack]); - setBoard(cloneGameBoard(previousBoard)); + setRedoStack([ + { + board: cloneGameBoard(board), + draftCells: cloneDraftCells(draftCells), + }, + ...redoStack, + ]); + setBoard(cloneGameBoard(previousSnapshot.board)); + setDraftCells(cloneDraftCells(previousSnapshot.draftCells)); }; const handleRedo = () => { if (isGameComplete || redoStack.length === 0) return; - const nextBoard = redoStack[0]; + const nextSnapshot = redoStack[0]; setRedoStack(redoStack.slice(1)); - setUndoStack([...undoStack, cloneGameBoard(board)]); - setBoard(cloneGameBoard(nextBoard)); + setUndoStack([ + ...undoStack, + { + board: cloneGameBoard(board), + draftCells: cloneDraftCells(draftCells), + }, + ]); + setBoard(cloneGameBoard(nextSnapshot.board)); + setDraftCells(cloneDraftCells(nextSnapshot.draftCells)); }; const canUndo = !isGameComplete && undoStack.length > 0; @@ -253,6 +290,7 @@ const SudokuGame: React.FC = () => { const nextRound = createSudokuRound(level, mode); setRound(nextRound); setFixedCells(nextRound.board.map(row => row.map(cell => cell !== 0))); + setDraftCells(createEmptyDraftCells()); setUndoStack([]); setRedoStack([]); setSelectedCell(null); @@ -345,7 +383,7 @@ const SudokuGame: React.FC = () => { window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); - }, [selectedCell, board, undoStack, redoStack, isGameComplete, fixedCells, timeElapsed, regionMap, difficulty]); + }, [selectedCell, board, draftCells, draftMode, undoStack, redoStack, isGameComplete, fixedCells, timeElapsed, regionMap, difficulty]); useEffect(() => { if (!isTimerRunning) return; @@ -462,6 +500,21 @@ const SudokuGame: React.FC = () => { }; }; + const getCellValueStyle = (row: number, col: number): React.CSSProperties => { + const isDraft = draftCells[row][col] && !fixedCells[row][col]; + const size = isDraft ? DRAFT_SIZE : CELL_SIZE; + + return { + width: `${size}px`, + height: `${size}px`, + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: `${isDraft ? DRAFT_FONT_SIZE : CELL_FONT_SIZE}px`, + lineHeight: 1, + }; + }; + return (
{/* 顶部控制和配置区 */} @@ -586,6 +639,14 @@ const SudokuGame: React.FC = () => { /> 显示剩余数量 +
{/* 数独棋盘 */} @@ -611,7 +672,9 @@ const SudokuGame: React.FC = () => { onClick={() => handleCellClick(rowIndex, colIndex)} style={cellStyle} > - {displayValue !== null ? displayValue : ''} + {displayValue !== null ? ( + {displayValue} + ) : ''} ); }) @@ -622,7 +685,7 @@ const SudokuGame: React.FC = () => {
- {selectedCell ? '按键盘 1-9 输入数字' : '选择一个格子后输入数字'} + {selectedCell ? (draftMode ? '草稿模式:按键盘 1-9 试填数字' : '按键盘 1-9 输入数字') : '选择一个格子后输入数字'} 用时:{formatTime(timeElapsed)} @@ -637,7 +700,7 @@ const SudokuGame: React.FC = () => { {/* 快捷键提示 */}
- 快捷键: 1-9 输入数字 | 空格/Backspace 清除 | Ctrl/Cmd+Z 撤销 | Ctrl/Cmd+Y 重做 | ↑↓←→ 移动选择 + 快捷键: 1-9 输入数字/草稿 | 空格/Backspace 清除 | Ctrl/Cmd+Z 撤销 | Ctrl/Cmd+Y 重做 | ↑↓←→ 移动选择
{/* 完成成功对话框 */}