From ddcf83397c77346b7bfec230e01d8f00f5490190 Mon Sep 17 00:00:00 2001 From: Komeil Mehranfar Date: Thu, 7 Dec 2023 11:07:17 +0100 Subject: [PATCH] fix: unique solution board generation --- src/index.ts | 33 ++++++++++++++++++++++++++++++++- tests/sudoku.test.ts | 28 ++++++++++++++-------------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/index.ts b/src/index.ts index f01a208..5237e2a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,11 @@ export { type AnalyzeData, type Board, type Difficulty, type SolvingStep }; export function generate(difficulty: Difficulty): Board { const { getBoard } = createSudokuInstance({ difficulty }); - return getBoard(); + const board = getBoard(); + if (hasUniqueSolution(board)) { + return board; + } + return generate(difficulty); } export function analyze(Board: Board): AnalyzeData { @@ -25,3 +29,30 @@ export function solve(Board: Board): { const board = solveAll(); return { board, steps: solvingSteps }; } + +export function hasUniqueSolution(Board: Board): boolean { + const slicedBoard = [...Board]; + const solvingAttempts: { board: Board; steps: SolvingStep[] }[] = []; + let index = 0; + while (slicedBoard.some((item) => !Boolean(item))) { + if (solvingAttempts[index - 1]) { + const { steps } = solvingAttempts[index - 1]; + const step = steps.find((step) => { + if (step.type === "value") { + return true; + } + }); + if (!step) { + break; + } + step.updates.forEach((update) => { + slicedBoard[update.index] = update.filledValue; + }); + } + solvingAttempts.push(solve(slicedBoard)); + index++; + } + return solvingAttempts[0].board.every( + (item, index) => slicedBoard[index] === item, + ); +} diff --git a/tests/sudoku.test.ts b/tests/sudoku.test.ts index 9c4966e..c1f09fd 100644 --- a/tests/sudoku.test.ts +++ b/tests/sudoku.test.ts @@ -1,4 +1,11 @@ -import { generate, analyze, solve } from "../src/index"; // Import the createSudokuInstance module (update path as needed) +import { + generate, + analyze, + solve, + Board, + SolvingStep, + hasUniqueSolution, +} from "../src/index"; // Import the createSudokuInstance module (update path as needed) import { EASY_SUDOKU_BOARD_FOR_TEST, EXPERT_SUDOKU_BOARD_FOR_TEST, @@ -12,12 +19,14 @@ describe("sudoku-core", () => { it("should generate a valid easy difficulty board", () => { //Arrange const sudokuBoard = generate("easy"); + //Act const data = analyze(sudokuBoard); // Assert expect(data.difficulty).toBe("easy"); expect(sudokuBoard.filter(Boolean).length).toBe(40); + expect(hasUniqueSolution(sudokuBoard)).toBe(true); }); it("should generate a valid medium difficulty board", () => { //Arrange @@ -25,10 +34,10 @@ describe("sudoku-core", () => { //Act const data = analyze(sudokuBoard); - // Assert expect(data.difficulty).toBe("medium"); expect(sudokuBoard.filter(Boolean).length).toBe(30); + expect(hasUniqueSolution(sudokuBoard)).toBe(true); }); it("should generate a valid hard difficulty board", () => { //Arrange @@ -39,6 +48,7 @@ describe("sudoku-core", () => { // Assert expect(data.difficulty).toBe("hard"); + expect(hasUniqueSolution(sudokuBoard)).toBe(true); }); it("should generate a valid expert difficulty board", () => { //Arrange @@ -49,6 +59,7 @@ describe("sudoku-core", () => { // Assert expect(data.difficulty).toBe("expert"); + expect(hasUniqueSolution(sudokuBoard)).toBe(true); }); it("should generate a valid master difficulty board", () => { //Arrange @@ -59,23 +70,12 @@ describe("sudoku-core", () => { // Assert expect(data.difficulty).toBe("master"); + expect(hasUniqueSolution(sudokuBoard)).toBe(true); }); }); describe("solve method", () => { it("should solve the board", () => { - //Arrange - const sudokuBoard = generate("expert"); - - //Act - const { board: solvedBoard } = solve(sudokuBoard); - - // Assert - expect(solvedBoard.every((cell) => cell !== null)).toBe(true); - }); - }); - describe("solveStep method", () => { - it("should solve one more step", () => { //Arrange const sudokuBoard = generate("master"); const unfilledCellsLength = sudokuBoard.filter(