Sudoku solver: Finish project
This commit is contained in:
parent
0d2e39e7ba
commit
f1ce58405c
5 changed files with 186 additions and 80 deletions
|
@ -29,8 +29,8 @@ export const invalidStrings = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const unsolvableStrings = [
|
export const unsolvableStrings = [
|
||||||
"115..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.420..8..1..16....926969.37.",
|
"115..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.421..8..1..16....926969.37.",
|
||||||
"4206942069.......................................................................",
|
"4216942169.......................................................................",
|
||||||
"111111111111111111111111111111111111111111111111111111111111111111111111111111111",
|
"111111111111111111111111111111111111111111111111111111111111111111111111111111111",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,47 +1,127 @@
|
||||||
const grid = [
|
let currentPuzzle = "";
|
||||||
[1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
||||||
[10, 11, 12, 13, 14, 15, 16, 17, 18],
|
|
||||||
[19, 20, 21, 22, 23, 24, 25, 26, 27],
|
|
||||||
[28, 29, 30, 31, 32, 33, 34, 35, 36],
|
|
||||||
[37, 38, 39, 40, 41, 42, 43, 44, 45],
|
|
||||||
[46, 47, 48, 49, 50, 51, 52, 53, 54],
|
|
||||||
[55, 56, 57, 58, 59, 60, 61, 62, 63],
|
|
||||||
[64, 65, 66, 67, 68, 69, 70, 71, 72],
|
|
||||||
[73, 74, 75, 76, 77, 78, 79, 80, 81],
|
|
||||||
];
|
|
||||||
|
|
||||||
class SudokuSolver {
|
class SudokuSolver {
|
||||||
getPuzzleArray(puzzleString) {
|
getPuzzleArray(puzzleString) {
|
||||||
const puzzleArray = [];
|
const puzzleArray = [[], [], [], [], [], [], [], [], []];
|
||||||
for (let i = 1; i <= 81; i++) {
|
for (let i = 0; i <= 80; i++) {
|
||||||
let value = puzzleString[i - 1];
|
let value = puzzleString[i];
|
||||||
value === "." || !value
|
value === "." || !value
|
||||||
? (puzzleArray[Math.ceil(i / 9)][(i % 9) + 1] = " ")
|
? (puzzleArray[Math.floor(i / 9)][i % 9] = " ")
|
||||||
: (puzzleArray[Math.ceil(i / 9)][(i % 9) + 1] = value);
|
: (puzzleArray[Math.floor(i / 9)][i % 9] = value);
|
||||||
}
|
}
|
||||||
|
return puzzleArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRow(puzzle, row) {
|
||||||
|
return puzzle[row];
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumn(puzzle, row, column) {
|
||||||
|
let col = [];
|
||||||
|
for (let i in puzzle) {
|
||||||
|
col.push(puzzle[i][column]);
|
||||||
|
}
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRegion(puzzle, row, column) {
|
||||||
|
let region = [];
|
||||||
|
for (let r = 0; r < 3; r++) {
|
||||||
|
for (let c = 0; c < 3; c++) {
|
||||||
|
region.push(
|
||||||
|
puzzle[Math.floor(row / 3) * 3 + r][Math.floor(column / 3) * 3 + c]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return region;
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(puzzleString) {
|
validate(puzzleString) {
|
||||||
if (puzzleString.length != 81) {
|
if (puzzleString.length !== 81) {
|
||||||
return {
|
return {
|
||||||
status: "error",
|
|
||||||
error: "Expected puzzle to be 81 characters long",
|
error: "Expected puzzle to be 81 characters long",
|
||||||
};
|
};
|
||||||
} else if (!/^[1-9\.]+$/.test(puzzleString)) {
|
} else if (!/^[1-9.]+$/.test(puzzleString)) {
|
||||||
return { status: "error", error: "Invalid characters in puzzle" };
|
return { error: "Invalid characters in puzzle" };
|
||||||
} else {
|
} else {
|
||||||
|
for (let i in puzzleString) {
|
||||||
|
let val = puzzleString[i];
|
||||||
|
let row = Math.floor(i / 9);
|
||||||
|
let column = i % 9;
|
||||||
|
if (val !== ".") {
|
||||||
|
let temp = puzzleString.split("");
|
||||||
|
temp[i] = ".";
|
||||||
|
if (
|
||||||
|
this.checkRowPlacement(temp, row, column, val) &&
|
||||||
|
this.checkColPlacement(temp, row, column, val) &&
|
||||||
|
this.checkRegionPlacement(temp, row, column, val)
|
||||||
|
) {
|
||||||
|
temp[i] = val;
|
||||||
|
puzzleString = temp.join("");
|
||||||
|
} else {
|
||||||
|
return { error: "Puzzle cannot be solved" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkRowPlacement(puzzleString, row, column, value) {}
|
checkRowPlacement(puzzleString, row, column, value) {
|
||||||
|
let puzzle = this.getPuzzleArray(puzzleString);
|
||||||
|
let puzzleRow = this.getRow(puzzle, row, column);
|
||||||
|
return !(
|
||||||
|
puzzleRow.includes(value.toString()) &&
|
||||||
|
puzzleRow[column] !== value.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
checkColPlacement(puzzleString, row, column, value) {}
|
checkColPlacement(puzzleString, row, column, value) {
|
||||||
|
let puzzle = this.getPuzzleArray(puzzleString);
|
||||||
|
let puzzleCol = this.getColumn(puzzle, row, column);
|
||||||
|
return !(
|
||||||
|
puzzleCol.includes(value.toString()) &&
|
||||||
|
puzzleCol[row] !== value.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
checkRegionPlacement(puzzleString, row, column, value) {}
|
checkRegionPlacement(puzzleString, row, column, value) {
|
||||||
|
let puzzle = this.getPuzzleArray(puzzleString);
|
||||||
|
let puzzleRegion = this.getRegion(puzzle, row, column);
|
||||||
|
return !(
|
||||||
|
puzzleRegion.includes(value.toString()) &&
|
||||||
|
puzzle[row][column] !== value.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
solve(puzzleString) {
|
solve(puzzleString) {
|
||||||
return { error: "test" };
|
if (puzzleString) currentPuzzle = puzzleString;
|
||||||
|
let valid = this.validate(currentPuzzle);
|
||||||
|
if (valid !== true) {
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
let empty = currentPuzzle.indexOf(".");
|
||||||
|
let row = Math.floor(empty / 9);
|
||||||
|
let column = empty % 9;
|
||||||
|
if (empty !== -1) {
|
||||||
|
for (let i = 1; i <= 9; i++) {
|
||||||
|
if (
|
||||||
|
this.checkRowPlacement(currentPuzzle, row, column, i) &&
|
||||||
|
this.checkColPlacement(currentPuzzle, row, column, i) &&
|
||||||
|
this.checkRegionPlacement(currentPuzzle, row, column, i)
|
||||||
|
) {
|
||||||
|
let temp = currentPuzzle.split("");
|
||||||
|
temp[empty] = i;
|
||||||
|
currentPuzzle = temp.join("");
|
||||||
|
this.solve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentPuzzle.indexOf(".") !== -1) {
|
||||||
|
let temp = currentPuzzle.split("");
|
||||||
|
temp[empty] = ".";
|
||||||
|
currentPuzzle = temp.join("");
|
||||||
|
}
|
||||||
|
return { solution: currentPuzzle };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,47 @@
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
const SudokuSolver = require('../controllers/sudoku-solver.js');
|
const SudokuSolver = require("../controllers/sudoku-solver.js");
|
||||||
|
|
||||||
module.exports = function (app) {
|
module.exports = function (app) {
|
||||||
|
|
||||||
let solver = new SudokuSolver();
|
let solver = new SudokuSolver();
|
||||||
|
|
||||||
app.route('/api/check')
|
app.route("/api/check").post((req, res) => {
|
||||||
.post((req, res) => {
|
if (!req.body.puzzle || !req.body.coordinate || !req.body.value) {
|
||||||
|
return res.json({ error: "Required field(s) missing" });
|
||||||
|
}
|
||||||
|
if (!(req.body.value > 0) || !(req.body.value < 10)) {
|
||||||
|
return res.json({ error: "Invalid value" });
|
||||||
|
}
|
||||||
|
if (!/^[A-I][1-9]$/.test(req.body.coordinate)) {
|
||||||
|
return res.json({ error: "Invalid coordinate" });
|
||||||
|
}
|
||||||
|
let validString = solver.validate(req.body.puzzle);
|
||||||
|
if (validString !== true) {
|
||||||
|
return res.json(validString);
|
||||||
|
}
|
||||||
|
let row = req.body.coordinate[0].charCodeAt(0) - 65;
|
||||||
|
let col = req.body.coordinate[1] - 1;
|
||||||
|
let conflict = [];
|
||||||
|
let valid;
|
||||||
|
if (!solver.checkRowPlacement(req.body.puzzle, row, col, req.body.value)) {
|
||||||
|
conflict.push("row");
|
||||||
|
}
|
||||||
|
if (!solver.checkColPlacement(req.body.puzzle, row, col, req.body.value)) {
|
||||||
|
conflict.push("column");
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!solver.checkRegionPlacement(req.body.puzzle, row, col, req.body.value)
|
||||||
|
) {
|
||||||
|
conflict.push("region");
|
||||||
|
}
|
||||||
|
conflict.length > 0 ? (valid = false) : (valid = true);
|
||||||
|
res.json({ valid: valid, conflict: conflict });
|
||||||
});
|
});
|
||||||
|
|
||||||
app.route('/api/solve')
|
app.route("/api/solve").post((req, res) => {
|
||||||
.post((req, res) => {
|
if (!req.body.puzzle) {
|
||||||
|
return res.json({ error: "Required field missing" });
|
||||||
|
}
|
||||||
|
res.json(solver.solve(req.body.puzzle));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,6 @@ const assert = chai.assert;
|
||||||
import {
|
import {
|
||||||
puzzlesAndSolutions,
|
puzzlesAndSolutions,
|
||||||
invalidStrings,
|
invalidStrings,
|
||||||
unsolvableStrings,
|
|
||||||
wrongLengthStrings,
|
wrongLengthStrings,
|
||||||
} from "../controllers/puzzle-strings";
|
} from "../controllers/puzzle-strings";
|
||||||
|
|
||||||
|
@ -36,11 +35,11 @@ suite("Unit Tests", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
suite("Placement Checks", () => {
|
suite("Placement Checks", () => {
|
||||||
test("Login handles a valid row placement", () => {
|
test("Logic handles a valid row placement", () => {
|
||||||
const checks = [
|
const checks = [
|
||||||
solver.checkRowPlacement(puzzlesAndSolutions[0][0], "A", 2, 3),
|
solver.checkRowPlacement(puzzlesAndSolutions[0][0], 0, 3, 3),
|
||||||
solver.checkRowPlacement(puzzlesAndSolutions[0][1], "A", 2, 6),
|
solver.checkRowPlacement(puzzlesAndSolutions[1][0], 0, 3, 6),
|
||||||
solver.checkRowPlacement(puzzlesAndSolutions[0][2], "A", 1, 2),
|
solver.checkRowPlacement(puzzlesAndSolutions[2][0], 0, 2, 2),
|
||||||
];
|
];
|
||||||
for (let i in checks) {
|
for (let i in checks) {
|
||||||
assert.isTrue(checks[i]);
|
assert.isTrue(checks[i]);
|
||||||
|
@ -48,9 +47,9 @@ suite("Unit Tests", () => {
|
||||||
});
|
});
|
||||||
test("Logic handles an invalid row placement", () => {
|
test("Logic handles an invalid row placement", () => {
|
||||||
const checks = [
|
const checks = [
|
||||||
solver.checkRowPlacement(puzzlesAndSolutions[0][0], "A", 2, 8),
|
solver.checkRowPlacement(puzzlesAndSolutions[0][0], 0, 3, 5),
|
||||||
solver.checkRowPlacement(puzzlesAndSolutions[0][1], "A", 2, 1),
|
solver.checkRowPlacement(puzzlesAndSolutions[1][0], 0, 3, 1),
|
||||||
solver.checkRowPlacement(puzzlesAndSolutions[0][2], "A", 1, 3),
|
solver.checkRowPlacement(puzzlesAndSolutions[2][0], 0, 2, 3),
|
||||||
];
|
];
|
||||||
for (let i in checks) {
|
for (let i in checks) {
|
||||||
assert.isFalse(checks[i]);
|
assert.isFalse(checks[i]);
|
||||||
|
@ -58,9 +57,9 @@ suite("Unit Tests", () => {
|
||||||
});
|
});
|
||||||
test("Login handles a valid column placement", () => {
|
test("Login handles a valid column placement", () => {
|
||||||
const checks = [
|
const checks = [
|
||||||
solver.checkColPlacement(puzzlesAndSolutions[0][0], "A", 2, 3),
|
solver.checkColPlacement(puzzlesAndSolutions[0][0], 0, 1, 3),
|
||||||
solver.checkColPlacement(puzzlesAndSolutions[0][1], "A", 2, 6),
|
solver.checkColPlacement(puzzlesAndSolutions[1][0], 0, 1, 6),
|
||||||
solver.checkColPlacement(puzzlesAndSolutions[0][2], "A", 1, 2),
|
solver.checkColPlacement(puzzlesAndSolutions[2][0], 0, 0, 2),
|
||||||
];
|
];
|
||||||
for (let i in checks) {
|
for (let i in checks) {
|
||||||
assert.isTrue(checks[i]);
|
assert.isTrue(checks[i]);
|
||||||
|
@ -68,9 +67,9 @@ suite("Unit Tests", () => {
|
||||||
});
|
});
|
||||||
test("Logic handles an invalid column placement", () => {
|
test("Logic handles an invalid column placement", () => {
|
||||||
const checks = [
|
const checks = [
|
||||||
solver.checkColPlacement(puzzlesAndSolutions[0][0], "B", 2, 9),
|
solver.checkColPlacement(puzzlesAndSolutions[0][0], 1, 1, 9),
|
||||||
solver.checkColPlacement(puzzlesAndSolutions[0][1], "B", 2, 8),
|
solver.checkColPlacement(puzzlesAndSolutions[1][0], 1, 1, 8),
|
||||||
solver.checkColPlacement(puzzlesAndSolutions[0][2], "C", 3, 2),
|
solver.checkColPlacement(puzzlesAndSolutions[2][0], 2, 2, 2),
|
||||||
];
|
];
|
||||||
for (let i in checks) {
|
for (let i in checks) {
|
||||||
assert.isFalse(checks[i]);
|
assert.isFalse(checks[i]);
|
||||||
|
@ -78,9 +77,9 @@ suite("Unit Tests", () => {
|
||||||
});
|
});
|
||||||
test("Logic handles a valid region (3x3 grid) placement", () => {
|
test("Logic handles a valid region (3x3 grid) placement", () => {
|
||||||
const checks = [
|
const checks = [
|
||||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][0], "A", 2, 3),
|
solver.checkRegionPlacement(puzzlesAndSolutions[0][0], 0, 1, 3),
|
||||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][1], "A", 2, 6),
|
solver.checkRegionPlacement(puzzlesAndSolutions[1][0], 0, 1, 6),
|
||||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][2], "A", 1, 2),
|
solver.checkRegionPlacement(puzzlesAndSolutions[2][0], 0, 0, 2),
|
||||||
];
|
];
|
||||||
for (let i in checks) {
|
for (let i in checks) {
|
||||||
assert.isTrue(checks[i]);
|
assert.isTrue(checks[i]);
|
||||||
|
@ -88,9 +87,9 @@ suite("Unit Tests", () => {
|
||||||
});
|
});
|
||||||
test("Logic handles an invalid region (3x3 grid) placement", () => {
|
test("Logic handles an invalid region (3x3 grid) placement", () => {
|
||||||
const checks = [
|
const checks = [
|
||||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][0], "C", 3, 5),
|
solver.checkRegionPlacement(puzzlesAndSolutions[0][0], 2, 2, 5),
|
||||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][1], "C", 3, 5),
|
solver.checkRegionPlacement(puzzlesAndSolutions[1][0], 2, 2, 5),
|
||||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][2], "C", 3, 5),
|
solver.checkRegionPlacement(puzzlesAndSolutions[2][0], 2, 2, 5),
|
||||||
];
|
];
|
||||||
for (let i in checks) {
|
for (let i in checks) {
|
||||||
assert.isFalse(checks[i]);
|
assert.isFalse(checks[i]);
|
||||||
|
@ -101,7 +100,7 @@ suite("Unit Tests", () => {
|
||||||
test("Valid puzzle strings pass the solver", () => {
|
test("Valid puzzle strings pass the solver", () => {
|
||||||
for (let i in puzzlesAndSolutions) {
|
for (let i in puzzlesAndSolutions) {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
solver.solve(puzzlesAndSolutions[i][1]),
|
solver.solve(puzzlesAndSolutions[i][0]).solution,
|
||||||
puzzlesAndSolutions[i][1]
|
puzzlesAndSolutions[i][1]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -109,8 +108,8 @@ suite("Unit Tests", () => {
|
||||||
test("Invalid puzzle strings fail the solver", () => {
|
test("Invalid puzzle strings fail the solver", () => {
|
||||||
for (let i in invalidStrings) {
|
for (let i in invalidStrings) {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
solver.solve(invalidStrings[i]),
|
solver.solve(invalidStrings[i]).error,
|
||||||
{ error: "Invalid characters in puzzle" },
|
"Invalid characters in puzzle",
|
||||||
"invalid characters"
|
"invalid characters"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -118,9 +117,8 @@ suite("Unit Tests", () => {
|
||||||
test("Solver returns the expected solution for an incomplete puzzle", () => {
|
test("Solver returns the expected solution for an incomplete puzzle", () => {
|
||||||
for (let i in puzzlesAndSolutions) {
|
for (let i in puzzlesAndSolutions) {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
solver.solve(puzzlesAndSolutions[i][0]),
|
solver.solve(puzzlesAndSolutions[i][0]).solution,
|
||||||
puzzlesAndSolutions[i][1],
|
puzzlesAndSolutions[i][1]
|
||||||
puzzlesAndSolutions[i]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,12 +22,11 @@ suite("Functional Tests", () => {
|
||||||
.end((err, res) => {
|
.end((err, res) => {
|
||||||
assert.equal(res.status, 200);
|
assert.equal(res.status, 200);
|
||||||
assert.equal(res.body.solution, puzzlesAndSolutions[i][1]);
|
assert.equal(res.body.solution, puzzlesAndSolutions[i][1]);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
test("Solve a puzzle with missing puzzle string: POST request to /api/solve", (done) => {
|
test("Solve a puzzle with missing puzzle string: POST request to /api/solve", (done) => {
|
||||||
for (let i in puzzlesAndSolutions) {
|
|
||||||
chai
|
chai
|
||||||
.request(server)
|
.request(server)
|
||||||
.post("/api/solve")
|
.post("/api/solve")
|
||||||
|
@ -37,7 +36,6 @@ suite("Functional Tests", () => {
|
||||||
assert.equal(res.body.error, "Required field missing");
|
assert.equal(res.body.error, "Required field missing");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
test("Solve a puzzle with invalid characters: POST request to /api/solve", (done) => {
|
test("Solve a puzzle with invalid characters: POST request to /api/solve", (done) => {
|
||||||
for (let i in invalidStrings) {
|
for (let i in invalidStrings) {
|
||||||
|
@ -48,9 +46,9 @@ suite("Functional Tests", () => {
|
||||||
.end((err, res) => {
|
.end((err, res) => {
|
||||||
assert.equal(res.status, 200);
|
assert.equal(res.status, 200);
|
||||||
assert.equal(res.body.error, "Invalid characters in puzzle");
|
assert.equal(res.body.error, "Invalid characters in puzzle");
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
test("Solve a puzzle with incorrect length: POST request to /api/solve", (done) => {
|
test("Solve a puzzle with incorrect length: POST request to /api/solve", (done) => {
|
||||||
for (let i in wrongLengthStrings) {
|
for (let i in wrongLengthStrings) {
|
||||||
|
@ -62,11 +60,11 @@ suite("Functional Tests", () => {
|
||||||
assert.equal(res.status, 200);
|
assert.equal(res.status, 200);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
res.body.error,
|
res.body.error,
|
||||||
"Expected puzzle to be 81 charcters long"
|
"Expected puzzle to be 81 characters long"
|
||||||
);
|
);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
test("Solve a puzzle that cannot be solved: POST request to /api/solve", (done) => {
|
test("Solve a puzzle that cannot be solved: POST request to /api/solve", (done) => {
|
||||||
for (let i in unsolvableStrings) {
|
for (let i in unsolvableStrings) {
|
||||||
|
@ -77,11 +75,12 @@ suite("Functional Tests", () => {
|
||||||
.end((err, res) => {
|
.end((err, res) => {
|
||||||
assert.equal(res.status, 200);
|
assert.equal(res.status, 200);
|
||||||
assert.equal(res.body.error, "Puzzle cannot be solved");
|
assert.equal(res.body.error, "Puzzle cannot be solved");
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
suite("Placement Checks", () => {
|
suite("Placement Checks", () => {
|
||||||
test("Check a puzzle placement with all fields: POST request to /api/check", (done) => {
|
test("Check a puzzle placement with all fields: POST request to /api/check", (done) => {
|
||||||
chai
|
chai
|
||||||
|
|
Reference in a new issue