Compare commits
2 commits
383af5be58
...
196f066281
Author | SHA1 | Date | |
---|---|---|---|
![]() |
196f066281 | ||
![]() |
6366a5e0f2 |
3 changed files with 113 additions and 24 deletions
|
@ -2,6 +2,7 @@ package sudoku;
|
||||||
|
|
||||||
public class Solver implements SudokuSolver {
|
public class Solver implements SudokuSolver {
|
||||||
private int[][] board = null;
|
private int[][] board = null;
|
||||||
|
private int tries = 0;
|
||||||
|
|
||||||
public Solver() {
|
public Solver() {
|
||||||
board = new int[9][9];
|
board = new int[9][9];
|
||||||
|
@ -10,6 +11,7 @@ public class Solver implements SudokuSolver {
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void setBoard(int[][] board) {
|
public void setBoard(int[][] board) {
|
||||||
this.board = board;
|
this.board = board;
|
||||||
}
|
}
|
||||||
|
@ -17,6 +19,7 @@ public class Solver implements SudokuSolver {
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public int[][] getBoard() {
|
public int[][] getBoard() {
|
||||||
return board;
|
return board;
|
||||||
}
|
}
|
||||||
|
@ -24,6 +27,7 @@ public class Solver implements SudokuSolver {
|
||||||
/**
|
/**
|
||||||
* Resets the board to all zeros
|
* Resets the board to all zeros
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
board = new int[9][9];
|
board = new int[9][9];
|
||||||
}
|
}
|
||||||
|
@ -31,6 +35,7 @@ public class Solver implements SudokuSolver {
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean solve() {
|
public boolean solve() {
|
||||||
return solve(0, 0);
|
return solve(0, 0);
|
||||||
}
|
}
|
||||||
|
@ -43,6 +48,12 @@ public class Solver implements SudokuSolver {
|
||||||
* @return true if solved
|
* @return true if solved
|
||||||
*/
|
*/
|
||||||
private boolean solve(int row, int col) {
|
private boolean solve(int row, int col) {
|
||||||
|
if (++tries >= 10000) {
|
||||||
|
if (tries == 10000)
|
||||||
|
System.out.println(String.format("Likely unsolvable. Tries: %d", tries));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (row < 0 || row > 9 || col < 0 || col > 9) {
|
if (row < 0 || row > 9 || col < 0 || col > 9) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -57,6 +68,7 @@ public class Solver implements SudokuSolver {
|
||||||
|
|
||||||
// If we have a "number" in the current cell
|
// If we have a "number" in the current cell
|
||||||
// recursively call solve() on the next cell
|
// recursively call solve() on the next cell
|
||||||
|
// until we find a zero (empty cell)
|
||||||
if (board[row][col] != 0) {
|
if (board[row][col] != 0) {
|
||||||
return solve(row + 1, col);
|
return solve(row + 1, col);
|
||||||
}
|
}
|
||||||
|
@ -72,16 +84,28 @@ public class Solver implements SudokuSolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the current cell to zero and backtrack
|
||||||
board[row][col] = 0;
|
board[row][col] = 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Randomizes the board. This guarantees a solvable board.
|
* {@inheritDoc}
|
||||||
|
* <p>
|
||||||
|
* Default difficulty is 3
|
||||||
*/
|
*/
|
||||||
public void randomizeBoard() {
|
public void randomizeBoard() {
|
||||||
|
randomizeBoard(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void randomizeBoard(int difficulty) {
|
||||||
|
int amount_prefilled = (difficulty * 9) + 1;
|
||||||
this.clear();
|
this.clear();
|
||||||
for (int i = 0; i < 9; ++i) {
|
for (int i = 0; i < amount_prefilled; ++i) {
|
||||||
int row = (int) (Math.random() * 9);
|
int row = (int) (Math.random() * 9);
|
||||||
int col = (int) (Math.random() * 9);
|
int col = (int) (Math.random() * 9);
|
||||||
int val = (int) (Math.random() * 9) + 1;
|
int val = (int) (Math.random() * 9) + 1;
|
||||||
|
@ -89,11 +113,18 @@ public class Solver implements SudokuSolver {
|
||||||
board[row][col] = val;
|
board[row][col] = val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recursively call randomizeBoard() until we get a solvable board
|
||||||
|
// This is expensive, and there should be some voodoo magic that computes this in n^2 time
|
||||||
|
if (!isSolvable()) {
|
||||||
|
randomizeBoard(difficulty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void set(int row, int col, int val) {
|
public void set(int row, int col, int val) {
|
||||||
if (row < 9 && col < 9) {
|
if (row < 9 && col < 9) {
|
||||||
board[row][col] = val;
|
board[row][col] = val;
|
||||||
|
@ -103,6 +134,7 @@ public class Solver implements SudokuSolver {
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public int get(int row, int col) {
|
public int get(int row, int col) {
|
||||||
if (row < 9 && col < 9) {
|
if (row < 9 && col < 9) {
|
||||||
return board[row][col];
|
return board[row][col];
|
||||||
|
@ -113,32 +145,26 @@ public class Solver implements SudokuSolver {
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public boolean isLegal(int row, int col, int val) {
|
@Override
|
||||||
if (row < 0 || row >= 9 || col < 0 || col >= 9 || val < 1 || val > 9) {
|
public boolean isLegal(int row, int col, int num) {
|
||||||
|
// Sanity check
|
||||||
|
if (row < 0 || row >= 9 || col < 0 || col >= 9 || num < 1 || num > 9) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if val is already in col
|
// Check both the row and column
|
||||||
for (int i = 0; i < 9; ++i) {
|
for (int i = 0; i < 9; i++) {
|
||||||
if (val == board[i][col]) {
|
if (board[row][i] == num || board[i][col] == num) {
|
||||||
return false;
|
return false; // 'num' is already in the row or column
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if val is already in row
|
|
||||||
for (int j = 0; j < 9; ++j) {
|
|
||||||
if (val == board[row][j]) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the 3x3 box
|
// Check the 3x3 box
|
||||||
int boxRowOffset = (row / 3) * 3;
|
int boxRowOffset = (row / 3) * 3;
|
||||||
int boxColOffset = (col / 3) * 3;
|
int boxColOffset = (col / 3) * 3;
|
||||||
|
|
||||||
for (int k = 0; k < 3; ++k) {
|
for (int k = 0; k < 3; ++k) {
|
||||||
for (int m = 0; m < 3; ++m) {
|
for (int m = 0; m < 3; ++m) {
|
||||||
if (val == board[boxRowOffset + k][boxColOffset + m]) {
|
if (num == board[boxRowOffset + k][boxColOffset + m]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,11 +174,28 @@ public class Solver implements SudokuSolver {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isSolvable() {
|
||||||
|
// We want to work on a copy
|
||||||
|
int[][] copy = new int[9][9];
|
||||||
|
for (int row = 0; row < 9; row++) {
|
||||||
|
System.arraycopy(board[row], 0, copy[row], 0, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
Solver copyModel = new Solver();
|
||||||
|
copyModel.setBoard(copy);
|
||||||
|
|
||||||
|
return copyModel.solve();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the board is solved
|
* Checks if the board is solved
|
||||||
*
|
*
|
||||||
* @return true if solved
|
* @return true if solved
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean isSolved() {
|
public boolean isSolved() {
|
||||||
return isSolved(0, 0);
|
return isSolved(0, 0);
|
||||||
}
|
}
|
||||||
|
@ -204,7 +247,7 @@ public class Solver implements SudokuSolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return isSolved(row + 1, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,7 +7,7 @@ public interface SudokuSolver {
|
||||||
* @param board a board to copy values from
|
* @param board a board to copy values from
|
||||||
* @throws IllegalArgumentException if board is invalid, e.g. not 9x9
|
* @throws IllegalArgumentException if board is invalid, e.g. not 9x9
|
||||||
*/
|
*/
|
||||||
void setBoard(int[][] board);
|
void setBoard(int[][] board) throws IllegalArgumentException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a copy of the sudoku board
|
* Get a copy of the sudoku board
|
||||||
|
@ -49,6 +49,32 @@ public interface SudokuSolver {
|
||||||
*/
|
*/
|
||||||
void set(int row, int col, int nbr);
|
void set(int row, int col, int nbr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Randomize the board. Guaranteed to be solvable.
|
||||||
|
*/
|
||||||
|
public void randomizeBoard();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Randomize the board. Guaranteed to be solvable.
|
||||||
|
*
|
||||||
|
* @param difficulty 0-9, 0 being easiest
|
||||||
|
*/
|
||||||
|
void randomizeBoard(int difficulty);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the board is solved
|
||||||
|
*
|
||||||
|
* @return true if solved
|
||||||
|
*/
|
||||||
|
boolean isSolved();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the board is solvable
|
||||||
|
*
|
||||||
|
* @return true if solvable
|
||||||
|
*/
|
||||||
|
boolean isSolvable();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the board
|
* Clear the board
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package sudoku;
|
package sudoku;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.RepeatedTest;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
@ -45,6 +45,7 @@ class SolverTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@RepeatedTest(100)
|
||||||
void solverTest() {
|
void solverTest() {
|
||||||
Solver solver = new Solver();
|
Solver solver = new Solver();
|
||||||
assertFalse(solver.isSolved());
|
assertFalse(solver.isSolved());
|
||||||
|
@ -52,12 +53,16 @@ class SolverTest {
|
||||||
assertTrue(solver.isSolved());
|
assertTrue(solver.isSolved());
|
||||||
|
|
||||||
solver.clear();
|
solver.clear();
|
||||||
solver.randomizeBoard();
|
|
||||||
assertFalse(solver.isSolved());
|
assertFalse(solver.isSolved());
|
||||||
assertTrue(solver.solve());
|
assertTrue(solver.solve());
|
||||||
solver.clear();
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RepeatedTest(100)
|
||||||
|
void randomizeBoardGuaranteeSolvableTest() {
|
||||||
|
Solver solver = new Solver();
|
||||||
solver.randomizeBoard();
|
solver.randomizeBoard();
|
||||||
assertFalse(solver.isSolved());
|
assertTrue(solver.solve());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -79,11 +84,26 @@ class SolverTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
void unsolvableTest() {
|
void unsolvableTest() {
|
||||||
Solver solver = new Solver();
|
Solver solver = new Solver();
|
||||||
|
|
||||||
|
// Simple example
|
||||||
|
solver.clear();
|
||||||
solver.set(0, 0, 1);
|
solver.set(0, 0, 1);
|
||||||
solver.set(0, 1, 1);
|
solver.set(0, 1, 1);
|
||||||
assertFalse(solver.solve());
|
assertFalse(solver.solve());
|
||||||
|
|
||||||
|
// More complex example
|
||||||
|
solver.clear();
|
||||||
|
solver.set(0, 5, 7);
|
||||||
|
solver.set(0, 6, 8);
|
||||||
|
solver.set(0, 7, 2);
|
||||||
|
solver.set(1, 5, 3);
|
||||||
|
solver.set(2, 7, 1);
|
||||||
|
solver.set(3, 1, 4);
|
||||||
|
solver.set(5, 2, 8);
|
||||||
|
solver.set(5, 8, 6);
|
||||||
|
solver.set(7, 1, 8);
|
||||||
|
assertFalse(solver.solve());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue