Compare commits
	
		
			No commits in common. "9f252703717a7a2a6ef2767480ca8e4e769d21b9" and "fda5ef3e938d0defd31b7af496052fc15524af25" have entirely different histories.
		
	
	
		
			9f25270371
			...
			fda5ef3e93
		
	
		
					 13 changed files with 61 additions and 719 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -5,4 +5,3 @@
 | 
				
			||||||
build
 | 
					build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.vscode
 | 
					.vscode
 | 
				
			||||||
app/bin
 | 
					 | 
				
			||||||
							
								
								
									
										10
									
								
								Justfile
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								Justfile
									
										
									
									
									
								
							| 
						 | 
					@ -2,16 +2,10 @@ run:
 | 
				
			||||||
    ./gradlew run
 | 
					    ./gradlew run
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test:
 | 
					test:
 | 
				
			||||||
    ./gradlew test --rerun-tasks
 | 
					    ./gradlew test
 | 
				
			||||||
 | 
					 | 
				
			||||||
doc:
 | 
					 | 
				
			||||||
    ./gradlew javadoc --rerun-tasks
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
clean:
 | 
					clean:
 | 
				
			||||||
    fd -td -I build -x rm -r
 | 
					    fd -td -I build -x rm -r
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch:
 | 
					watch:
 | 
				
			||||||
    watchexec -r -c -w app/src "just test && just run" 
 | 
					    watchexec -c -w app/src "just test && just run" 
 | 
				
			||||||
 | 
					 | 
				
			||||||
watchdoc:
 | 
					 | 
				
			||||||
    watchexec -r -c -w app/src "just doc"
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
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
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
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
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
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
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,142 +0,0 @@
 | 
				
			||||||
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());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,239 +0,0 @@
 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,44 +1,36 @@
 | 
				
			||||||
package sudoku;
 | 
					package sudoku;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Solver is a class that implements the SudokuSolver interface */
 | 
					 | 
				
			||||||
public class Solver implements SudokuSolver {
 | 
					public class Solver implements SudokuSolver {
 | 
				
			||||||
    private int[][] board = null;
 | 
					    private int[][] board = null;
 | 
				
			||||||
    private int tries = 0;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** Constructor */
 | 
					 | 
				
