@fgv/ts-sudoku-lib 5.0.1-9 → 5.0.1

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 (55) hide show
  1. package/dist/index.browser.js +37 -0
  2. package/dist/index.js +30 -0
  3. package/dist/packlets/collections/collections.js +136 -0
  4. package/dist/packlets/collections/data/puzzles.json +56 -0
  5. package/dist/packlets/collections/index.js +25 -0
  6. package/dist/packlets/common/cage.js +83 -0
  7. package/dist/packlets/common/cell.js +88 -0
  8. package/dist/packlets/common/common.js +70 -0
  9. package/dist/packlets/common/converters.js +65 -0
  10. package/dist/packlets/common/ids.js +152 -0
  11. package/dist/packlets/common/index.js +36 -0
  12. package/dist/packlets/common/logging.js +32 -0
  13. package/dist/packlets/common/public.js +25 -0
  14. package/dist/packlets/common/puzzle.js +411 -0
  15. package/dist/packlets/common/puzzleDefinitions.js +247 -0
  16. package/dist/packlets/common/puzzleSession.js +369 -0
  17. package/dist/packlets/common/puzzleState.js +99 -0
  18. package/dist/packlets/files/converters.js +67 -0
  19. package/dist/packlets/files/fileTreeHelpers.js +37 -0
  20. package/dist/packlets/files/filesystem.js +37 -0
  21. package/dist/packlets/files/index.browser.js +32 -0
  22. package/dist/packlets/files/index.js +30 -0
  23. package/dist/packlets/files/model.js +25 -0
  24. package/dist/packlets/hints/baseHintProvider.js +199 -0
  25. package/dist/packlets/hints/explanations.js +270 -0
  26. package/dist/packlets/hints/hiddenSingles.js +233 -0
  27. package/dist/packlets/hints/hintRegistry.js +222 -0
  28. package/dist/packlets/hints/hints.js +311 -0
  29. package/dist/packlets/hints/index.js +39 -0
  30. package/dist/packlets/hints/interfaces.js +25 -0
  31. package/dist/packlets/hints/nakedSingles.js +198 -0
  32. package/dist/packlets/hints/puzzleSessionHints.js +495 -0
  33. package/dist/packlets/hints/types.js +43 -0
  34. package/dist/packlets/puzzles/anyPuzzle.js +47 -0
  35. package/dist/packlets/puzzles/index.js +31 -0
  36. package/dist/packlets/puzzles/internal/combinationCache.js +75 -0
  37. package/dist/packlets/puzzles/internal/combinationGenerator.js +142 -0
  38. package/dist/packlets/puzzles/internal/possibilityAnalyzer.js +121 -0
  39. package/dist/packlets/puzzles/killerCombinations.js +222 -0
  40. package/dist/packlets/puzzles/killerCombinationsTypes.js +25 -0
  41. package/dist/packlets/puzzles/killerSudokuPuzzle.js +136 -0
  42. package/dist/packlets/puzzles/sudokuPuzzle.js +43 -0
  43. package/dist/packlets/puzzles/sudokuXPuzzle.js +65 -0
  44. package/dist/test/unit/helpers/puzzleBuilders.js +149 -0
  45. package/dist/ts-sudoku-lib.d.ts +12 -7
  46. package/dist/tsdoc-metadata.json +1 -1
  47. package/lib/index.browser.d.ts +7 -0
  48. package/lib/index.browser.js +78 -0
  49. package/lib/packlets/files/fileTreeHelpers.d.ts +13 -0
  50. package/lib/packlets/files/fileTreeHelpers.js +40 -0
  51. package/lib/packlets/files/index.browser.d.ts +5 -0
  52. package/lib/packlets/files/index.browser.js +70 -0
  53. package/lib/packlets/files/index.d.ts +3 -2
  54. package/lib/packlets/files/index.js +7 -4
  55. package/package.json +19 -5
