updated solving method was added

This commit is contained in:
Edwin Casady
2024-04-01 13:51:22 -06:00
parent 414bf3c104
commit 7900d5bc2f
3 changed files with 413 additions and 378 deletions
+3
View File
@@ -0,0 +1,3 @@
/bin/
.classpath
.project
+2 -1
View File
@@ -296,7 +296,8 @@ public class Nav extends JPanel {
* @param String * @param String
*/ */
private void openFile(String filename) { private void openFile(String filename) {
File f = new File("resources/" + filename + ".sdku"); //file path is not working for everyone had to append src/ to run
File f = new File("src/resources/" + filename + ".sdku");
// If the file does not exist, print an error message and return. // If the file does not exist, print an error message and return.
if(f == null || !f.exists() || f.isDirectory() || !f.canRead()) { if(f == null || !f.exists() || f.isDirectory() || !f.canRead()) {
System.out.println( System.out.println(
+114 -83
View File
@@ -1,25 +1,29 @@
package gui.backend; package gui.backend;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/** /**
* The SudokuChecker class is responsible for calculating the solution to * The SudokuChecker class is responsible for calculating the solution to a
* a given Sudoku puzzle. * given Sudoku puzzle.
* *
* TODO: The Sudoku algorithm used is efficient and effective for solving * TODO: The Sudoku algorithm used is efficient and effective for solving easy
* easy and medium puzzles, but it is not optimized for hard puzzles. It * and medium puzzles, but it is not optimized for hard puzzles. It is
* is recommended to use a different algorithm for hard puzzles, likely * recommended to use a different algorithm for hard puzzles, likely a tree &
* a tree & back-track approach. * back-track approach.
*/ */
public class SudokuChecker { public class SudokuChecker {
private Cell[][] grid; private Cell[][] grid;
private Cell[][] origGrid; private Cell[][] origGrid;
/** /**
* Create a new SudokuChecker object, initializing the grid to the given * Create a new SudokuChecker object, initializing the grid to the given 9x9
* 9x9 grid of numbers. This should either check each cell as the user * grid of numbers. This should either check each cell as the user inputs a
* inputs a value, or be used to check the validity of a puzzle when the * value, or be used to check the validity of a puzzle when the user requests
* user requests it. * it.
* *
* @param grid * @param grid
*/ */
@@ -39,8 +43,8 @@ public class SudokuChecker {
/** /**
* Check if the given value can be placed in the given cell of the grid. * 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 * False means that the value is incorrect, and true means that the value is
* is correct. * correct.
* *
* @param row * @param row
* @param col * @param col
@@ -65,10 +69,8 @@ public class SudokuChecker {
int boxCol = col / 3; int boxCol = col / 3;
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
if( if (grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == value
grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == value && && (boxRow * 3 + i != row || boxCol * 3 + j != col)) {
(boxRow * 3 + i != row || boxCol * 3 + j != col)
) {
return false; return false;
} }
} }
@@ -91,15 +93,10 @@ public class SudokuChecker {
continue; continue;
// Replace intersection with union? // Replace intersection with union?
ArrayList<Integer> intersection = intersection( ArrayList<Integer> intersection = intersection(getRowRemainingNumbers(row),
getRowRemainingNumbers(row), getColRemainingNumbers(col));
getColRemainingNumbers(col)
);
intersection = intersection( intersection = intersection(intersection, getBoxRemainingNumbers(row, col));
intersection,
getBoxRemainingNumbers(row, col)
);
// Join the arrays and find the intersection of the three arrays // Join the arrays and find the intersection of the three arrays
ArrayList<Integer> availableNumbers = new ArrayList<Integer>(); ArrayList<Integer> availableNumbers = new ArrayList<Integer>();
@@ -107,9 +104,7 @@ public class SudokuChecker {
availableNumbers.add(intersection.get(i)); availableNumbers.add(intersection.get(i));
} }
grid[row][col].setPossibleValues( grid[row][col].setPossibleValues(arrayListToArray(availableNumbers));
arrayListToArray(availableNumbers)
);
} }
} }
@@ -134,10 +129,7 @@ public class SudokuChecker {
* @param b * @param b
* @return * @return
*/ */
private ArrayList<Integer> intersection( private ArrayList<Integer> intersection(ArrayList<Integer> a, ArrayList<Integer> b) {
ArrayList<Integer> a,
ArrayList<Integer> b
) {
ArrayList<Integer> intersection = new ArrayList<Integer>(); ArrayList<Integer> intersection = new ArrayList<Integer>();
for (int i = 0; i < a.size(); i++) { for (int i = 0; i < a.size(); i++) {
if (b.contains(a.get(i))) if (b.contains(a.get(i)))
@@ -148,25 +140,18 @@ public class SudokuChecker {
} }
/** /**
* Determine what numbers are available to be placed in the given cell of * Determine what numbers are available to be placed in the given cell of the
* the grid. This is effectively an intersection of the numbers available * grid. This is effectively an intersection of the numbers available in the
* in the row, column, and box of the cell. * row, column, and box of the cell.
* *
* @param row * @param row
* @param col * @param col
* @return an array of numbers that are available to be placed in the * @return an array of numbers that are available to be placed in the given cell
* given cell
*/ */
private void getAvailableNumbers(int row, int col) { private void getAvailableNumbers(int row, int col) {
ArrayList<Integer> intersection = intersection( ArrayList<Integer> intersection = intersection(getRowRemainingNumbers(row), getColRemainingNumbers(col));
getRowRemainingNumbers(row),
getColRemainingNumbers(col)
);
intersection = intersection( intersection = intersection(intersection, getBoxRemainingNumbers(row, col));
intersection,
getBoxRemainingNumbers(row, col)
);
if (intersection.size() == 0) if (intersection.size() == 0)
return; return;
@@ -182,16 +167,14 @@ public class SudokuChecker {
for (int i = 0; i < intersection.size(); i++) for (int i = 0; i < intersection.size(); i++)
availableNumbers.add(intersection.get(i)); availableNumbers.add(intersection.get(i));
grid[row][col].setPossibleValues( grid[row][col].setPossibleValues(arrayListToArray(availableNumbers));
arrayListToArray(availableNumbers)
);
} }
} }
/** /**
* Update the possible values for cells in the same row, column, and box * Update the possible values for cells in the same row, column, and box as the
* as the given cell. This should always be called once a cell's value has * given cell. This should always be called once a cell's value has been set, to
* been set, to remove that value from the possible values of other cells. * remove that value from the possible values of other cells.
* *
* @param row * @param row
* @param col * @param col
@@ -221,28 +204,79 @@ public class SudokuChecker {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
if (grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == 0) if (grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == 0)
grid[boxRow * 3 + i][boxCol * 3 + j]. grid[boxRow * 3 + i][boxCol * 3 + j].removePossibleValue(value);
removePossibleValue(value);
} }
} }
} }
/** /**
* Solve the Sudoku puzzle. * Solve the Sudoku puzzle.
*
* @param grid
*/ */
public void solve() { private boolean solve() {
// Continue until a valid solution is reached. return solve(0, 0);
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);
}
}
}
} }
// /**
* Overloaded method that solves the Sudoku puzzle moving from the position given to the end
*
* @param row
* @param col
* @return
*/
private boolean solve(int row, int col) {
int nextCol = (col + 1) % 9;
int nextRow = (nextCol == 0) ? row + 1 : row;
// Base case - progressed past the last row and column.
if (row == 9) {
// grid.printTable();
return true;
}
// If the cell has a value it is skipped.
if (grid[row][col].getValue() != 0) {
solve(nextRow, nextCol);
} else {
// An empty cell prompts a generation of possible numbers
List<Integer> possibleNumbers = getAllRemainingNumbers(row, col);
if (possibleNumbers.size() == 0) // If there are no available numbers the solve() returns false
return false;
// each possible number is given in ascending order
for (Integer el : possibleNumbers) {
grid[row][col].setValue(el.intValue(), true);
// here is the check to see if the next possible number needs to be tested
if (solve(nextRow, nextCol))
return true;
// reset the cell so that it is not assumed to be solved after failing the
// current tested values }
}
}
return false;
}
/**
* This may be redundant but I needed an method that could be called to get a list of all
* remaining numbers after eliminating 1-9 by standard Sudoku rules.
* This method calls the 3 methods already in this class to build a HashSet of
* known values which are used to verify which numbers remain as possible solutions.
* @param row
* @param col
* @return ArrayList<Integer> results;
*/
private ArrayList<Integer> getAllRemainingNumbers(int row, int col) {
HashSet<Integer> nums = new HashSet<>();
ArrayList<Integer> results = new ArrayList<>();
nums.addAll(getColRemainingNumbers(col));
nums.addAll(getRowRemainingNumbers(row));
nums.addAll(getBoxRemainingNumbers(row, col));
for (int i = 1; i <= 9; i++) {
if (!nums.contains(i)) {
results.add(i);
}
}
return results;
}
/** /**
* Get any number between 1 and 9 that is not in the row. * Get any number between 1 and 9 that is not in the row.
@@ -260,7 +294,8 @@ public class SudokuChecker {
break; break;
} }
} }
if(!found) remainingNumbers.add(i); if (!found)
remainingNumbers.add(i);
} }
return remainingNumbers; return remainingNumbers;
@@ -318,11 +353,12 @@ public class SudokuChecker {
return remainingNumbers; return remainingNumbers;
} }
/** /**
* Given a list of numbers, return an array of the numbers. * Given a list of numbers, return an array of the numbers.
* *
* A helper method to keep the code using arrays instead of lists * A helper method to keep the code using arrays instead of lists whenever
* whenever possible. * possible.
* *
* @param list * @param list
* @return * @return
@@ -336,11 +372,11 @@ public class SudokuChecker {
} }
/** /**
* Given a 9x9 grid of numbers, return true if the grid is a valid Sudoku * Given a 9x9 grid of numbers, return true if the grid is a valid Sudoku puzzle
* puzzle solution, and false otherwise. * solution, and false otherwise.
* *
* A valid Sudoku puzzle is one where each row, column, and 3x3 subgrid * A valid Sudoku puzzle is one where each row, column, and 3x3 subgrid contains
* contains the numbers 1-9 exactly once. * the numbers 1-9 exactly once.
* *
* @return true if the grid is a valid Sudoku puzzle, and false otherwise * @return true if the grid is a valid Sudoku puzzle, and false otherwise
*/ */
@@ -348,10 +384,8 @@ public class SudokuChecker {
// Check that every cell has a value between 1 and 9. // Check that every cell has a value between 1 and 9.
for (int i = 0; i < 9; i++) { for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) { for (int j = 0; j < 9; j++) {
if( if (grid[i][j].getValue() < 1 || grid[i][j].getValue() > 9)
grid[i][j].getValue() < 1 || return false;
grid[i][j].getValue() > 9
) return false;
} }
} }
@@ -388,10 +422,7 @@ public class SudokuChecker {
box[k * 3 + l] = grid[i * 3 + k][j * 3 + l].getValue(); box[k * 3 + l] = grid[i * 3 + k][j * 3 + l].getValue();
} }
if (!isValidSet(box)) { if (!isValidSet(box)) {
System.out.println( System.out.println("Box at row " + i + " and column " + j + " is invalid.");
"Box at row " + i +
" and column " + j + " is invalid."
);
return false; return false;
} }
} }
@@ -401,12 +432,12 @@ public class SudokuChecker {
} }
/** /**
* Given an array of 9 numbers, return true if the array contains the * Given an array of 9 numbers, return true if the array contains the numbers
* numbers 1-9 exactly once, and false otherwise. * 1-9 exactly once, and false otherwise.
* *
* @param set an array of 9 numbers * @param set an array of 9 numbers
* @return true if the array contains the numbers 1-9 exactly once, and * @return true if the array contains the numbers 1-9 exactly once, and false
* false otherwise * otherwise
*/ */
private boolean isValidSet(int[] set) { private boolean isValidSet(int[] set) {
boolean[] found = new boolean[9]; boolean[] found = new boolean[9];