sudoku-pro 1.0.10 → 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.
Files changed (4) hide show
  1. package/cli.js +10 -0
  2. package/esm.mjs +12 -0
  3. package/package.json +52 -45
  4. package/sudoku-pro.js +74 -99
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,59 +1,66 @@
1
1
  {
2
2
  "name": "sudoku-pro",
3
- "version": "1.0.10",
4
- "description": "sudoku-pro humbly presents itself as your go-to toolkit for generating, solving, and printing Sudoku puzzles spanning different difficulty levels. Powered by the reliable backtracking algorithm, it aspires to offer enthusiasts and learners alike a simple yet effective platform to engage with Sudoku puzzles. With Sudoku-Pro, embark on a journey of brain-teasing challenges or delve into educational endeavors with ease. This humble interface aims to make Sudoku puzzle creation and enjoyment accessible to all, ensuring a delightful experience for users of varying skill levels. Join us in exploring the possibilities of Sudoku puzzles while we humbly accompany you on your puzzle-solving adventures.",
5
- "changelog": {
6
- "1.0.6": "generateSudoku, printSudoku, solveSudoku",
7
- "1.0.7": "getHint function added",
8
- "1.0.8": "fixed the bug which cause issue while generating hard sudoku",
9
- "1.0.10": "streamlined the code with concise logic, added very hard sudoku level"
10
- },
11
- "main": "sudoku-pro.js",
12
- "repository": {
13
- "type": "git",
14
- "url": "git+ssh://git@github.com/concurdev/sudoku-pro.git"
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
+ }
15
15
  },
