98 lines
2.4 KiB
JavaScript
98 lines
2.4 KiB
JavaScript
function isValid(grid, row, col, num) {
|
|
for (let c = 0; c < 9; c++) if (grid[row][c] === num) return false;
|
|
for (let r = 0; r < 9; r++) if (grid[r][col] === num) return false;
|
|
const br = Math.floor(row / 3) * 3;
|
|
const bc = Math.floor(col / 3) * 3;
|
|
for (let r = br; r < br + 3; r++)
|
|
for (let c = bc; c < bc + 3; c++)
|
|
if (grid[r][c] === num) return false;
|
|
return true;
|
|
}
|
|
|
|
function shuffle(arr) {
|
|
for (let i = arr.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
function fillBox(grid, startRow, startCol) {
|
|
const nums = shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
let idx = 0;
|
|
for (let r = startRow; r < startRow + 3; r++)
|
|
for (let c = startCol; c < startCol + 3; c++)
|
|
grid[r][c] = nums[idx++];
|
|
}
|
|
|
|
function solve(grid) {
|
|
for (let r = 0; r < 9; r++) {
|
|
for (let c = 0; c < 9; c++) {
|
|
if (grid[r][c] === 0) {
|
|
for (const num of shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9])) {
|
|
if (isValid(grid, r, c, num)) {
|
|
grid[r][c] = num;
|
|
if (solve(grid)) return true;
|
|
grid[r][c] = 0;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function generateSolution() {
|
|
const grid = Array.from({ length: 9 }, () => Array(9).fill(0));
|
|
// Seed diagonal boxes first (no inter-box constraints) for faster solve
|
|
fillBox(grid, 0, 0);
|
|
fillBox(grid, 3, 3);
|
|
fillBox(grid, 6, 6);
|
|
solve(grid);
|
|
return grid;
|
|
}
|
|
|
|
function cloneGrid(g) {
|
|
return g.map(row => [...row]);
|
|
}
|
|
|
|
function countGivens(grid) {
|
|
return grid.flat().filter(v => v !== 0).length;
|
|
}
|
|
|
|
const GIVENS = {
|
|
'very-easy': 50,
|
|
'easy': 36,
|
|
'regular': 28,
|
|
'hard': 24,
|
|
'brutal': 18,
|
|
};
|
|
|
|
function digHoles(solution, targetGivens) {
|
|
const grid = cloneGrid(solution);
|
|
const positions = [];
|
|
for (let r = 0; r < 9; r++)
|
|
for (let c = 0; c < 9; c++)
|
|
positions.push([r, c]);
|
|
|
|
shuffle(positions);
|
|
|
|
for (const [r, c] of positions) {
|
|
if (countGivens(grid) <= targetGivens) break;
|
|
const mr = 8 - r, mc = 8 - c;
|
|
if (grid[r][c] === 0 && grid[mr][mc] === 0) continue;
|
|
grid[r][c] = 0;
|
|
if (r !== mr || c !== mc) grid[mr][mc] = 0;
|
|
}
|
|
|
|
return grid;
|
|
}
|
|
|
|
export function generatePuzzle(difficulty) {
|
|
const target = GIVENS[difficulty] ?? 28;
|
|
const solution = generateSolution();
|
|
const grid = digHoles(solution, target);
|
|
return { grid, solution, difficulty };
|
|
}
|