diff --git a/app/sample_sudokus/demo_from_lab.txt b/app/sample_sudokus/demo_from_lab.txt new file mode 100644 index 0000000..f6b5bbc --- /dev/null +++ b/app/sample_sudokus/demo_from_lab.txt @@ -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 \ No newline at end of file diff --git a/app/src/main/java/gui/SudokuController.java b/app/src/main/java/gui/SudokuController.java index 58043d4..83b5f2d 100644 --- a/app/src/main/java/gui/SudokuController.java +++ b/app/src/main/java/gui/SudokuController.java @@ -54,7 +54,23 @@ public class SudokuController { } }); - view.addCellClickListener(new CellActionListener()); + 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); + // Update the view + view.updateView(model.getBoard()); + } + } + }); + + view.addCellActionListener(new CellActionListener()); } /** Start the GUI */ diff --git a/app/src/main/java/gui/SudokuView.java b/app/src/main/java/gui/SudokuView.java index 9570746..81d6bcf 100644 --- a/app/src/main/java/gui/SudokuView.java +++ b/app/src/main/java/gui/SudokuView.java @@ -4,6 +4,8 @@ import javax.swing.*; import java.awt.*; import java.awt.event.*; +import sudoku.SudokuParser; + /** * SolverView is a GUI for the SudokuSolver interface */ @@ -17,6 +19,8 @@ public class SudokuView extends JFrame { private JButton resetButton; /** Button for random */ private JButton randomButton; + /** Button for picking a Sudoku-file */ + private JButton fileButton; /** Constructor */ public SudokuView() { @@ -52,11 +56,13 @@ public class SudokuView extends JFrame { 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); } @@ -105,6 +111,15 @@ public class SudokuView extends JFrame { 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 *
@@ -114,7 +129,7 @@ public class SudokuView extends JFrame { * * @param listener the ActionListener to add */ - public void addCellClickListener(ActionListener listener) { + public void addCellActionListener(ActionListener listener) { for (int row = 0; row < 9; row++) { for (int col = 0; col < 9; col++) { grid[row][col].addActionListener(listener); @@ -183,4 +198,35 @@ public class SudokuView extends JFrame { 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; + } + } } diff --git a/app/src/main/java/sudoku/Solver.java b/app/src/main/java/sudoku/Solver.java index 2c9bb37..18f2254 100644 --- a/app/src/main/java/sudoku/Solver.java +++ b/app/src/main/java/sudoku/Solver.java @@ -47,8 +47,6 @@ public class Solver implements SudokuSolver { */ 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; } diff --git a/app/src/main/java/sudoku/SudokuParser.java b/app/src/main/java/sudoku/SudokuParser.java new file mode 100644 index 0000000..bbe7ac9 --- /dev/null +++ b/app/src/main/java/sudoku/SudokuParser.java @@ -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; + } +} diff --git a/app/src/test/java/sudoku/SudokuParserTest.java b/app/src/test/java/sudoku/SudokuParserTest.java new file mode 100644 index 0000000..b63be11 --- /dev/null +++ b/app/src/test/java/sudoku/SudokuParserTest.java @@ -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); + } +}