Skip to content

Cuarto proyecto requisito obligatorio para obtener la Quality Assurance Certification de freecodecamp

Notifications You must be signed in to change notification settings

waldohidalgo/sudoku-solver

Repository files navigation

Sudoku Solver

Cuarto proyecto requisito obligatorio para obtener la Quality Assurance Certification

El proyecto consiste en la realización de un solucionador de sudokus a partir del ingreso del sudoku como cadena de texto en la que los valores vacíos del sudoku se representan como puntos(.) en el string. El string se envía en una petición POST al servidor y este responde con la solución o con errores descriptivos. Si el sudoku que representa la cadena de texto no es válido ya sea en los siguientes casos:

  • Cadena vacía

  • No esta compuesto de números del 1 al 9 ó de puntos

  • Posee un largo distinto de 81 caracteres

  • Posee números en posiciones que violan las reglas de sudokus ya sea números repetidos en filas, columnas o área.

Entonces se muestra un mensaje señalando los errores siguientes para cada caso:

  • Required field missing

  • Invalid characters in puzzle

  • Expected puzzle to be 81 characters long

  • Puzzle cannot be solved

Se implementa un Backtracking Algorithm para la resolución de sudokus el cual va probando todos los valores posibles para cada posición vacía y "devolviéndose" en caso de que el sudoku no sea válido en alguna rama. La cadena de caracteres válida se convierte en un array de arrays el cual facilita el recorrido por filas y columnas de una matriz de valores para luego pasar la matriz al algoritmo y este va modificando dicha matriz con valores válidos.

Por último, he creado 12 tests unitarios y 14 tests funcionales los cuales todos han sido superados.

Tabla de Contenidos

Deploy

El proyecto ha sido desplegado en mi cuenta gratuita en Render.com. Como es sabido los proyectos inactivos se "congelan" luego de un período de inactividad por lo que cuando se ingresa luego de la ocurrencia de lo anterior, el proyecto tarda al menos 50 segundos en volver a activarse.

Requisitos Aprobados

Imagen de Requisitos Aprobados

Tests Requeridos

Imagen de Tests Requeridos

Proyecto Aprobado

Imagen de Proyecto Aprobado

Tests Requeridos Aprobados

Imagen de Tests Requeridos

Código de tests unitarios y funcionales

1. Tests Unitarios

1.1. Logic handles a valid puzzle string of 81 characters

test("Logic handles a valid puzzle string of 81 characters", (done) => {
  assert.equal(solver.validate(onePuzzleValid)[0], true);
  done();
});

1.2.Logic handles a puzzle string with invalid characters (not 1-9 or .)

test("Logic handles a puzzle string with invalid characters (not 1-9 or .)", (done) => {
  assert.equal(solver.validate(onePuzzleInvalidCharacter)[0], false);
  done();
});

1.3.Logic handles a puzzle string that is not 81 characters in length

test("Logic handles a puzzle string that is not 81 characters in length", (done) => {
  assert.equal(solver.validate(onePuzzleNot81Characters)[0], false);
  done();
});

1.4.Logic handles a valid row placement

test("Logic handles a valid row placement", (done) => {
  const matrix = solver.puzzleMatrix(onePuzzleValid);
  assert.equal(solver.checkRowPlacement(matrix, 0, 1, 3), true);
  done();
});

1.5.Logic handles an invalid row placement

test("Logic handles an invalid row placement", (done) => {
  const matrix = solver.puzzleMatrix(onePuzzleValid);
  assert.equal(solver.checkRowPlacement(matrix, 0, 1, "2"), false);
  done();
});

1.6.Logic handles a valid column placement

test("Logic handles a valid column placement", (done) => {
  const matrix = solver.puzzleMatrix(onePuzzleValid);
  assert.equal(solver.checkColPlacement(matrix, 0, 1, "4"), true);
  done();
});

1.7.Logic handles an invalid column placement

test("Logic handles an invalid column placement", (done) => {
  const matrix = solver.puzzleMatrix(onePuzzleValid);
  assert.equal(solver.checkColPlacement(matrix, 0, 1, "9"), false);
  done();
});

1.8.Logic handles a valid region (3x3 grid) placement

test("Logic handles a valid region (3x3 grid) placement", (done) => {
  const matrix = solver.puzzleMatrix(onePuzzleValid);
  assert.equal(solver.checkRegionPlacement(matrix, 0, 1, "3"), true);
  done();
});

1.9.Logic handles an invalid region (3x3 grid) placement

test("Logic handles an invalid region (3x3 grid) placement", (done) => {
  const matrix = solver.puzzleMatrix(onePuzzleValid);
  assert.equal(solver.checkRegionPlacement(matrix, 0, 1, "1"), false);
  done();
});

1.10.Valid puzzle strings pass the solver

test("Valid puzzle strings pass the solver", (done) => {
  const isValid = solver.validate(onePuzzleValid)[0];
  const matrix = solver.puzzleMatrix(onePuzzleValid);
  const isSolvedValidMatrix = isValid && solver.solve(matrix);
  assert.equal(isSolvedValidMatrix, true);

  done();
});

1.11.Invalid puzzle strings fail the solver

test("Invalid puzzle strings fail the solver", (done) => {
  const isValid = solver.validate(onePuzzleInvalidCharacter)[0];
  const matrix = solver.puzzleMatrix(onePuzzleInvalidCharacter);
  const isSolvedValidMatrix = isValid && solver.solve(matrix);
  assert.equal(isSolvedValidMatrix, false);
  done();
});

1.12.Solver returns the expected solution for an incomplete puzzle

