@wsabol/sudoku-solver 0.1.5 → 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.
package/README.md CHANGED
@@ -30,11 +30,12 @@ console.log(solved.board); // number[][]
30
30
 
31
31
  const next = Sudoku.nextMove(board);
32
32
  console.log(next.status); // "In progress"
33
- console.log(next.message); // e.g. "Place 4 in r1c3" or "Eliminate 5 from 2 cell(s) (Pointing Pair/Triple)"
33
+ console.log(next.message); // same as next.move?.message when a move exists, e.g. "Place 4 in r1c3 (Naked Single)"
34
34
  if (next.move?.type === "placement") {
35
- console.log(next.move.row, next.move.col, next.move.value);
35
+ console.log(next.move.row, next.move.col, next.move.value, next.move.reasoning);
36
36
  } else if (next.move?.type === "elimination") {
37
37
  console.log(next.move.eliminations); // [{ row, col, value }, ...]
38
+ // multi-value eliminations: message uses sorted set like "2/5/7" in the Eliminate … part
38
39
  }
39
40
 
40
41
  const check = Sudoku.validate(board);
@@ -81,16 +82,32 @@ Returns:
81
82
 
82
83
  ```ts
83
84
  // digit placement
84
- { type: "placement"; row: number; col: number; value: number; algorithm: Algorithm }
85
+ {
86
+ type: "placement";
87
+ row: number;
88
+ col: number;
89
+ value: number;
90
+ algorithm: Algorithm;
91
+ message: string;
92
+ reasoning: string;
93
+ }
85
94
 
86
- // candidate elimination (e.g. Pointing Pair/Triple)
87
- { type: "elimination"; eliminations: Array<{ row: number; col: number; value: number }>; algorithm: Algorithm }
95
+ // candidate elimination (e.g. Pointing Pair or Pointing Triple)
96
+ {
97
+ type: "elimination";
98
+ eliminations: Array<{ row: number; col: number; value: number }>;
99
+ algorithm: Algorithm;
100
+ message: string;
101
+ reasoning: string;
102
+ }
88
103
  ```
89
104
 
90
105
  Notes:
91
106
  - `move` is `null` when the board is complete or invalid.
92
107
  - `move.type` must be checked before accessing placement-specific fields (`row`, `col`, `value`).
93
- - `message` is human-readable: `"Place 4 in r1c3"` for placements, `"Eliminate 5 from 2 cell(s) (Pointing Pair/Triple)"` for eliminations.
108
+ - `message`: placement uses 1-based coords in text, e.g. `Place 4 in r1c3 (Naked Single)`. Elimination counts distinct cells and names a common house when all targets share one row, column, or 3×3 box, e.g. `Eliminate 5 from 2 cells in row 4 (Pointing Pair)` or `Eliminate 2/7/9 from 3 cells in box 7 (Naked Triple)`; several distinct values use `{2/5/7}` in the digit part.
109
+ - `reasoning`: one sentence describing why the move is sound (technique-specific).
110
+ - Top-level `message` matches `move.message` when `move` is non-null.
94
111
 
95
112
  ### `Sudoku.validate(boardInput)`
96
113
 
@@ -117,12 +134,12 @@ Returns:
117
134
  isValid: boolean;
118
135
  isComplete: boolean;
119
136
  message: string;
120
- difficulty: "Easy" | "Medium" | "Hard" | "Diabolical" | "Impossible";
137
+ difficulty: "Easy" | "Medium" | "Hard" | "Diabolical" | "Impossible" | null;
121
138
  solutions: number;
122
139
  }
