Sudoku solver: Finish project

This commit is contained in:
CherryKitten 2022-11-12 22:40:00 +01:00
parent 0d2e39e7ba
commit f1ce58405c
Signed by: sammy
GPG key ID: 0B696A86A853E955
5 changed files with 186 additions and 80 deletions

View file

@ -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",
];

View file

@ -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 };
}
}

View file

@ -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));
});
};

View file

@ -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]
);
}
});

View file

@ -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