16
- "scripts": {
17
- "easy": "node sudoku-pro e",
18
- "medium": "node sudoku-pro m",
19
- "hard": "node sudoku-pro h",
20
- "very": "node sudoku-pro v"
16
+
17
+ "bin": {
18
+ "sudoku-pro": "./cli.js"
21
19
  },
20
+
22
21
  "files": [
23
- "sudoku-pro.js"
22
+ "sudoku-pro.js",
23
+ "esm.mjs",
24
+ "cli.js",
25
+ "README.md",
26
+ "LICENSE"
24
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
+
25
37
  "keywords": [
26
- "Sudoku",
27
- "Sudoku Algorithm",
28
- "Sudoku Generator",
29
- "Sudoku Solver",
30
- "Sudoku Teaser",
31
- "Sudoku Puzzle",
32
- "Sudoku Engine",
33
- "Sudoku Difficulty",
34
- "Sudoku Logic",
35
- "Easy Sudoku",
36
- "Medium Sudoku",
37
- "Hard Sudoku",
38
- "Very hard Sudoku",
39
- "Brain Teaser",
40
- "Puzzle",
41
- "Puzzle Game",
42
- "Game",
43
- "Board Game",
44
- "Logic Game",
45
- "Numbers Game",
46
- "Recreational Mathematics",
47
- "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"
48
48
  ],
49
- "author": "Ashish Vashisht",
50
- "license": "MIT",
51
- "engines": {
52
- "node": ">= 10"
49
+
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+ssh://git@github.com/concurdev/sudoku-pro.git"
53
53
  },
54
+
54
55
  "bugs": {
55
56
  "url": "https://github.com/concurdev/sudoku-pro/issues"
56
57
  },
58
+
57
59
  "homepage": "https://github.com/concurdev/sudoku-pro#readme",
58
- "devDependencies": {}
60
+
61
+ "engines": {
62
+ "node": ">=14"
63
+ },
64
+
65
+ "sideEffects": false
59
66
  }
package/sudoku-pro.js CHANGED
@@ -1,10 +1,9 @@
1
- const readline = require("readline");
1
+ "use strict";
2
2
 
3
- // Get the difficulty level
4
- const args = process.argv.slice(2);
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 the diagonal blocks (3x3 sub-grids) with valid numbers
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,23 +26,23 @@ function generateSudoku(difficulty) {
27
26
  }
28
27
 
29
28
  // Solve the puzzle
30
- const solvedSudoku = JSON.parse(JSON.stringify(sudoku)); // Create a deep copy for later use
29
+ const solvedSudoku = JSON.parse(JSON.stringify(sudoku));
31
30
  solveSudoku(solvedSudoku);
32
31
 
33
- // Determine the number of cells to remove based on difficulty level
32
+ // Determine the number of cells to remove
34
33
  let numToRemove;
35
34
  switch (difficulty) {
36
35
  case "e":
37
- numToRemove = ensureNumToRemoveInRange(20, 20); // Easy difficulty
36
+ numToRemove = ensureNumToRemoveInRange(20, 20);
38
37
  break;
39
38
  case "m":
40
- numToRemove = ensureNumToRemoveInRange(25, 50); // Medium difficulty
39
+ numToRemove = ensureNumToRemoveInRange(25, 50);
41
40
  break;
42
41
  case "h":
43
- numToRemove = ensureNumToRemoveInRange(30, 60); // Hard difficulty
42
+ numToRemove = ensureNumToRemoveInRange(30, 60);
44
43
  break;
45
44
  case "v":
46
- numToRemove = ensureNumToRemoveInRange(35, 75); // Very hard difficulty
45
+ numToRemove = ensureNumToRemoveInRange(35, 75);
47
46
  break;
48
47
  default:
49
48
  throw new Error(
@@ -51,17 +50,15 @@ function generateSudoku(difficulty) {
51
50
  );
52
51
  }
53
52
 
54
- // Create a copy of the solved Sudoku to remove cells from
55
53
  const sudokuCopy = JSON.parse(JSON.stringify(solvedSudoku));
56
54
 
57
- // Remove some numbers to create the puzzle
58
55
  for (let i = 0; i < numToRemove; i++) {
59
56
  const row = Math.floor(Math.random() * 9);
60
57
  const col = Math.floor(Math.random() * 9);
61
58
  if (sudokuCopy[row][col] !== 0) {
62
59
  sudokuCopy[row][col] = 0;
63
60
  } else {
64
- i--; // Try again if the cell is already empty
61
+ i--;
65
62
  }
66
63
  }
67
64
 
@@ -72,87 +69,61 @@ function ensureNumToRemoveInRange(lowerBound, upperBound) {
72
69
  let numToRemove;
73
70
  do {
74
71
  numToRemove = Math.floor(Math.random() * lowerBound) + upperBound;
75
- } while (numToRemove > 81); // Ensure numToRemove is within the range 0-81
72
+ } while (numToRemove > 81);
76
73
  return numToRemove;
77
74
  }
78
75
 
79
76
  function solveSudoku(sudoku) {
80
77
  const emptyCell = findEmptyCell(sudoku);
81
- if (!emptyCell) {
82
- return true; // Puzzle solved successfully
83
- }
78
+ if (!emptyCell) return true;
84
79
 
85
80
  const [row, col] = emptyCell;
86
81
  for (let num = 1; num <= 9; num++) {
87
82
  if (isValidMove(sudoku, row, col, num)) {
88
83
  sudoku[row][col] = num;
89
- if (solveSudoku(sudoku)) {
90
- return true;
91
- }
92
- sudoku[row][col] = 0; // Undo the assignment
84
+ if (solveSudoku(sudoku)) return true;
85
+ sudoku[row][col] = 0;
93
86
  }
94
87
  }
95
-
96
- return false; // No solution found, backtrack
88
+ return false;
97
89
  }
98
90
 
99
91
  function findEmptyCell(sudoku) {
100
- // Function to find an empty cell in the Sudoku grid
101
92
  for (let row = 0; row < 9; row++) {
102
93
  for (let col = 0; col < 9; col++) {
103
- if (sudoku[row][col] === 0) {
104
- return [row, col];
105
- }
94
+ if (sudoku[row][col] === 0) return [row, col];
106
95
  }
107
96
  }
108
- return null; // No empty cell found
97
+ return null;
109
98
  }
110
99
 
111
100
  function isValidMove(sudoku, row, col, num) {
112
- // Check if assigning num to the cell at (row, col) is a valid move
113
- // Check row
114
101
  for (let i = 0; i < 9; i++) {
115
- if (sudoku[row][i] === num) {
116
- return false; // Number already exists in the row
117
- }
102
+ if (sudoku[row][i] === num) return false;
118
103
  }
119
-
120
- // Check column
121
104
  for (let i = 0; i < 9; i++) {
122
- if (sudoku[i][col] === num) {
123
- return false; // Number already exists in the column
124
- }
105
+ if (sudoku[i][col] === num) return false;
125
106
  }
126
107
 
127
- // Check 3x3 subgrid
128
108
  const startRow = Math.floor(row / 3) * 3;
129
109
  const startCol = Math.floor(col / 3) * 3;
130
110
  for (let i = 0; i < 3; i++) {
131
111
  for (let j = 0; j < 3; j++) {
132
- if (sudoku[startRow + i][startCol + j] === num) {
133
- return false; // Number already exists in the subgrid
134
- }
112
+ if (sudoku[startRow + i][startCol + j] === num) return false;
135
113
  }
136
114
  }
137
-
138
- return true; // Valid move
115
+ return true;
139
116
  }
140
117
 
141
118
  function printSudoku(sudoku) {
142
119
  console.log("Sudoku Puzzle:");
143
120
  console.log("-------------------------");
144
121
  sudoku.forEach((row, rowIndex) => {
145
- if (rowIndex % 3 === 0 && rowIndex !== 0) {
146
- console.log("------|-------|-------");
147
- }
122
+ if (rowIndex % 3 === 0 && rowIndex !== 0) console.log("------|-------|-------");
148
123
  row.forEach((cell, cellIndex) => {
149
- if (cellIndex % 3 === 0 && cellIndex !== 0) {
150
- process.stdout.write("| ");
151
- }
124
+ if (cellIndex % 3 === 0 && cellIndex !== 0) process.stdout.write("| ");
152
125
  process.stdout.write(String(cell || ".") + " ");
153
- if (cellIndex === 8) {
154
- console.log();
155
- }
126
+ if (cellIndex === 8) console.log();
156
127
  });
157
128
  });
158
129
  console.log("-------------------------");
@@ -162,78 +133,82 @@ function printSudokuWithAnswers(sudoku) {
162
133
  console.log("Sudoku Puzzle with Answers:");
163
134
  console.log("-------------------------");
164
135
  sudoku.forEach((row, rowIndex) => {
165
- if (rowIndex % 3 === 0 && rowIndex !== 0) {
166
- console.log("------|-------|-------");
167
- }
136
+ if (rowIndex % 3 === 0 && rowIndex !== 0) console.log("------|-------|-------");
168
137
  row.forEach((cell, cellIndex) => {
169
- if (cellIndex % 3 === 0 && cellIndex !== 0) {
170
- process.stdout.write("| ");
171
- }
138
+ if (cellIndex % 3 === 0 && cellIndex !== 0) process.stdout.write("| ");
172
139
  process.stdout.write(String(cell) + " ");
173
- if (cellIndex === 8) {
174
- console.log();
175
- }
140
+ if (cellIndex === 8) console.log();
176
141
  });
177
142
  });
178
143
  console.log("-------------------------");
179
144
  }
180
145
 
181
- function getHint(sudoku, solvedSudoku) {
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 = {}) {
182
155
  const emptyCells = [];
183
156
  for (let i = 0; i < 9; i++) {
184
157
  for (let j = 0; j < 9; j++) {
185
- if (sudoku[i][j] === 0) {
186
- emptyCells.push([i, j]);
187
- }
158
+ if (sudoku[i][j] === 0) emptyCells.push([i, j]);
188
159
  }
189
160
  }
190
161
  if (emptyCells.length === 0) {
191
- console.log("No empty cells left to provide a hint.");
192
- return;
162
+ if (opts.log) console.log("No empty cells left to provide a hint.");
163
+ return null;
193
164
  }
165
+
194
166
  const randomIndex = Math.floor(Math.random() * emptyCells.length);
195
167
  const [row, col] = emptyCells[randomIndex];
196
- sudoku[row][col] = solvedSudoku[row][col];
197
- console.log(
198
- `Hint: The number at row ${row + 1}, column ${col + 1} is ${
199
- solvedSudoku[row][col]
200
- }`
201
- );
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 };
202
176
  }
203
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
+ */
204
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
+
205
191
  const rl = readline.createInterface({
206
192
  input: process.stdin,
207
193
  output: process.stdout,
208
194
  });
209
195
 
210
- rl.question(
211
- "Press 'h' for a hint or 'c' for the complete solution: ",
212
- (answer) => {
213
- if (answer.trim().toLowerCase() === "h") {
214
- getHint(sudoku, solvedSudoku);
215
- printSudoku(sudoku);
216
- } else if (answer.trim().toLowerCase() === "c") {
217
- printSudokuWithAnswers(solvedSudoku);
218
- process.exit();
219
- } else {
220
- console.log(
221
- "Invalid input. Please press 'h' for a hint or 'c' for the complete solution."
222
- );
223
- }
224
- rl.close();
225
- 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.");
226
206
  }
227
- );
207
+ rl.close();
208
+ waitForHint(sudoku, solvedSudoku);
209
+ });
228
210
  }
229
211
 
230
- const difficulty = args[0]; // Get the difficulty level from command line arguments
231
-
232
- const { sudoku, solvedSudoku } = generateSudoku(difficulty);
233
- printSudoku(sudoku);
234
-
235
- waitForHint(sudoku, solvedSudoku);
236
-
237
212
  module.exports = {
238
213
  generateSudoku,
239
214
  printSudoku,