@@ -0,0 +1,37 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2025 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ // Browser entry point - excludes Node.js filesystem dependencies
25
+ // eslint-disable-next-line @rushstack/packlets/mechanics -- browser-specific entry point excludes Node.js fs dependencies
26
+ import * as Files from './packlets/files/index.browser';
27
+ import * as Hints from './packlets/hints';
28
+ import * as Puzzles from './packlets/puzzles';
29
+ export * from './packlets/collections';
30
+ export * from './packlets/common';
31
+ export { Files, Hints, Puzzles };
32
+ // Excluded from browser:
33
+ // - Files.loadJsonPuzzlesFileSync (requires Node.js fs via JsonFile.convertJsonFileSync)
34
+ //
35
+ // Included in browser (via FileTree):
36
+ // - Files.loadJsonPuzzlesFromTree (browser-compatible via FileTree)
37
+ //# sourceMappingURL=index.browser.js.map
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ import * as Files from './packlets/files';
25
+ import * as Hints from './packlets/hints';
26
+ import * as Puzzles from './packlets/puzzles';
27
+ export * from './packlets/collections';
28
+ export * from './packlets/common';
29
+ export { Files, Hints, Puzzles };
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,136 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ import * as Files from '../files';
25
+ import * as Puzzles from '../puzzles';
26
+ import { captureResult, fail, succeed } from '@fgv/ts-utils';
27
+ import { PuzzleDefinitionFactory, PuzzleSession } from '../common';
28
+ import DefaultPuzzles from './data/puzzles.json';
29
+ /**
30
+ * A collection of puzzles of various types.
31
+ * @public
32
+ */
33
+ export class PuzzleCollection {
34
+ constructor(puzzles) {
35
+ this.puzzles = puzzles.puzzles;
36
+ this._byId = new Map(this.puzzles
37
+ .map((p) => [p.id, p])
38
+ .filter((p) => p[0] !== undefined));
39
+ }
40
+ /**
41
+ * Creates a new puzzle from a loaded {@link Files.Model.IPuzzlesFile | PuzzlesFile}
42
+ * @param from - The {@link Files.Model.IPuzzlesFile | puzzles file} to be loaded.
43
+ * @returns `Success` with the resulting {@link PuzzleCollection | PuzzleCollection}
44
+ * or `Failure` with details if an error occurs.
45
+ */
46
+ static create(from) {
47
+ return captureResult(() => new PuzzleCollection(from));
48
+ }
49
+ /**
50
+ * Creates a new puzzle from a JSON file.
51
+ * @param path - path to the JSON file to be loaded.
52
+ * @returns `Success` with the resulting {@link PuzzleCollection | PuzzleCollection}
53
+ * or `Failure` with details if an error occurs.
54
+ */
55
+ static load(file) {
56
+ return Files.Converters.loadPuzzlesFile(file).onSuccess(PuzzleCollection.create);
57
+ }
58
+ /**
59
+ * Gets a puzzle by id from this collection.
60
+ * @param id - The string ID of the puzzle to be returned.
61
+ * @returns `Success` with the requested {@link PuzzleSession | puzzle}, or
62
+ * `Failure` with details if an error occurs.
63
+ */
64
+ getPuzzle(id) {
65
+ return this.getDescription(id)
66
+ .onSuccess((desc) => {
67
+ return this._convertToDefinition(desc);
68
+ })
69
+ .onSuccess((def) => {
70
+ return Puzzles.Any.create(def);
71
+ })
72
+ .onSuccess((puzzle) => {
73
+ return PuzzleSession.create(puzzle);
74
+ });
75
+ }
76
+ /**
77
+ * Converts IPuzzleFileData to IPuzzleDefinition format.
78
+ * @param fileData - The puzzle file data to convert
79
+ * @returns Result containing the puzzle definition
80
+ */
81
+ _convertToDefinition(fileData) {
82
+ // Validate that dimensions match cell count
83
+ const config = fileData.dimensions;
84
+ const expectedCells = config.cageWidthInCells *
85
+ config.cageHeightInCells *
86
+ config.boardWidthInCages *
87
+ config.boardHeightInCages;
88
+ // For killer sudoku, cells includes cage definitions after '|' separator
89
+ const gridCells = fileData.type === 'killer-sudoku' ? fileData.cells.split('|')[0] : fileData.cells;
90
+ const actualCells = gridCells.length;
91
+ if (actualCells !== expectedCells) {
92
+ return fail(`Dimensions mismatch: dimensions specify ${expectedCells} cells (${config.cageWidthInCells}x${config.cageHeightInCells} cages in ${config.boardWidthInCages}x${config.boardHeightInCages} layout) but cells string contains ${actualCells} cells`);
93
+ }
94
+ return PuzzleDefinitionFactory.create(config, {
95
+ id: fileData.id,
96
+ description: fileData.description,
97
+ type: fileData.type,
98
+ level: fileData.level,
99
+ cells: fileData.cells
100
+ });
101
+ }
102
+ /**
103
+ * Gets a puzzle by id from this collection.
104
+ * @param id - The string ID of the puzzle to be returned.
105
+ * @returns `Success` with the requested {@link PuzzleSession | puzzle}, or
106
+ * `Failure` with details if an error occurs.
107
+ */
108
+ getDescription(id) {
109
+ const fileData = this._byId.get(id);
110
+ /* c8 ignore next 3 - tested but coverage fails intermittently */
111
+ if (!fileData) {
112
+ return fail(`Puzzle "${id}" not found`);
113
+ }
114
+ return succeed(fileData);
115
+ }
116
+ }
117
+ /**
118
+ * Get well-known {@link PuzzleCollection | puzzle collections}.
119
+ * @public
120
+ */
121
+ export class PuzzleCollections {
122
+ /**
123
+ * The default {@link PuzzleCollection | puzzle collection}.
124
+ */
125
+ static get default() {
126
+ if (!PuzzleCollections._default) {
127
+ PuzzleCollections._default = Files.Converters.puzzlesFile
128
+ .convert(DefaultPuzzles)
129
+ .onSuccess(PuzzleCollection.create)
130
+ .orThrow();
131
+ }
132
+ return PuzzleCollections._default;
133
+ }
134
+ }
135
+ PuzzleCollections._default = undefined;
136
+ //# sourceMappingURL=collections.js.map
@@ -0,0 +1,56 @@
1
+ {
2
+ "puzzles": [
3
+ {
4
+ "id": "hidden-pair",
5
+ "description": "hidden pair sample",
6
+ "type": "sudoku",
7
+ "level": 1,
8
+ "cells": ".........9.46.7....768.41..3.97.1.8.7.8...3.1.513.87.2..75.261...54.32.8.........",
9
+ "dimensions": {
10
+ "cageWidthInCells": 3,
11
+ "cageHeightInCells": 3,
12
+ "boardWidthInCages": 3,
13
+ "boardHeightInCages": 3
14
+ }
15
+ },
16
+ {
17
+ "id": "almost-done",
18
+ "description": "nearly complete puzzle missing only 1 in cell A1",
19
+ "type": "sudoku",
20
+ "level": 1,
21
+ "cells": ".46728539837549126259316748685297413312684957794153862428965371571832694963471285",
22
+ "dimensions": {
23
+ "cageWidthInCells": 3,
24
+ "cageHeightInCells": 3,
25
+ "boardWidthInCages": 3,
26
+ "boardHeightInCages": 3
27
+ }
28
+ },
29
+ {
30
+ "id": "sudoku-x-example",
31
+ "description": "sample x-sudoku puzzle",
32
+ "type": "sudoku-x",
33
+ "level": 1,
34
+ "cells": "4.....13....6.1.....7..29...76.....2....3..9.9.1....577...1.6..3...5.7...4......1",
35
+ "dimensions": {
36
+ "cageWidthInCells": 3,
37
+ "cageHeightInCells": 3,
38
+ "boardWidthInCages": 3,
39
+ "boardHeightInCages": 3
40
+ }
41
+ },
42
+ {
43
+ "id": "killer-insane",
44
+ "description": "Insane (3208329) example from djape.net",
45
+ "type": "killer-sudoku",
46
+ "level": 100,
47
+ "cells": "ABCCCDDDEABFFGGGDEHIJKMGLLLHIJKMGLNNHOPPMQQNROOSTMUVWRSSSTTUVWRXYTTTZZabXYYYcccab|A11,B09,C09,D20,E16,F17,G30,H17,I13,J09,K11,L16,M16,N11,O16,P07,Q11,R10,S14,T39,U08,V17,W16,X06,Y26,Z06,a09,b09,c11",
48
+ "dimensions": {
49
+ "cageWidthInCells": 3,
50
+ "cageHeightInCells": 3,
51
+ "boardWidthInCages": 3,
52
+ "boardHeightInCages": 3
53
+ }
54
+ }
55
+ ]
56
+ }
@@ -0,0 +1,25 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ export * from './collections';
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,83 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ import { captureResult } from '@fgv/ts-utils';
25
+ /**
26
+ * @internal
27
+ */
28
+ export class Cage {
29
+ constructor(id, type, total, cells) {
30
+ this.id = id;
31
+ this.cageType = type;
32
+ this.total = total;
33
+ this._cellIds = Array.from(cells);
34
+ this._values = new Set();
35
+ }
36
+ get cellIds() {
37
+ return Array.from(this._cellIds);
38
+ }
39
+ get numCells() {
40
+ return this._cellIds.length;
41
+ }
42
+ static create(id, type, total, cells) {
43
+ return captureResult(() => new Cage(id, type, total, cells));
44
+ }
45
+ containsCell(id) {
46
+ return this._cellIds.includes(id);
47
+ }
48
+ containsValue(value, state, ignore) {
49
+ var _a;
50
+ for (const cellId of this._cellIds) {
51
+ if (!(ignore === null || ignore === void 0 ? void 0 : ignore.includes(cellId))) {
52
+ /* c8 ignore next - defense in depth, ? should never happen */
53
+ if (((_a = state.getCellContents(cellId).orDefault()) === null || _a === void 0 ? void 0 : _a.value) === value) {
54
+ return true;
55
+ }
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ containedValues(state) {
61
+ var _a;
62
+ const values = new Set();
63
+ for (const cellId of this._cellIds) {
64
+ /* c8 ignore next - defense in depth, ? should never happen */
65
+ const value = (_a = state.getCellContents(cellId).orDefault()) === null || _a === void 0 ? void 0 : _a.value;
66
+ if (value !== undefined) {
67
+ values.add(value);
68
+ }
69
+ }
70
+ return values;
71
+ }
72
+ toString(state) {
73
+ return this._cellIds
74
+ .map((id) => {
75
+ var _a;
76
+ /* c8 ignore next - defense in depth, ? should never happen */
77
+ const value = (_a = state === null || state === void 0 ? void 0 : state.getCellContents(id).orDefault()) === null || _a === void 0 ? void 0 : _a.value;
78
+ return value ? String(value) : '.';
79
+ })
80
+ .join('');
81
+ }
82
+ }
83
+ //# sourceMappingURL=cage.js.map
@@ -0,0 +1,88 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ import { fail, succeed } from '@fgv/ts-utils';
25
+ /**
26
+ * @internal
27
+ */
28
+ export class Cell {
29
+ constructor(init, cages) {
30
+ this.id = init.id;
31
+ this.row = init.row;
32
+ this.col = init.col;
33
+ this.cages = [...cages];
34
+ this.immutableValue = init.immutableValue;
35
+ this.immutable = init.immutableValue !== undefined;
36
+ }
37
+ isValid(state) {
38
+ if (this.immutable) {
39
+ return true;
40
+ }
41
+ const cell = state.getCellContents(this.id).orDefault();
42
+ return cell !== undefined && this.isValidValue(cell.value, state);
43
+ }
44
+ hasValue(state) {
45
+ if (this.immutable) {
46
+ return true;
47
+ }
48
+ const cell = state.getCellContents(this.id).orDefault();
49
+ /* c8 ignore next - defense in depth ? should never happen */
50
+ return (cell === null || cell === void 0 ? void 0 : cell.value) !== undefined;
51
+ }
52
+ isValidValue(value, state) {
53
+ if (this.immutable) {
54
+ return false;
55
+ }
56
+ else if (value === undefined) {
57
+ return true;
58
+ }
59
+ return !this.cages.find((cage) => cage.containsValue(value, state, [this.id]));
60
+ }
61
+ update(value, notes) {
62
+ if (this.immutable) {
63
+ return fail(`cannot set value "${value}" in immutable cell ${this.id}`);
64
+ }
65
+ return succeed({
66
+ id: this.id,
67
+ value,
68
+ notes
69
+ });
70
+ }
71
+ updateValue(value, state) {
72
+ return state.getCellContents(this.id).onSuccess((contents) => {
73
+ return this.update(value, contents.notes);
74
+ });
75
+ }
76
+ updateNotes(notes, state) {
77
+ return state.getCellContents(this.id).onSuccess((contents) => {
78
+ return this.update(contents.value, notes);
79
+ });
80
+ }
81
+ toString(state) {
82
+ var _a;
83
+ const contents = state === null || state === void 0 ? void 0 : state.getCellContents(this.id).orDefault();
84
+ const value = (_a = this.immutableValue) !== null && _a !== void 0 ? _a : contents === null || contents === void 0 ? void 0 : contents.value;
85
+ return value && value >= 1 && value <= 9 ? String(value) : '.';
86
+ }
87
+ }
88
+ //# sourceMappingURL=cell.js.map
@@ -0,0 +1,70 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ /**
25
+ * All supported public types.
26
+ * @public
27
+ */
28
+ export const allPuzzleTypes = ['killer-sudoku', 'sudoku', 'sudoku-x'];
29
+ /**
30
+ * The minimum and maximum possible values for a {@link ICage | cage}, by cage size in
31
+ * {@link ICell | cells}.
32
+ * @public
33
+ */
34
+ /**
35
+ * Calculate the minimum and maximum possible totals for a cage of given size
36
+ * @param cageSize - Number of cells in the cage
37
+ * @param maxValue - Maximum value allowed in a cell (e.g., 9 for 9x9, 16 for 16x16)
38
+ * @returns Object with min and max possible totals
39
+ * @public
40
+ */
41
+ export function getCageTotalBounds(cageSize, maxValue) {
42
+ /* c8 ignore next 3 - functional code tested but coverage intermittently missed */
43
+ if (cageSize <= 0) {
44
+ return { min: 0, max: 0 };
45
+ }
46
+ // Minimum is sum of first cageSize numbers (1, 2, 3, ...)
47
+ const min = (cageSize * (cageSize + 1)) / 2;
48
+ // Maximum is sum of last cageSize numbers (maxValue, maxValue-1, ...)
49
+ // But cageSize cannot exceed maxValue
50
+ const effectiveCageSize = Math.min(cageSize, maxValue);
51
+ const max = effectiveCageSize * maxValue - (effectiveCageSize * (effectiveCageSize - 1)) / 2;
52
+ return { min, max };
53
+ }
54
+ /**
55
+ * Legacy array for backward compatibility - supports standard 9x9 Sudoku
56
+ * @public
57
+ */
58
+ export const totalsByCageSize = [
59
+ { min: 0, max: 0 },
60
+ { min: 1, max: 9 },
61
+ { min: 3, max: 17 },
62
+ { min: 6, max: 24 },
63
+ { min: 10, max: 30 },
64
+ { min: 15, max: 35 },
65
+ { min: 21, max: 39 },
66
+ { min: 28, max: 42 },
67
+ { min: 36, max: 44 },
68
+ { min: 45, max: 45 }
69
+ ];
70
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1,65 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ import { Converters, fail, succeed } from '@fgv/ts-utils';
25
+ import { allPuzzleTypes } from './common';
26
+ // Updated to support larger grids:
27
+ // - Row IDs: R followed by A-Z or AA-ZZ for rows
28
+ // - Column IDs: C followed by 1-99+ for columns
29
+ // - Section IDs: S followed by row letter(s) and column number(s)
30
+ // - X diagonals: X1, X2
31
+ // - Killer cages: K followed by letter(s)
32
+ const cageIdRegExp = /^(R[A-Z]{1,2}$)|(C[0-9]{1,3}$)|(S[A-Z]{1,2}[0-9]{1,3}$)|(X[1-2]$)|(K[A-Za-z]+$)/;
33
+ function validateCageId(from) {
34
+ if (cageIdRegExp.test(from)) {
35
+ return succeed(from);
36
+ }
37
+ return fail(`malformed cage ID "${from}"`);
38
+ }
39
+ // Updated to support larger grids:
40
+ // - Rows: A-Z for 0-25, then AA-ZZ for 26+
41
+ // - Columns: 1-999 for any size grid
42
+ const cellIdRegExp = /^[A-Z]{1,2}[0-9]{1,3}$/;
43
+ function validateCellId(from) {
44
+ // Check basic format
45
+ if (!cellIdRegExp.test(from)) {
46
+ return fail(`malformed cell ID "${from}" (expected row letter(s) followed by column number)`);
47
+ }
48
+ return succeed(from);
49
+ }
50
+ /**
51
+ * Converts an arbitrary value to a {@link CageId | CageId}.
52
+ * @public
53
+ */
54
+ export const cageId = Converters.string.map(validateCageId);
55
+ /**
56
+ * Converts an arbitrary value to a {@link CellId | CellId}.
57
+ * @public
58
+ */
59
+ export const cellId = Converters.string.map(validateCellId);
60
+ /**
61
+ * Converts an arbitrary value to a {@link PuzzleType | PuzzleType}.
62
+ * @public
63
+ */
64
+ export const puzzleType = Converters.enumeratedValue(allPuzzleTypes);
65
+ //# sourceMappingURL=converters.js.map