@wsabol/sudoku-solver 0.1.6 → 0.1.7

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,7 +1,7 @@
1
1
  import { ValidationResult } from "./validate.js";
2
2
  import type { Board } from "./boardGeo.js";
3
3
  import type { Move, EliminationMove } from "./move.js";
4
- export type Algorithm = "Last Digit" | "Full House" | "Naked Single" | "Hidden Single" | "Pointing Pair" | "Pointing Triple" | "Naked Pair" | "Naked Triple" | "Naked Quad";
4
+ export type Algorithm = "Last Digit" | "Full House" | "Naked Single" | "Hidden Single" | "Pointing Pair" | "Pointing Triple" | "Naked Pair" | "Naked Triple" | "Naked Quad" | "Hidden Pair" | "Hidden Triple" | "Hidden Quad";
5
5
  export type DifficultyLevel = "Easy" | "Medium" | "Hard" | "Diabolical" | "Impossible";
6
6
  export type ValidationReasonType = "duplicate_in_row" | "duplicate_in_column" | "duplicate_in_box" | "invalid_value" | "invalid_board_length" | "invalid_board_characters" | "empty_cell_no_candidates" | "too_many_empty_cells";
7
7
  export default class SudokuSolver {
@@ -48,6 +48,9 @@ export default class SudokuSolver {
48
48
  /** `houseCells` is one full row, column, or box (9 cells). */
49
49
  private getHouseContext;
50
50
  private tryNakedSubsetInHouse;
51
+ private findHiddenSubsetElimination;
52
+ private findNakedHiddenQuadsElimination;
53
+ private tryHiddenSubsetInHouse;
51
54
  private findHiddenSingle;
52
55
  }
53
56
  //# 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;AACrF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAiB,eAAe,EAAE,MAAM,WAAW,CAAC;AAEtE,MAAM,MAAM,SAAS,GACf,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,eAAe,GACf,eAAe,GACf,iBAAiB,GACjB,YAAY,GACZ,cAAc,GACd,YAAY,CAAC;AAEnB,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,GAAG,YAAY,CAAC;AAEvF,MAAM,MAAM,oBAAoB,GAC1B,kBAAkB,GAClB,qBAAqB,GACrB,kBAAkB,GAClB,eAAe,GACf,sBAAsB,GACtB,0BAA0B,GAC1B,0BAA0B,GAC1B,sBAAsB,CAAC;AA+C7B,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,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAQ3B,UAAU,IAAI,OAAO;IAIrB,eAAe,IAAI,MAAM;IAIzB,WAAW,CAAC,KAAK,GAAE,MAAU,GAAG,MAAM;IAOtC,QAAQ,IAAI,gBAAgB;IAiG5B,OAAO,IAAI,OAAO;IAKlB,UAAU,IAAI,eAAe;IAiB7B,WAAW,IAAI,IAAI,GAAG,IAAI;IAO1B,KAAK,IAAI,OAAO;IAchB,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,iBAAiB;IAkBzB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,eAAe;IA+CvB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,qBAAqB;IAsB7B,OAAO,CAAC,qBAAqB;IAsB7B,OAAO,CAAC,qBAAqB;IAuB7B,OAAO,CAAC,sBAAsB;IAqD9B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,0BAA0B;IAUlC,8DAA8D;IAC9D,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,qBAAqB;IAwD7B,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;AACrF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAiB,eAAe,EAAE,MAAM,WAAW,CAAC;AAEtE,MAAM,MAAM,SAAS,GACf,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,eAAe,GACf,eAAe,GACf,iBAAiB,GACjB,YAAY,GACZ,cAAc,GACd,YAAY,GACZ,aAAa,GACb,eAAe,GACf,aAAa,CAAC;AAEpB,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,GAAG,YAAY,CAAC;AAEvF,MAAM,MAAM,oBAAoB,GAC1B,kBAAkB,GAClB,qBAAqB,GACrB,kBAAkB,GAClB,eAAe,GACf,sBAAsB,GACtB,0BAA0B,GAC1B,0BAA0B,GAC1B,sBAAsB,CAAC;AA+C7B,MAAM,CAAC,OAAO,OAAO,YAAY;IAC7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAOnC;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,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAQ3B,UAAU,IAAI,OAAO;IAIrB,eAAe,IAAI,MAAM;IAIzB,WAAW,CAAC,KAAK,GAAE,MAAU,GAAG,MAAM;IAOtC,QAAQ,IAAI,gBAAgB;IAiG5B,OAAO,IAAI,OAAO;IAKlB,UAAU,IAAI,eAAe;IAiB7B,WAAW,IAAI,IAAI,GAAG,IAAI;IAO1B,KAAK,IAAI,OAAO;IAchB,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,iBAAiB;IAkBzB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,eAAe;IA+CvB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,qBAAqB;IAsB7B,OAAO,CAAC,qBAAqB;IAsB7B,OAAO,CAAC,qBAAqB;IAuB7B,OAAO,CAAC,sBAAsB;IAqD9B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,0BAA0B;IAUlC,8DAA8D;IAC9D,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,qBAAqB;IAwD7B,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,+BAA+B;IAWvC,OAAO,CAAC,sBAAsB;IAgE9B,OAAO,CAAC,gBAAgB;CAe3B"}
@@ -40,6 +40,8 @@ export default class SudokuSolver {
40
40
  "HiddenSingle",
41
41
  "Pointing",
42
42
  "NakedSubset",
43
+ "HiddenSubset",
44
+ "NakedHiddenQuads",
43
45
  ];
44
46
  board;
45
47
  possiblesGrid;
@@ -290,7 +292,7 @@ export default class SudokuSolver {
290
292
  }
291
293
  finalizeElimination(eliminations, algorithm, reasoning) {
292
294
  const digits = [...new Set(eliminations.map((e) => e.value))].sort((a, b) => a - b);
293
- const digitPart = digits.length === 1 ? String(digits[0]) : `{${digits.join("/")}}`;
295
+ const digitPart = digits.length === 1 ? String(digits[0]) : `${digits.join("/")}`;
294
296
  const uniqueCells = uniqueCellCoordinates(eliminations);
295
297
  const uniqueCellCount = uniqueCells.length;
296
298
  const cellWord = uniqueCellCount === 1 ? "cell" : "cells";
@@ -322,6 +324,12 @@ export default class SudokuSolver {
322
324
  return this.findPointingPairTriple();
323
325
  case "NakedSubset":
324
326
  return this.findNakedSubsetElimination();
327
+ case "HiddenSubset":
328
+ return this.findHiddenSubsetElimination();
329
+ case "NakedHiddenQuads":
330
+ return this.findNakedHiddenQuadsElimination();
331
+ default:
332
+ return null;
325
333
  }
326
334
  }
327
335
  findNakedSingle() {
@@ -509,7 +517,7 @@ export default class SudokuSolver {
509
517
  }
510
518
  findNakedSubsetElimination() {
511
519
  for (const houseCells of this.eachHouseInOrder()) {
512
- for (let k = 2; k <= 4; k++) {
520
+ for (let k = 2; k <= 3; k++) {
513
521
  const move = this.tryNakedSubsetInHouse(houseCells, k);
514
522
  if (move)
515
523
  return move;
@@ -578,6 +586,89 @@ export default class SudokuSolver {
578
586
  };
579
587
  return dfs(0);
580
588
  }
589
+ findHiddenSubsetElimination() {
590
+ for (const houseCells of this.eachHouseInOrder()) {
591
+ for (let k = 2; k <= 3; k++) {
592
+ const move = this.tryHiddenSubsetInHouse(houseCells, k);
593
+ if (move)
594
+ return move;
595
+ }
596
+ }
597
+ return null;
598
+ }
599
+ findNakedHiddenQuadsElimination() {
600
+ for (const houseCells of this.eachHouseInOrder()) {
601
+ const moveNaked = this.tryNakedSubsetInHouse(houseCells, 4);
602
+ if (moveNaked)
603
+ return moveNaked;
604
+ const moveHidden = this.tryHiddenSubsetInHouse(houseCells, 4);
605
+ if (moveHidden)
606
+ return moveHidden;
607
+ }
608
+ return null;
609
+ }
610
+ tryHiddenSubsetInHouse(houseCells, k) {
611
+ const algorithm = k === 2 ? "Hidden Pair" : k === 3 ? "Hidden Triple" : "Hidden Quad";
612
+ const { wherePhrase, sameKindWord } = this.getHouseContext(houseCells);
613
+ const digitIndices = [];
614
+ const dfsDigits = (start) => {
615
+ if (digitIndices.length === k) {
616
+ const digits = digitIndices.map((i) => COMPLETE[i]);
617
+ const digitSet = new Set(digits);
618
+ for (const d of digits) {
619
+ let seen = false;
620
+ for (const { row, col } of houseCells) {
621
+ if (this.board[row][col] === 0 && this.possiblesGrid[row][col].includes(d)) {
622
+ seen = true;
623
+ break;
624
+ }
625
+ }
626
+ if (!seen) {
627
+ return null;
628
+ }
629
+ }
630
+ const reserved = [];
631
+ for (const { row, col } of houseCells) {
632
+ if (this.board[row][col] !== 0) {
633
+ continue;
634
+ }
635
+ for (const d of digits) {
636
+ if (this.possiblesGrid[row][col].includes(d)) {
637
+ reserved.push({ row, col });
638
+ break;
639
+ }
640
+ }
641
+ }
642
+ if (reserved.length !== k) {
643
+ return null;
644
+ }
645
+ const eliminations = [];
646
+ for (const { row, col } of reserved) {
647
+ for (const v of this.possiblesGrid[row][col]) {
648
+ if (!digitSet.has(v)) {
649
+ eliminations.push({ row, col, value: v });
650
+ }
651
+ }
652
+ }
653
+ if (eliminations.length === 0) {
654
+ return null;
655
+ }
656
+ const digitStr = [...digits].sort((a, b) => a - b).join("/");
657
+ const cellPhrase = reserved.map(({ row, col }) => `r${row + 1}c${col + 1}`).join(", ");
658
+ const reasoning = `${algorithm} ${digitStr} in ${wherePhrase}: in that ${sameKindWord}, those digits appear only in ${cellPhrase}, so candidates other than ${digitStr} can be removed from those cells.`;
659
+ return this.finalizeElimination(eliminations, algorithm, reasoning);
660
+ }
661
+ for (let i = start; i < 9; i++) {
662
+ digitIndices.push(i);
663
+ const res = dfsDigits(i + 1);
664
+ digitIndices.pop();
665
+ if (res)
666
+ return res;
667
+ }
668
+ return null;
669
+ };
670
+ return dfsDigits(0);
671
+ }
581
672
  findHiddenSingle() {
582
673
  for (let row = 0; row < 9; row++) {
583
674
  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.6",
3
+ "version": "0.1.7",
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",