Tell us what’s happening:
Hello. I have a few issues with this project I have problems to solve.
Project files
controllers/sudoku-solver.js
class SudokuSolver {
validate(puzzleString) {
if (/[^1-9\.]/.test(puzzleString)) {
return { valid: false, error: 'Invalid characters in puzzle' };
}
if (puzzleString.length !== 81) {
return { valid: false, error: 'Expected puzzle to be 81 characters long' };
}
return { valid: true };
}
checkRowPlacement(puzzleString, row, column, value) {
const rowStart = row * 9;
const rowValues = puzzleString.slice(rowStart, rowStart + 9);
if (rowValues.includes(value)) {
return false;
}
return true;
}
checkColPlacement(puzzleString, row, column, value) {
for (let i = 0; i < 9; i++) {
if (puzzleString[column + i * 9] === value) {
return false;
}
}
return true;
}
checkRegionPlacement(puzzleString, row, column, value) {
const startRow = Math.floor(row / 3) * 3;
const startCol = Math.floor(column / 3) * 3;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (puzzleString[(startRow + i) * 9 + (startCol + j)] === value) {
return false;
}
}
}
return true;
}
solve(puzzleString) {
const validationResult = this.validate(puzzleString);
if (!validationResult.valid) {
return 'No solution exists';
}
const solveRecursive = (puzzle) => {
const emptyIndex = puzzle.indexOf('.');
if (emptyIndex === -1) {
return puzzle;
}
const row = Math.floor(emptyIndex / 9);
const col = emptyIndex % 9;
for (let value = 1; value <= 9; value++) {
const stringValue = value.toString();
if (
this.checkRowPlacement(puzzle, row, col, stringValue) &&
this.checkColPlacement(puzzle, row, col, stringValue) &&
this.checkRegionPlacement(puzzle, row, col, stringValue)
) {
const newPuzzle = puzzle.slice(0, emptyIndex) + stringValue + puzzle.slice(emptyIndex + 1);
const solvedPuzzle = solveRecursive(newPuzzle);
if (solvedPuzzle) {
return solvedPuzzle;
}
}
}
return null;
};
return solveRecursive(puzzleString) || 'No solution exists';
}
}
module.exports = SudokuSolver;
routes/api.js
'use strict';
const SudokuSolver = require('../controllers/sudoku-solver.js');
module.exports = function (app) {
let solver = new SudokuSolver();
app.route('/api/solve')
.post((req, res) => {
const { puzzle } = req.body;
if (!puzzle) {
return res.json({ error: 'Required field missing' });
}
const validationResult = solver.validate(puzzle);
if (!validationResult.valid) {
return res.json({ error: validationResult.error });
}
const solution = solver.solve(puzzle);
if (solution === 'No solution exists') {
return res.json({ error: 'Puzzle cannot be solved' });
}
res.json({ solution });
});
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' });
}
if (puzzle.length > 81 || puzzle.length < 81) {
return res.json({ error: 'Expected puzzle to be 81 characters long' });
}
const validationResult = solver.validate(puzzle);
if (!validationResult.valid) {
return res.json({ error: validationResult.error });
}
const rowLetters = 'ABCDEFGHI';
const rowLetter = coordinate[0].toUpperCase();
const column = parseInt(coordinate[1], 10) - 1;
const row = rowLetters.indexOf(rowLetter);
if (row === -1 || column < 0 || column > 8) {
return res.json({ error: 'Invalid coordinate' });
}
if (!/^[1-9]$/.test(value)) {
return res.json({ error: 'Invalid value' });
}
if (puzzle[row * 9 + column] === value) {
return res.json({ valid: true });
}
const rowValid = solver.checkRowPlacement(puzzle, row, column, value);
const colValid = solver.checkColPlacement(puzzle, row, column, value);
const regionValid = solver.checkRegionPlacement(puzzle, row, column, value);
if (rowValid && colValid && regionValid) {
return res.json({ valid: true });
} else {
let conflict = [];
if (!rowValid) conflict.push('row');
if (!colValid) conflict.push('column');
if (!regionValid) conflict.push('region');
return res.json({ valid: false, conflict });
}
});
};
tests/1_unit-tests.js
const chai = require('chai');
const assert = chai.assert;
const Solver = require('../controllers/sudoku-solver.js');
let solver = new Solver();
suite('Unit Tests', () => {
test('Logic handles a valid puzzle string of 81 characters', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
assert.isTrue(solver.validate(puzzleString).valid);
});
test('Logic handles a puzzle string with invalid characters', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....X..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
assert.deepEqual(solver.validate(puzzleString), { valid: false, error: 'Invalid characters in puzzle' });
});
test('Logic handles a puzzle string that is not 81 characters in length', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37'; // 80 characters
assert.deepEqual(solver.validate(puzzleString), { valid: false, error: 'Expected puzzle to be 81 characters long' });
});
test('Logic handles a valid row placement', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
assert.isTrue(solver.checkRowPlacement(puzzleString, 0, 1, '3'));
});
test('Logic handles an invalid row placement', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
assert.isFalse(solver.checkRowPlacement(puzzleString, 0, 1, '5'));
});
test('Logic handles a valid column placement', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
assert.isTrue(solver.checkColPlacement(puzzleString, 0, 1, '3'));
});
test('Logic handles an invalid column placement', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
assert.isFalse(solver.checkColPlacement(puzzleString, 0, 1, '6'));
});
test('Logic handles a valid region placement', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
assert.isTrue(solver.checkRegionPlacement(puzzleString, 0, 1, '3'));
});
test('Logic handles an invalid region placement', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
assert.isFalse(solver.checkRegionPlacement(puzzleString, 0, 1, '5'));
});
test('Valid puzzle strings pass the solver', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
let solution = solver.solve(puzzleString);
assert.equal(solution, '135762984946381257728459613694517832812936745357824196473298561581673429269145378');
});
test('Invalid puzzle strings fail the solver', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....X..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
assert.equal(solver.solve(puzzleString), 'No solution exists');
});
test('Solver returns the expected solution for an incomplete puzzle', function() {
let puzzleString = '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.';
let expectedSolution = '135762984946381257728459613694517832812936745357824196473298561581673429269145378';
assert.equal(solver.solve(puzzleString), expectedSolution);
});
});
tests/2_functional-tests.js
const chai = require("chai");
const chaiHttp = require('chai-http');
const assert = chai.assert;
const server = require('../server');
chai.use(chaiHttp);
suite('Functional Tests', () => {
test('Solve a puzzle with valid puzzle string: POST request to /api/solve', function(done) {
chai.request(server)
.post('/api/solve')
.send({ puzzle: '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.solution, '135762984946381257728459613694517832812936745357824196473298561581673429269145378');
done();
});
});
test('Solve a puzzle with missing puzzle string: POST request to /api/solve', function(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', function(done) {
chai.request(server)
.post('/api/solve')
.send({ puzzle: '1.5..2.84..63.12.X.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.error, 'Invalid characters in puzzle');
done();
});
});
test('Solve a puzzle with incorrect length: POST request to /api/solve', function(done) {
chai.request(server)
.post('/api/solve')
.send({ puzzle: '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.error, 'Expected puzzle to be 81 characters long');
done();
});
});
test('Solve a puzzle that cannot be solved: POST request to /api/solve', function(done) {
chai.request(server)
.post('/api/solve')
.send({ puzzle: '5.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.error, 'Puzzle cannot be solved');
done();
});
});
test('Check a puzzle placement with all fields: POST request to /api/check', function(done) {
chai.request(server)
.post('/api/check')
.send({ puzzle: '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.', coordinate: 'A1', value: '3' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.valid, true);
done();
});
});
test('Check a puzzle placement with single placement conflict: POST request to /api/check', function(done) {
chai.request(server)
.post('/api/check')
.send({ puzzle: '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.', coordinate: 'A1', value: '5' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.valid, false);
assert.include(res.body.conflict, 'row');
done();
});
});
test('Check a puzzle placement with multiple placement conflicts: POST request to /api/check', function(done) {
chai.request(server)
.post('/api/check')
.send({ puzzle: '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.', coordinate: 'A1', value: '2' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.valid, false);
assert.include(res.body.conflict, 'row');
assert.include(res.body.conflict, 'column');
done();
});
});
test('Check a puzzle placement with all placement conflicts: POST request to /api/check', function(done) {
chai.request(server)
.post('/api/check')
.send({ puzzle: '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.', coordinate: 'A1', value: '1' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.valid, false);
assert.include(res.body.conflict, 'row');
assert.include(res.body.conflict, 'column');
assert.include(res.body.conflict, 'region');
done();
});
});
test('Check a puzzle placement with missing required fields: POST request to /api/check', function(done) {
chai.request(server)
.post('/api/check')
.send({ puzzle: '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.error, 'Required field(s) missing');
done();
});
});
test('Check a puzzle placement with invalid characters: POST request to /api/check', function(done) {
chai.request(server)
.post('/api/check')
.send({ puzzle: '1.5..2.84..63.12.X.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.', coordinate: 'A1', value: '3' })
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.error, 'Invalid characters in puzzle');
done();
});
});
test('Check a puzzle placement with incorrect length: POST request to /api/check', function(done) {
chai.request(server)
.post('/api/check')
.send({ puzzle: '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37' }) // 80 chars
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.error, 'Expected puzzle to be 81 characters long');
done();
});
});
test('Check a puzzle placement with invalid placement coordinate: POST request to /api/check', function(done) {
chai.request(server)
.post('/api/check')
.send({ puzzle: '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.', coordinate: 'J1', value: '3' }) // Invalid row 'J'
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.error, 'Invalid coordinate');
done();
});
});
test('Check a puzzle placement with invalid placement value: POST request to /api/check', function(done) {
chai.request(server)
.post('/api/check')
.send({ puzzle: '1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.', coordinate: 'A1', value: 'X' }) // Invalid value 'X'
.end((err, res) => {
assert.equal(res.status, 200);
assert.equal(res.body.error, 'Invalid value');
done();
});
});
});
Your project link(s)
solution: https://3000-freecodecam-boilerplate-6zsq7aqxzgf.ws-eu116.gitpod.io
Problems
One of my freeCodeCamp tests is not passing:
- If the coordinate submitted to
api/check
does not point to an existing grid cell, the returned value will be{ error: 'Invalid coordinate'}
.
I tried to adjust the order in api/check
of the two if conditions, but then two additional freeCodeCamp tests are failing.
And in addition, these functional tests are failing:
1) Functional Tests
Check a puzzle placement with all fields: POST request to /api/check:
Uncaught AssertionError: expected false to equal true
+ expected - actual
-false
+true
at /workspace/boilerplate-project-sudoku-solver/tests/2_functional-tests.js:70:16
at Test.Request.callback (node_modules/superagent/lib/node/index.js:716:12)
at /workspace/boilerplate-project-sudoku-solver/node_modules/superagent/lib/node/index.js:916:18
at IncomingMessage.<anonymous> (node_modules/superagent/lib/node/parsers/json.js:19:7)
at IncomingMessage.emit (node:events:531:35)
at endReadableNT (node:internal/streams/readable:1696:12)
at processTicksAndRejections (node:internal/process/task_queues:82:21)
2) Functional Tests
Check a puzzle placement with all placement conflicts: POST request to /api/check:
Uncaught AssertionError: expected true to equal false
+ expected - actual
-true
+false
at /workspace/boilerplate-project-sudoku-solver/tests/2_functional-tests.js:106:16
at Test.Request.callback (node_modules/superagent/lib/node/index.js:716:12)
at /workspace/boilerplate-project-sudoku-solver/node_modules/superagent/lib/node/index.js:916:18
at IncomingMessage.<anonymous> (node_modules/superagent/lib/node/parsers/json.js:19:7)
at IncomingMessage.emit (node:events:531:35)
at endReadableNT (node:internal/streams/readable:1696:12)
at processTicksAndRejections (node:internal/process/task_queues:82:21)
3) Functional Tests
Check a puzzle placement with incorrect length: POST request to /api/check:
Uncaught AssertionError: expected 'Required field(s) missing' to equal 'Expected puzzle to be 81 characters long'
+ expected - actual
-Required field(s) missing
+Expected puzzle to be 81 characters long
at /workspace/boilerplate-project-sudoku-solver/tests/2_functional-tests.js:142:16
at Test.Request.callback (node_modules/superagent/lib/node/index.js:716:12)
at /workspace/boilerplate-project-sudoku-solver/node_modules/superagent/lib/node/index.js:916:18
at IncomingMessage.<anonymous> (node_modules/superagent/lib/node/parsers/json.js:19:7)
at IncomingMessage.emit (node:events:531:35)
at endReadableNT (node:internal/streams/readable:1696:12)
at processTicksAndRejections (node:internal/process/task_queues:82:21)
Could someone give me any indication? I am unclear where to look for problems. Thank you very much!
Your browser information:
User Agent is: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Challenge Information:
Quality Assurance Projects - Sudoku Solver