added draft mode
This commit is contained in:
@@ -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<SudokuRound>(() => createSudokuRound('hard', 'standard'));
|
||||
const { board, regionMap } = round;
|
||||
const [undoStack, setUndoStack] = useState<GameBoard[]>([]);
|
||||
const [redoStack, setRedoStack] = useState<GameBoard[]>([]);
|
||||
const [draftCells, setDraftCells] = useState<DraftCells>(() => createEmptyDraftCells());
|
||||
const [draftMode, setDraftMode] = useState(false);
|
||||
const [undoStack, setUndoStack] = useState<BoardSnapshot[]>([]);
|
||||
const [redoStack, setRedoStack] = useState<BoardSnapshot[]>([]);
|
||||
|
||||
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<boolean[][]>(() =>
|
||||
@@ -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 (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '20px', padding: '20px' }}>
|
||||
{/* 顶部控制和配置区 */}
|
||||
@@ -586,6 +639,14 @@ const SudokuGame: React.FC = () => {
|
||||
/>
|
||||
显示剩余数量
|
||||
</label>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={draftMode}
|
||||
onChange={(e) => setDraftMode(e.target.checked)}
|
||||
/>
|
||||
草稿模式
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* 数独棋盘 */}
|
||||
@@ -611,7 +672,9 @@ const SudokuGame: React.FC = () => {
|
||||
onClick={() => handleCellClick(rowIndex, colIndex)}
|
||||
style={cellStyle}
|
||||
>
|
||||
{displayValue !== null ? displayValue : ''}
|
||||
{displayValue !== null ? (
|
||||
<span style={getCellValueStyle(rowIndex, colIndex)}>{displayValue}</span>
|
||||
) : ''}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
@@ -622,7 +685,7 @@ const SudokuGame: React.FC = () => {
|
||||
<div style={{ display: 'flex', gap: '30px', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'center' }}>
|
||||
<div style={{ padding: '15px 25px', backgroundColor: '#f5f5f5', borderRadius: '8px', textAlign: 'center' }}>
|
||||
<span style={{ fontSize: '14px', color: '#666', display: 'block', marginBottom: '8px' }}>
|
||||
{selectedCell ? '按键盘 1-9 输入数字' : '选择一个格子后输入数字'}
|
||||
{selectedCell ? (draftMode ? '草稿模式:按键盘 1-9 试填数字' : '按键盘 1-9 输入数字') : '选择一个格子后输入数字'}
|
||||
</span>
|
||||
<span style={{ fontSize: '16px', color: '#666', display: 'block', marginBottom: '8px' }}>
|
||||
用时:<strong style={{ color: '#1890ff', fontSize: '20px' }}>{formatTime(timeElapsed)}</strong>
|
||||
@@ -637,7 +700,7 @@ const SudokuGame: React.FC = () => {
|
||||
|
||||
{/* 快捷键提示 */}
|
||||
<div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#f9f9f9', borderRadius: '4px', fontSize: '12px', color: '#666' }}>
|
||||
<strong>快捷键:</strong> 1-9 输入数字 | 空格/Backspace 清除 | Ctrl/Cmd+Z 撤销 | Ctrl/Cmd+Y 重做 | ↑↓←→ 移动选择
|
||||
<strong>快捷键:</strong> 1-9 输入数字/草稿 | 空格/Backspace 清除 | Ctrl/Cmd+Z 撤销 | Ctrl/Cmd+Y 重做 | ↑↓←→ 移动选择
|
||||
</div>
|
||||
|
||||
{/* 完成成功对话框 */}
|
||||
|
||||
Reference in New Issue
Block a user