			||||||
    public Solver() {
 | 
					    public Solver() {
 | 
				
			||||||
        board = new int[9][9];
 | 
					        board = new int[9][9];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** {@inheritDoc} */
 | 
					    /**
 | 
				
			||||||
    @Override
 | 
					     * {@inheritDoc}
 | 
				
			||||||
    public void setBoard(int[][] board) throws IllegalArgumentException, NullPointerException {
 | 
					     */
 | 
				
			||||||
        if (board == null)
 | 
					    public void setBoard(int[][] board) {
 | 
				
			||||||
            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;
 | 
					        this.board = board;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** {@inheritDoc} */
 | 
					    /**
 | 
				
			||||||
    @Override
 | 
					     * {@inheritDoc}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    public int[][] getBoard() {
 | 
					    public int[][] getBoard() {
 | 
				
			||||||
        return board;
 | 
					        return board;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** Resets the board to all zeros */
 | 
					    /**
 | 
				
			||||||
    @Override
 | 
					     * Resets the board to all zeros
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    public void clear() {
 | 
					    public void clear() {
 | 
				
			||||||
        for (int[] row : board) {
 | 
					        board = new int[9][9];
 | 
				
			||||||
            for (int i = 0; i < row.length; ++i) {
 | 
					 | 
				
			||||||
                row[i] = 0;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // board = new int[9][9];
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* {@inheritDoc} */
 | 
					    /**
 | 
				
			||||||
    @Override
 | 
					     * {@inheritDoc}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    public boolean solve() {
 | 
					    public boolean solve() {
 | 
				
			||||||
        return solve(0, 0);
 | 
					        return solve(0, 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -51,10 +43,6 @@ 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) {
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (row < 0 || row > 9 || col < 0 || col > 9) {
 | 
					        if (row < 0 || row > 9 || col < 0 || col > 9) {
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -69,7 +57,6 @@ 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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -85,26 +72,16 @@ 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;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * {@inheritDoc}
 | 
					     * Randomizes the board. This guarantees a solvable board.
 | 
				
			||||||
     * <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 < amount_prefilled; ++i) {
 | 
					        for (int i = 0; i < 9; ++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;
 | 
				
			||||||
| 
						 | 
					@ -112,28 +89,20 @@ 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 a better way to do this
 | 
					 | 
				
			||||||
        if (!isSolvable()) {
 | 
					 | 
				
			||||||
            randomizeBoard(difficulty);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * {@inheritDoc}
 | 
					     * {@inheritDoc}
 | 
				
			||||||
     * <p>
 | 
					 | 
				
			||||||
     * This is <b>not</b> checked for validity
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @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;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** {@inheritDoc} */
 | 
					    /**
 | 
				
			||||||
    @Override
 | 
					     * {@inheritDoc}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    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];
 | 
				
			||||||
| 
						 | 
					@ -141,32 +110,35 @@ public class Solver implements SudokuSolver {
 | 
				
			||||||
        return 0;
 | 
					        return 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** {@inheritDoc} */
 | 
					    /**
 | 
				
			||||||
    @Override
 | 
					     * {@inheritDoc}
 | 
				
			||||||
    public boolean isLegal(int row, int col, int num) {
 | 
					     */
 | 
				
			||||||
        // Sanity check
 | 
					    public boolean isLegal(int row, int col, int val) {
 | 
				
			||||||
        if (row < 0 || row >= 9 || col < 0 || col >= 9 || num < 1 || num > 9) {
 | 
					        if (row < 0 || row >= 9 || col < 0 || col >= 9 || val < 1 || val > 9) {
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Ihe the number is already present in the cell
 | 
					        // Check if val is already in col
 | 
				
			||||||
        if (board[row][col] == num) {
 | 
					        for (int i = 0; i < 9; ++i) {
 | 
				
			||||||
            return true;
 | 
					            if (val == board[i][col]) {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Check both the row and column
 | 
					        // Check if val is already in row
 | 
				
			||||||
        for (int i = 0; i < 9; i++) {
 | 
					        for (int j = 0; j < 9; ++j) {
 | 
				
			||||||
            if (board[row][i] == num || board[i][col] == num) {
 | 
					            if (val == board[row][j]) {
 | 
				
			||||||
                return false; // 'num' is already in the row or column
 | 
					                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 (num == board[boxRowOffset + k][boxColOffset + m]) {
 | 
					                if (val == board[boxRowOffset + k][boxColOffset + m]) {
 | 
				
			||||||
                    return false;
 | 
					                    return false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -176,26 +148,11 @@ 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);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -247,7 +204,7 @@ public class Solver implements SudokuSolver {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return isSolved(row + 1, col);
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,34 +1,12 @@
 | 
				
			||||||
package sudoku;
 | 
					package sudoku;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import gui.SudokuController;
 | 
					 | 
				
			||||||
import gui.SudokuView;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** SolverMain is the main class for the Sudoku Solver */
 | 
					 | 
				
			||||||
public class SolverMain {
 | 
					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) {
 | 
					    public static void main(String[] args) {
 | 
				
			||||||
        SolverMain main = new SolverMain();
 | 
					        Solver s = new Solver();
 | 
				
			||||||
        main.start();
 | 
					        System.out.println(s.toString());
 | 
				
			||||||
 | 
					        s.randomizeBoard();
 | 
				
			||||||
 | 
					        System.out.println(s.toString());
 | 
				
			||||||
 | 
					        s.solve();
 | 
				
			||||||
 | 
					        System.out.println(s.toString());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,73 +0,0 @@
 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
package sudoku;
 | 
					package sudoku;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** SudokuSolver is an interface for implementing Sudoku solvers */
 | 
					 | 
				
			||||||
public interface SudokuSolver {
 | 
					public interface SudokuSolver {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Set sudoku board, numbers 1-9 are fixed values, 0 is unsolved.
 | 
					     * Set sudoku board, numbers 1-9 are fixed values, 0 is unsolved.
 | 
				
			||||||
| 
						 | 
					@ -8,12 +7,10 @@ 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) throws IllegalArgumentException, NullPointerException;
 | 
					    void setBoard(int[][] board);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get a copy of the sudoku board
 | 
					     * Get a copy of the sudoku board
 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @return a <b>copy</b> of the sudoku board
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    int[][] getBoard();
 | 
					    int[][] getBoard();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,9 +24,9 @@ public interface SudokuSolver {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Check if digit is legal on the current board
 | 
					     * Check if digit is legal on the current board
 | 
				
			||||||
     * 
 | 
					     * 
 | 
				
			||||||
     * @param row row
 | 
					     * @param row
 | 
				
			||||||
     * @param col column
 | 
					     * @param col
 | 
				
			||||||
     * @param nbr number to check
 | 
					     * @param nbr
 | 
				
			||||||
     * @return true if legal
 | 
					     * @return true if legal
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    boolean isLegal(int row, int col, int nbr);
 | 
					    boolean isLegal(int row, int col, int nbr);
 | 
				
			||||||
| 
						 | 
					@ -37,8 +34,8 @@ public interface SudokuSolver {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get number on board
 | 
					     * Get number on board
 | 
				
			||||||
     * 
 | 
					     * 
 | 
				
			||||||
     * @param row row
 | 
					     * @param row
 | 
				
			||||||
     * @param col column
 | 
					     * @param col
 | 
				
			||||||
     * @return number on board
 | 
					     * @return number on board
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    int get(int row, int col);
 | 
					    int get(int row, int col);
 | 
				
			||||||
| 
						 | 
					@ -46,38 +43,12 @@ public interface SudokuSolver {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Set number on board, numbers 1-9 are fixed values, 0 is unsolved.
 | 
					     * Set number on board, numbers 1-9 are fixed values, 0 is unsolved.
 | 
				
			||||||
     * 
 | 
					     * 
 | 
				
			||||||
     * @param row row
 | 
					     * @param row
 | 
				
			||||||
     * @param col column
 | 
					     * @param col
 | 
				
			||||||
     * @param nbr number to set
 | 
					     * @param nbr
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    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.RepeatedTest;
 | 
					import org.junit.jupiter.api.Disabled;
 | 
				
			||||||
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;
 | 
				
			||||||
| 
						 | 
					@ -34,8 +34,7 @@ class SolverTest {
 | 
				
			||||||
        assertTrue(solver.isLegal(0, 0, 1));
 | 
					        assertTrue(solver.isLegal(0, 0, 1));
 | 
				
			||||||
        solver.set(0, 0, 1);
 | 
					        solver.set(0, 0, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Start from one, since setting the same value is legal
 | 
					        IntStream.range(0, 9).forEach(i -> {
 | 
				
			||||||
        IntStream.range(1, 9).forEach(i -> {
 | 
					 | 
				
			||||||
            assertFalse(solver.isLegal(0, i, 1));
 | 
					            assertFalse(solver.isLegal(0, i, 1));
 | 
				
			||||||
            assertFalse(solver.isLegal(i, 0, 1));
 | 
					            assertFalse(solver.isLegal(i, 0, 1));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
| 
						 | 
					@ -46,7 +45,6 @@ class SolverTest {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    @RepeatedTest(100)
 | 
					 | 
				
			||||||
    void solverTest() {
 | 
					    void solverTest() {
 | 
				
			||||||
        Solver solver = new Solver();
 | 
					        Solver solver = new Solver();
 | 
				
			||||||
        assertFalse(solver.isSolved());
 | 
					        assertFalse(solver.isSolved());
 | 
				
			||||||
| 
						 | 
					@ -54,16 +52,12 @@ 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();
 | 
				
			||||||
        assertTrue(solver.solve());
 | 
					        assertFalse(solver.isSolved());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
| 
						 | 
					@ -85,64 +79,11 @@ 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());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @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]));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,17 +0,0 @@
 | 
				
			||||||
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);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue