Quality Assurance Projects - Sudoku Solver

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:

  1. 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.
  2. If the puzzle submitted to /api/solve is invalid or cannot be solved, the returned value will be { error: ‘Puzzle cannot be solved’ }
  3. All 12 unit tests are complete and passing.
  4. 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

I opened up the boiler plate in the browser and used all your code and lost localhost etc… Where are you using this and are you putting the files in with existing code server.js, unit, functional and js?

1 Like

@robheyays I am using gitpod Sir. I put the files with existing code server. I didn’t modify it.

1 Like

I don’t think the solve is correct. You could check it against the demo project solve.