diff --git a/sudoku-app/src/App.js b/sudoku-app/src/App.js index 0f42af2..9aa047a 100644 --- a/sudoku-app/src/App.js +++ b/sudoku-app/src/App.js @@ -5,11 +5,13 @@ import Theme from './Theme.js'; import Sudoku from './logic/SudokuGenerator.js'; import Header from './Header.js'; import Buttons from './Buttons.js'; -import { sudokuSolver, sudokuChecker } from './logic/SudokuSolver.js'; +import { sudokuSolver, sudokuChecker, generateErrorGrid } from './logic/SudokuSolver.js'; function App() { - const [grid, setGrid] = useState(Array(9).fill(Array(9).fill(''))); - const [givenGrid, setGivenGrid] = useState(Array(9).fill(Array(9).fill(''))); + const [grid, setGrid] = useState(new Array(9).fill().map(() => new Array(9).fill(''))); + const [givenGrid, setGivenGrid] = useState(new Array(9).fill().map(() => new Array(9).fill(''))); + const [errorsGrid, setErrorsGrid] = useState(new Array(9).fill().map(() => new Array(9).fill(false))); + const [errorsVisibility, setErrorsVisibility] = useState(false) const [solution, setSolution] = useState([]); const [remainingTime, setRemainingTime] = useState(0); const [timerId, setTimerId] = useState(null) @@ -46,6 +48,8 @@ function App() { ); setGrid(newGrid); + setErrorsGrid(generateErrorGrid(newGrid)) + if (isFullyFilled(newGrid)) { if (sudokuChecker(newGrid.map(row => row.slice()))) { alert("Sudoku solved!"); @@ -79,6 +83,7 @@ function App() { setGivenGrid(sudoku.mat); setGrid(sudoku.mat); setSolution([]); + setErrorsGrid(new Array(9).fill().map(() => new Array(9).fill(false))); }; const solveForMe = () => { @@ -92,6 +97,10 @@ function App() { stopTimer() }; + const toggleErrors = () => { + setErrorsVisibility(!errorsVisibility) + } + const startTimer = (minutes) => { setRemainingTime(minutes * 60); }; @@ -104,11 +113,13 @@ function App() {
handleGridChange(rowIndex, colIndex, value)} + errorsVisibility={errorsVisibility} /> @@ -116,6 +127,8 @@ function App() { solveForMe={solveForMe} startTimer={startTimer} remainingTime={remainingTime} + toggleErrors={toggleErrors} + errorsVisibility={errorsVisibility} />
diff --git a/sudoku-app/src/Buttons.js b/sudoku-app/src/Buttons.js index 82c80ff..d896717 100644 --- a/sudoku-app/src/Buttons.js +++ b/sudoku-app/src/Buttons.js @@ -1,6 +1,6 @@ import { useEffect } from "react"; -function Buttons({ startTimer, handleDifficultySelection, solveForMe, remainingTime }) { +function Buttons({ startTimer, handleDifficultySelection, solveForMe, remainingTime, toggleErrors, errorsVisibility }) { const modes = ["easy", "medium", "hard"]; useEffect(() => { @@ -16,6 +16,7 @@ function Buttons({ startTimer, handleDifficultySelection, solveForMe, remainingT
+
Time remaining: {Math.floor(remainingTime / 60)}:{(remainingTime % 60).toString().padStart(2, '0')}
diff --git a/sudoku-app/src/SudokuGrid.css b/sudoku-app/src/SudokuGrid.css index 6e3aff2..994717e 100644 --- a/sudoku-app/src/SudokuGrid.css +++ b/sudoku-app/src/SudokuGrid.css @@ -19,11 +19,11 @@ } .r-border { - border-right: 2px solid rgb(49, 43, 51); + border-right: 3px solid rgb(49, 43, 51); } .t-border { - border-top: 2px solid rgb(49, 43, 51); + border-top: 3px solid rgb(49, 43, 51); } .sudoku-cell-given { @@ -51,4 +51,9 @@ .button { font-size: 1rem; } +} + +input.cell-error { + color: red; + caret-color: black; } \ No newline at end of file diff --git a/sudoku-app/src/SudokuGrid.js b/sudoku-app/src/SudokuGrid.js index 183e09c..54ecca4 100644 --- a/sudoku-app/src/SudokuGrid.js +++ b/sudoku-app/src/SudokuGrid.js @@ -1,6 +1,6 @@ import './SudokuGrid.css'; -const SudokuGrid = ({ givenGrid, grid, solution, handleInputChange }) => { +const SudokuGrid = ({ errorsGrid, givenGrid, grid, solution, handleInputChange, errorsVisibility }) => { return (
{grid.map((rowArray, rowIndex) => ( @@ -11,8 +11,9 @@ const SudokuGrid = ({ givenGrid, grid, solution, handleInputChange }) => { type="text" className={ `sudoku-cell - ${rowIndex === 2 || rowIndex === 5 ? "t-border" : ""} - ${colIndex === 2 || colIndex === 5 ? "r-border" : ""}`} + ${rowIndex === 3 || rowIndex === 6 ? "t-border" : ""} + ${colIndex === 2 || colIndex === 5 ? "r-border" : ""} + ${errorsVisibility && errorsGrid[rowIndex][colIndex] ? "cell-error" : ""}`} value={solution.length > 0 ? solution[rowIndex][colIndex] : cellValue} maxLength="1" readOnly={givenGrid[rowIndex][colIndex] !== ""} @@ -28,9 +29,3 @@ const SudokuGrid = ({ givenGrid, grid, solution, handleInputChange }) => { }; export default SudokuGrid; - - - - - - diff --git a/sudoku-app/src/logic/SudokuGenerator.js b/sudoku-app/src/logic/SudokuGenerator.js index 75af2b8..f573e84 100644 --- a/sudoku-app/src/logic/SudokuGenerator.js +++ b/sudoku-app/src/logic/SudokuGenerator.js @@ -30,7 +30,7 @@ export default class Sudoku { let index = 0; for (let i = 0; i < this.SRN; i++) { for (let j = 0; j < this.SRN; j++) { - this.mat[row + i][col + j] = numbers[index]; + this.mat[row + i][col + j] = `${numbers[index]}`; index++; } } diff --git a/sudoku-app/src/logic/SudokuSolver.js b/sudoku-app/src/logic/SudokuSolver.js index 23b78e5..16cff08 100644 --- a/sudoku-app/src/logic/SudokuSolver.js +++ b/sudoku-app/src/logic/SudokuSolver.js @@ -23,7 +23,7 @@ const isEmpty = grid => grid === ''; * @returns {boolean} returns a boolean that determines if * the puzzle was solved. */ -export function sudokuSolver(data) { +function sudokuSolver(data) { for (let row = 0; row < 9; row++) { for (let col = 0; col < 9; col++) { if (!isEmpty(data[row][col])) continue; @@ -45,7 +45,7 @@ export function sudokuSolver(data) { } -export function sudokuChecker(board) { +function sudokuChecker(board) { const AVAILABLE_NUMBERS = [1, 2, 3, 4, 5, 6, 7, 8, 9]; let set = new Set(); @@ -54,16 +54,16 @@ export function sudokuChecker(board) { if (parseInt(board[row][col] == NaN | !AVAILABLE_NUMBERS.includes(parseInt(board[row][col]))) | isEmpty(board[row][col])) return false; - + if (set.has( "row" + row + "" + board[row][col], "col" + col + "" + board[row][col], - "grid" + Math.ceil((row + 1)/3.0) + Math.ceil((col + 1)/3.0) + board[row][col])) return false; + "grid" + Math.ceil((row + 1) / 3.0) + Math.ceil((col + 1) / 3.0) + board[row][col])) return false; else { set.add( "row" + row + "" + board[row][col], "col" + col + "" + board[row][col], - "grid" + Math.ceil((row + 1)/3.0) + Math.ceil((col + 1)/3.0) + board[row][col]); + "grid" + Math.ceil((row + 1) / 3.0) + Math.ceil((col + 1) / 3.0) + board[row][col]); } } } @@ -71,3 +71,36 @@ export function sudokuChecker(board) { return true; } +/** + * Generate error grid that maps to grid data + * @param {string[][]} data 2D array of number strings in sudoku board + * @returns {boolean[][]} 2D array of boolean with true as invalid cells and false as valid cells + */ +function generateErrorGrid(data) { + const errors = new Array(9).fill().map(() => new Array(9).fill(0)); + + // Adapted from isValid function above + const isValid = (row, col, k) => { + for (let i = 0; i < 9; i++) { + const m = 3 * Math.floor(row / 3) + Math.floor(i / 3); + const n = 3 * Math.floor(col / 3) + i % 3; + // Ignore the same (row, col) to not mark it as invalid + if ((data[row][i] == k && i !== col) || (data[i][col] == k && i !== row) || (data[m][n] == k && (m !== row || n !== col))) { + return false; + } + } + return true; + } + + for (let row = 0; row < 9; row++) { + for (let col = 0; col < 9; col++) { + if (isEmpty(data[row][col])) continue; + if (!isValid(row, col, data[row][col])) { + errors[row][col] = true; + } + } + } + return errors; +} + +export { sudokuSolver, isValid, sudokuChecker, generateErrorGrid }