@wsabol/sudoku-solver 0.1.1 → 0.1.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,EAAE,EAAE,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAClI,OAAO,EAA8C,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEzH,UAAU,WAAW;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CAChB;AAED,UAAU,cAAc;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,UAAU,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,CAAC;AAEzD,UAAU,UAAU;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,iBAAS,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,WAAW,CAOtD;AAED,iBAAS,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,UAAU,CA8BxD;AAED,iBAAS,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,gBAAgB,CAY9D;AAED,iBAAS,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,cAAc,CAoCjE;AAED,QAAA,MAAM,MAAM;;;;;CAKX,CAAC;AAEF,OAAO,EACH,YAAY,EACf,CAAA;AAGD,YAAY,EACR,SAAS,EACT,KAAK,EACL,IAAI,EACJ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,UAAU,EACV,cAAc,EACjB,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,EAAE,EAAE,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAClI,OAAO,EAA8C,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEzH,UAAU,WAAW;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CAChB;AAED,UAAU,cAAc;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,UAAU,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,CAAC;AAEzD,UAAU,UAAU;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,iBAAS,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,WAAW,CAOtD;AAED,iBAAS,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,UAAU,CAkCxD;AAED,iBAAS,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,gBAAgB,CAY9D;AAED,iBAAS,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,cAAc,CAoCjE;AAED,QAAA,MAAM,MAAM;;;;;CAKX,CAAC;AAEF,OAAO,EACH,YAAY,EACf,CAAA;AAGD,YAAY,EACR,SAAS,EACT,KAAK,EACL,IAAI,EACJ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,UAAU,EACV,cAAc,EACjB,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -28,8 +28,11 @@ function nextMove(boardInput) {
28
28
  sudoku.setSquareValue(move.row, move.col, move.value);
29
29
  }
30
30
  else {
31
- const digit = move.eliminations[0].value;
32
- message = `Eliminate ${digit} from ${move.eliminations.length} cell(s) (${move.algorithm})`;
31
+ const digits = [...new Set(move.eliminations.map((e) => e.value))].sort((a, b) => a - b);
32
+ const digitPart = digits.length === 1
33
+ ? String(digits[0])
34
+ : `{${digits.join(",")}}`;
35
+ message = `Eliminate ${digitPart} from ${move.eliminations.length} cell(s) (${move.algorithm})`;
33
36
  sudoku.applyElimination(move);
34
37
  }
