From 74346a761c70e7cd849e23d36b4d4ce9ce5e7a43 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Thu, 21 Mar 2024 14:50:40 -0600 Subject: [PATCH] documentation and formatting to 80 character width. --- install.ps1 | 0 install.sh | 0 src/App.java | 19 ++++--- src/Nav.java | 40 +++++++++++---- src/SudokuChecker.java | 104 +++++++++++++++++++++++--------------- src/gui/FileChooser.java | 21 ++------ src/gui/Settings.java | 105 ++++++++++++++++++++++++--------------- 7 files changed, 176 insertions(+), 113 deletions(-) create mode 100644 install.ps1 create mode 100644 install.sh diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..e69de29 diff --git a/src/App.java b/src/App.java index aa4a300..4447296 100644 --- a/src/App.java +++ b/src/App.java @@ -8,7 +8,8 @@ import java.awt.BorderLayout; import java.awt.Dimension; /** - * Runner class for the Sudoku App, managing the GUI and coordinating internal logic. + * Runner class for the Sudoku App, managing the GUI and coordinating + * internal logic. */ public class App extends JFrame { // GUI fields @@ -33,7 +34,8 @@ public class App extends JFrame { * Basic JFrame settings and setup for the GUI. */ private void initSetup() { - // TODO: These are just some default values, these should use the values from Settings. + // TODO: These are just some default values, these should use the + // values from Settings. setDefaultCloseOperation(EXIT_ON_CLOSE); setLayout(new BorderLayout()); setPreferredSize(new Dimension(750, 550)); @@ -42,9 +44,13 @@ public class App extends JFrame { setLocationRelativeTo(null); try { - UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + UIManager.setLookAndFeel( + UIManager.getCrossPlatformLookAndFeelClassName() + ); } catch (Exception e) { - System.out.println("Error with cross platform look/feel in frameSetup()."); + System.out.println( + "Error with cross platform look/feel in frameSetup()." + ); } } @@ -76,9 +82,10 @@ public class App extends JFrame { public static void main(String[] args) { if(args.length == 0) new App().setVisible(true); - else if(args[0].equals("-c") || args[0].equals("--cli")) + else if(args[0].equals("-c") || + args[0].equals("--cli")) cli(); else new App().setVisible(true); } -} +} \ No newline at end of file diff --git a/src/Nav.java b/src/Nav.java index a40d17f..184eac7 100644 --- a/src/Nav.java +++ b/src/Nav.java @@ -33,7 +33,7 @@ public class Nav extends JPanel { * Create a new Nav in a command-line interface. This is intended for * debugging rather than practical use. * - * Additionally, even for debugging, this should be used since default + * Additionally, even for debugging, this should not be used since default * file I/O behavior is dictated by the values in the Settings object. */ public Nav() { @@ -61,6 +61,8 @@ public class Nav extends JPanel { super(); this.s = s; + // If the program is being run in the command-line, prompt the user + // for the name of the input file. if(cli) { System.out.println("Welcome to the Sudoku Solver!"); @@ -73,10 +75,10 @@ public class Nav extends JPanel { in.close(); openFile(inputFilename); - } else { - // TODO: Create the GUI for the Nav bar. - createGUI(); - } + + // Otherwise, create the GUI for the Nav bar. + } else createGUI(); + } /** @@ -92,6 +94,8 @@ public class Nav extends JPanel { JButton solve = new JButton("Solve"); solve.setFont(s.getFont()); // TODO: Make custom Button class. + // Add the ComboBox for the file options, the elapsed time label, and + // the solve button to the Nav Panel. add(createFileOptions()); add(elapsedTime); add(solve); @@ -103,12 +107,14 @@ public class Nav extends JPanel { * @return ComboBox */ private ComboBox createFileOptions() { + // Create a new ComboBox with the default styling. ComboBox fileOptions = new ComboBox<>(s); fileOptions.addItem("New"); fileOptions.addItem("Open"); fileOptions.addItem("Save"); fileOptions.addItem("Exit"); + // Add an ActionListener to the ComboBox to handle the user's selection. fileOptions.addActionListener(e -> { String selected = (String) fileOptions.getSelectedItem(); switch(selected) { @@ -154,19 +160,19 @@ public class Nav extends JPanel { * Open an existing .sdku file in the user's file system, using a GUI. */ private void openFile() { + // No error handling is needed here, since the Settings class ensures + // that the default directory is always a valid directory. File defaultDirectory = new File(s.getDefaultDirectory()); - if(defaultDirectory != null || !defaultDirectory.isDirectory()) { - defaultDirectory.mkdirs(); - } - FileChooser fc = new FileChooser(defaultDirectory); + // Custom FileChooser class for style and to ensure the user can only + // select .sdku files. + FileChooser fc = new FileChooser(defaultDirectory, s); int result = fc.showOpenDialog(this); + // If the user selects a file, open it. if(result == FileChooser.APPROVE_OPTION) { f = fc.getSelectedFile(); createGrid(f); - } else { - System.out.println("No file was selected."); } } @@ -179,6 +185,7 @@ public class Nav extends JPanel { */ private void openFile(String filename) { File f = new File("resources/" + filename + ".sdku"); + // If the file does not exist, print an error message and return. if(f == null || !f.exists() || f.isDirectory() || !f.canRead()) { System.out.println( "The file " + filename + @@ -192,6 +199,7 @@ public class Nav extends JPanel { return; } + // Otherwise, open the file and populate the grid. System.out.println("Opening the file " + filename + ".sdku."); createGrid(f); } @@ -241,21 +249,31 @@ public class Nav extends JPanel { */ private void createGrid(File f) { try (Scanner in = new Scanner(f)) { + // Create a new 9x9 grid of Cells. Cell[][] grid = new Cell[9][9]; + + // Read in the file line by line. for(int i = 0; i < 9; i++) { + + // Read in the line and create a new Cell for each character. String line = in.nextLine(); for(int j = 0; j < 9; j++) { + + // If the character is a period, the cell is empty. if(line.charAt(j) == '.') { grid[i][j] = new Cell(i, j, 0); continue; } + // Otherwise, the character is a digit. int value = Character.getNumericValue(line.charAt(j)); grid[i][j] = new Cell(i, j, value); } } this.grid = grid; + + // If the file is not found, print an error message and return. } catch (FileNotFoundException e) { String filename = f.getName(); System.err.println("An error occurred while reading the file " + diff --git a/src/SudokuChecker.java b/src/SudokuChecker.java index e23aa91..34cc148 100644 --- a/src/SudokuChecker.java +++ b/src/SudokuChecker.java @@ -1,35 +1,40 @@ -import java.io.File; -import java.io.FileNotFoundException; import java.util.ArrayList; -import java.util.Scanner; - /** - * The SudokuChecker class is responsible for checking the validity of a given Sudoku puzzle solution. + * The SudokuChecker class is responsible for calculating the solution to + * a given Sudoku puzzle. * - * The SudokuChecker class is also responsible for solving a given Sudoku puzzle. + * TODO: The Sudoku algorithm used is efficient and effective for solving + * easy and medium puzzles, but it is not optimized for hard puzzles. It + * is recommended to use a different algorithm for hard puzzles, likely + * a tree & back-track approach. */ public class SudokuChecker { private Cell[][] grid; /** - * Create a new SudokuChecker object, initializing the grid to the given 9x9 grid of numbers. - * This should either check each cell as the user inputs a value, or be used to check the validity of a - * puzzle when the user requests it. + * Create a new SudokuChecker object, initializing the grid to the given + * 9x9 grid of numbers. This should either check each cell as the user + * inputs a value, or be used to check the validity of a puzzle when the + * user requests it. + * + * TODO: Currently, this class is not used in the GUI. It is only used in + * the command-line interface. * - * TODO: Currently, this class is not used in the GUI. It is only used in the command-line interface. * @param grid */ public SudokuChecker(Cell[][] grid) { this.grid = grid; solve(); - System.out.println("The solution to the Sudoku puzzle is:"); - for(int i = 0; i < 9; i++) { - for(int j = 0; j < 9; j++) { - System.out.print(grid[i][j].getValue()); - } - System.out.println(); - } + } + + /** + * Get the solution to the Sudoku puzzle. + * + * @return Cell[][] + */ + public Cell[][] getSolution() { + return grid; } /** @@ -39,7 +44,10 @@ public class SudokuChecker { * @param b * @return */ - private ArrayList intersection(ArrayList a, ArrayList b) { + private ArrayList intersection( + ArrayList a, + ArrayList b + ) { ArrayList intersection = new ArrayList(); for(int i = 0; i < a.size(); i++) { if(b.contains(a.get(i))) { @@ -51,16 +59,25 @@ public class SudokuChecker { } /** - * Determine what numbers are available to be placed in the given cell of the grid. - * This is effectively an intersection of the numbers available in the row, column, and box of the cell. + * Determine what numbers are available to be placed in the given cell of + * the grid. This is effectively an intersection of the numbers available + * in the row, column, and box of the cell. * * @param row * @param col - * @return an array of numbers that are available to be placed in the given cell + * @return an array of numbers that are available to be placed in the + * given cell */ private void getAvailableNumbers(int row, int col) { - ArrayList intersection = intersection(getRowRemainingNumbers(row), getColRemainingNumbers(col)); - intersection = intersection(intersection, getBoxRemainingNumbers(row, col)); + ArrayList intersection = intersection( + getRowRemainingNumbers(row), + getColRemainingNumbers(col) + ); + + intersection = intersection( + intersection, + getBoxRemainingNumbers(row, col) + ); if(intersection.size() == 0) { return; @@ -77,13 +94,16 @@ public class SudokuChecker { availableNumbers.add(intersection.get(i)); } - grid[row][col].setPossibleValues(arrayListToArray(availableNumbers)); + grid[row][col].setPossibleValues( + arrayListToArray(availableNumbers) + ); } } /** - * Update the possible values for cells in the same row, column, and box as the given cell. - * This should always be called once a cell's value has been set, to remove that value from the possible values of other cells. + * Update the possible values for cells in the same row, column, and box + * as the given cell. This should always be called once a cell's value has + * been set, to remove that value from the possible values of other cells. * * @param row * @param col @@ -112,9 +132,9 @@ public class SudokuChecker { int boxCol = col / 3; for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { - if(grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == 0) { - grid[boxRow * 3 + i][boxCol * 3 + j].removePossibleValue(value); - } + if(grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == 0) + grid[boxRow * 3 + i][boxCol * 3 + j]. + removePossibleValue(value); } } } @@ -218,7 +238,8 @@ public class SudokuChecker { /** * Given a list of numbers, return an array of the numbers. * - * A helper method to keep the code using arrays instead of lists whenever possible. + * A helper method to keep the code using arrays instead of lists + * whenever possible. * * @param list * @return @@ -232,9 +253,11 @@ public class SudokuChecker { } /** - * Given a 9x9 grid of numbers, return true if the grid is a valid Sudoku puzzle solution, and false otherwise. + * Given a 9x9 grid of numbers, return true if the grid is a valid Sudoku + * puzzle solution, and false otherwise. * - * A valid Sudoku puzzle is one where each row, column, and 3x3 subgrid contains the numbers 1-9 exactly once. + * A valid Sudoku puzzle is one where each row, column, and 3x3 subgrid + * contains the numbers 1-9 exactly once. * * @return true if the grid is a valid Sudoku puzzle, and false otherwise */ @@ -242,10 +265,8 @@ public class SudokuChecker { // Check that every cell has a value between 1 and 9. for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { - if(grid[i][j].getValue() < 1 || grid[i][j].getValue() > 9) { - //System.out.println("Cell at row " + i + " and column " + j + " has an invalid value of " + grid[i][j].getValue() + "."); + if(grid[i][j].getValue() < 1 || grid[i][j].getValue() > 9) return false; - } } } @@ -283,7 +304,10 @@ public class SudokuChecker { } } if(!isValidSet(box)) { - System.out.println("Box at row " + i + " and column " + j + " is invalid."); + System.out.println( + "Box at row " + i + + " and column " + j + " is invalid." + ); return false; } } @@ -293,10 +317,12 @@ public class SudokuChecker { } /** - * Given an array of 9 numbers, return true if the array contains the numbers 1-9 exactly once, and false otherwise. + * Given an array of 9 numbers, return true if the array contains the + * numbers 1-9 exactly once, and false otherwise. * * @param set an array of 9 numbers - * @return true if the array contains the numbers 1-9 exactly once, and false otherwise + * @return true if the array contains the numbers 1-9 exactly once, and + * false otherwise */ private boolean isValidSet(int[] set) { boolean[] found = new boolean[9]; @@ -311,4 +337,4 @@ public class SudokuChecker { } return true; } -} +} \ No newline at end of file diff --git a/src/gui/FileChooser.java b/src/gui/FileChooser.java index 80bdcc9..a84353b 100644 --- a/src/gui/FileChooser.java +++ b/src/gui/FileChooser.java @@ -11,22 +11,6 @@ import javax.swing.JFileChooser; public class FileChooser extends JFileChooser { private Settings s; - /** - * Create a new FileChooser with the default directory set to the user's - * Sudoku directory and no special styling. - * - * @param defaultDirectory - */ - public FileChooser(File defaultDirectory) { - super(defaultDirectory); - for (File f: defaultDirectory.listFiles()) { - ensureFileIsVisible(f); - } - - style(); - setupSettings(); - } - /** * Create a new FileChooser with the default directory set to the user's * Sudoku directory. @@ -70,7 +54,8 @@ public class FileChooser extends JFileChooser { public boolean accept(File f) { String name = f.getName().toLowerCase(); if(name.length() > 5) { - return name.substring(name.length() - 5).equals(".sdku") || !f.isDirectory(); + return name.substring(name.length() - 5).equals(".sdku") + || !f.isDirectory(); } return false; } @@ -79,4 +64,4 @@ public class FileChooser extends JFileChooser { return "Sudoku Puzzle Files (*.sdku)"; } } -} +} \ No newline at end of file diff --git a/src/gui/Settings.java b/src/gui/Settings.java index 583f4ad..ee88797 100644 --- a/src/gui/Settings.java +++ b/src/gui/Settings.java @@ -1,6 +1,5 @@ package gui; -import java.util.Scanner; import java.io.File; import java.io.IOException; @@ -12,13 +11,30 @@ import java.awt.FontFormatException; * A class solely to store user settings relating to default behaviors, * difficulty, filepaths, and more. * - * These user settings are stored in a {TBD} file located at {TBD}. + * These user settings are stored in a settings file that must be stored + * either in "~/.config/sudoku/settings.json" or + * "%APPDATA%/Local/Sudoku/settings.json" depending on the user's operating + * system. + * + * (Currently) Configurable settings include: + * - Default Directory + * - New File Properties + * - Font + * */ public class Settings { private final String os; + // The directory where the settings file is located. + private String appDirectory; + + // The default directory for .sdku files. + private String defaultDirectory; + + // The properties for creating a new .sdku file. private int newFileProperties; - private String defaultDirectory; + + // The font to be used throughout the application. private Font font; /** @@ -33,11 +49,13 @@ public class Settings { public Settings() { os = System.getProperty("os.name").toLowerCase(); if(os.startsWith("windows")) - defaultDirectory = System.getProperty("user.home") + "\\AppData\\Local\\Sudoku\\"; + appDirectory = System.getProperty("user.home") + + "\\AppData\\Local\\Sudoku\\"; else - defaultDirectory = System.getProperty("user.home") + "/.config/sudoku/"; + appDirectory = System.getProperty("user.home") + + "/.config/sudoku/"; - File dir = new File(defaultDirectory); + File dir = new File(appDirectory); if (!dir.exists() || !dir.isDirectory()) { dir.mkdirs(); @@ -46,6 +64,31 @@ public class Settings { readSettings(); } + /** + * The Default Directory is a configurable setting, where the user can + * specify a default directory to store .sdku files. This is useful for + * eliminating the need for user input for file operations. + * + * By default, the default directory is either "~/.config/sudoku/puzzles" + * or "%APPDATA%/Local/Sudoku/Puzzles", depending on the user's operating + * system. + * + * @return String + */ + public String getDefaultDirectory() { + return defaultDirectory; + } + + /** + * Set the default directory and write it to the settings file. + * + * @param defaultDirectory + */ + public void setDefaultDirectory(String defaultDirectory) { + this.defaultDirectory = defaultDirectory; + updateSettingsFile(); + } + /** * New File Properties is a configurable setting, where the following * values are allowable: @@ -69,32 +112,6 @@ public class Settings { updateSettingsFile(); } - /** - * The Default Directory is a configurable setting, where the user can - * specify a default directory to store .sdku files. This is useful for - * eliminating the need for user input for file operations. - * - * NOTE: This does not change the location that the settings file may - * be located in. At this time, the settings file is always located in - * either "~/.config/sudoku/" or "%APPDATA%/Local/Sudoku/", depending - * on the user's operating system. - * - * @return String - */ - public String getDefaultDirectory() { - return defaultDirectory; - } - - /** - * Set the default directory and write it to the settings file. - * - * @param defaultDirectory - */ - public void setDefaultDirectory(String defaultDirectory) { - this.defaultDirectory = defaultDirectory; - updateSettingsFile(); - } - /** * The Font is a configurable setting, where the user can specify a * font to be used throughout the application. @@ -134,6 +151,7 @@ public class Settings { * settings file. */ private void defaultSettings() { + defaultDirectory = appDirectory + "puzzles"; newFileProperties = 0; // Load the font from the system directory. @@ -154,8 +172,8 @@ public class Settings { * to populate the Settings file. */ private void readSettings() { - File settingsFile = new File(defaultDirectory + "/settings"); - if (!settingsFile.exists()) { + File settingsFile = new File(appDirectory + "/settings.json"); + if (!settingsFile.exists() && !settingsFile.isDirectory()) { defaultSettings(); return; } @@ -172,7 +190,9 @@ public class Settings { // Load the font from the system directory. String fontFilepath; if(os.startsWith("windows")) - fontFilepath = System.getProperty("user.home") + "\\AppData\\Local\\Microsoft\\Windows\\Fonts\\" + fontName + ".ttf"; + fontFilepath = System.getProperty("user.home") + + "\\AppData\\Local\\Microsoft\\Windows\\Fonts\\" + + fontName + ".ttf"; else if(os.startsWith("mac")) fontFilepath = "/Library/Fonts/" + fontName + ".ttf"; else @@ -180,13 +200,20 @@ public class Settings { // Register the font with the GraphicsEnvironment. try { - GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); - ge.registerFont(Font.createFont(Font.TRUETYPE_FONT, new File(fontFilepath))); + GraphicsEnvironment ge = GraphicsEnvironment. + getLocalGraphicsEnvironment(); + + ge.registerFont(Font.createFont( + Font.TRUETYPE_FONT, + new File(fontFilepath) + )); + return 0; } catch(IOException | FontFormatException e) { - System.out.println("Filepath of font not found: " + fontFilepath + " does not exist."); + System.out.println("Filepath of font not found: " + + fontFilepath + " does not exist."); return 1; } } -} +} \ No newline at end of file