123
140
  ```
124
141
 
125
- `difficulty` is derived from empty cell count. `solutions` is `0` when the board is invalid/unsolvable, `1` when a unique solution exists.
142
+ `difficulty` is derived from empty cell count when the board is valid; `null` when `isValid === false`. `solutions` is `0` when the board is invalid/unsolvable, `1` when a unique solution exists.
126
143
 
127
144
  ## Development
128
145
 
@@ -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/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,sBAAsB,GACtB,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;AAO7B,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;IAgB3B,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;IAmD9B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,0BAA0B;IAUlC,8DAA8D;IAC9D,OAAO,CAAC,eAAe;IAcvB,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"}
@@ -1,12 +1,47 @@
1
1
  import { cloneBoard, assertBoardShape, parseBoardString, duplicateValues, boxNumber } from "./utils.js";
2
2
  import { pushUniqueReason } from "./validate.js";
3
3
  const COMPLETE = [1, 2, 3, 4, 5, 6, 7, 8, 9];
4
+ function uniqueCellCoordinates(items) {
5
+ const seen = new Set();
6
+ const out = [];
7
+ for (const { row, col } of items) {
8
+ const k = `${row},${col}`;
9
+ if (!seen.has(k)) {
10
+ seen.add(k);
11
+ out.push({ row, col });
12
+ }
13
+ }
14
+ return out;
15
+ }
16
+ /**
17
+ * If every cell lies in the same row, same column, or same 3×3 box, return that house.
18
+ * Row wins over column when both apply (e.g. a single cell). Otherwise `null`.
19
+ */
20
+ function sharedHouseContainingCells(cells) {
21
+ if (cells.length === 0) {
22
+ return null;
23
+ }
24
+ const first = cells[0];
25
+ if (cells.every((c) => c.row === first.row)) {
26
+ return { kind: "row", wherePhrase: `row ${first.row + 1}` };
27
+ }
28
+ if (cells.every((c) => c.col === first.col)) {
29
+ return { kind: "column", wherePhrase: `column ${first.col + 1}` };
30
+ }
31
+ const box = boxNumber(first.row, first.col);
32
+ if (cells.every((c) => boxNumber(c.row, c.col) === box)) {
33
+ return { kind: "box", wherePhrase: `box ${box}` };
34
+ }
35
+ return null;
36
+ }
4
37
  export default class SudokuSolver {
5
38
  static SEARCH_PHASES = [
6
39
  "NakedSingle",
7
40
  "HiddenSingle",
8
41
  "Pointing",
9
42
  "NakedSubset",
43
+ "HiddenSubset",
44
+ "NakedHiddenQuads",
10
45
  ];
11
46
  board;
12
47
  possiblesGrid;
@@ -257,12 +292,17 @@ export default class SudokuSolver {
257
292
  }
258
293
  finalizeElimination(eliminations, algorithm, reasoning) {
259
294
  const digits = [...new Set(eliminations.map((e) => e.value))].sort((a, b) => a - b);
260
- const digitPart = digits.length === 1 ? String(digits[0]) : `{${digits.join("/")}}`;
295
+ const digitPart = digits.length === 1 ? String(digits[0]) : `${digits.join("/")}`;
296
+ const uniqueCells = uniqueCellCoordinates(eliminations);
297
+ const uniqueCellCount = uniqueCells.length;
298
+ const cellWord = uniqueCellCount === 1 ? "cell" : "cells";
299
+ const housePhrase = sharedHouseContainingCells(uniqueCells)?.wherePhrase;
300
+ const wherePart = housePhrase ? ` in ${housePhrase}` : "";
261
301
  return {
262
302
  type: "elimination",
263
303
  eliminations,
264
304
  algorithm,
265
- message: `Eliminate ${digitPart} from ${eliminations.length} cell(s) (${algorithm})`,
305
+ message: `Eliminate ${digitPart} from ${uniqueCellCount} ${cellWord}${wherePart} (${algorithm})`,
266
306
  reasoning,
267
307
  };
268
308
  }
@@ -284,6 +324,12 @@ export default class SudokuSolver {
284
324
  return this.findPointingPairTriple();
285
325
  case "NakedSubset":
286
326
  return this.findNakedSubsetElimination();
327
+ case "HiddenSubset":
328
+ return this.findHiddenSubsetElimination();
329
+ case "NakedHiddenQuads":
330
+ return this.findNakedHiddenQuadsElimination();
331
+ default:
332
+ return null;
287
333
  }
288
334
  }
289
335
  findNakedSingle() {
@@ -427,8 +473,9 @@ export default class SudokuSolver {
427
473
  }
428
474
  }
429
475
  if (eliminations.length > 0) {
476
+ const algorithm = cells.length === 2 ? "Pointing Pair" : "Pointing Triple";
430
477
  const reasoning = `In box ${boxNum}, every candidate for ${digit} lies in row ${sharedRow + 1}, so ${digit} cannot appear elsewhere in that row outside the box.`;
431
- return this.finalizeElimination(eliminations, "Pointing Pair/Triple", reasoning);
478
+ return this.finalizeElimination(eliminations, algorithm, reasoning);
432
479
  }
433
480
  }
434
481
  if (cells.every((c) => c.col === cells[0].col)) {
@@ -442,8 +489,9 @@ export default class SudokuSolver {
442
489
  }
443
490
  }
444
491
  if (eliminations.length > 0) {
492
+ const algorithm = cells.length === 2 ? "Pointing Pair" : "Pointing Triple";
445
493
  const reasoning = `In box ${boxNum}, every candidate for ${digit} lies in column ${sharedCol + 1}, so ${digit} cannot appear elsewhere in that column outside the box.`;
446
- return this.finalizeElimination(eliminations, "Pointing Pair/Triple", reasoning);
494
+ return this.finalizeElimination(eliminations, algorithm, reasoning);
447
495
  }
448
496
  }
449
497
  }
@@ -469,7 +517,7 @@ export default class SudokuSolver {
469
517
  }
470
518
  findNakedSubsetElimination() {
471
519
  for (const houseCells of this.eachHouseInOrder()) {
472
- for (let k = 2; k <= 4; k++) {
520
+ for (let k = 2; k <= 3; k++) {
473
521
  const move = this.tryNakedSubsetInHouse(houseCells, k);
474
522
  if (move)
475
523
  return move;
@@ -479,13 +527,11 @@ export default class SudokuSolver {
479
527
  }
480
528
  /** `houseCells` is one full row, column, or box (9 cells). */
481
529
  getHouseContext(houseCells) {
482
- const h = houseCells[0];
483
- if (houseCells.every((c) => c.row === h.row)) {
484
- return { wherePhrase: `row ${h.row + 1}`, sameKindWord: "row" };
485
- }
486
- if (houseCells.every((c) => c.col === h.col)) {
487
- return { wherePhrase: `column ${h.col + 1}`, sameKindWord: "column" };
530
+ const shared = sharedHouseContainingCells(houseCells);
531
+ if (shared) {
532
+ return { wherePhrase: shared.wherePhrase, sameKindWord: shared.kind };
488
533
  }
534
+ const h = houseCells[0];
489
535
  return { wherePhrase: `box ${boxNumber(h.row, h.col)}`, sameKindWord: "box" };
490
536
  }
491
537
  tryNakedSubsetInHouse(houseCells, k) {
@@ -540,6 +586,89 @@ export default class SudokuSolver {
540
586
  };
541
587
  return dfs(0);
542
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
+ }
543
672
  findHiddenSingle() {
544
673
  for (let row = 0; row < 9; row++) {
545
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.5",
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",