@luckyfoxdesign/sudoku-generator 1.0.3 → 1.1.0

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 CHANGED
@@ -1,14 +1,15 @@
1
1
  # Sudoku Generator
2
2
 
3
- A lightweight Sudoku puzzle generator that creates complete solutions and playable puzzles using a backtracking algorithm. Works in browser and Node.js environments.
3
+ A lightweight Sudoku puzzle generator that creates complete solutions and playable puzzles with controlled difficulty. Works in browser and Node.js environments.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@luckyfoxdesign/sudoku-generator)](https://www.npmjs.com/package/@luckyfoxdesign/sudoku-generator) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
7
  ## Features
8
8
 
9
9
  - 🎲 Generates randomized, valid Sudoku puzzles and solutions
10
- - 🧩 Creates playable puzzles with ~50% cells removed
11
- - Fast generation using backtracking algorithm
10
+ - 🎯 Difficulty-based generation: easy, medium, hard, expert
11
+ - 🧩 Creates playable puzzles with controlled cell removal
12
+ - ⚡ Fast generation using backtracking + constraint solving
12
13
  - 🌐 Works in browser and Node.js
13
14
  - 📦 Zero dependencies
14
15
  - 🔧 Multiple export formats (ESM, CommonJS, IIFE)
@@ -23,21 +24,18 @@ npm install @luckyfoxdesign/sudoku-generator
23
24
  ## Quick Start
24
25
 
25
26
  ```javascript
26
- import { generateSudokuGrid, generateCompleteSudokuGrid } from '@luckyfoxdesign/sudoku-generator';
27
-
28
- // Generate a puzzle (with empty cells marked as 0)
29
- const puzzle = generateSudokuGrid();
30
- console.log(puzzle);
31
- // [[5, 0, 4, 6, 0, 8, 9, 0, 2],
32
- // [6, 7, 0, 1, 9, 0, 3, 4, 0],
33
- // ...]
34
-
35
- // Generate a complete solution (all cells filled)
36
- const solution = generateCompleteSudokuGrid();
37
- console.log(solution);
38
- // [[5, 3, 4, 6, 7, 8, 9, 1, 2],
39
- // [6, 7, 2, 1, 9, 5, 3, 4, 8],
40
- // ...]
27
+ import { generateSudoku, generateSudokuGrid, generateCompleteSudokuGrid } from '@luckyfoxdesign/sudoku-generator';
28
+
29
+ // Generate a puzzle with controlled difficulty
30
+ const { puzzle, solution, difficulty, score } = generateSudoku('hard');
31
+ console.log(difficulty); // 'hard'
32
+ console.log(score); // 41-80
33
+ // puzzle: [[5, 0, 0, 6, 0, 0, 9, 0, 2], ...] — zeros are empty cells
34
+ // solution: [[5, 3, 4, 6, 7, 8, 9, 1, 2], ...] — complete grid
35
+
36
+ // Or generate a simple puzzle (legacy API)
37
+ const simplePuzzle = generateSudokuGrid();
38
+ // [[5, 0, 4, 6, 0, 8, 9, 0, 2], ...]
41
39
  ```
42
40
 
43
41
  ## Usage
@@ -53,13 +51,12 @@ console.log(solution);
53
51
  <body>
54
52
  <script src="https://unpkg.com/@luckyfoxdesign/sudoku-generator/dist/index.global.js"></script>
55
53
  <script>
56
- // Generate a puzzle
57
- const puzzle = Sudoku.generateSudokuGrid();
58
- console.log(puzzle);
59
-
60
- // Generate a complete solution
61
- const solution = Sudoku.generateCompleteSudokuGrid();
62
- console.log(solution);
54
+ // Generate a puzzle with difficulty
55
+ const { puzzle, solution, difficulty } = Sudoku.generateSudoku('medium');
56
+ console.log(difficulty); // 'medium'
57
+
58
+ // Legacy API still works
59
+ const simplePuzzle = Sudoku.generateSudokuGrid();
63
60
  </script>
64
61
  </body>
65
62
  </html>
@@ -68,14 +65,14 @@ console.log(solution);
68
65
  ### React / Vue / Svelte
69
66
 
70
67
  ```javascript
71
- import { generateSudokuGrid } from '@luckyfoxdesign/sudoku-generator';
68
+ import { generateSudoku } from '@luckyfoxdesign/sudoku-generator';
72
69
 
73
70
  function SudokuGame() {
74
- const [grid, setGrid] = useState(generateSudokuGrid());
75
-
71
+ const [{ puzzle, solution }] = useState(() => generateSudoku('medium'));
72
+
76
73
  return (
77
74
  <div>
78
- {grid.map((row, i) => (
75
+ {puzzle.map((row, i) => (
79
76
  <div key={i}>
80
77
  {row.map((cell, j) => (
81
78
  <span key={j}>
@@ -92,23 +89,48 @@ function SudokuGame() {
92
89
  ### Node.js (ESM)
93
90
 
94
91
  ```javascript
95
- import { generateSudokuGrid, generateCompleteSudokuGrid } from '@luckyfoxdesign/sudoku-generator';
92
+ import { generateSudoku, generateSudokuGrid, generateCompleteSudokuGrid } from '@luckyfoxdesign/sudoku-generator';
96
93
 
97
- const puzzle = generateSudokuGrid();
98
- const solution = generateCompleteSudokuGrid();
94
+ const { puzzle, solution, difficulty, score } = generateSudoku('expert');
99
95
  ```
100
96
 
101
97
  ### Node.js (CommonJS)
102
98
 
103
99
  ```javascript
104
- const { generateSudokuGrid, generateCompleteSudokuGrid } = require('@luckyfoxdesign/sudoku-generator');
100
+ const { generateSudoku, generateSudokuGrid, generateCompleteSudokuGrid } = require('@luckyfoxdesign/sudoku-generator');
105
101
 
106
- const puzzle = generateSudokuGrid();
107
- const solution = generateCompleteSudokuGrid();
102
+ const { puzzle, solution, difficulty, score } = generateSudoku('hard');
108
103
  ```
109
104
 
110
105
  ## API
111
106
 
107
+ ### `generateSudoku(difficulty?)`
108
+
109
+ Generates a Sudoku puzzle with controlled difficulty. Returns both the puzzle and solution together.
110
+
111
+ **Parameters:**
112
+ - `difficulty` — `'easy' | 'medium' | 'hard' | 'expert'` (default: `'easy'`)
113
+
114
+ **Returns:** `{ puzzle: number[][], solution: number[][], difficulty: string, score: number }`
115
+
116
+ | Difficulty | Score range | Description |
117
+ |------------|-------------|-------------|
118
+ | `easy` | 0–15 | Solved by naked singles only |
119
+ | `medium` | 16–40 | Requires hidden singles |
120
+ | `hard` | 41–80 | Requires more advanced techniques |
121
+ | `expert` | 81+ | Requires complex constraint solving |
122
+
123
+ **Example:**
124
+ ```javascript
125
+ const { puzzle, solution, difficulty, score } = generateSudoku('hard');
126
+ console.log(difficulty); // 'hard'
127
+ console.log(score); // e.g. 57
128
+ console.log(puzzle[0]); // [5, 0, 0, 6, 0, 0, 9, 0, 2]
129
+ console.log(solution[0]); // [5, 3, 4, 6, 7, 8, 9, 1, 2]
130
+ ```
131
+
132
+ ---
133
+
112
134
  ### `generateSudokuGrid()`
113
135
 
114
136
  Generates a playable Sudoku puzzle with some cells removed (marked as 0).
@@ -159,12 +181,21 @@ console.log(grid[0][0].removedValues); // [2, 7]
159
181
 
160
182
  ## Puzzle Generation Details
161
183
 
162
- ### Cell Removal Strategy
184
+ ### Difficulty-based generation (`generateSudoku`)
163
185
 
164
- When generating puzzles:
186
+ Puzzles are generated by iteratively removing cells and scoring the result using a constraint-solving engine. The score is determined by the techniques required to solve the puzzle:
187
+
188
+ - **Naked singles** — only one candidate in a cell (low weight)
189
+ - **Hidden singles** — only one cell in a unit can hold a value (medium weight)
190
+ - **More advanced techniques** — pointing pairs, naked/hidden subsets, etc. (higher weights)
191
+
192
+ Cells are removed until the puzzle's score falls within the target difficulty range. If the target cannot be reached within a time limit, the closest available result is returned.
193
+
194
+ ### Legacy cell removal strategy (`generateSudokuGrid`)
195
+
196
+ When generating puzzles with the legacy API:
165
197
  - **~50% of cells** are randomly removed
166
198
  - **Columns 0, 3, and 6** (first column of each 3×3 block) are **never removed**
167
- - This ensures structural integrity and solvability
168
199
 
169
200
  ### Example Grid Structure
170
201
 
@@ -211,6 +242,7 @@ npm test
211
242
 
212
243
  Tests cover:
213
244
  - ✅ Puzzle generation (with empty cells)
245
+ - ✅ Difficulty-based generation (all 4 levels)
214
246
  - ✅ Complete solution generation (no empty cells)
215
247
  - ✅ Grid structure validation
216
248
  - ✅ Sudoku rules (rows, columns, 3×3 blocks)
@@ -275,23 +307,21 @@ Check the package:
275
307
 
276
308
  ### Generation Algorithm
277
309
 
278
- The generator uses a **backtracking algorithm** to fill the grid:
279
-
280
310
  1. **Generate Complete Solution:**
281
- - Start with an empty 9×9 grid
282
- - For each cell (left to right, top to bottom):
283
- - Try a random number from 1-9
284
- - Check if it's valid (no duplicates in row, column, or 3×3 block)
285
- - If valid, move to next cell
286
- - If no valid number exists, backtrack to previous cell
287
- - Repeat until the entire grid is filled
311
+ - Uses a backtracking algorithm on an empty 9×9 grid
312
+ - Tries random numbers 1–9 per cell; backtracks on conflicts
313
+ - Produces a guaranteed valid, fully filled solution
314
+
315
+ 2. **Create Puzzle:**
316
+ - **`generateSudoku(difficulty)`** removes cells one by one, scores the result using a constraint solver, and stops when the score hits the target range
317
+ - **`generateSudokuGrid()`** removes ~50% of cells randomly, preserving columns 0, 3, and 6
288
318
 
289
- 2. **Create Puzzle (optional):**
290
- - Remove approximately 50% of cells randomly
291
- - Preserve columns 0, 3, and 6 for structure
292
- - Empty cells are marked as 0
319
+ 3. **Difficulty Scoring:**
320
+ - The solver applies human-like techniques (naked singles, hidden singles, pointing pairs, etc.)
321
+ - Each technique has a weight; score = sum of (weight × applications)
322
+ - Score determines difficulty: easy (0–15), medium (16–40), hard (41–80), expert (81+)
293
323
 
294
- This ensures every generated grid is a complete, valid Sudoku solution, and every puzzle is solvable.
324
+ Every generated puzzle has a unique solution.
295
325
 
296
326
  ## Performance
297
327
 
@@ -311,19 +341,20 @@ This ensures every generated grid is a complete, valid Sudoku solution, and ever
311
341
 
312
342
  ### Game Development
313
343
  ```javascript
314
- import { generateSudokuGrid, generateCompleteSudokuGrid } from '@luckyfoxdesign/sudoku-generator';
344
+ import { generateSudoku } from '@luckyfoxdesign/sudoku-generator';
315
345
 
316
- const puzzle = generateSudokuGrid(); // For player to solve
317
- const solution = generateCompleteSudokuGrid(); // For validation
346
+ // Puzzle and solution in one call, matched by difficulty
347
+ const { puzzle, solution, difficulty } = generateSudoku('medium');
348
+ // Use puzzle for the board, solution for validation
318
349
  ```
319
350
 
320
351
  ### Puzzle Books / Print
321
352
  ```javascript
322
- import { generateCompleteSudokuGrid } from '@luckyfoxdesign/sudoku-generator';
353
+ import { generateSudoku } from '@luckyfoxdesign/sudoku-generator';
323
354
 
324
- // Generate 100 unique puzzles
355
+ // Generate 100 hard puzzles
325
356
  for (let i = 0; i < 100; i++) {
326
- const puzzle = generateSudokuGrid();
357
+ const { puzzle } = generateSudoku('hard');
327
358
  printPuzzle(puzzle);
328
359
  }
329
360
  ```
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
- var r=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var g=(t,e)=>{for(var n in e)r(t,n,{get:e[n],enumerable:!0})},M=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of S(e))!x.call(t,s)&&s!==n&&r(t,s,{get:()=>e[s],enumerable:!(o=d(e,s))||o.enumerable});return t};var k=t=>M(r({},"__esModule",{value:!0}),t);var z={};g(z,{generateCompleteSudokuGrid:()=>I,generateSudokuGrid:()=>v,generateSudokuGridWithMetadata:()=>h,getBlockRange:()=>a,isInBlock:()=>p,isInColumn:()=>m,isInRow:()=>V});module.exports=k(z);/**
1
+ var l=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var S=Object.prototype.hasOwnProperty;var g=(t,e)=>{for(var n in e)l(t,n,{get:e[n],enumerable:!0})},y=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of p(e))!S.call(t,s)&&s!==n&&l(t,s,{get:()=>e[s],enumerable:!(o=d(e,s))||o.enumerable});return t};var z=t=>y(l({},"__esModule",{value:!0}),t);var D={};g(D,{generateCompleteSudokuGrid:()=>m,generateSudoku:()=>x,generateSudokuGrid:()=>k,generateSudokuGridWithMetadata:()=>V,getBlockRange:()=>r});module.exports=z(D);var h=require("./solver.js");/**
2
2
  * @luckyfoxdesign/sudoku-generator
3
3
  * Generates complete, valid 9x9 Sudoku grids using backtracking algorithm
4
4
  *
5
5
  * @license MIT
6
6
  * @author Lucky Fox Design <luckyfoxinthebox@gmail.com>
7
7
  * @see https://github.com/luckyfoxdesign/sudoku-generator
8
- */function v(){return h().map(e=>e.map(n=>n.chosenValue))}function I(){const t=i();for(let e=0;e<81;e++){const n=Math.floor(e/9),o=e%9;c(n,o,t)===0&&(e-=2,e<-1&&(e=-1))}return t.map(e=>e.map(n=>n.chosenValue))}function h(){const t=i();for(let e=0;e<81;e++){const n=Math.floor(e/9),o=e%9;c(n,o,t)===0&&(e-=2,e<-1&&(e=-1))}return B(t),t}function i(){const t=[];for(let e=0;e<9;e++){t[e]=[];for(let n=0;n<9;n++)t[e][n]={chosenValue:0,removedValues:[],gameSet:new Set([1,2,3,4,5,6,7,8,9])}}return t}function y(){return Math.random()<.5}function B(t){for(let e=0;e<t.length;e++)for(let n=0;n<t[e].length;n++)y()&&n%3!==0&&(t[e][n].chosenValue=0)}function c(t,e,n){for(;;){if(j(t,e,n))return b(t,e,n),0;const o=E(t,e,n);if(!V(o,t,e,n)&&!m(o,t,e,n)&&!p(o,t,e,n))return 1}}function V(t,e,n,o){for(let s=0;s<n;s++)if(o[e][s].chosenValue===t)return!0;return!1}function m(t,e,n,o){for(let s=0;s<e;s++)if(o[s][n].chosenValue===t)return!0;return!1}function p(t,e,n,o){const s=a(e),u=a(n);for(const f of s)for(const l of u)if(!(f>e||f===e&&l>=n)&&o[f][l].chosenValue===t)return!0;return!1}function a(t){return t>=0&&t<=2?[0,1,2]:t>=3&&t<=5?[3,4,5]:[6,7,8]}function E(t,e,n){const o=[...n[t][e].gameSet],s=Math.floor(Math.random()*o.length),u=o[s];return n[t][e].gameSet.delete(u),n[t][e].removedValues.push(u),n[t][e].chosenValue=u,u}function b(t,e,n){n[t][e].removedValues.forEach(o=>{n[t][e].gameSet.add(o)}),n[t][e].removedValues.length=0,n[t][e].chosenValue=0}function j(t,e,n){return n[t][e].gameSet.size===0&&n[t][e].removedValues.length===9}0&&(module.exports={generateCompleteSudokuGrid,generateSudokuGrid,generateSudokuGridWithMetadata,getBlockRange,isInBlock,isInColumn,isInRow});
8
+ */function k(){return V().map(e=>e.map(n=>n.chosenValue))}function x(t="easy"){const e=m(),n=(0,h.generatePuzzle)(e,t);return{puzzle:n.puzzle,solution:n.solution,difficulty:n.difficulty,score:n.score}}function m(){const t=i();for(let e=0;e<81;e++){const n=Math.floor(e/9),o=e%9;c(n,o,t)===0&&(e-=2,e<-1&&(e=-1))}return t.map(e=>e.map(n=>n.chosenValue))}function V(){const t=i();for(let e=0;e<81;e++){const n=Math.floor(e/9),o=e%9;c(n,o,t)===0&&(e-=2,e<-1&&(e=-1))}return v(t),t}function i(){const t=[];for(let e=0;e<9;e++){t[e]=[];for(let n=0;n<9;n++)t[e][n]={chosenValue:0,removedValues:[],gameSet:new Set([1,2,3,4,5,6,7,8,9])}}return t}function M(){return Math.random()<.5}function v(t){for(let e=0;e<t.length;e++)for(let n=0;n<t[e].length;n++)M()&&n%3!==0&&(t[e][n].chosenValue=0)}function c(t,e,n){for(;;){if(A(t,e,n))return j(t,e,n),0;const o=b(t,e,n);if(!I(o,t,e,n)&&!B(o,t,e,n)&&!E(o,t,e,n))return 1}}function I(t,e,n,o){for(let s=0;s<n;s++)if(o[e][s].chosenValue===t)return!0;return!1}function B(t,e,n,o){for(let s=0;s<e;s++)if(o[s][n].chosenValue===t)return!0;return!1}function E(t,e,n,o){const s=r(e),u=r(n);for(const f of s)for(const a of u)if(!(f>e||f===e&&a>=n)&&o[f][a].chosenValue===t)return!0;return!1}function r(t){return t>=0&&t<=2?[0,1,2]:t>=3&&t<=5?[3,4,5]:[6,7,8]}function b(t,e,n){const o=[...n[t][e].gameSet],s=Math.floor(Math.random()*o.length),u=o[s];return n[t][e].gameSet.delete(u),n[t][e].removedValues.push(u),n[t][e].chosenValue=u,u}function j(t,e,n){n[t][e].removedValues.forEach(o=>{n[t][e].gameSet.add(o)}),n[t][e].removedValues.length=0,n[t][e].chosenValue=0}function A(t,e,n){return n[t][e].gameSet.size===0&&n[t][e].removedValues.length===9}0&&(module.exports={generateCompleteSudokuGrid,generateSudoku,generateSudokuGrid,generateSudokuGridWithMetadata,getBlockRange});
@@ -1,8 +1,15 @@
1
- var Sudoku=(()=>{var r=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var g=(t,e)=>{for(var n in e)r(t,n,{get:e[n],enumerable:!0})},M=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of S(e))!x.call(t,s)&&s!==n&&r(t,s,{get:()=>e[s],enumerable:!(o=d(e,s))||o.enumerable});return t};var k=t=>M(r({},"__esModule",{value:!0}),t);var z={};g(z,{generateCompleteSudokuGrid:()=>I,generateSudokuGrid:()=>v,generateSudokuGridWithMetadata:()=>i,getBlockRange:()=>a,isInBlock:()=>p,isInColumn:()=>m,isInRow:()=>V});/**
1
+ var Sudoku=(()=>{var z=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var C=(f,t)=>{for(var e in t)z(f,e,{get:t[e],enumerable:!0})},R=(f,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let c of F(t))!T.call(f,c)&&c!==e&&z(f,c,{get:()=>t[c],enumerable:!(i=B(t,c))||i.enumerable});return f};var U=f=>R(z({},"__esModule",{value:!0}),f);var dt={};C(dt,{generateCompleteSudokuGrid:()=>N,generateSudoku:()=>st,generateSudokuGrid:()=>ot,generateSudokuGridWithMetadata:()=>E,getBlockRange:()=>w});/**
2
+ * Solver module for Sudoku Generator
3
+ *
4
+ * Техники решения судоку — от простых к продвинутым.
5
+ * Используется для определения сложности пазла и контролируемой генерации.
6
+ *
7
+ * @license MIT
8
+ */function P(f,t,e){if(f[t][e]!==0)return[];let i=new Set;for(let n=0;n<9;n++)f[t][n]!==0&&i.add(f[t][n]);for(let n=0;n<9;n++)f[n][e]!==0&&i.add(f[n][e]);let c=Math.floor(t/3)*3,r=Math.floor(e/3)*3;for(let n=c;n<c+3;n++)for(let s=r;s<r+3;s++)f[n][s]!==0&&i.add(f[n][s]);let o=[];for(let n=1;n<=9;n++)i.has(n)||o.push(n);return o}function H(f){let t=new Map;for(let e=0;e<9;e++)for(let i=0;i<9;i++)f[e][i]===0&&t.set(`${e},${i}`,new Set(P(f,e,i)));return t}function K(f,t,e,i){for(let o=0;o<9;o++){let n=`${t},${o}`,s=f.get(n);s&&s.delete(i)}for(let o=0;o<9;o++){let n=`${o},${e}`,s=f.get(n);s&&s.delete(i)}let c=Math.floor(t/3)*3,r=Math.floor(e/3)*3;for(let o=c;o<c+3;o++)for(let n=r;n<r+3;n++){let s=`${o},${n}`,l=f.get(s);l&&l.delete(i)}}function j(f,t,e,i,c){f[e][i]=c,t.delete(`${e},${i}`),K(t,e,i,c)}function L(f,t){let e=0;for(let[i,c]of t)if(c.size===1){let[r,o]=i.split(",").map(Number),n=[...c][0];j(f,t,r,o,n),e++}return e}function Q(f,t){let e=0,i=y();for(let c of i)for(let r=1;r<=9;r++){let o=null,n=0;for(let[s,l]of c){let u=`${s},${l}`,d=t.get(u);if(d&&d.has(r)&&(o={r:s,c:l,key:u},n++,n>1))break}n===1&&(j(f,t,o.r,o.c,r),e++)}return e}function y(){let f=[];for(let t=0;t<9;t++){let e=[];for(let i=0;i<9;i++)e.push([t,i]);f.push(e)}for(let t=0;t<9;t++){let e=[];for(let i=0;i<9;i++)e.push([i,t]);f.push(e)}for(let t=0;t<9;t+=3)for(let e=0;e<9;e+=3){let i=[];for(let c=t;c<t+3;c++)for(let r=e;r<e+3;r++)i.push([c,r]);f.push(i)}return f}function Y(f,t){let e=0,i=y();for(let c of i){let r=[];for(let[o,n]of c){let s=t.get(`${o},${n}`);s&&s.size===2&&r.push({r:o,c:n,candidates:s})}for(let o=0;o<r.length;o++)for(let n=o+1;n<r.length;n++){let s=r[o],l=r[n];if(s.candidates.size!==l.candidates.size)continue;let u=!0;for(let a of s.candidates)if(!l.candidates.has(a)){u=!1;break}if(!u)continue;let d=[...s.candidates];for(let[a,h]of c){if(a===s.r&&h===s.c||a===l.r&&h===l.c)continue;let g=t.get(`${a},${h}`);if(g)for(let p of d)g.has(p)&&(g.delete(p),e++)}}}return e}function _(f,t){let e=0,i=y();for(let c of i){let r=new Map;for(let n=1;n<=9;n++){let s=[];for(let[l,u]of c){let d=t.get(`${l},${u}`);d&&d.has(n)&&s.push(`${l},${u}`)}s.length===2&&r.set(n,s)}let o=[...r.keys()];for(let n=0;n<o.length;n++)for(let s=n+1;s<o.length;s++){let l=r.get(o[n]),u=r.get(o[s]);if(l[0]!==u[0]||l[1]!==u[1])continue;let d=new Set([o[n],o[s]]);for(let a of l){let h=t.get(a);if(h)for(let g of[...h])d.has(g)||(h.delete(g),e++)}}}return e}function J(f,t){let e=0,i=y();for(let c of i){let r=[];for(let[o,n]of c){let s=t.get(`${o},${n}`);s&&s.size>=2&&s.size<=3&&r.push({r:o,c:n,candidates:s})}for(let o=0;o<r.length;o++)for(let n=o+1;n<r.length;n++)for(let s=n+1;s<r.length;s++){let l=new Set([...r[o].candidates,...r[n].candidates,...r[s].candidates]);if(l.size!==3)continue;let u=new Set([`${r[o].r},${r[o].c}`,`${r[n].r},${r[n].c}`,`${r[s].r},${r[s].c}`]);for(let[d,a]of c){let h=`${d},${a}`;if(u.has(h))continue;let g=t.get(h);if(g)for(let p of l)g.has(p)&&(g.delete(p),e++)}}}return e}function O(f,t){let e=0;for(let i=1;i<=9;i++){let c=[];for(let o=0;o<9;o++){let n=[];for(let s=0;s<9;s++){let l=t.get(`${o},${s}`);l&&l.has(i)&&n.push(s)}n.length===2&&c.push({line:o,positions:n})}for(let o=0;o<c.length;o++)for(let n=o+1;n<c.length;n++){let s=c[o],l=c[n];if(!(s.positions[0]!==l.positions[0]||s.positions[1]!==l.positions[1]))for(let u of s.positions)for(let d=0;d<9;d++){if(d===s.line||d===l.line)continue;let a=t.get(`${d},${u}`);a&&a.has(i)&&(a.delete(i),e++)}}let r=[];for(let o=0;o<9;o++){let n=[];for(let s=0;s<9;s++){let l=t.get(`${s},${o}`);l&&l.has(i)&&n.push(s)}n.length===2&&r.push({line:o,positions:n})}for(let o=0;o<r.length;o++)for(let n=o+1;n<r.length;n++){let s=r[o],l=r[n];if(!(s.positions[0]!==l.positions[0]||s.positions[1]!==l.positions[1]))for(let u of s.positions)for(let d=0;d<9;d++){if(d===s.line||d===l.line)continue;let a=t.get(`${u},${d}`);a&&a.has(i)&&(a.delete(i),e++)}}}return e}function X(f,t){let e=0;for(let i=0;i<9;i+=3)for(let c=0;c<9;c+=3)for(let r=1;r<=9;r++){let o=new Set,n=new Set;for(let s=i;s<i+3;s++)for(let l=c;l<c+3;l++){let u=t.get(`${s},${l}`);u&&u.has(r)&&(o.add(s),n.add(l))}if(o.size===1){let s=[...o][0];for(let l=0;l<9;l++){if(l>=c&&l<c+3)continue;let u=t.get(`${s},${l}`);u&&u.has(r)&&(u.delete(r),e++)}}if(n.size===1){let s=[...n][0];for(let l=0;l<9;l++){if(l>=i&&l<i+3)continue;let u=t.get(`${l},${s}`);u&&u.has(r)&&(u.delete(r),e++)}}}for(let i=0;i<9;i++)for(let c=1;c<=9;c++){let r=new Set;for(let o=0;o<9;o++){let n=t.get(`${i},${o}`);n&&n.has(c)&&r.add(Math.floor(o/3))}if(r.size===1){let o=[...r][0]*3,n=Math.floor(i/3)*3;for(let s=n;s<n+3;s++)if(s!==i)for(let l=o;l<o+3;l++){let u=t.get(`${s},${l}`);u&&u.has(c)&&(u.delete(c),e++)}}}for(let i=0;i<9;i++)for(let c=1;c<=9;c++){let r=new Set;for(let o=0;o<9;o++){let n=t.get(`${o},${i}`);n&&n.has(c)&&r.add(Math.floor(o/3))}if(r.size===1){let o=[...r][0]*3,n=Math.floor(i/3)*3;for(let s=n;s<n+3;s++)if(s!==i)for(let l=o;l<o+3;l++){let u=t.get(`${l},${s}`);u&&u.has(c)&&(u.delete(c),e++)}}}return e}function Z(f,t){let e=0;for(let i=1;i<=9;i++){let c=[];for(let o=0;o<9;o++){let n=[];for(let s=0;s<9;s++){let l=t.get(`${o},${s}`);l&&l.has(i)&&n.push(s)}n.length>=2&&n.length<=3&&c.push({line:o,positions:n})}for(let o=0;o<c.length;o++)for(let n=o+1;n<c.length;n++)for(let s=n+1;s<c.length;s++){let l=new Set([...c[o].positions,...c[n].positions,...c[s].positions]);if(l.size!==3)continue;let u=new Set([c[o].line,c[n].line,c[s].line]);for(let d of l)for(let a=0;a<9;a++){if(u.has(a))continue;let h=t.get(`${a},${d}`);h&&h.has(i)&&(h.delete(i),e++)}}let r=[];for(let o=0;o<9;o++){let n=[];for(let s=0;s<9;s++){let l=t.get(`${s},${o}`);l&&l.has(i)&&n.push(s)}n.length>=2&&n.length<=3&&r.push({line:o,positions:n})}for(let o=0;o<r.length;o++)for(let n=o+1;n<r.length;n++)for(let s=n+1;s<r.length;s++){let l=new Set([...r[o].positions,...r[n].positions,...r[s].positions]);if(l.size!==3)continue;let u=new Set([r[o].line,r[n].line,r[s].line]);for(let d of l)for(let a=0;a<9;a++){if(u.has(a))continue;let h=t.get(`${d},${a}`);h&&h.has(i)&&(h.delete(i),e++)}}}return e}function G(f,t){let e=0,i=[];for(let[c,r]of t)if(r.size===2){let[o,n]=c.split(",").map(Number);i.push({r:o,c:n,candidates:[...r]})}for(let c of i){let[r,o]=c.candidates,n=i.filter(s=>s!==c&&S(c.r,c.c,s.r,s.c));for(let s of n){if(!s.candidates.includes(r)||s.candidates.includes(o))continue;let l=s.candidates[0]===r?s.candidates[1]:s.candidates[0];for(let u of n)if(u!==s&&!(!u.candidates.includes(o)||!u.candidates.includes(l)))for(let[d,a]of t){if(!a.has(l))continue;let[h,g]=d.split(",").map(Number);h===s.r&&g===s.c||h===u.r&&g===u.c||h===c.r&&g===c.c||S(h,g,s.r,s.c)&&S(h,g,u.r,u.c)&&(a.delete(l),e++)}}}return e}function S(f,t,e,i){return f===e&&t===i?!1:f===e||t===i||Math.floor(f/3)===Math.floor(e/3)&&Math.floor(t/3)===Math.floor(i/3)}function tt(f,t){let e=0,i=[];for(let[c,r]of t)if(r.size===2){let[o,n]=c.split(",").map(Number);i.push({r:o,c:n,candidates:[...r].sort((s,l)=>s-l)})}for(let c=0;c<i.length;c++)for(let r=c+1;r<i.length;r++){let o=i[c],n=i[r];if(o.candidates[0]!==n.candidates[0]||o.candidates[1]!==n.candidates[1]||o.r!==n.r||Math.floor(o.c/3)===Math.floor(n.c/3))continue;let[s,l]=o.candidates;for(let u=0;u<9;u++){if(u===o.r||Math.floor(u/3)===Math.floor(o.r/3))continue;let d=`${u},${o.c}`,a=`${u},${n.c}`,h=t.get(d),g=t.get(a);!h||!g||!h.has(s)||!h.has(l)||!g.has(s)||!g.has(l)||(h.size===2&&g.size>2?(g.delete(s),g.delete(l),e+=2):g.size===2&&h.size>2&&(h.delete(s),h.delete(l),e+=2))}}return e}var I=[{name:"nakedSingle",fn:L,weight:1,type:"place"},{name:"hiddenSingle",fn:Q,weight:2,type:"place"},{name:"nakedPair",fn:Y,weight:5,type:"eliminate"},{name:"hiddenPair",fn:_,weight:7,type:"eliminate"},{name:"nakedTriple",fn:J,weight:8,type:"eliminate"},{name:"pointingPairs",fn:X,weight:6,type:"eliminate"},{name:"xWing",fn:O,weight:15,type:"eliminate"},{name:"swordfish",fn:Z,weight:20,type:"eliminate"},{name:"xyWing",fn:G,weight:18,type:"eliminate"},{name:"uniqueRectangle",fn:tt,weight:20,type:"eliminate"}];function x(f){let t=H(f),e=[];for(;t.size>0;){let i=!1;for(let c of I){let r=c.fn(f,t);if(r>0){e.push({technique:c.name,count:r}),i=!0;break}}if(!i)break}return{solved:t.size===0,steps:e,grid:f}}function et(f){let t=f.map(c=>[...c]),e=0;function i(c){if(e>=2)return;for(;c<81&&t[Math.floor(c/9)][c%9]!==0;)c++;if(c===81){e++;return}let r=Math.floor(c/9),o=c%9,n=P(t,r,o);for(let s of n)if(t[r][o]=s,i(c+1),e>=2)return;t[r][o]=0}return i(0),e}var k={easy:{min:0,max:15},medium:{min:16,max:40},hard:{min:41,max:80},expert:{min:81,max:1/0}};function V(f){let t=0,e={};for(let i of I)e[i.name]=i.weight;for(let i of f)t+=(e[i.technique]||0)*i.count;return t}function v(f){return f<=k.easy.max?"easy":f<=k.medium.max?"medium":f<=k.hard.max?"hard":"expert"}function M(f,t,e={}){let{maxAttempts:i=200}=e,c=k[t],r=f.map(l=>[...l]),o=[];for(let l=0;l<9;l++)for(let u=0;u<9;u++)o.push([l,u]);nt(o);let n=new Set,s=null;for(let l=0;l<i;l++){let u=!1;for(let d=0;d<o.length;d++){let[a,h]=o[d],g=`${a},${h}`;if(r[a][h]===0||n.has(g))continue;let p=r[a][h];if(r[a][h]=0,et(r)!==1){r[a][h]=p,n.add(g);continue}let D=r.map($=>[...$]),b=x(D);if(!b.solved){r[a][h]=p,n.add(g);continue}let m=V(b.steps),W=v(m);if(m>=c.min&&m<=c.max&&(s={puzzle:r.map($=>[...$]),solution:f.map($=>[...$]),difficulty:W,score:m,steps:b.steps}),m>c.max){r[a][h]=p,n.add(g);continue}u=!0;break}if(!u)break}if(!s){let l=r.map(a=>[...a]),u=x(l),d=V(u.steps);s={puzzle:r.map(a=>[...a]),solution:f.map(a=>[...a]),difficulty:v(d),score:d,steps:u.steps}}return s}function nt(f){for(let t=f.length-1;t>0;t--){let e=Math.floor(Math.random()*(t+1));[f[t],f[e]]=[f[e],f[t]]}}/**
2
9
  * @luckyfoxdesign/sudoku-generator
3
10
  * Generates complete, valid 9x9 Sudoku grids using backtracking algorithm
4
11
  *
5
12
  * @license MIT
6
13
  * @author Lucky Fox Design <luckyfoxinthebox@gmail.com>
7
14
  * @see https://github.com/luckyfoxdesign/sudoku-generator
8
- */function v(){return i().map(e=>e.map(n=>n.chosenValue))}function I(){let t=c();for(let e=0;e<81;e++){let n=Math.floor(e/9),o=e%9;h(n,o,t)===0&&(e-=2,e<-1&&(e=-1))}return t.map(e=>e.map(n=>n.chosenValue))}function i(){let t=c();for(let e=0;e<81;e++){let n=Math.floor(e/9),o=e%9;h(n,o,t)===0&&(e-=2,e<-1&&(e=-1))}return B(t),t}function c(){let t=[];for(let e=0;e<9;e++){t[e]=[];for(let n=0;n<9;n++)t[e][n]={chosenValue:0,removedValues:[],gameSet:new Set([1,2,3,4,5,6,7,8,9])}}return t}function y(){return Math.random()<.5}function B(t){for(let e=0;e<t.length;e++)for(let n=0;n<t[e].length;n++)y()&&n%3!==0&&(t[e][n].chosenValue=0)}function h(t,e,n){for(;;){if(j(t,e,n))return b(t,e,n),0;let o=E(t,e,n);if(!V(o,t,e,n)&&!m(o,t,e,n)&&!p(o,t,e,n))return 1}}function V(t,e,n,o){for(let s=0;s<n;s++)if(o[e][s].chosenValue===t)return!0;return!1}function m(t,e,n,o){for(let s=0;s<e;s++)if(o[s][n].chosenValue===t)return!0;return!1}function p(t,e,n,o){let s=a(e),u=a(n);for(let f of s)for(let l of u)if(!(f>e||f===e&&l>=n)&&o[f][l].chosenValue===t)return!0;return!1}function a(t){return t>=0&&t<=2?[0,1,2]:t>=3&&t<=5?[3,4,5]:[6,7,8]}function E(t,e,n){let o=[...n[t][e].gameSet],s=Math.floor(Math.random()*o.length),u=o[s];return n[t][e].gameSet.delete(u),n[t][e].removedValues.push(u),n[t][e].chosenValue=u,u}function b(t,e,n){n[t][e].removedValues.forEach(o=>{n[t][e].gameSet.add(o)}),n[t][e].removedValues.length=0,n[t][e].chosenValue=0}function j(t,e,n){return n[t][e].gameSet.size===0&&n[t][e].removedValues.length===9}return k(z);})();
15
+ */function ot(){return E().map(t=>t.map(e=>e.chosenValue))}function st(f="easy"){let t=N(),e=M(t,f);return{puzzle:e.puzzle,solution:e.solution,difficulty:e.difficulty,score:e.score}}function N(){let f=q();for(let t=0;t<81;t++){let e=Math.floor(t/9),i=t%9;A(e,i,f)===0&&(t-=2,t<-1&&(t=-1))}return f.map(t=>t.map(e=>e.chosenValue))}function E(){let f=q();for(let t=0;t<81;t++){let e=Math.floor(t/9),i=t%9;A(e,i,f)===0&&(t-=2,t<-1&&(t=-1))}return ft(f),f}function q(){let f=[];for(let t=0;t<9;t++){f[t]=[];for(let e=0;e<9;e++)f[t][e]={chosenValue:0,removedValues:[],gameSet:new Set([1,2,3,4,5,6,7,8,9])}}return f}function it(){return Math.random()<.5}function ft(f){for(let t=0;t<f.length;t++)for(let e=0;e<f[t].length;e++)it()&&e%3!==0&&(f[t][e].chosenValue=0)}function A(f,t,e){for(;;){if(ht(f,t,e))return at(f,t,e),0;let i=ut(f,t,e);if(!ct(i,f,t,e)&&!rt(i,f,t,e)&&!lt(i,f,t,e))return 1}}function ct(f,t,e,i){for(let c=0;c<e;c++)if(i[t][c].chosenValue===f)return!0;return!1}function rt(f,t,e,i){for(let c=0;c<t;c++)if(i[c][e].chosenValue===f)return!0;return!1}function lt(f,t,e,i){let c=w(t),r=w(e);for(let o of c)for(let n of r)if(!(o>t||o===t&&n>=e)&&i[o][n].chosenValue===f)return!0;return!1}function w(f){return f>=0&&f<=2?[0,1,2]:f>=3&&f<=5?[3,4,5]:[6,7,8]}function ut(f,t,e){let i=[...e[f][t].gameSet],c=Math.floor(Math.random()*i.length),r=i[c];return e[f][t].gameSet.delete(r),e[f][t].removedValues.push(r),e[f][t].chosenValue=r,r}function at(f,t,e){e[f][t].removedValues.forEach(i=>{e[f][t].gameSet.add(i)}),e[f][t].removedValues.length=0,e[f][t].chosenValue=0}function ht(f,t,e){return e[f][t].gameSet.size===0&&e[f][t].removedValues.length===9}return U(dt);})();
package/dist/index.js CHANGED
@@ -5,4 +5,4 @@
5
5
  * @license MIT
6
6
  * @author Lucky Fox Design <luckyfoxinthebox@gmail.com>
7
7
  * @see https://github.com/luckyfoxdesign/sudoku-generator
8
- */function M(){return c().map(e=>e.map(n=>n.chosenValue))}function k(){const t=a();for(let e=0;e<81;e++){const n=Math.floor(e/9),o=e%9;l(n,o,t)===0&&(e-=2,e<-1&&(e=-1))}return t.map(e=>e.map(n=>n.chosenValue))}function c(){const t=a();for(let e=0;e<81;e++){const n=Math.floor(e/9),o=e%9;l(n,o,t)===0&&(e-=2,e<-1&&(e=-1))}return V(t),t}function a(){const t=[];for(let e=0;e<9;e++){t[e]=[];for(let n=0;n<9;n++)t[e][n]={chosenValue:0,removedValues:[],gameSet:new Set([1,2,3,4,5,6,7,8,9])}}return t}function h(){return Math.random()<.5}function V(t){for(let e=0;e<t.length;e++)for(let n=0;n<t[e].length;n++)h()&&n%3!==0&&(t[e][n].chosenValue=0)}function l(t,e,n){for(;;){if(g(t,e,n))return x(t,e,n),0;const o=S(t,e,n);if(!m(o,t,e,n)&&!p(o,t,e,n)&&!d(o,t,e,n))return 1}}function m(t,e,n,o){for(let s=0;s<n;s++)if(o[e][s].chosenValue===t)return!0;return!1}function p(t,e,n,o){for(let s=0;s<e;s++)if(o[s][n].chosenValue===t)return!0;return!1}function d(t,e,n,o){const s=i(e),u=i(n);for(const f of s)for(const r of u)if(!(f>e||f===e&&r>=n)&&o[f][r].chosenValue===t)return!0;return!1}function i(t){return t>=0&&t<=2?[0,1,2]:t>=3&&t<=5?[3,4,5]:[6,7,8]}function S(t,e,n){const o=[...n[t][e].gameSet],s=Math.floor(Math.random()*o.length),u=o[s];return n[t][e].gameSet.delete(u),n[t][e].removedValues.push(u),n[t][e].chosenValue=u,u}function x(t,e,n){n[t][e].removedValues.forEach(o=>{n[t][e].gameSet.add(o)}),n[t][e].removedValues.length=0,n[t][e].chosenValue=0}function g(t,e,n){return n[t][e].gameSet.size===0&&n[t][e].removedValues.length===9}export{k as generateCompleteSudokuGrid,M as generateSudokuGrid,c as generateSudokuGridWithMetadata,i as getBlockRange,d as isInBlock,p as isInColumn,m as isInRow};
8
+ */import{generatePuzzle as c}from"./solver.js";function B(){return m().map(e=>e.map(n=>n.chosenValue))}function E(t="easy"){const e=h(),n=c(e,t);return{puzzle:n.puzzle,solution:n.solution,difficulty:n.difficulty,score:n.score}}function h(){const t=r();for(let e=0;e<81;e++){const n=Math.floor(e/9),o=e%9;a(n,o,t)===0&&(e-=2,e<-1&&(e=-1))}return t.map(e=>e.map(n=>n.chosenValue))}function m(){const t=r();for(let e=0;e<81;e++){const n=Math.floor(e/9),o=e%9;a(n,o,t)===0&&(e-=2,e<-1&&(e=-1))}return d(t),t}function r(){const t=[];for(let e=0;e<9;e++){t[e]=[];for(let n=0;n<9;n++)t[e][n]={chosenValue:0,removedValues:[],gameSet:new Set([1,2,3,4,5,6,7,8,9])}}return t}function V(){return Math.random()<.5}function d(t){for(let e=0;e<t.length;e++)for(let n=0;n<t[e].length;n++)V()&&n%3!==0&&(t[e][n].chosenValue=0)}function a(t,e,n){for(;;){if(k(t,e,n))return z(t,e,n),0;const o=y(t,e,n);if(!p(o,t,e,n)&&!S(o,t,e,n)&&!g(o,t,e,n))return 1}}function p(t,e,n,o){for(let s=0;s<n;s++)if(o[e][s].chosenValue===t)return!0;return!1}function S(t,e,n,o){for(let s=0;s<e;s++)if(o[s][n].chosenValue===t)return!0;return!1}function g(t,e,n,o){const s=i(e),u=i(n);for(const f of s)for(const l of u)if(!(f>e||f===e&&l>=n)&&o[f][l].chosenValue===t)return!0;return!1}function i(t){return t>=0&&t<=2?[0,1,2]:t>=3&&t<=5?[3,4,5]:[6,7,8]}function y(t,e,n){const o=[...n[t][e].gameSet],s=Math.floor(Math.random()*o.length),u=o[s];return n[t][e].gameSet.delete(u),n[t][e].removedValues.push(u),n[t][e].chosenValue=u,u}function z(t,e,n){n[t][e].removedValues.forEach(o=>{n[t][e].gameSet.add(o)}),n[t][e].removedValues.length=0,n[t][e].chosenValue=0}function k(t,e,n){return n[t][e].gameSet.size===0&&n[t][e].removedValues.length===9}export{h as generateCompleteSudokuGrid,E as generateSudoku,B as generateSudokuGrid,m as generateSudokuGridWithMetadata,i as getBlockRange};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@luckyfoxdesign/sudoku-generator",
3
- "version": "1.0.3",
4
- "description": "A lightweight Sudoku puzzle generator that creates randomized 9×9 grids using a backtracking algorithm. Generates complete, valid Sudoku boards suitable for games and puzzle applications.",
3
+ "version": "1.1.0",
4
+ "description": "A lightweight Sudoku puzzle generator with difficulty control (easy–expert). Generates complete solutions and playable puzzles using backtracking and constraint-solving techniques.",
5
5
  "keywords": [
6
6
  "sudoku",
7
7
  "puzzle",