sudoku-pro 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/cli.js +10 -0
- package/esm.mjs +12 -0
- package/package.json +52 -42
- package/sudoku-pro.js +94 -106
package/README.md
CHANGED
package/cli.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { generateSudoku, printSudoku, waitForHint } = require("./sudoku-pro.js");
|
|
5
|
+
|
|
6
|
+
const difficulty = (process.argv[2] || "m").toLowerCase();
|
|
7
|
+
const { sudoku, solvedSudoku } = generateSudoku(difficulty);
|
|
8
|
+
|
|
9
|
+
printSudoku(sudoku);
|
|
10
|
+
waitForHint(sudoku, solvedSudoku);
|
package/esm.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import cjs from "./sudoku-pro.js";
|
|
2
|
+
|
|
3
|
+
// Re-export as named exports (frontend-friendly)
|
|
4
|
+
export const generateSudoku = cjs.generateSudoku;
|
|
5
|
+
export const printSudoku = cjs.printSudoku;
|
|
6
|
+
export const solveSudoku = cjs.solveSudoku;
|
|
7
|
+
export const printSudokuWithAnswers = cjs.printSudokuWithAnswers;
|
|
8
|
+
export const getHint = cjs.getHint;
|
|
9
|
+
export const waitForHint = cjs.waitForHint;
|
|
10
|
+
|
|
11
|
+
// Optional default export too (nice for interop)
|
|
12
|
+
export default cjs;
|
package/package.json
CHANGED
|
@@ -1,56 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sudoku-pro",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
"version": "1.0.11",
|
|
4
|
+
"description": "Sudoku generator, solver, and CLI tool. Works in terminal and frontend apps with a clean library-first design.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Ashish Vashisht",
|
|
7
|
+
|
|
8
|
+
"main": "./sudoku-pro.js",
|
|
9
|
+
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./esm.mjs",
|
|
13
|
+
"require": "./sudoku-pro.js"
|
|
14
|
+
}
|
|
14
15
|
},
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"hard": "node sudoku-pro h"
|
|
16
|
+
|
|
17
|
+
"bin": {
|
|
18
|
+
"sudoku-pro": "./cli.js"
|
|
19
19
|
},
|
|
20
|
+
|
|
20
21
|
"files": [
|
|
21
|
-
"sudoku-pro.js"
|
|
22
|
+
"sudoku-pro.js",
|
|
23
|
+
"esm.mjs",
|
|
24
|
+
"cli.js",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
22
27
|
],
|
|
28
|
+
|
|
29
|
+
"scripts": {
|
|
30
|
+
"easy": "node cli.js e",
|
|
31
|
+
"medium": "node cli.js m",
|
|
32
|
+
"hard": "node cli.js h",
|
|
33
|
+
"very": "node cli.js v",
|
|
34
|
+
"test": "node -e \"const { generateSudoku } = require('./sudoku-pro'); const g = generateSudoku('m'); console.log(g.sudoku.length === 9 ? 'ok' : 'fail')\""
|
|
35
|
+
},
|
|
36
|
+
|
|
23
37
|
"keywords": [
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"Medium Sudoku",
|
|
35
|
-
"Hard Sudoku",
|
|
36
|
-
"Brain Teaser",
|
|
37
|
-
"Puzzle",
|
|
38
|
-
"Puzzle Game",
|
|
39
|
-
"Game",
|
|
40
|
-
"Board Game",
|
|
41
|
-
"Logic Game",
|
|
42
|
-
"Numbers Game",
|
|
43
|
-
"Recreational Mathematics",
|
|
44
|
-
"Backtracking"
|
|
38
|
+
"sudoku",
|
|
39
|
+
"sudoku-generator",
|
|
40
|
+
"sudoku-solver",
|
|
41
|
+
"sudoku-cli",
|
|
42
|
+
"puzzle",
|
|
43
|
+
"game",
|
|
44
|
+
"logic-game",
|
|
45
|
+
"backtracking",
|
|
46
|
+
"board-game",
|
|
47
|
+
"brain-teaser"
|
|
45
48
|
],
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
"
|
|
49
|
+
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+ssh://git@github.com/concurdev/sudoku-pro.git"
|
|
50
53
|
},
|
|
54
|
+
|
|
51
55
|
"bugs": {
|
|
52
56
|
"url": "https://github.com/concurdev/sudoku-pro/issues"
|
|
53
57
|
},
|
|
58
|
+
|
|
54
59
|
"homepage": "https://github.com/concurdev/sudoku-pro#readme",
|
|
55
|
-
|
|
60
|
+
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=14"
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
"sideEffects": false
|
|
56
66
|
}
|
package/sudoku-pro.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
3
|
+
// NOTE: No top-level require("readline").
|
|
4
|
+
// This file is safe to import in frontend bundlers.
|
|
5
5
|
|
|
6
6
|
function shuffle(array) {
|
|
7
|
-
// Fisher-Yates shuffle algorithm
|
|
8
7
|
for (let i = array.length - 1; i > 0; i--) {
|
|
9
8
|
const j = Math.floor(Math.random() * (i + 1));
|
|
10
9
|
[array[i], array[j]] = [array[j], array[i]];
|
|
@@ -15,7 +14,7 @@ function shuffle(array) {
|
|
|
15
14
|
function generateSudoku(difficulty) {
|
|
16
15
|
const sudoku = Array.from({ length: 9 }, () => Array(9).fill(0));
|
|
17
16
|
|
|
18
|
-
// Fill
|
|
17
|
+
// Fill diagonal blocks
|
|
19
18
|
for (let i = 0; i < 9; i += 3) {
|
|
20
19
|
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
21
20
|
shuffle(nums);
|
|
@@ -27,119 +26,104 @@ function generateSudoku(difficulty) {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
// Solve the puzzle
|
|
30
|
-
const solvedSudoku = JSON.parse(JSON.stringify(sudoku));
|
|
29
|
+
const solvedSudoku = JSON.parse(JSON.stringify(sudoku));
|
|
31
30
|
solveSudoku(solvedSudoku);
|
|
32
31
|
|
|
33
|
-
// Determine the number of cells to remove
|
|
32
|
+
// Determine the number of cells to remove
|
|
34
33
|
let numToRemove;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
switch (difficulty) {
|
|
35
|
+
case "e":
|
|
36
|
+
numToRemove = ensureNumToRemoveInRange(20, 20);
|
|
37
|
+
break;
|
|
38
|
+
case "m":
|
|
39
|
+
numToRemove = ensureNumToRemoveInRange(25, 50);
|
|
40
|
+
break;
|
|
41
|
+
case "h":
|
|
42
|
+
numToRemove = ensureNumToRemoveInRange(30, 60);
|
|
43
|
+
break;
|
|
44
|
+
case "v":
|
|
45
|
+
numToRemove = ensureNumToRemoveInRange(35, 75);
|
|
46
|
+
break;
|
|
47
|
+
default:
|
|
48
|
+
throw new Error(
|
|
49
|
+
'Invalid difficulty level. Please use "e" for easy, "m" for medium, "h" for hard, or "v" for very hard.'
|
|
50
|
+
);
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
// Create a copy of the solved Sudoku to remove cells from
|
|
50
53
|
const sudokuCopy = JSON.parse(JSON.stringify(solvedSudoku));
|
|
51
54
|
|
|
52
|
-
// Remove some numbers to create the puzzle
|
|
53
55
|
for (let i = 0; i < numToRemove; i++) {
|
|
54
56
|
const row = Math.floor(Math.random() * 9);
|
|
55
57
|
const col = Math.floor(Math.random() * 9);
|
|
56
58
|
if (sudokuCopy[row][col] !== 0) {
|
|
57
59
|
sudokuCopy[row][col] = 0;
|
|
58
60
|
} else {
|
|
59
|
-
i--;
|
|
61
|
+
i--;
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
return { sudoku: sudokuCopy, solvedSudoku };
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
function ensureNumToRemoveInRange(lowerBound, upperBound) {
|
|
69
|
+
let numToRemove;
|
|
70
|
+
do {
|
|
71
|
+
numToRemove = Math.floor(Math.random() * lowerBound) + upperBound;
|
|
72
|
+
} while (numToRemove > 81);
|
|
73
|
+
return numToRemove;
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
function solveSudoku(sudoku) {
|
|
67
77
|
const emptyCell = findEmptyCell(sudoku);
|
|
68
|
-
if (!emptyCell)
|
|
69
|
-
return true; // Puzzle solved successfully
|
|
70
|
-
}
|
|
78
|
+
if (!emptyCell) return true;
|
|
71
79
|
|
|
72
80
|
const [row, col] = emptyCell;
|
|
73
81
|
for (let num = 1; num <= 9; num++) {
|
|
74
82
|
if (isValidMove(sudoku, row, col, num)) {
|
|
75
83
|
sudoku[row][col] = num;
|
|
76
|
-
if (solveSudoku(sudoku))
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
sudoku[row][col] = 0; // Undo the assignment
|
|
84
|
+
if (solveSudoku(sudoku)) return true;
|
|
85
|
+
sudoku[row][col] = 0;
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
|
-
|
|
83
|
-
return false; // No solution found, backtrack
|
|
88
|
+
return false;
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
function findEmptyCell(sudoku) {
|
|
87
|
-
// Function to find an empty cell in the Sudoku grid
|
|
88
92
|
for (let row = 0; row < 9; row++) {
|
|
89
93
|
for (let col = 0; col < 9; col++) {
|
|
90
|
-
if (sudoku[row][col] === 0)
|
|
91
|
-
return [row, col];
|
|
92
|
-
}
|
|
94
|
+
if (sudoku[row][col] === 0) return [row, col];
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
|
-
return null;
|
|
97
|
+
return null;
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
function isValidMove(sudoku, row, col, num) {
|
|
99
|
-
// Check if assigning num to the cell at (row, col) is a valid move
|
|
100
|
-
// Check row
|
|
101
101
|
for (let i = 0; i < 9; i++) {
|
|
102
|
-
if (sudoku[row][i] === num)
|
|
103
|
-
return false; // Number already exists in the row
|
|
104
|
-
}
|
|
102
|
+
if (sudoku[row][i] === num) return false;
|
|
105
103
|
}
|
|
106
|
-
|
|
107
|
-
// Check column
|
|
108
104
|
for (let i = 0; i < 9; i++) {
|
|
109
|
-
if (sudoku[i][col] === num)
|
|
110
|
-
return false; // Number already exists in the column
|
|
111
|
-
}
|
|
105
|
+
if (sudoku[i][col] === num) return false;
|
|
112
106
|
}
|
|
113
107
|
|
|
114
|
-
// Check 3x3 subgrid
|
|
115
108
|
const startRow = Math.floor(row / 3) * 3;
|
|
116
109
|
const startCol = Math.floor(col / 3) * 3;
|
|
117
110
|
for (let i = 0; i < 3; i++) {
|
|
118
111
|
for (let j = 0; j < 3; j++) {
|
|
119
|
-
if (sudoku[startRow + i][startCol + j] === num)
|
|
120
|
-
return false; // Number already exists in the subgrid
|
|
121
|
-
}
|
|
112
|
+
if (sudoku[startRow + i][startCol + j] === num) return false;
|
|
122
113
|
}
|
|
123
114
|
}
|
|
124
|
-
|
|
125
|
-
return true; // Valid move
|
|
115
|
+
return true;
|
|
126
116
|
}
|
|
127
117
|
|
|
128
118
|
function printSudoku(sudoku) {
|
|
129
119
|
console.log("Sudoku Puzzle:");
|
|
130
120
|
console.log("-------------------------");
|
|
131
121
|
sudoku.forEach((row, rowIndex) => {
|
|
132
|
-
if (rowIndex % 3 === 0 && rowIndex !== 0)
|
|
133
|
-
console.log("------|-------|-------");
|
|
134
|
-
}
|
|
122
|
+
if (rowIndex % 3 === 0 && rowIndex !== 0) console.log("------|-------|-------");
|
|
135
123
|
row.forEach((cell, cellIndex) => {
|
|
136
|
-
if (cellIndex % 3 === 0 && cellIndex !== 0)
|
|
137
|
-
process.stdout.write("| ");
|
|
138
|
-
}
|
|
124
|
+
if (cellIndex % 3 === 0 && cellIndex !== 0) process.stdout.write("| ");
|
|
139
125
|
process.stdout.write(String(cell || ".") + " ");
|
|
140
|
-
if (cellIndex === 8)
|
|
141
|
-
console.log();
|
|
142
|
-
}
|
|
126
|
+
if (cellIndex === 8) console.log();
|
|
143
127
|
});
|
|
144
128
|
});
|
|
145
129
|
console.log("-------------------------");
|
|
@@ -149,78 +133,82 @@ function printSudokuWithAnswers(sudoku) {
|
|
|
149
133
|
console.log("Sudoku Puzzle with Answers:");
|
|
150
134
|
console.log("-------------------------");
|
|
151
135
|
sudoku.forEach((row, rowIndex) => {
|
|
152
|
-
if (rowIndex % 3 === 0 && rowIndex !== 0)
|
|
153
|
-
console.log("------|-------|-------");
|
|
154
|
-
}
|
|
136
|
+
if (rowIndex % 3 === 0 && rowIndex !== 0) console.log("------|-------|-------");
|
|
155
137
|
row.forEach((cell, cellIndex) => {
|
|
156
|
-
if (cellIndex % 3 === 0 && cellIndex !== 0)
|
|
157
|
-
process.stdout.write("| ");
|
|
158
|
-
}
|
|
138
|
+
if (cellIndex % 3 === 0 && cellIndex !== 0) process.stdout.write("| ");
|
|
159
139
|
process.stdout.write(String(cell) + " ");
|
|
160
|
-
if (cellIndex === 8)
|
|
161
|
-
console.log();
|
|
162
|
-
}
|
|
140
|
+
if (cellIndex === 8) console.log();
|
|
163
141
|
});
|
|
164
142
|
});
|
|
165
143
|
console.log("-------------------------");
|
|
166
144
|
}
|
|
167
145
|
|
|
168
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Browser-friendly hint:
|
|
148
|
+
* - same logic (fill a random empty cell)
|
|
149
|
+
* - DOES NOT console.log by default
|
|
150
|
+
* - Returns {row,col,value} or null
|
|
151
|
+
*
|
|
152
|
+
* If you want old behavior (printing), pass { log: true }.
|
|
153
|
+
*/
|
|
154
|
+
function getHint(sudoku, solvedSudoku, opts = {}) {
|
|
169
155
|
const emptyCells = [];
|
|
170
156
|
for (let i = 0; i < 9; i++) {
|
|
171
157
|
for (let j = 0; j < 9; j++) {
|
|
172
|
-
if (sudoku[i][j] === 0)
|
|
173
|
-
emptyCells.push([i, j]);
|
|
174
|
-
}
|
|
158
|
+
if (sudoku[i][j] === 0) emptyCells.push([i, j]);
|
|
175
159
|
}
|
|
176
160
|
}
|
|
177
161
|
if (emptyCells.length === 0) {
|
|
178
|
-
console.log("No empty cells left to provide a hint.");
|
|
179
|
-
return;
|
|
162
|
+
if (opts.log) console.log("No empty cells left to provide a hint.");
|
|
163
|
+
return null;
|
|
180
164
|
}
|
|
165
|
+
|
|
181
166
|
const randomIndex = Math.floor(Math.random() * emptyCells.length);
|
|
182
167
|
const [row, col] = emptyCells[randomIndex];
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}`
|
|
188
|
-
|
|
168
|
+
const value = solvedSudoku[row][col];
|
|
169
|
+
sudoku[row][col] = value;
|
|
170
|
+
|
|
171
|
+
if (opts.log) {
|
|
172
|
+
console.log(`Hint: The number at row ${row + 1}, column ${col + 1} is ${value}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { row, col, value };
|
|
189
176
|
}
|
|
190
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Terminal-only function. Safe for bundlers because it loads readline dynamically.
|
|
180
|
+
* If you call it in the browser, it throws a clean error.
|
|
181
|
+
*/
|
|
191
182
|
function waitForHint(sudoku, solvedSudoku) {
|
|
183
|
+
if (typeof process === "undefined" || !process.stdin) {
|
|
184
|
+
throw new Error("waitForHint() is terminal-only (Node.js). Use getHint() in the browser.");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Lazy require so browser builds don't crash
|
|
188
|
+
// eslint-disable-next-line global-require
|
|
189
|
+
const readline = require("readline");
|
|
190
|
+
|
|
192
191
|
const rl = readline.createInterface({
|
|
193
192
|
input: process.stdin,
|
|
194
193
|
output: process.stdout,
|
|
195
194
|
});
|
|
196
195
|
|
|
197
|
-
rl.question(
|
|
198
|
-
|
|
199
|
-
(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
console.log(
|
|
208
|
-
"Invalid input. Please press 'h' for a hint or 'c' for the complete solution."
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
rl.close();
|
|
212
|
-
waitForHint(sudoku, solvedSudoku); // Ask again for hint or complete solution
|
|
196
|
+
rl.question("Press 'h' for a hint or 'c' for the complete solution: ", (answer) => {
|
|
197
|
+
const a = answer.trim().toLowerCase();
|
|
198
|
+
if (a === "h") {
|
|
199
|
+
getHint(sudoku, solvedSudoku, { log: true });
|
|
200
|
+
printSudoku(sudoku);
|
|
201
|
+
} else if (a === "c") {
|
|
202
|
+
printSudokuWithAnswers(solvedSudoku);
|
|
203
|
+
process.exit(0);
|
|
204
|
+
} else {
|
|
205
|
+
console.log("Invalid input. Please press 'h' for a hint or 'c' for the complete solution.");
|
|
213
206
|
}
|
|
214
|
-
|
|
207
|
+
rl.close();
|
|
208
|
+
waitForHint(sudoku, solvedSudoku);
|
|
209
|
+
});
|
|
215
210
|
}
|
|
216
211
|
|
|
217
|
-
const difficulty = args[0]; // Get the difficulty level from command line arguments
|
|
218
|
-
|
|
219
|
-
const { sudoku, solvedSudoku } = generateSudoku(difficulty);
|
|
220
|
-
printSudoku(sudoku);
|
|
221
|
-
|
|
222
|
-
waitForHint(sudoku, solvedSudoku);
|
|
223
|
-
|
|
224
212
|
module.exports = {
|
|
225
213
|
generateSudoku,
|
|
226
214
|
printSudoku,
|