Compare commits

...

23 commits

Author SHA1 Message Date
dDogge
9f25270371 Sudoku board style 2023-12-12 21:01:01 +01:00
Imbus
ffd1d4bd51 Fixing broken test case 2023-12-11 16:06:50 +01:00
Imbus
5bf0c92d10 Error checking inputs and reverting if illegal 2023-12-11 15:53:22 +01:00
Imbus
26df774ca3 Watchexec targets modified in justfile 2023-12-11 14:59:11 +01:00
Imbus
270a9f381d File picker now refuses to load unsolvable files 2023-12-11 14:58:54 +01:00
Imbus
e6cd5a2915 Added bin directory to gitignore 2023-12-11 14:35:25 +01:00
dDogge
af77468edc Extending testcases 2023-12-11 14:34:51 +01:00
dDogge
f9fa515651 Extending testcases 2023-12-11 14:32:51 +01:00
Imbus
d056732add Polishing invalid input handling 2023-12-11 14:25:04 +01:00
Imbus
7e5253fb4b Slightly less ugly fix for invalid input 2023-12-11 13:58:05 +01:00
Imbus
2209dd7786 Ugly fix for invalid input 2023-12-11 13:50:58 +01:00
Imbus
3cf7005151 User feedback on unsolvable 2023-12-11 13:12:50 +01:00
Imbus
2acf69d466 More clear documentation 2023-12-10 16:08:51 +01:00
Imbus
15d58e52a5 Simplifying 2023-12-10 16:08:28 +01:00
Imbus
521b3fb05b Working parser, file chooser and sample files 2023-12-10 15:54:20 +01:00
Imbus
175545d3d5 Suitable exceptions for setBoard with corresponding tests 2023-12-10 15:04:04 +01:00
Imbus
f449d2343e Documentation now passes the gradle doctest 2023-12-10 15:03:10 +01:00
Imbus
71c43b35c3 MVC architecture user interface implemented with swing 2023-12-10 14:54:56 +01:00
Imbus
e7e8979128 Set gradle to rerun tasks 2023-12-10 14:54:34 +01:00
Imbus
a6dba79d9d Inline docstrings where return is void and method takes no parameters 2023-12-10 14:53:50 +01:00
Imbus
196f066281 Recursion limit for bailing on unsolvable, more extensive testing and additions to interface 2023-12-10 13:36:51 +01:00
Imbus
6366a5e0f2 Extending SudokuSolver interface and specifying @overrides in implementation 2023-12-10 11:53:17 +01:00
383af5be58 Merge pull request 'Extending test cases' (#1) from dogge into master
Reviewed-on: #1
2023-12-06 15:25:33 +01:00
13 changed files with 719 additions and 61 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@
build
.vscode
app/bin

View file

@ -2,10 +2,16 @@ run:
./gradlew run
test:
./gradlew test
./gradlew test --rerun-tasks
doc:
./gradlew javadoc --rerun-tasks
clean:
fd -td -I build -x rm -r
watch:
watchexec -c -w app/src "just test && just run"
watchexec -r -c -w app/src "just test && just run"
watchdoc:
watchexec -r -c -w app/src "just doc"

View file

@ -0,0 +1,9 @@
0 0 9 0 7 1 3 0 0
0 0 1 0 0 0 0 0 0
6 0 0 0 9 0 0 4 7
5 0 0 9 0 4 0 0 0
1 0 4 0 0 0 2 0 9
0 0 0 1 0 8 0 0 4
7 3 0 0 1 0 0 0 2
0 0 0 0 0 0 5 0 0
0 0 8 2 4 0 6 0 0

View file

@ -0,0 +1,9 @@
1 2 3 0 0 0 0 0 0
4 5 6 0 0 0 0 0 0
0 0 0 7 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0

View file

@ -0,0 +1,9 @@
0 0 8 0 0 9 0 6 2
0 0 0 0 0 0 0 0 5
1 0 2 5 0 0 0 0 0
0 0 0 2 1 0 0 9 0
0 5 0 0 0 0 6 0 0
6 0 0 0 0 0 0 2 8
4 1 0 6 0 8 0 0 0
8 6 0 0 3 0 1 0 0
0 0 0 0 0 0 4 0 0

View file

@ -0,0 +1,142 @@
package gui;
import sudoku.SudokuSolver;
import java.awt.event.*;
/**
* SolverController is a controller for the SudokuSolver interface
*/
public class SudokuController {
SudokuSolver model;
SudokuView view;
/**
* Constructor
*
* @param model SudokuSolver model
* @param view SudokuView view
*/
public SudokuController(SudokuSolver model, SudokuView view) {
this.model = model;
this.view = view;
// Add action listeners to the buttons
view.addSolveButtonListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Solve the board
boolean solved = model.solve();
// Update the view
view.updateView(model.getBoard());
if (!solved) {
view.showErrorMessage("Could not solve the board.");
System.out.println("Could not solve the board.");
System.out.println(model.toString());
} else {
}
}
});
view.addResetButtonListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Clear the board
model.clear();
// Update the view
view.updateView(model.getBoard());
}
});
view.addRandomButtonListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Randomize the board
model.randomizeBoard();
// Update the view
view.updateView(model.getBoard());
}
});
view.addFileButtonListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Open a file, view handles the parsing internally via SudokuParser
int[][] newBoard = view.openFile();
// If the file was parsed successfully
if (newBoard != null) {
// Set the model
model.setBoard(newBoard);
// Warn and clear if the board is not solvable
if(!model.isSolvable()) {
view.showErrorMessage("The board is not solvable.");
model.clear();
}
// Update the view
view.updateView(model.getBoard());
}
}
});
view.addCellActionListener(new CellActionListener());
}
/** Start the GUI */
public void start() {
view.setVisible(true);
}
/**
* CellActionListener is an ActionListener for the Sudoku grid cells
*/
private class CellActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// Get the row and column from the clicked cell
int row = view.getSelectedRow();
int col = view.getSelectedColumn();
// The value to be inserted into the cell
// Zero inicates an empty cell
// Negative values are invalid
int value = 0;
String cellValue = view.getCellValue(row, col);
// We need to check for null and empty string
if (cellValue == null || cellValue.equals("")) {
value = 0;
} else {
try {
value = Integer.parseInt(cellValue);
} catch (NumberFormatException ex) {
value = -1;
}
}
// If the input is invalid, value < 0 indicates parse error
if (value != 0 && !model.isLegal(row, col, value) || value < 0) {
value = 0;
view.showErrorMessage("Invalid input. Try again.");
}
// Update the model and view
model.set(row, col, value);
// Warn if the board is not solvable (e.g. if the user has made a mistake)
// This is very messy, error prone and computationally expensive
if(!model.isSolvable()) {
model.set(row, col, 0);
view.showErrorMessage("Illegal move. The board is not solvable.");
}
// Sync the view with the model
view.updateView(model.getBoard());
}
}
}

