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 = [
|
||||
"115..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.420..8..1..16....926969.37.",
|
||||
"4206942069.......................................................................",
|
||||
"115..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.421..8..1..16....926969.37.",
|
||||
"4216942169.......................................................................",
|
||||
"111111111111111111111111111111111111111111111111111111111111111111111111111111111",
|
||||
];
|
||||
|
||||
|
|
|
@ -1,47 +1,127 @@
|
|||
const grid = [
|
||||
[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],
|
||||
];
|
||||
|
||||
let currentPuzzle = "";
|
||||
class SudokuSolver {
|
||||
getPuzzleArray(puzzleString) {
|
||||
const puzzleArray = [];
|
||||
for (let i = 1; i <= 81; i++) {
|
||||
let value = puzzleString[i - 1];
|
||||
const puzzleArray = [[], [], [], [], [], [], [], [], []];
|
||||
for (let i = 0; i <= 80; i++) {
|
||||
let value = puzzleString[i];
|
||||
value === "." || !value
|
||||
? (puzzleArray[Math.ceil(i / 9)][(i % 9) + 1] = " ")
|
||||
: (puzzleArray[Math.ceil(i / 9)][(i % 9) + 1] = value);
|
||||
? (puzzleArray[Math.floor(i / 9)][i % 9] = " ")
|
||||
: (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) {
|
||||
if (puzzleString.length != 81) {
|
||||
if (puzzleString.length !== 81) {
|
||||
return {
|
||||
status: "error",
|
||||
error: "Expected puzzle to be 81 characters long",
|
||||
};
|
||||
} else if (!/^[1-9\.]+$/.test(puzzleString)) {
|
||||
return { status: "error", error: "Invalid characters in puzzle" };
|
||||
} else if (!/^[1-9.]+$/.test(puzzleString)) {
|
||||
return { error: "Invalid characters in puzzle" };
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
|
||||
let solver = new SudokuSolver();
|
||||
|
||||
app.route('/api/check')
|
||||
.post((req, res) => {
|
||||
app.route("/api/check").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')
|
||||
.post((req, res) => {
|
||||
|
||||
});
|
||||
app.route("/api/solve").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 {
|
||||
puzzlesAndSolutions,
|
||||
invalidStrings,
|
||||
unsolvableStrings,
|
||||
wrongLengthStrings,
|
||||
} from "../controllers/puzzle-strings";
|
||||
|
||||
|
@ -36,11 +35,11 @@ suite("Unit Tests", () => {
|
|||
});
|
||||
});
|
||||
suite("Placement Checks", () => {
|
||||
test("Login handles a valid row placement", () => {
|
||||
test("Logic handles a valid row placement", () => {
|
||||
const checks = [
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[0][0], "A", 2, 3),
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[0][1], "A", 2, 6),
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[0][2], "A", 1, 2),
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[0][0], 0, 3, 3),
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[1][0], 0, 3, 6),
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[2][0], 0, 2, 2),
|
||||
];
|
||||
for (let i in checks) {
|
||||
assert.isTrue(checks[i]);
|
||||
|
@ -48,9 +47,9 @@ suite("Unit Tests", () => {
|
|||
});
|
||||
test("Logic handles an invalid row placement", () => {
|
||||
const checks = [
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[0][0], "A", 2, 8),
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[0][1], "A", 2, 1),
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[0][2], "A", 1, 3),
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[0][0], 0, 3, 5),
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[1][0], 0, 3, 1),
|
||||
solver.checkRowPlacement(puzzlesAndSolutions[2][0], 0, 2, 3),
|
||||
];
|
||||
for (let i in checks) {
|
||||
assert.isFalse(checks[i]);
|
||||
|
@ -58,9 +57,9 @@ suite("Unit Tests", () => {
|
|||
});
|
||||
test("Login handles a valid column placement", () => {
|
||||
const checks = [
|
||||
solver.checkColPlacement(puzzlesAndSolutions[0][0], "A", 2, 3),
|
||||
solver.checkColPlacement(puzzlesAndSolutions[0][1], "A", 2, 6),
|
||||
solver.checkColPlacement(puzzlesAndSolutions[0][2], "A", 1, 2),
|
||||
solver.checkColPlacement(puzzlesAndSolutions[0][0], 0, 1, 3),
|
||||
solver.checkColPlacement(puzzlesAndSolutions[1][0], 0, 1, 6),
|
||||
solver.checkColPlacement(puzzlesAndSolutions[2][0], 0, 0, 2),
|
||||
];
|
||||
for (let i in checks) {
|
||||
assert.isTrue(checks[i]);
|
||||
|
@ -68,9 +67,9 @@ suite("Unit Tests", () => {
|
|||
});
|
||||
test("Logic handles an invalid column placement", () => {
|
||||
const checks = [
|
||||
solver.checkColPlacement(puzzlesAndSolutions[0][0], "B", 2, 9),
|
||||
solver.checkColPlacement(puzzlesAndSolutions[0][1], "B", 2, 8),
|
||||
solver.checkColPlacement(puzzlesAndSolutions[0][2], "C", 3, 2),
|
||||
solver.checkColPlacement(puzzlesAndSolutions[0][0], 1, 1, 9),
|
||||
solver.checkColPlacement(puzzlesAndSolutions[1][0], 1, 1, 8),
|
||||
solver.checkColPlacement(puzzlesAndSolutions[2][0], 2, 2, 2),
|
||||
];
|
||||
for (let i in checks) {
|
||||
assert.isFalse(checks[i]);
|
||||
|
@ -78,9 +77,9 @@ suite("Unit Tests", () => {
|
|||
});
|
||||
test("Logic handles a valid region (3x3 grid) placement", () => {
|
||||
const checks = [
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][0], "A", 2, 3),
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][1], "A", 2, 6),
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][2], "A", 1, 2),
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][0], 0, 1, 3),
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[1][0], 0, 1, 6),
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[2][0], 0, 0, 2),
|
||||
];
|
||||
for (let i in checks) {
|
||||
assert.isTrue(checks[i]);
|
||||
|
@ -88,9 +87,9 @@ suite("Unit Tests", () => {
|
|||
});
|
||||
test("Logic handles an invalid region (3x3 grid) placement", () => {
|
||||
const checks = [
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][0], "C", 3, 5),
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][1], "C", 3, 5),
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][2], "C", 3, 5),
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[0][0], 2, 2, 5),
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[1][0], 2, 2, 5),
|
||||
solver.checkRegionPlacement(puzzlesAndSolutions[2][0], 2, 2, 5),
|
||||
];
|
||||
for (let i in checks) {
|
||||
assert.isFalse(checks[i]);
|
||||
|
@ -101,7 +100,7 @@ suite("Unit Tests", () => {
|
|||
test("Valid puzzle strings pass the solver", () => {
|
||||
for (let i in puzzlesAndSolutions) {
|
||||
assert.equal(
|
||||
solver.solve(puzzlesAndSolutions[i][1]),
|
||||
solver.solve(puzzlesAndSolutions[i][0]).solution,
|
||||
puzzlesAndSolutions[i][1]
|
||||
);
|
||||
}
|
||||
|
@ -109,8 +108,8 @@ suite("Unit Tests", () => {
|
|||
test("Invalid puzzle strings fail the solver", () => {
|
||||
for (let i in invalidStrings) {
|
||||
assert.equal(
|
||||
solver.solve(invalidStrings[i]),
|
||||
{ error: "Invalid characters in puzzle" },
|
||||
solver.solve(invalidStrings[i]).error,
|
||||
"Invalid characters in puzzle",
|
||||
"invalid characters"
|
||||
);
|
||||
}
|
||||
|
@ -118,9 +117,8 @@ suite("Unit Tests", () => {
|
|||
test("Solver returns the expected solution for an incomplete puzzle", () => {
|
||||
for (let i in puzzlesAndSolutions) {
|
||||
assert.equal(
|
||||
solver.solve(puzzlesAndSolutions[i][0]),
|
||||
puzzlesAndSolutions[i][1],
|
||||
puzzlesAndSolutions[i]
|
||||
solver.solve(puzzlesAndSolutions[i][0]).solution,
|
||||
puzzlesAndSolutions[i][1]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -22,22 +22,20 @@ suite("Functional Tests", () => {
|
|||
.end((err, res) => {
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.solution, puzzlesAndSolutions[i][1]);
|
||||
done();
|
||||
});
|
||||
}
|
||||
done();
|
||||
});
|
||||
test("Solve a puzzle with missing puzzle string: POST request to /api/solve", (done) => {
|
||||
for (let i in puzzlesAndSolutions) {
|
||||
chai
|
||||
.request(server)
|
||||
.post("/api/solve")
|
||||
.send({})
|
||||
.end((err, res) => {
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.error, "Required field missing");
|
||||
done();
|
||||
});
|
||||
}
|
||||
chai
|
||||
.request(server)
|
||||
.post("/api/solve")
|
||||
.send({})
|
||||
.end((err, res) => {
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.error, "Required field missing");
|
||||
done();
|
||||
});
|
||||
});
|
||||
test("Solve a puzzle with invalid characters: POST request to /api/solve", (done) => {
|
||||
for (let i in invalidStrings) {
|
||||
|
@ -48,9 +46,9 @@ suite("Functional Tests", () => {
|
|||
.end((err, res) => {
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.error, "Invalid characters in puzzle");
|
||||
done();
|
||||
});
|
||||
}
|
||||
done();
|
||||
});
|
||||
test("Solve a puzzle with incorrect length: POST request to /api/solve", (done) => {
|
||||
for (let i in wrongLengthStrings) {
|
||||
|
@ -62,11 +60,11 @@ suite("Functional Tests", () => {
|
|||
assert.equal(res.status, 200);
|
||||
assert.equal(
|
||||
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) => {
|
||||
for (let i in unsolvableStrings) {
|
||||
|
@ -77,11 +75,12 @@ suite("Functional Tests", () => {
|
|||
.end((err, res) => {
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.error, "Puzzle cannot be solved");
|
||||
done();
|
||||
});
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite("Placement Checks", () => {
|
||||
test("Check a puzzle placement with all fields: POST request to /api/check", (done) => {
|
||||
chai
|
||||
|
|
Reference in a new issue