createsudokupuzzle
Version:
Generate and solve sudoku puzzles easily!
136 lines (135 loc) • 3.89 kB
JavaScript
// 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
};