UNPKG

createsudokupuzzle

Version:
136 lines (135 loc) 3.89 kB
// src/index.ts function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } function isUnique(arr) { const nums = arr.filter((num) => num > 0); return new Set(nums).size === nums.length; } function findFirstEmpty(grid) { for (let row = 0; row < 9; row++) { for (let col = 0; col < 9; col++) { if (grid[row][col] === 0) { return [row, col]; } } } return []; } function findAllEmpty(grid) { const emptyCells = []; for (let row = 0; row < 9; row++) { for (let col = 0; col < 9; col++) { if (grid[row][col] === 0) { emptyCells.push([row, col]); } } } return emptyCells; } function validatePlacement(grid, row, col, num, outputReason = false) { const boxRow = Math.floor(row / 3) * 3; const boxCol = Math.floor(col / 3) * 3; if (grid[row].includes(num)) { const reason = `Number ${num} already exists in row ${row}`; if (outputReason) return { valid: false, reason }; return false; } if (grid.some((r) => r[col] === num)) { const reason = `Number ${num} already exists in column ${col}`; if (outputReason) return { valid: false, reason }; return false; } if (grid.slice(boxRow, boxRow + 3).some((r) => r.slice(boxCol, boxCol + 3).includes(num))) { const reason = `Number ${num} already exists in box starting at (${boxRow}, ${boxCol})`; if (outputReason) return { valid: false, reason }; return false; } if (outputReason) return { valid: true }; return true; } function validate(puzzle, allowZero = false) { if (puzzle.length !== 9 || puzzle.some((row) => row.length !== 9)) { throw new Error("Invalid puzzle size"); } for (let i = 0; i < 9; i++) { const row = puzzle[i]; const col = puzzle.map((row2) => row2[i]); const box = []; const boxRow = Math.floor(i / 3) * 3; const boxCol = i % 3 * 3; for (let r = 0; r < 3; r++) { for (let c = 0; c < 3; c++) { box.push(puzzle[boxRow + r][boxCol + c]); } } if (!isUnique(row) || !isUnique(col) || !isUnique(box)) return false; } return allowZero || puzzle.every((row) => row.every((cell) => cell > 0)); } function generate(blank = 0) { if (blank < 0 || blank > 81) { throw new Error("Invalid number of blanks"); } const grid = Array.from({ length: 9 }, () => Array(9).fill(0)); const numbers = Array.from({ length: 9 }, (_, i) => i + 1); function fillGrid(grid2) { const emptyCell = findFirstEmpty(grid2); if (emptyCell.length === 0) return true; const [row, col] = emptyCell; shuffle(numbers); for (const num of numbers) { if (validatePlacement(grid2, row, col, num)) { grid2[row][col] = num; if (fillGrid(grid2)) return true; grid2[row][col] = 0; } } return false; } fillGrid(grid); while (blank > 0) { const row = Math.floor(Math.random() * 9); const col = Math.floor(Math.random() * 9); if (grid[row][col] !== 0) { grid[row][col] = 0; blank--; } } return grid; } function solve(puzzle) { if (!validate(puzzle, true)) { throw new Error("Invalid puzzle"); } function solveHelper(grid) { const emptyCell = findFirstEmpty(grid); if (emptyCell.length === 0) return true; const [row, col] = emptyCell; for (let num = 1; num <= 9; num++) { if (validatePlacement(grid, row, col, num)) { grid[row][col] = num; if (solveHelper(grid)) return true; grid[row][col] = 0; } } return false; } const gridCopy = puzzle.map((row) => [...row]); if (!solveHelper(gridCopy)) { throw new Error("Unsolvable puzzle"); } return gridCopy; } export { findAllEmpty, findFirstEmpty, generate, solve, validate, validatePlacement };