test("Solver returns the expected solution for an incomplete puzzle", (done) => {
  const matrix = solver.puzzleMatrix(onePuzzleValid);
  const isSolved = solver.solve(matrix);
  assert.deepEqual(matrix.map((row) => row.join("")).join(""), onePuzzleSolved);
  done();
});

2. Tests Funcionales

2.1. Solve a puzzle with valid puzzle string: POST request to /api/solve

test("Solve a puzzle with valid puzzle string: POST request to /api/solve", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/solve")
    .send({
      puzzle: onePuzzleValid,
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.solution, onePuzzleSolved);
      done();
    });
});

2.2.Solve a puzzle with missing puzzle string: POST request to /api/solve

test("Solve a puzzle with missing puzzle string: POST request to /api/solve", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/solve")
    .send({
      puzzle: "",
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.error, "Required field missing");
      done();
    });
});

2.3.Solve a puzzle with invalid characters: POST request to /api/solve

test("Solve a puzzle with invalid characters: POST request to /api/solve", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/solve")
    .send({
      puzzle: onePuzzleInvalidCharacter,
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.error, "Invalid characters in puzzle");
      done();
    });
});

2.4.Solve a puzzle with incorrect length: POST request to /api/solve

test("Solve a puzzle with incorrect length: POST request to /api/solve", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/solve")
    .send({
      puzzle: onePuzzleNot81Characters,
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.error, "Expected puzzle to be 81 characters long");
      done();
    });
});

2.5. Solve a puzzle that cannot be solved: POST request to /api/solve

test("Solve a puzzle that cannot be solved: POST request to /api/solve", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/solve")
    .send({
      puzzle: onePuzzleCannotBeSolved,
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.error, "Puzzle cannot be solved");
      done();
    });
});

2.6.Check a puzzle placement with all fields: POST request to /api/check

test("Check a puzzle placement with all fields: POST request to /api/check", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/check")
    .send({
      puzzle: onePuzzleValid,
      coordinate: "a1",
      value: "1",
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.valid, true);
      done();
    });
});

2.7.Check a puzzle placement with single placement conflict: POST request to /api/check

test("Check a puzzle placement with single placement conflict: POST request to /api/check", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/check")
    .send({
      puzzle: onePuzzleValid,
      coordinate: "a2",
      value: "4",
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.valid, false);
      assert.equal(res.body.conflict.length, 1);
      assert.equal(res.body.conflict[0], "row");
      done();
    });
});

2.8.Check a puzzle placement with multiple placement conflicts: POST request to /api/check

test("Check a puzzle placement with multiple placement conflicts: POST request to /api/check", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/check")
    .send({
      puzzle: onePuzzleValid,
      coordinate: "a2",
      value: "1",
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.valid, false);
      assert.equal(res.body.conflict.length, 2);
    });
  done();
});

2.9.Check a puzzle placement with all placement conflicts: POST request to /api/check

test("Check a puzzle placement with all placement conflicts: POST request to /api/check", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/check")
    .send({
      puzzle: onePuzzleValid,
      coordinate: "a2",
      value: "2",
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.valid, false);
      assert.equal(res.body.conflict.length, 3);
    });
  done();
});

2.10.Check a puzzle placement with missing required fields: POST request to /api/check

test("Check a puzzle placement with missing required fields: POST request to /api/check", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/check")
    .send({
      puzzle: onePuzzleValid,
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.error, "Required field(s) missing");
      done();
    });
});

2.11.Check a puzzle placement with invalid characters: POST request to /api/check

test("Check a puzzle placement with invalid characters: POST request to /api/check", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/check")
    .send({
      puzzle: onePuzzleInvalidCharacter,
      coordinate: "a2",
      value: "2",
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.error, "Invalid characters in puzzle");
      done();
    });
});

2.12.Check a puzzle placement with incorrect length: POST request to /api/check

test("Check a puzzle placement with incorrect length: POST request to /api/check", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/check")
    .send({
      puzzle: onePuzzleNot81Characters,
      coordinate: "a2",
      value: "2",
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.error, "Expected puzzle to be 81 characters long");
      done();
    });
});

2.13.Check a puzzle placement with invalid placement coordinate: POST request to /api/check

test("Check a puzzle placement with invalid placement coordinate: POST request to /api/check", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/check")
    .send({
      puzzle: onePuzzleValid,
      coordinate: "ap9",
      value: "2",
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.error, "Invalid coordinate");
      done();
    });
});

2.14.Check a puzzle placement with invalid placement value: POST request to /api/check

test("Check a puzzle placement with invalid placement value: POST request to /api/check", (done) => {
  chai
    .request(server)
    .keepOpen()
    .post("/api/check")
    .send({
      puzzle: onePuzzleValid,
      coordinate: "a2",
      value: "10",
    })
    .end((err, res) => {
      assert.equal(res.status, 200);
      assert.equal(res.body.error, "Invalid value");
      done();
    });
});

Screenshots

1. Home Page Initial Load

Home Page Initial Load

2. Ejemplo de Sudoku Resuelto

Ejemplo de Sudoku Resuelto

3. String Sudoku Inválido por Largo

String Sudoku Inválido

4.String Sudoku Inválido por caracter no válido

String Sudoku Inválido por caracter no válido

5. String Sudoku Faltante

String Sudoku Faltante

6. String Sudoku Inválido violación de Reglas de Sudoku

String Sudoku Inválido violación de Reglas de Sudoku

7. Valor en coordenada inválido por repetición en fila y columna

Valor en coordenada inválido por repetición en fila y columna

8. Valor en coordenada inválido por repetición en fila, columna y área

Valor en coordenada inválido por repetición en fila, columna y área

9. Coordenada ingresada inválida

Coordenada inválida

10. Valor ingresado inválido en coordenada

Valor ingresado inválido en coordenada

11. Valor y coordenada válido

Valor y coordenada válido