Hello @everyone,
Kindly assist me to get out of this issue. I’ve been stuck on this project for over 3 months now. I do really need help. Thanks in anticipation for your response.
Tell us what’s happening:
- You can POST /api/solve with form data containing puzzle which will be a string containing a combination of numbers (1-9) and periods . to represent empty spaces. The returned object will contain a solution property with the solved puzzle.
- If the puzzle submitted to /api/solve is invalid or cannot be solved, the returned value will be { error: ‘Puzzle cannot be solved’ }
- All 12 unit tests are complete and passing.
- All 14 functional tests are complete and passing.
Not passing
sudoku-solver.js
class SudokuSolver {
validate(puzzleString) {
if (!puzzleString) {
return { error: "Required field missing" };
}
if (!/^[1-9.]+$/.test(puzzleString)) {
return { error: "Invalid characters in puzzle" };
}
if (puzzleString.length !== 81) {
return { error: "Expected puzzle to be 81 characters long" };
}
return null;
}
checkRowPlacement(puzzleString, row, column, value) {
let rowIndex = row * 9;
for (let col = 0; col < 9; col++) {
if (puzzleString[rowIndex + col] == value) {
return false;
}
}
return true;
}
checkColPlacement(puzzleString, row, column, value) {
for (let r = 0; r < 9; r++) {
if (puzzleString[r * 9 + column] == value) {
return false;
}
}
return true;
}
checkRegionPlacement(puzzleString, row, column, value) {
let startRow = Math.floor(row / 3) * 3;
let startCol = Math.floor(column / 3) * 3;
for (let r = 0; r < 3; r++) {
for (let c = 0; c < 3; c++) {
let index = (startRow + r) * 9 + (startCol + c);
if (index !== row * 9 + column && puzzleString[index] === value.toString()) {
return false;
}
}
}
return true;
}
solve(puzzleString) {
let validationError = this.validate(puzzleString);
if (validationError) return validationError;
let board = puzzleString.split('');
const findEmpty = () => board.indexOf('.');
const isValid = (index, value) => {
let row = Math.floor(index / 9);
let col = index % 9;
return (
this.checkRowPlacement(puzzleString, row, col, value) &&
this.checkColPlacement(puzzleString, row, col, value) &&
this.checkRegionPlacement(puzzleString, row, col, value)
);
};
const backtrack = () => {
let index = findEmpty();
if (index === -1) return board.join('');
for (let num = 1; num <= 9; num++) {
if (isValid(index, num)) {
board[index] = String(num);
let result = backtrack();
if (result) return result;
board[index] = '.';
}
}
return null;
};
let solved = backtrack();
return solved ? { solution: solved } : { error: "Puzzle cannot be solved" };
}
}
module.exports = SudokuSolver;
api.js
'use strict';
const SudokuSolver = require('../controllers/sudoku-solver.js');
module.exports = function (app) {
let solver = new SudokuSolver();
app.route('/api/check')
.post((req, res) => {
const { puzzle, coordinate, value } = req.body;
if (!puzzle || !coordinate || !value) {
return res.json({ error: 'Required field(s) missing' });
}
const validationError = solver.validate(puzzle);
if (validationError) return res.json(validationError);
if (!/^[A-I][1-9]$/.test(coordinate)) {
return res.json({ error: 'Invalid coordinate' });
}
if (!/^[1-9]$/.test(value)) {
return res.json({ error: 'Invalid value' });
}
const row = coordinate.charCodeAt(0) - 'A'.charCodeAt(0);
const col = parseInt(coordinate[1]) - 1;
if (puzzle[row * 9 + col] == value) {
return res.json({ valid: true });
}
let conflicts = [];
if (!solver.checkRowPlacement(puzzle, row, col, value)) conflicts.push('row');
if (!solver.checkColPlacement(puzzle, row, col, value)) conflicts.push('column');
if (!solver.checkRegionPlacement(puzzle, row, col, value)) conflicts.push('region');
return res.json(conflicts.length ? { valid: false, conflict: conflicts } : { valid: true });
});
app.route('/api/solve')
.post((req, res) => {
const { puzzle } = req.body;
const validationError = solver.validate(puzzle);
if (validationError) return res.json(validationError);
return res.json(solver.solve(puzzle));
});
};
unit test
const chai = require('chai');
const assert = chai.assert;
const Solver = require('../controllers/sudoku-solver.js');
let solver = new Solver();
suite('Unit Tests', () => {
test('Valid puzzle of 81 characters', () => {
const puzzle = '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79';
assert.isNull(solver.validate(puzzle), 'Puzzle should be valid');
});
test('Puzzle with invalid characters', () => {
const puzzle = '5.3.7....6..195...X8....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79';
assert.deepEqual(solver.validate(puzzle), { error: 'Invalid characters in puzzle' });
});
test('Puzzle not 81 characters long', () => {
const puzzle = '5.3.7...6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79';
assert.deepEqual(solver.validate(puzzle), { error: 'Expected puzzle to be 81 characters long' });
});
test('Valid row placement', () => {
const puzzle = '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79';
assert.isTrue(solver.checkRowPlacement(puzzle, 0, 1, 1));
});
test('Invalid row placement', () => {
const puzzle = '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79';
assert.isFalse(solver.checkRowPlacement(puzzle, 0, 1, 5));
});
test('Valid column placement', () => {
const puzzle = '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79';
assert.isTrue(solver.checkColPlacement(puzzle, 0, 1, 1));
});
test('Invalid column placement', () => {
const puzzle = '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79';
assert.isFalse(solver.checkColPlacement(puzzle, 0, 1, 9));
});
test('Valid region placement', () => {
const puzzle = '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79';
assert.isTrue(solver.checkRegionPlacement(puzzle, 0, 1, 1));
});
test('Invalid region placement', () => {
const puzzle = '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79';
assert.isFalse(solver.checkRegionPlacement(puzzle, 0, 1, 7));
});
test('Solver finds the correct solution', () => {
const puzzle = '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79';
const solution = solver.solve(puzzle);
assert.isString(solution.solution, 'Solution should be a string');
assert.lengthOf(solution.solution, 81, 'Solution should be 81 characters long');
});
test('Solver returns error if puzzle is unsolvable', () => {
const puzzle = '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..77';
assert.deepEqual(solver.solve(puzzle), { error: 'Puzzle cannot be solved' });
});
});
functional test
const chai = require("chai");
const chaiHttp = require('chai-http');
const assert = chai.assert;
const server = require('../server');
chai.use(chaiHttp);
suite('Functional Tests', () => {
test('POST /api/solve with valid puzzle', (done) => {
chai.request(server)
.post('/api/solve')
.send({ puzzle: '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.isString(res.body.solution, 'Solution should be a string');
assert.lengthOf(res.body.solution, 81, 'Solution should be 81 characters long');
done();
});
});
test('POST /api/solve with missing puzzle field', (done) => {
chai.request(server)
.post('/api/solve')
.send({})
.end((err, res) => {
assert.equal(res.status, 200);
assert.deepEqual(res.body, { error: 'Required field missing' });
done();
});
});
test('POST /api/solve with invalid characters', (done) => {
chai.request(server)
.post('/api/solve')
.send({ puzzle: '5.3.7....6..195...X8....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.deepEqual(res.body, { error: 'Invalid characters in puzzle' });
done();
});
});
test('POST /api/check with valid placement', (done) => {
chai.request(server)
.post('/api/check')
.send({ puzzle: '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79', coordinate: 'A2', value: '1' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.deepEqual(res.body, { valid: true });
done();
});
});
test('POST /api/check with invalid placement (row conflict)', (done) => {
chai.request(server)
.post('/api/check')
.send({ puzzle: '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79', coordinate: 'A2', value: '5' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.isFalse(res.body.valid);
assert.include(res.body.conflict, 'row');
done();
});
});
test('POST /api/check with invalid coordinate', (done) => {
chai.request(server)
.post('/api/check')
.send({ puzzle: '5.3.7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79', coordinate: 'Z9', value: '3' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.deepEqual(res.body, { error: 'Invalid coordinate' });
done();
});
});
});
###Your project link(s)
solution: https://3000-freecodecam-boilerplate-t7qfo4alx47.ws-eu118.gitpod.io
Your browser information:
User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Challenge Information:
Quality Assurance Projects - Sudoku Solver