35
38
  return {
@@ -1,6 +1,6 @@
1
1
  import { ValidationResult } from "./validate.js";
2
2
  export type Board = number[][];
3
- export type Algorithm = "Full House" | "Naked Single" | "Hidden Single" | "Pointing Pair/Triple";
3
+ export type Algorithm = "Last Digit" | "Full House" | "Naked Single" | "Hidden Single" | "Pointing Pair/Triple" | "Naked Pair" | "Naked Triple" | "Naked Quad";
4
4
  export interface PlacementMove {
5
5
  type: "placement";
6
6
  row: number;
@@ -20,13 +20,14 @@ export interface EliminationMove {
20
20
  export type Move = PlacementMove | EliminationMove;
21
21
  export type DifficultyLevel = "Easy" | "Medium" | "Hard" | "Diabolical" | "Impossible";
22
22
  export default class SudokuSolver {
23
- static readonly ALGORITHMS: Algorithm[];
23
+ private static readonly SEARCH_PHASES;
24
24
  private board;
25
25
  private possiblesGrid;
26
26
  constructor(input: string | Board);
27
27
  setBoard(board: Board): void;
28
28
  toArray(): Board;
29
29
  getPossibles(row: number, col: number): number[];
30
+ setPossibles(row: number, col: number, possibles: number[]): void;
30
31
  setSquareValue(row: number, col: number, value: number): void;
31
32
  applyElimination(move: EliminationMove): void;
32
33
  isComplete(): boolean;
@@ -45,12 +46,15 @@ export default class SudokuSolver {
45
46
  private calcSquarePossibles;
46
47
  private calcPossibles;
47
48
  private findBestMove;
48
- private findNextPlacement;
49
+ private findMoveForPhase;
49
50
  private findNakedSingle;
50
51
  private findHiddenSingleInRow;
51
52
  private findHiddenSingleInCol;
52
53
  private findHiddenSingleInBox;
53
54
  private findPointingPairTriple;
55
+ private eachHouseInOrder;
56
+ private findNakedSubsetElimination;
57
+ private tryNakedSubsetInHouse;
54
58
  private findHiddenSingle;
55
59
  }
56
60
  //# sourceMappingURL=sudokuSolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sudokuSolver.d.ts","sourceRoot":"","sources":["../src/sudokuSolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,gBAAgB,EAAoB,MAAM,eAAe,CAAC;AAErF,MAAM,MAAM,KAAK,GAAG,MAAM,EAAE,EAAE,CAAC;AAE/B,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,eAAe,GAAG,sBAAsB,CAAC;AAEjG,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,aAAa,CAAC;IACpB,YAAY,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,SAAS,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,MAAM,IAAI,GAAG,aAAa,GAAG,eAAe,CAAC;AAEnD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,GAAG,YAAY,CAAC;AAIvF,MAAM,CAAC,OAAO,OAAO,YAAY;IAC7B,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,EAAE,CAA2E;IAElH,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,aAAa,CAAe;gBAExB,KAAK,EAAE,MAAM,GAAG,KAAK;IAOjC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAS5B,OAAO,IAAI,KAAK;IAIhB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;IAIhD,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAmB7D,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI;IAM7C,UAAU,IAAI,OAAO;IAIrB,eAAe,IAAI,MAAM;IAIzB,QAAQ,IAAI,gBAAgB;IAiG5B,OAAO,IAAI,OAAO;IAKlB,UAAU,IAAI,eAAe;IAiB7B,WAAW,IAAI,IAAI,GAAG,IAAI;IAO1B,KAAK,IAAI,OAAO;IAkBhB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,MAAM;IAYd,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,iBAAiB;IAiBzB,OAAO,CAAC,eAAe;IA+BvB,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,sBAAsB;IAgD9B,OAAO,CAAC,gBAAgB;CAe3B"}
1
+ {"version":3,"file":"sudokuSolver.d.ts","sourceRoot":"","sources":["../src/sudokuSolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,gBAAgB,EAAoB,MAAM,eAAe,CAAC;AAErF,MAAM,MAAM,KAAK,GAAG,MAAM,EAAE,EAAE,CAAC;AAE/B,MAAM,MAAM,SAAS,GACf,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,eAAe,GACf,sBAAsB,GACtB,YAAY,GACZ,cAAc,GACd,YAAY,CAAC;AAKnB,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,aAAa,CAAC;IACpB,YAAY,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,SAAS,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,MAAM,IAAI,GAAG,aAAa,GAAG,eAAe,CAAC;AAEnD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,GAAG,YAAY,CAAC;AAIvF,MAAM,CAAC,OAAO,OAAO,YAAY;IAC7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAKnC;IAEF,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,aAAa,CAAe;gBAExB,KAAK,EAAE,MAAM,GAAG,KAAK;IAOjC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAS5B,OAAO,IAAI,KAAK;IAIhB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;IAIhD,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAIjE,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAmB7D,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI;IAM7C,UAAU,IAAI,OAAO;IAIrB,eAAe,IAAI,MAAM;IAIzB,QAAQ,IAAI,gBAAgB;IAiG5B,OAAO,IAAI,OAAO;IAKlB,UAAU,IAAI,eAAe;IAiB7B,WAAW,IAAI,IAAI,GAAG,IAAI;IAO1B,KAAK,IAAI,OAAO;IAkBhB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,MAAM;IAYd,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,eAAe;IAuCvB,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,sBAAsB;IAgD9B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,0BAA0B;IAUlC,OAAO,CAAC,qBAAqB;IAqD7B,OAAO,CAAC,gBAAgB;CAe3B"}
@@ -2,7 +2,12 @@ import { cloneBoard, assertBoardShape, parseBoardString, duplicateValues } from
2
2
  import { pushUniqueReason } from "./validate.js";
3
3
  const COMPLETE = [1, 2, 3, 4, 5, 6, 7, 8, 9];
4
4
  export default class SudokuSolver {
5
- static ALGORITHMS = ["Full House", "Naked Single", "Hidden Single", "Pointing Pair/Triple"];
5
+ static SEARCH_PHASES = [
6
+ "NakedSingle",
7
+ "HiddenSingle",
8
+ "Pointing",
9
+ "NakedSubset",
10
+ ];
6
11
  board;
7
12
  possiblesGrid;
8
13
  constructor(input) {
@@ -23,6 +28,9 @@ export default class SudokuSolver {
23
28
  getPossibles(row, col) {
24
29
  return [...this.possiblesGrid[row][col]];
25
30
  }
31
+ setPossibles(row, col, possibles) {
32
+ this.possiblesGrid[row][col] = possibles.filter((v) => v > 0 && v <= 9).sort((a, b) => a - b);
33
+ }
26
34
  setSquareValue(row, col, value) {
27
35
  this.board[row][col] = value;
28
36
  this.possiblesGrid[row][col] = [];
@@ -228,27 +236,23 @@ export default class SudokuSolver {
228
236
  }
229
237
  }
230
238
  findBestMove() {
231
- for (const algorithm of SudokuSolver.ALGORITHMS) {
232
- const move = this.findNextPlacement(algorithm);
239
+ for (const phase of SudokuSolver.SEARCH_PHASES) {
240
+ const move = this.findMoveForPhase(phase);
233
241
  if (move)
234
242
  return move;
235
243
  }
236
244
  return null;
237
245
  }
238
- findNextPlacement(algorithm) {
239
- switch (algorithm) {
240
- case "Full House":
241
- case "Naked Single": {
242
- const move = this.findNakedSingle();
243
- if (algorithm === "Full House") {
244
- return move?.algorithm === "Full House" ? move : null;
245
- }
246
- return move;
247
- }
248
- case "Hidden Single":
246
+ findMoveForPhase(phase) {
247
+ switch (phase) {
248
+ case "NakedSingle":
249
+ return this.findNakedSingle();
250
+ case "HiddenSingle":
249
251
  return this.findHiddenSingle();
250
- case "Pointing Pair/Triple":
252
+ case "Pointing":
251
253
  return this.findPointingPairTriple();
254
+ case "NakedSubset":
255
+ return this.findNakedSubsetElimination();
252
256
  }
253
257
  }
254
258
  findNakedSingle() {
@@ -272,6 +276,13 @@ export default class SudokuSolver {
272
276
  if (boxValues.filter((v) => v === 0).length === 1) {
273
277
  algo = "Full House";
274
278
  }
279
+ if (algo === "Naked Single") {
280
+ // check if last digit
281
+ const placementsOfDigit = this.board.flat().filter((v) => v === placeValue).length;
282
+ if (placementsOfDigit === 8) {
283
+ algo = "Last Digit";
284
+ }
285
+ }
275
286
  return { type: "placement", row, col, value: placeValue, algorithm: algo };
276
287
  }
277
288
  }
@@ -368,6 +379,82 @@ export default class SudokuSolver {
368
379
  }
369
380
  return null;
370
381
  }
382
+ eachHouseInOrder() {
383
+ const houses = [];
384
+ for (let r = 0; r < 9; r++) {
385
+ houses.push(Array.from({ length: 9 }, (_, c) => ({ row: r, col: c })));
386
+ }
387
+ for (let c = 0; c < 9; c++) {
388
+ houses.push(Array.from({ length: 9 }, (_, row) => ({ row, col: c })));
389
+ }
390
+ for (let b = 0; b < 9; b++) {
391
+ const cells = [];
392
+ for (let idx = 0; idx < 9; idx++) {
393
+ cells.push(this.boxToPuzzle(b, idx));
394
+ }
395
+ houses.push(cells);
396
+ }
397
+ return houses;
398
+ }
399
+ findNakedSubsetElimination() {
400
+ for (const houseCells of this.eachHouseInOrder()) {
401
+ for (let k = 2; k <= 4; k++) {
402
+ const move = this.tryNakedSubsetInHouse(houseCells, k);
403
+ if (move)
404
+ return move;
405
+ }
406
+ }
407
+ return null;
408
+ }
409
+ tryNakedSubsetInHouse(houseCells, k) {
410
+ const empties = houseCells.filter(({ row, col }) => this.board[row][col] === 0 && this.possiblesGrid[row][col].length > 0);
411
+ if (empties.length < k) {
412
+ return null;
413
+ }
414
+ const algorithm = k === 2 ? "Naked Pair" : k === 3 ? "Naked Triple" : "Naked Quad";
415
+ const n = empties.length;
416
+ const indices = [];
417
+ const dfs = (start) => {
418
+ if (indices.length === k) {
419
+ const subset = indices.map((i) => empties[i]);
420
+ const union = new Set();
421
+ for (const { row, col } of subset) {
422
+ for (const v of this.possiblesGrid[row][col]) {
423
+ union.add(v);
424
+ }
425
+ }
426
+ if (union.size !== k) {
427
+ return null;
428
+ }
429
+ const subsetKeys = new Set(subset.map(({ row, col }) => row * 9 + col));
430
+ const eliminations = [];
431
+ for (const { row, col } of houseCells) {
432
+ if (this.board[row][col] !== 0 || subsetKeys.has(row * 9 + col)) {
433
+ continue;
434
+ }
435
+ const poss = this.possiblesGrid[row][col];
436
+ for (const d of union) {
437
+ if (poss.includes(d)) {
438
+ eliminations.push({ row, col, value: d });
439
+ }
440
+ }
441
+ }
442
+ if (eliminations.length === 0) {
443
+ return null;
444
+ }
445
+ return { type: "elimination", eliminations, algorithm };
446
+ }
447
+ for (let i = start; i < n; i++) {
448
+ indices.push(i);
449
+ const res = dfs(i + 1);
450
+ indices.pop();
451
+ if (res)
452
+ return res;
453
+ }
454
+ return null;
455
+ };
456
+ return dfs(0);
457
+ }
371
458
  findHiddenSingle() {
372
459
  for (let row = 0; row < 9; row++) {
373
460
  const move = this.findHiddenSingleInRow(row);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsabol/sudoku-solver",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "TypeScript Sudoku solver module with solve, next move, describe, and validate APIs.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",