View file

@ -0,0 +1,239 @@
package gui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import sudoku.SudokuParser;
/**
* SolverView is a GUI for the SudokuSolver interface
*/
public class SudokuView extends JFrame {
/** The grid of text fields */
private JTextField[][] grid;
/** Button for solve */
private JButton solveButton;
/** Button for reset */
private JButton resetButton;
/** Button for random */
private JButton randomButton;
/** Button for picking a Sudoku-file */
private JButton fileButton;
/** Constructor */
public SudokuView() {
setTitle("Sudoku Solver");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
initializeGrid();
initializeButtons();
pack();
setLocationRelativeTo(null);
}
/** Initialize the grid, called by the constructor */
private void initializeGrid() {
grid = new JTextField[9][9];
JPanel gridPanel = new JPanel(new GridLayout(9, 9));
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
grid[row][col] = new JTextField(2);
grid[row][col].setHorizontalAlignment(JTextField.CENTER);
// Set background color to gray for every third JTextField
if ((row / 3 + col / 3) % 2 == 1) {
grid[row][col].setBackground(Color.LIGHT_GRAY);
}
gridPanel.add(grid[row][col]);
}
}
add(gridPanel, BorderLayout.CENTER);
}
/** Initialize the buttons, called by the constructor */
private void initializeButtons() {
solveButton = new JButton("Solve");
resetButton = new JButton("Reset");
randomButton = new JButton("Randomize");
fileButton = new JButton("Open file");
JPanel buttonPanel = new JPanel();
buttonPanel.add(solveButton);
buttonPanel.add(resetButton);
buttonPanel.add(randomButton);
buttonPanel.add(fileButton);
add(buttonPanel, BorderLayout.SOUTH);
}
/**
* Update the view with a new grid
*
* @param newGrid the new grid to display
*/
public void updateView(int[][] newGrid) {
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
if (newGrid[row][col] != 0) {
grid[row][col].setText(String.valueOf(newGrid[row][col]));
} else {
grid[row][col].setText("");
}
}
}
}
/**
* Method to add ActionListener to solve button
*
* @param listener the ActionListener to add
*/
public void addSolveButtonListener(ActionListener listener) {
solveButton.addActionListener(listener);
}
/**
* Method to add ActionListener to reset button
*
* @param listener the ActionListener to add
*/
public void addResetButtonListener(ActionListener listener) {
resetButton.addActionListener(listener);
}
/**
* Method to add ActionListener to randomize button
*
* @param listener the ActionListener to add
*/
public void addRandomButtonListener(ActionListener listener) {
randomButton.addActionListener(listener);
}
/**
* Method to add ActionListener to file button
*
* @param listener the ActionListener to add
*/
public void addFileButtonListener(ActionListener listener) {
fileButton.addActionListener(listener);
}
/**
* Method to add ActionListener to individual cells in the grid
* <p>
* Assumes that the ActionListener will be the same for all cells
* and that the listener will be capable of determining which cell
* was clicked
*
* @param listener the ActionListener to add
*/
public void addCellActionListener(ActionListener listener) {
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
grid[row][col].addActionListener(listener);
}
}
}
/**
* Getter method to retrieve the values from the text fields
*
* @param row the row of the cell
* @param col the column of the cell
* @return the value of the cell
*/
public String getCellValue(int row, int col) {
return grid[row][col].getText();
}
/**
* Method to get the selected row (example implementation)
*
* @return the selected row, or -1 if no cell is selected
*/
public int getSelectedRow() {
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
if (grid[row][col].isFocusOwner()) {
return row;
}
}
}
return -1; // Return -1 if no cell is selected
}
/**
* Method to get the selected column (example implementation)
*
* @return the selected row, or -1 if no cell is selected
*/
public int getSelectedColumn() {
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
if (grid[row][col].isFocusOwner()) {
return col;
}
}
}
return -1; // Return -1 if no cell is selected
}
/**
* Methods to show dialogs
*
* @param message the message to display
* @return the user input
*/
public String showInputDialog(String message) {
return JOptionPane.showInputDialog(this, message);
}
/**
* Method to show error messages
*
* @param message the message to display
*/
public void showErrorMessage(String message) {
JOptionPane.showMessageDialog(this, message);
}
/**
* Method to open a file picker dialog
*
* @return 2D array of integers representing the Sudoku board
*/
public int[][] openFile() {
// Create a file chooser and set all related options
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new java.io.File("."));
fileChooser.setDialogTitle("Select a Sudoku file");
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileChooser.setAcceptAllFileFilterUsed(false);
// Show the file chooser and return if the user cancels
int returnValue = fileChooser.showOpenDialog(this);
if (returnValue != JFileChooser.APPROVE_OPTION) {
return null;
}
// Get the path
String filepath = fileChooser.getSelectedFile().getAbsolutePath();
// Try to parse it
try {
return SudokuParser.parseSudoku(filepath);
} catch (Exception e) {
showErrorMessage(e.getMessage());
return null;
}
}
}

