nmx.sudokugen.library 1.1.0 → 1.2.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 +32 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/utility.d.ts +50 -8
- package/dist/utility.js +112 -19
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
1
3
|
# Sudoku Gen Library
|
|
2
4
|
|
|
3
5
|
`sudoku` `javascript` `typescript` `library` `npm`
|
|
@@ -99,8 +101,31 @@ A square that is rule-clean but still wrong (`board[i] !== solution[i]`) returns
|
|
|
99
101
|
|
|
100
102
|
| Function | Signature | Description |
|
|
101
103
|
|-----------------|---------------------------------------------------|---------------------------------------------------------------------------------------------------------|
|
|
102
|
-
| `getPuzzleCode` | `getPuzzleCode(grid: ArrayLike<number
|
|
103
|
-
| `getPuzzleArr` | `getPuzzleArr(code: string): number[]`
|
|
104
|
+
| `getPuzzleCode` | `getPuzzleCode(grid: ArrayLike<number>, encoding?: EncodingOption): string` | Encodes a grid as a puzzle code: one char per square (`0` blank, `1`-`9`, `A` = 10…), `.` between rows. With an `encoding`, writes in that one instead (default `All`). Throws a `RangeError` if a value doesn't fit the encoding. |
|
|
105
|
+
| `getPuzzleArr` | `getPuzzleArr(code: string, encoding?: EncodingOption): number[]` | Parses a puzzle code back into a grid array; unrecognised characters are ignored. The encoding can't be detected from the code, so pass the one it was written in (default `All`). |
|
|
106
|
+
| `getSymbol` | `getSymbol(value: number, encoding: EncodingOption): string` | The character one square value is written as. |
|
|
107
|
+
| `maxRank` | `maxRank(encoding: EncodingOption): number` | The highest rank the encoding can write. |
|
|
108
|
+
|
|
109
|
+
### `EncodingOption`
|
|
110
|
+
|
|
111
|
+
Frozen character sets for puzzle codes. Blank is `0` in every one. Letters read in either case.
|
|
112
|
+
|
|
113
|
+
| Value | Values written as | Max rank |
|
|
114
|
+
|---------------------|------------------------------------------------|----------|
|
|
115
|
+
| `All` | `1`-`9`, then `A`-`Z` (`A` = 10). The default. | 5 |
|
|
116
|
+
| `AllMinusO` | `1`-`9`, then `A`-`Z` without `O` | 5 |
|
|
117
|
+
| `DigitsOnly` | `1`-`9` | 3 |
|
|
118
|
+
| `LettersOnly` | `A`-`Z` (`A` = 1) | 5 |
|
|
119
|
+
| `LettersOnlyMinusO` | `A`-`Z` without `O` (`A` = 1) | 5 |
|
|
120
|
+
|
|
121
|
+
The minus-O sets skip `O` so it can't be misread as the blank `0`.
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
import { EncodingOption, getPuzzleArr, getPuzzleCode } from "nmx.sudokugen.library";
|
|
125
|
+
|
|
126
|
+
const a_letters = getPuzzleCode(a_sudoku.puzzle, EncodingOption.LettersOnly); // e.g. "CI0EF00BG.00AH0I0DE...."
|
|
127
|
+
const a_grid = getPuzzleArr(a_letters, EncodingOption.LettersOnly); // read it back in the same encoding
|
|
128
|
+
```
|
|
104
129
|
|
|
105
130
|
### Notes
|
|
106
131
|
|
|
@@ -132,6 +157,10 @@ A square that is rule-clean but still wrong (`board[i] !== solution[i]`) returns
|
|
|
132
157
|
|
|
133
158
|
* None. Needs an ES2022 runtime: current browsers, Node.js 20 or later, Deno, Bun.
|
|
134
159
|
|
|
160
|
+
* Used in
|
|
161
|
+
|
|
162
|
+
* Sudoku Gen web app [(link)](https://www.newmediaxp.com/app/sudokugen)
|
|
163
|
+
|
|
135
164
|
* Other languages
|
|
136
165
|
|
|
137
166
|
* C# library: [NMX.SudokuGen.Library](https://www.nuget.org/packages/NMX.SudokuGen.Library)
|
|
@@ -142,7 +171,7 @@ A square that is rule-clean but still wrong (`board[i] !== solution[i]`) returns
|
|
|
142
171
|
|
|
143
172
|
* Source
|
|
144
173
|
|
|
145
|
-
* [GitHub](https://github.com/newmediaxp/sudokugen/tree/main/
|
|
174
|
+
* [GitHub](https://github.com/newmediaxp/sudokugen/tree/main/javascript/nmx.sudokugen.library)
|
|
146
175
|
|
|
147
176
|
---
|
|
148
177
|
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Copyright (c) 2024-2026 New Media XP. Licensed under the MIT License.
|
|
2
2
|
export { Conflict, Sudoku } from "./sudoku.js";
|
|
3
|
-
export { getPuzzleArr, getPuzzleCode } from "./utility.js";
|
|
3
|
+
export { EncodingOption, getPuzzleArr, getPuzzleCode, getSymbol, maxRank } from "./utility.js";
|
package/dist/utility.d.ts
CHANGED
|
@@ -1,19 +1,61 @@
|
|
|
1
1
|
import type { Random } from "./random.ts";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
* Character sets a puzzle code can be written in. Blank (0) is '0' in every one of them.
|
|
4
|
+
*/
|
|
5
|
+
export declare const EncodingOption: Readonly<{
|
|
6
|
+
/** 1 to 9 as digits, then 10 to 35 as A to Z. Fits every rank. */
|
|
7
|
+
readonly All: 0;
|
|
8
|
+
/** 1 to 9 as digits, then 10 to 34 as A to Z without O, so it can't be misread as blank '0'. Fits every rank. */
|
|
9
|
+
readonly AllMinusO: 1;
|
|
10
|
+
/** 1 to 9 as digits only. Fits rank 3 and below. */
|
|
11
|
+
readonly DigitsOnly: 2;
|
|
12
|
+
/** 1 to 26 as A to Z. Fits every rank. */
|
|
13
|
+
readonly LettersOnly: 3;
|
|
14
|
+
/** 1 to 25 as A to Z without O, so it can't be misread as blank '0'. Fits every rank. */
|
|
15
|
+
readonly LettersOnlyMinusO: 4;
|
|
16
|
+
}>;
|
|
17
|
+
/** One of the {@link EncodingOption} values. */
|
|
18
|
+
export type EncodingOption = (typeof EncodingOption)[keyof typeof EncodingOption];
|
|
19
|
+
/**
|
|
20
|
+
* The highest rank whose grids an encoding can write; a rank r grid holds values up to r^2.
|
|
21
|
+
* Never above Sudoku.maxRank, since no encoding writes past 35.
|
|
22
|
+
* @param p_encoding The character set to check.
|
|
23
|
+
* @returns 3 for {@link EncodingOption.DigitsOnly}, 5 for every other encoding.
|
|
24
|
+
* @throws {RangeError} The encoding is not an {@link EncodingOption} value.
|
|
25
|
+
*/
|
|
26
|
+
export declare function maxRank(p_encoding: EncodingOption): number;
|
|
27
|
+
/**
|
|
28
|
+
* The character an encoding writes for one square value; blank (0) is '0' in every encoding.
|
|
29
|
+
* @param p_value The square value, from 0 (blank) up to what the encoding can write.
|
|
30
|
+
* @param p_encoding The character set to write it in.
|
|
31
|
+
* @returns A single character.
|
|
32
|
+
* @throws {RangeError} The value is not an integer within 0 and what the encoding can write,
|
|
33
|
+
* or the encoding is not an {@link EncodingOption} value.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getSymbol(p_value: number, p_encoding: EncodingOption): string;
|
|
36
|
+
/**
|
|
37
|
+
* Encodes a grid as one character per square, row by row, '.' between rows.
|
|
38
|
+
* By default ({@link EncodingOption.All}): 0 = blank, 1-9 as digits, 10 and above as letters ('A' = 10).
|
|
39
|
+
* Values above 35 ('Z') are unrepresentable and throw, so rank 5 (25x25) at most.
|
|
6
40
|
* @param p_puzz The grid to encode, row by row.
|
|
7
|
-
* @
|
|
41
|
+
* @param p_encoding The character set to write; {@link maxRank} gives the ranks it fits.
|
|
42
|
+
* Blank (0) is '0' in every encoding.
|
|
43
|
+
* @returns The puzzle code, e.g. "0012.3400..." by default or in {@link EncodingOption.All},
|
|
44
|
+
* or "00AB.CD00..." in {@link EncodingOption.LettersOnly}.
|
|
45
|
+
* @throws {RangeError} A value is not an integer within 0 and what the encoding can write,
|
|
46
|
+
* or the encoding is not an {@link EncodingOption} value.
|
|
8
47
|
*/
|
|
9
|
-
export declare function getPuzzleCode(p_puzz: ArrayLike<number
|
|
48
|
+
export declare function getPuzzleCode(p_puzz: ArrayLike<number>, p_encoding?: EncodingOption): string;
|
|
10
49
|
/**
|
|
11
50
|
* Parses a puzzle code back into a grid array.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
51
|
+
* The encoding can't be told from the code itself (a code without 'O' may or may not be a minus-O one), so it is given.
|
|
52
|
+
* @param p_code Text containing the grid, '0' = blank, letters in either case ('A' or 'a' = 10 by default);
|
|
53
|
+
* every character the encoding doesn't use is ignored, including 'O' in the minus-O encodings.
|
|
54
|
+
* @param p_encoding The character set the code was written in; the default reads it as {@link EncodingOption.All}.
|
|
14
55
|
* @returns One element per recognised character, in order.
|
|
56
|
+
* @throws {RangeError} The encoding is not an {@link EncodingOption} value.
|
|
15
57
|
*/
|
|
16
|
-
export declare function getPuzzleArr(p_code: string): number[];
|
|
58
|
+
export declare function getPuzzleArr(p_code: string, p_encoding?: EncodingOption): number[];
|
|
17
59
|
export declare function count(p_arr: ArrayLike<number>, p_num: number): number;
|
|
18
60
|
/**
|
|
19
61
|
* Fills an array block-wise so that every consecutive block ends up a random permutation of the same value range.
|
package/dist/utility.js
CHANGED
|
@@ -1,40 +1,133 @@
|
|
|
1
1
|
// Copyright (c) 2024-2026 New Media XP. Licensed under the MIT License.
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
* Character sets a puzzle code can be written in. Blank (0) is '0' in every one of them.
|
|
4
|
+
*/
|
|
5
|
+
export const EncodingOption = Object.freeze({
|
|
6
|
+
/** 1 to 9 as digits, then 10 to 35 as A to Z. Fits every rank. */
|
|
7
|
+
All: 0,
|
|
8
|
+
/** 1 to 9 as digits, then 10 to 34 as A to Z without O, so it can't be misread as blank '0'. Fits every rank. */
|
|
9
|
+
AllMinusO: 1,
|
|
10
|
+
/** 1 to 9 as digits only. Fits rank 3 and below. */
|
|
11
|
+
DigitsOnly: 2,
|
|
12
|
+
/** 1 to 26 as A to Z. Fits every rank. */
|
|
13
|
+
LettersOnly: 3,
|
|
14
|
+
/** 1 to 25 as A to Z without O, so it can't be misread as blank '0'. Fits every rank. */
|
|
15
|
+
LettersOnlyMinusO: 4,
|
|
16
|
+
});
|
|
17
|
+
// the highest square value each encoding can write, indexed by EncodingOption; blank (0) is '0' on top of these
|
|
18
|
+
const maxValues = [35, 34, 9, 26, 25];
|
|
19
|
+
const encodingNames = Object.keys(EncodingOption);
|
|
20
|
+
function maxValue(p_encoding) {
|
|
21
|
+
const a_maxValue = Number.isInteger(p_encoding) ? maxValues[p_encoding] : undefined;
|
|
22
|
+
if (a_maxValue === undefined)
|
|
23
|
+
throw new RangeError(`unknown encoding ${p_encoding}`);
|
|
24
|
+
return a_maxValue;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The highest rank whose grids an encoding can write; a rank r grid holds values up to r^2.
|
|
28
|
+
* Never above Sudoku.maxRank, since no encoding writes past 35.
|
|
29
|
+
* @param p_encoding The character set to check.
|
|
30
|
+
* @returns 3 for {@link EncodingOption.DigitsOnly}, 5 for every other encoding.
|
|
31
|
+
* @throws {RangeError} The encoding is not an {@link EncodingOption} value.
|
|
32
|
+
*/
|
|
33
|
+
export function maxRank(p_encoding) {
|
|
34
|
+
return Math.trunc(Math.sqrt(maxValue(p_encoding)));
|
|
35
|
+
}
|
|
36
|
+
// the letter at index p_letterIdx of A to Z with O skipped; 'A' = 65, 'O' - 'A' = 14
|
|
37
|
+
function letterMinusO(p_letterIdx) {
|
|
38
|
+
return String.fromCharCode(65 + (p_letterIdx < 14 ? p_letterIdx : p_letterIdx + 1));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The character an encoding writes for one square value; blank (0) is '0' in every encoding.
|
|
42
|
+
* @param p_value The square value, from 0 (blank) up to what the encoding can write.
|
|
43
|
+
* @param p_encoding The character set to write it in.
|
|
44
|
+
* @returns A single character.
|
|
45
|
+
* @throws {RangeError} The value is not an integer within 0 and what the encoding can write,
|
|
46
|
+
* or the encoding is not an {@link EncodingOption} value.
|
|
47
|
+
*/
|
|
48
|
+
export function getSymbol(p_value, p_encoding) {
|
|
49
|
+
const a_maxValue = maxValue(p_encoding);
|
|
50
|
+
if (!Number.isInteger(p_value) || p_value < 0 || p_value > a_maxValue)
|
|
51
|
+
throw new RangeError(`value ${p_value} is outside 0..${a_maxValue} for ${encodingNames[p_encoding]} encoding`);
|
|
52
|
+
if (p_value === 0)
|
|
53
|
+
return "0";
|
|
54
|
+
// '0' = 48, 'A' - 10 = 55, 'A' - 1 = 64
|
|
55
|
+
switch (p_encoding) {
|
|
56
|
+
case EncodingOption.All:
|
|
57
|
+
return String.fromCharCode(p_value <= 9 ? 48 + p_value : 55 + p_value);
|
|
58
|
+
case EncodingOption.AllMinusO:
|
|
59
|
+
return p_value <= 9 ? String.fromCharCode(48 + p_value) : letterMinusO(p_value - 10);
|
|
60
|
+
case EncodingOption.DigitsOnly:
|
|
61
|
+
return String.fromCharCode(48 + p_value);
|
|
62
|
+
case EncodingOption.LettersOnly:
|
|
63
|
+
return String.fromCharCode(64 + p_value);
|
|
64
|
+
case EncodingOption.LettersOnlyMinusO:
|
|
65
|
+
return letterMinusO(p_value - 1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// the value one character code stands for under p_encoding, or -1 when the encoding doesn't use that character
|
|
69
|
+
function getValue(p_char, p_encoding) {
|
|
70
|
+
// '0'..'9' = 48..57, 'A'..'Z' = 65..90, 'a'..'z' = 97..122, 'O' = 79
|
|
71
|
+
if (p_char === 48)
|
|
72
|
+
return 0;
|
|
73
|
+
// ASCII-only case fold; toUpperCase would also turn 'ı' and 'ſ' into letters
|
|
74
|
+
const a_upper = p_char >= 97 && p_char <= 122 ? p_char - 32 : p_char;
|
|
75
|
+
const a_digit = a_upper >= 49 && a_upper <= 57 ? a_upper - 48 : -1;
|
|
76
|
+
const a_letter = a_upper >= 65 && a_upper <= 90 ? a_upper - 65 : -1;
|
|
77
|
+
// O is not a symbol of the minus-O encodings, so it is ignored like any other unused character
|
|
78
|
+
const a_letterMinusO = a_letter < 0 || a_upper === 79 ? -1 : a_upper < 79 ? a_letter : a_letter - 1;
|
|
79
|
+
switch (p_encoding) {
|
|
80
|
+
case EncodingOption.All:
|
|
81
|
+
return a_digit > 0 ? a_digit : a_letter >= 0 ? a_letter + 10 : -1;
|
|
82
|
+
case EncodingOption.AllMinusO:
|
|
83
|
+
return a_digit > 0 ? a_digit : a_letterMinusO >= 0 ? a_letterMinusO + 10 : -1;
|
|
84
|
+
case EncodingOption.DigitsOnly:
|
|
85
|
+
return a_digit;
|
|
86
|
+
case EncodingOption.LettersOnly:
|
|
87
|
+
return a_letter >= 0 ? a_letter + 1 : -1;
|
|
88
|
+
case EncodingOption.LettersOnlyMinusO:
|
|
89
|
+
return a_letterMinusO >= 0 ? a_letterMinusO + 1 : -1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Encodes a grid as one character per square, row by row, '.' between rows.
|
|
94
|
+
* By default ({@link EncodingOption.All}): 0 = blank, 1-9 as digits, 10 and above as letters ('A' = 10).
|
|
95
|
+
* Values above 35 ('Z') are unrepresentable and throw, so rank 5 (25x25) at most.
|
|
6
96
|
* @param p_puzz The grid to encode, row by row.
|
|
7
|
-
* @
|
|
97
|
+
* @param p_encoding The character set to write; {@link maxRank} gives the ranks it fits.
|
|
98
|
+
* Blank (0) is '0' in every encoding.
|
|
99
|
+
* @returns The puzzle code, e.g. "0012.3400..." by default or in {@link EncodingOption.All},
|
|
100
|
+
* or "00AB.CD00..." in {@link EncodingOption.LettersOnly}.
|
|
101
|
+
* @throws {RangeError} A value is not an integer within 0 and what the encoding can write,
|
|
102
|
+
* or the encoding is not an {@link EncodingOption} value.
|
|
8
103
|
*/
|
|
9
|
-
export function getPuzzleCode(p_puzz) {
|
|
104
|
+
export function getPuzzleCode(p_puzz, p_encoding = EncodingOption.All) {
|
|
105
|
+
maxValue(p_encoding);
|
|
10
106
|
const a_rows = Math.trunc(Math.sqrt(p_puzz.length));
|
|
11
107
|
let a_puzzleCode = "";
|
|
12
108
|
for (let i = 0; i < p_puzz.length; ++i) {
|
|
13
109
|
if (i > 0 && i % a_rows === 0)
|
|
14
110
|
a_puzzleCode += ".";
|
|
15
|
-
|
|
16
|
-
// 'A' - 10 = 55, '0' = 48
|
|
17
|
-
a_puzzleCode += String.fromCharCode(a_value > 9 ? 55 + a_value : 48 + a_value);
|
|
111
|
+
a_puzzleCode += getSymbol(p_puzz[i], p_encoding);
|
|
18
112
|
}
|
|
19
113
|
return a_puzzleCode;
|
|
20
114
|
}
|
|
21
115
|
/**
|
|
22
116
|
* Parses a puzzle code back into a grid array.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
117
|
+
* The encoding can't be told from the code itself (a code without 'O' may or may not be a minus-O one), so it is given.
|
|
118
|
+
* @param p_code Text containing the grid, '0' = blank, letters in either case ('A' or 'a' = 10 by default);
|
|
119
|
+
* every character the encoding doesn't use is ignored, including 'O' in the minus-O encodings.
|
|
120
|
+
* @param p_encoding The character set the code was written in; the default reads it as {@link EncodingOption.All}.
|
|
25
121
|
* @returns One element per recognised character, in order.
|
|
122
|
+
* @throws {RangeError} The encoding is not an {@link EncodingOption} value.
|
|
26
123
|
*/
|
|
27
|
-
export function getPuzzleArr(p_code) {
|
|
124
|
+
export function getPuzzleArr(p_code, p_encoding = EncodingOption.All) {
|
|
125
|
+
maxValue(p_encoding);
|
|
28
126
|
const a_puzzle = [];
|
|
29
127
|
for (let i = 0; i < p_code.length; ++i) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
a_puzzle.push(c - 48);
|
|
34
|
-
else if (c >= 65 && c <= 90)
|
|
35
|
-
a_puzzle.push(c - 55);
|
|
36
|
-
else if (c >= 97 && c <= 122)
|
|
37
|
-
a_puzzle.push(c - 87);
|
|
128
|
+
const a_value = getValue(p_code.charCodeAt(i), p_encoding);
|
|
129
|
+
if (a_value !== -1)
|
|
130
|
+
a_puzzle.push(a_value);
|
|
38
131
|
}
|
|
39
132
|
return a_puzzle;
|
|
40
133
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nmx.sudokugen.library",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Sudoku generator and solver: unique-solution puzzle creation (optionally seeded), solving, validation, conflict reporting and isomorphic shuffling.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sudoku",
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"solver",
|
|
9
9
|
"generator"
|
|
10
10
|
],
|
|
11
|
-
"homepage": "https://github.com/newmediaxp/sudokugen/tree/main/
|
|
11
|
+
"homepage": "https://github.com/newmediaxp/sudokugen/tree/main/javascript/nmx.sudokugen.library#readme",
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/newmediaxp/sudokugen/issues"
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
17
|
"url": "git+https://github.com/newmediaxp/sudokugen.git",
|
|
18
|
-
"directory": "
|
|
18
|
+
"directory": "javascript/nmx.sudokugen.library"
|
|
19
19
|
},
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"author": "New Media XP <contact@newmediaxp.com> (https://www.newmediaxp.com)",
|