package gui.backend; import java.util.ArrayList; /** * The SudokuChecker class is responsible for calculating the solution to * a given Sudoku puzzle. * * TODO: The Sudoku algorithm used is efficient and effective for solving * easy and medium puzzles, but it is not optimized for hard puzzles. It * is recommended to use a different algorithm for hard puzzles, likely * a tree & back-track approach. */ public class SudokuChecker { private Cell[][] grid; private Cell[][] origGrid; /** * Create a new SudokuChecker object, initializing the grid to the given * 9x9 grid of numbers. This should either check each cell as the user * inputs a value, or be used to check the validity of a puzzle when the * user requests it. * * @param grid */ public SudokuChecker(Cell[][] grid) { this.grid = grid; // Create a copy of the original grid to be used for resetting the // grid to its original state. origGrid = new Cell[9][9]; for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { origGrid[i][j] = new Cell(i, j, grid[i][j].getValue()); } } } /** * Check if the given value can be placed in the given cell of the grid. * * False means that the value is incorrect, and true means that the value * is correct. * * @param row * @param col * @param value * @return boolean */ public boolean checkValue(int row, int col, int value) { // Check the row for(int i = 0; i < 9; i++) { if(grid[row][i].getValue() == value && i != col) return false; } // Check the column for(int i = 0; i < 9; i++) { if(grid[i][col].getValue() == value && i != row) return false; } // Check the box int boxRow = row / 3; int boxCol = col / 3; for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { if( grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == value && (boxRow * 3 + i != row || boxCol * 3 + j != col) ) { return false; } } } return true; } /** * Get the possible values for each cell in the Sudoku puzzle. * * Intended to be used for auto-filling in possible values in the GUI. * * @return */ public Cell[][] getPossibleValues(Cell[][] grid) { for(int row = 0; row < grid.length; row++) { for(int col = 0; col < grid[row].length; col++) { if(grid[row][col].getValue() != 0) continue; // Replace intersection with union? ArrayList intersection = intersection( getRowRemainingNumbers(row), getColRemainingNumbers(col) ); intersection = intersection( intersection, getBoxRemainingNumbers(row, col) ); // Join the arrays and find the intersection of the three arrays ArrayList availableNumbers = new ArrayList(); for(int i = 0; i < intersection.size(); i++) { availableNumbers.add(intersection.get(i)); } grid[row][col].setPossibleValues( arrayListToArray(availableNumbers) ); } } this.grid = grid; return grid; } /** * Get the solution to the Sudoku puzzle. * * @return Cell[][] */ public Cell[][] getSolution() { solve(); return grid; } /** * Given two arrays of numbers, return the intersection of the two arrays. * * @param a * @param b * @return */ private ArrayList intersection( ArrayList a, ArrayList b ) { ArrayList intersection = new ArrayList(); for(int i = 0; i < a.size(); i++) { if(b.contains(a.get(i))) intersection.add(a.get(i)); } return intersection; } /** * Determine what numbers are available to be placed in the given cell of * the grid. This is effectively an intersection of the numbers available * in the row, column, and box of the cell. * * @param row * @param col * @return an array of numbers that are available to be placed in the * given cell */ private void getAvailableNumbers(int row, int col) { ArrayList intersection = intersection( getRowRemainingNumbers(row), getColRemainingNumbers(col) ); intersection = intersection( intersection, getBoxRemainingNumbers(row, col) ); if(intersection.size() == 0) return; else if(intersection.size() == 1) { int value = intersection.get(0); grid[row][col].setValue(value, true); updatePossibleValues(row, col); return; } else { // Join the arrays and find the intersection of the three arrays. ArrayList availableNumbers = new ArrayList(); for(int i = 0; i < intersection.size(); i++) availableNumbers.add(intersection.get(i)); grid[row][col].setPossibleValues( arrayListToArray(availableNumbers) ); } } /** * Update the possible values for cells in the same row, column, and box * as the given cell. This should always be called once a cell's value has * been set, to remove that value from the possible values of other cells. * * @param row * @param col */ private void updatePossibleValues(int row, int col) { int value = grid[row][col].getValue(); for(int i = 0; i < 9; i++) { if(grid[row][i].getValue() == 0) { grid[row][i].removePossibleValue(value); if(grid[row][i].getPossibleValues().length == 1) { grid[row][i].setValue(grid[row][i].getPossibleValues()[0], true); updatePossibleValues(row, i); } } if(grid[i][col].getValue() == 0) { grid[i][col].removePossibleValue(value); if(grid[i][col].getPossibleValues().length == 1) { grid[i][col].setValue(grid[i][col].getPossibleValues()[0], true); updatePossibleValues(i, col); } } } int boxRow = row / 3; int boxCol = col / 3; for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { if(grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == 0) grid[boxRow * 3 + i][boxCol * 3 + j]. removePossibleValue(value); } } } /** * Solve the Sudoku puzzle. */ public void solve() { // Continue until a valid solution is reached. while(!isValidSolution()) { for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { if(grid[i][j].getValue() == 0) getAvailableNumbers(i, j); } } } } // /** * Get any number between 1 and 9 that is not in the row. * * @param row * @return */ private ArrayList getRowRemainingNumbers(int row) { ArrayList remainingNumbers = new ArrayList(); for(int i = 1; i <= 9; i++) { boolean found = false; for(int j = 0; j < 9; j++) { if(grid[row][j].getValue() == i) { found = true; break; } } if(!found) remainingNumbers.add(i); } return remainingNumbers; } /** * Get any number between 1 and 9 that is not in the column. * * @param col * @return */ private ArrayList getColRemainingNumbers(int col) { ArrayList remainingNumbers = new ArrayList(); for(int i = 1; i <= 9; i++) { boolean found = false; for(int j = 0; j < 9; j++) { if(grid[j][col].getValue() == i) { found = true; break; } } if(!found) remainingNumbers.add(i); } return remainingNumbers; } /** * Get any number between 1 and 9 that is not in the box. * * A box is a 3x3 subgrid of the 9x9 grid. * * @param box * @return */ private ArrayList getBoxRemainingNumbers(int row, int col) { ArrayList remainingNumbers = new ArrayList(); int boxRow = row / 3; int boxCol = col / 3; for(int i = 1; i <= 9; i++) { boolean found = false; for(int j = 0; j < 3; j++) { for(int k = 0; k < 3; k++) { if(grid[boxRow * 3 + j][boxCol * 3 + k].getValue() == i) { found = true; break; } } } if(!found) remainingNumbers.add(i); } return remainingNumbers; } /** * Given a list of numbers, return an array of the numbers. * * A helper method to keep the code using arrays instead of lists * whenever possible. * * @param list * @return */ private int[] arrayListToArray(ArrayList list) { int[] array = new int[list.size()]; for(int i = 0; i < list.size(); i++) array[i] = list.get(i); return array; } /** * Given a 9x9 grid of numbers, return true if the grid is a valid Sudoku * puzzle solution, and false otherwise. * * A valid Sudoku puzzle is one where each row, column, and 3x3 subgrid * contains the numbers 1-9 exactly once. * * @return true if the grid is a valid Sudoku puzzle, and false otherwise */ private boolean isValidSolution() { // Check that every cell has a value between 1 and 9. for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { if( grid[i][j].getValue() < 1 || grid[i][j].getValue() > 9 ) return false; } } // Check that every row contains the numbers 1-9 exactly once. for(int i = 0; i < 9; i++) { int[] row = new int[9]; for(int j = 0; j < 9; j++) row[j] = grid[i][j].getValue(); if(!isValidSet(row)) { System.out.println("Row " + i + " is invalid."); return false; } } // Check that every column contains the numbers 1-9 exactly once. for(int i = 0; i < 9; i++) { int[] col = new int[9]; for(int j = 0; j < 9; j++) col[j] = grid[j][i].getValue(); if(!isValidSet(col)) { System.out.println("Column " + i + " is invalid."); return false; } } // Check that every 3x3 subgrid contains the numbers 1-9 exactly once. for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { int[] box = new int[9]; for(int k = 0; k < 3; k++) { for(int l = 0; l < 3; l++) box[k * 3 + l] = grid[i * 3 + k][j * 3 + l].getValue(); } if(!isValidSet(box)) { System.out.println( "Box at row " + i + " and column " + j + " is invalid." ); return false; } } } return true; } /** * Given an array of 9 numbers, return true if the array contains the * numbers 1-9 exactly once, and false otherwise. * * @param set an array of 9 numbers * @return true if the array contains the numbers 1-9 exactly once, and * false otherwise */ private boolean isValidSet(int[] set) { boolean[] found = new boolean[9]; for(int i = 0; i < 9; i++) { if(set[i] < 1 || set[i] > 9) return false; else if(found[set[i] - 1]) return false; else found[set[i] - 1] = true; } return true; } }