View file

@ -1,36 +1,44 @@
package sudoku;
/** Solver is a class that implements the SudokuSolver interface */
public class Solver implements SudokuSolver {
private int[][] board = null;
private int tries = 0;
/** Constructor */
public Solver() {
board = new int[9][9];
}
/**
* {@inheritDoc}
*/
public void setBoard(int[][] board) {
/** {@inheritDoc} */
@Override
public void setBoard(int[][] board) throws IllegalArgumentException, NullPointerException {
if (board == null)
throw new NullPointerException("Board cannot be null");
if (board.length != 9 || board[0].length != 9)
throw new IllegalArgumentException("Board must be 9x9");
this.board = board;
}
/**
* {@inheritDoc}
*/
/** {@inheritDoc} */
@Override
public int[][] getBoard() {
return board;
}
/**
* Resets the board to all zeros
*/
/** Resets the board to all zeros */
@Override
public void clear() {
board = new int[9][9];
for (int[] row : board) {
for (int i = 0; i < row.length; ++i) {
row[i] = 0;
}
}
// board = new int[9][9];
}
/**
* {@inheritDoc}
*/
/* {@inheritDoc} */
@Override
public boolean solve() {
return solve(0, 0);
}
@ -43,6 +51,10 @@ public class Solver implements SudokuSolver {
* @return true if solved
*/
private boolean solve(int row, int col) {
if (++tries >= 10000) {
return false;
}
if (row < 0 || row > 9 || col < 0 || col > 9) {
return false;
}
@ -57,6 +69,7 @@ public class Solver implements SudokuSolver {
// If we have a "number" in the current cell
// recursively call solve() on the next cell
// until we find a zero (empty cell)
if (board[row][col] != 0) {
return solve(row + 1, col);
}
@ -72,16 +85,26 @@ public class Solver implements SudokuSolver {
}
}
// Reset the current cell to zero and backtrack
board[row][col] = 0;
return false;
}
/**
* Randomizes the board. This guarantees a solvable board.
* {@inheritDoc}
* <p>
* Default difficulty is 3
*/
public void randomizeBoard() {
randomizeBoard(3);
}
/** {@inheritDoc} */
@Override
public void randomizeBoard(int difficulty) {
int amount_prefilled = (difficulty * 9) + 1;
this.clear();
for (int i = 0; i < 9; ++i) {
for (int i = 0; i < amount_prefilled; ++i) {
int row = (int) (Math.random() * 9);
int col = (int) (Math.random() * 9);
int val = (int) (Math.random() * 9) + 1;
@ -89,20 +112,28 @@ public class Solver implements SudokuSolver {
board[row][col] = val;
}
}
// Recursively call randomizeBoard() until we get a solvable board
// This is expensive, and there should be a better way to do this
if (!isSolvable()) {
randomizeBoard(difficulty);
}
}
/**
* {@inheritDoc}
* <p>
* This is <b>not</b> checked for validity
*/
@Override
public void set(int row, int col, int val) {
if (row < 9 && col < 9) {
board[row][col] = val;
}
}
/**
* {@inheritDoc}
*/
/** {@inheritDoc} */
@Override
public int get(int row, int col) {
if (row < 9 && col < 9) {
return board[row][col];
@ -110,35 +141,32 @@ public class Solver implements SudokuSolver {
return 0;
}
/**
* {@inheritDoc}
*/
public boolean isLegal(int row, int col, int val) {
if (row < 0 || row >= 9 || col < 0 || col >= 9 || val < 1 || val > 9) {
/** {@inheritDoc} */
@Override
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;
}
// Check if val is already in col
for (int i = 0; i < 9; ++i) {
if (val == board[i][col]) {
return false;
}
// Ihe the number is already present in the cell
if (board[row][col] == num) {
return true;
}
// Check if val is already in row
for (int j = 0; j < 9; ++j) {
if (val == board[row][j]) {
return false;
// Check both the row and column
for (int i = 0; i < 9; i++) {
if (board[row][i] == num || board[i][col] == num) {
return false; // 'num' is already in the row or column
}
}
// Check the 3x3 box
int boxRowOffset = (row / 3) * 3;
int boxColOffset = (col / 3) * 3;
for (int k = 0; k < 3; ++k) {
for (int m = 0; m < 3; ++m) {
if (val == board[boxRowOffset + k][boxColOffset + m]) {
if (num == board[boxRowOffset + k][boxColOffset + m]) {
return false;
}
}
@ -148,11 +176,26 @@ public class Solver implements SudokuSolver {
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
*
* @return true if solved
*/
@Override
public boolean isSolved() {
return isSolved(0, 0);
}
@ -204,7 +247,7 @@ public class Solver implements SudokuSolver {
}
}
return true;
return isSolved(row + 1, col);
}
/**

View file

@ -1,12 +1,34 @@
package sudoku;
import gui.SudokuController;
import gui.SudokuView;
/** SolverMain is the main class for the Sudoku Solver */
public class SolverMain {
private Solver model;
private SudokuView view;
private SudokuController controller;
/** Constructor */
SolverMain() {
model = new Solver();
view = new SudokuView();
controller = new SudokuController(model, view);
}
/** Start the GUI */
void start() {
controller.start();
}
/**
* Main method
*
* @param args command line arguments
*/
public static void main(String[] args) {
Solver s = new Solver();
System.out.println(s.toString());
s.randomizeBoard();
System.out.println(s.toString());
s.solve();
System.out.println(s.toString());
SolverMain main = new SolverMain();
main.start();
}
}

View file

@ -0,0 +1,73 @@
package sudoku;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.FileNotFoundException;
/** Helpers class for parsing Sudoku files */
public class SudokuParser {
private static final int BOARD_SIZE = 9;
// * Empty private constructor */
private SudokuParser() {
// Empty
};
/**
* Parses a Sudoku file and returns a 2D array of integers
*
* @param filePath Path to the Sudoku file
* @return 2D array of integers representing the Sudoku board
* @throws IOException If an IO error occurs
* @throws FileNotFoundException When the file cannot be found
* @throws NumberFormatException When the file contains invalid characters
* @throws IllegalArgumentException When the file contains an invalid number of
* rows or columns
*/
public static int[][] parseSudoku(String filePath)
throws IOException, FileNotFoundException, NumberFormatException, IllegalArgumentException {
int[][] sudokuBoard = new int[BOARD_SIZE][BOARD_SIZE];
// In practice we could just split the entire file into a single string and then
// parse it into an array of integers, which is then partitioned into a 2D
// array.
// However, this is how the assignment is specified, so we will do it this way.
// Try to read the file with a BufferedReader
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
int row = 0;
// While there are lines to read and we haven't reached the end of the board
while ((line = reader.readLine()) != null && row < BOARD_SIZE) {
// Split it into an array of strings
String[] values = line.trim().split("\\s+");
// Check that the number of columns is correct
if (values.length != BOARD_SIZE) {
throw new IllegalArgumentException("Invalid number of columns in the Sudoku file.");
}
// Parse the strings into integers and add them to the board
for (int col = 0; col < BOARD_SIZE; col++) {
sudokuBoard[row][col] = Integer.parseInt(values[col]); // Throws NumberFormatException
}
row++;
}
if (row != BOARD_SIZE) {
throw new IllegalArgumentException("Invalid number of rows in the Sudoku file.");
}
} catch (FileNotFoundException e) {
throw new FileNotFoundException("The Sudoku file could not be found.");
} catch (IOException e) {
throw new IOException("An error occurred while reading the Sudoku file.");
} catch (NumberFormatException e) {
throw new NumberFormatException("The Sudoku file contains invalid characters.");
}
return sudokuBoard;
}
}

View file

@ -1,5 +1,6 @@
package sudoku;
/** SudokuSolver is an interface for implementing Sudoku solvers */
public interface SudokuSolver {
/**
* Set sudoku board, numbers 1-9 are fixed values, 0 is unsolved.
@ -7,10 +8,12 @@ public interface SudokuSolver {
* @param board a board to copy values from
* @throws IllegalArgumentException if board is invalid, e.g. not 9x9
*/
void setBoard(int[][] board);
void setBoard(int[][] board) throws IllegalArgumentException, NullPointerException;
/**
* Get a copy of the sudoku board
*
* @return a <b>copy</b> of the sudoku board
*/
int[][] getBoard();
@ -24,9 +27,9 @@ public interface SudokuSolver {
/**
* Check if digit is legal on the current board
*
* @param row
* @param col
* @param nbr
* @param row row
* @param col column
* @param nbr number to check
* @return true if legal
*/
boolean isLegal(int row, int col, int nbr);
@ -34,8 +37,8 @@ public interface SudokuSolver {
/**
* Get number on board
*
* @param row
* @param col
* @param row row
* @param col column
* @return number on board
*/
int get(int row, int col);
@ -43,12 +46,38 @@ public interface SudokuSolver {
/**
* Set number on board, numbers 1-9 are fixed values, 0 is unsolved.
*
* @param row
* @param col
* @param nbr
* @param row row
* @param col column
* @param nbr number to set
*/
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
*/

View file

@ -1,6 +1,6 @@
package sudoku;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.util.stream.IntStream;
@ -34,7 +34,8 @@ class SolverTest {
assertTrue(solver.isLegal(0, 0, 1));
solver.set(0, 0, 1);
IntStream.range(0, 9).forEach(i -> {
// Start from one, since setting the same value is legal
IntStream.range(1, 9).forEach(i -> {
assertFalse(solver.isLegal(0, i, 1));
assertFalse(solver.isLegal(i, 0, 1));
});
@ -45,6 +46,7 @@ class SolverTest {
}
@Test
@RepeatedTest(100)
void solverTest() {
Solver solver = new Solver();
assertFalse(solver.isSolved());
@ -52,12 +54,16 @@ class SolverTest {
assertTrue(solver.isSolved());
solver.clear();
solver.randomizeBoard();
assertFalse(solver.isSolved());
assertTrue(solver.solve());
solver.clear();
}
@Test
@RepeatedTest(100)
void randomizeBoardGuaranteeSolvableTest() {
Solver solver = new Solver();
solver.randomizeBoard();
assertFalse(solver.isSolved());
assertTrue(solver.solve());
}
@Test
@ -79,11 +85,64 @@ class SolverTest {
}
@Test
@Disabled
void unsolvableTest() {
Solver solver = new Solver();
// Simple example
solver.clear();
solver.set(0, 0, 1);
solver.set(0, 1, 1);
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());
}
@Test
void unsolvableTestCase3() {
Solver solver = new Solver();
// More complex example
solver.clear();
solver.set(0, 0, 1);
solver.set(0, 1, 2);
solver.set(0, 2, 3);
solver.set(1, 0, 4);
solver.set(1, 1, 5);
solver.set(1, 2, 6);
solver.set(2, 3, 7);
assertFalse(solver.isSolvable());
}
@Test
void solvableTestCase3() {
Solver solver = new Solver();
// More complex example
solver.clear();
solver.set(0, 0, 1);
solver.set(0, 1, 2);
solver.set(0, 2, 3);
solver.set(1, 0, 4);
solver.set(1, 1, 5);
solver.set(1, 2, 6);
assertTrue(solver.isSolvable());
}
@Test
void setBoardInvalidInputThrowsTest() {
Solver solver = new Solver();
assertThrows(NullPointerException.class, () -> solver.setBoard(null));
assertThrows(IllegalArgumentException.class, () -> solver.setBoard(new int[8][8]));
}
}

View file

@ -0,0 +1,17 @@
package sudoku;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class SudokuParserTest {
@Test
void constructorTest() {
int[][] board;
try {
board = SudokuParser.parseSudoku("sample_sudokus/demo_from_lab.txt");
} catch (Exception e) {
board = null;
}
assertNotNull(board);
}
}