documentation and formatting to 80 character width.

This commit is contained in:
Josh Ashton
2024-03-21 14:50:40 -06:00
parent ac1464a9fb
commit 74346a761c
7 changed files with 176 additions and 113 deletions
View File
View File
+13 -6
View File
@@ -8,7 +8,8 @@ import java.awt.BorderLayout;
import java.awt.Dimension; 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 { public class App extends JFrame {
// GUI fields // GUI fields
@@ -33,7 +34,8 @@ public class App extends JFrame {
* Basic JFrame settings and setup for the GUI. * Basic JFrame settings and setup for the GUI.
*/ */
private void initSetup() { 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); setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout()); setLayout(new BorderLayout());
setPreferredSize(new Dimension(750, 550)); setPreferredSize(new Dimension(750, 550));
@@ -42,9 +44,13 @@ public class App extends JFrame {
setLocationRelativeTo(null); setLocationRelativeTo(null);
try { try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); UIManager.setLookAndFeel(
UIManager.getCrossPlatformLookAndFeelClassName()
);
} catch (Exception e) { } 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) { public static void main(String[] args) {
if(args.length == 0) if(args.length == 0)
new App().setVisible(true); 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(); cli();
else else
new App().setVisible(true); new App().setVisible(true);
} }
} }
+29 -11
View File
@@ -33,7 +33,7 @@ public class Nav extends JPanel {
* Create a new Nav in a command-line interface. This is intended for * Create a new Nav in a command-line interface. This is intended for
* debugging rather than practical use. * 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. * file I/O behavior is dictated by the values in the Settings object.
*/ */
public Nav() { public Nav() {
@@ -61,6 +61,8 @@ public class Nav extends JPanel {
super(); super();
this.s = s; 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) { if(cli) {
System.out.println("Welcome to the Sudoku Solver!"); System.out.println("Welcome to the Sudoku Solver!");
@@ -73,10 +75,10 @@ public class Nav extends JPanel {
in.close(); in.close();
openFile(inputFilename); openFile(inputFilename);
} else {
// TODO: Create the GUI for the Nav bar. // Otherwise, create the GUI for the Nav bar.
createGUI(); } else createGUI();
}
} }
/** /**
@@ -92,6 +94,8 @@ public class Nav extends JPanel {
JButton solve = new JButton("Solve"); JButton solve = new JButton("Solve");
solve.setFont(s.getFont()); // TODO: Make custom Button class. 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(createFileOptions());
add(elapsedTime); add(elapsedTime);
add(solve); add(solve);
@@ -103,12 +107,14 @@ public class Nav extends JPanel {
* @return ComboBox<String> * @return ComboBox<String>
*/ */
private ComboBox<String> createFileOptions() { private ComboBox<String> createFileOptions() {
// Create a new ComboBox with the default styling.
ComboBox<String> fileOptions = new ComboBox<>(s); ComboBox<String> fileOptions = new ComboBox<>(s);
fileOptions.addItem("New"); fileOptions.addItem("New");
fileOptions.addItem("Open"); fileOptions.addItem("Open");
fileOptions.addItem("Save"); fileOptions.addItem("Save");
fileOptions.addItem("Exit"); fileOptions.addItem("Exit");
// Add an ActionListener to the ComboBox to handle the user's selection.
fileOptions.addActionListener(e -> { fileOptions.addActionListener(e -> {
String selected = (String) fileOptions.getSelectedItem(); String selected = (String) fileOptions.getSelectedItem();
switch(selected) { 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. * Open an existing .sdku file in the user's file system, using a GUI.
*/ */
private void openFile() { 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()); 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); int result = fc.showOpenDialog(this);
// If the user selects a file, open it.
if(result == FileChooser.APPROVE_OPTION) { if(result == FileChooser.APPROVE_OPTION) {
f = fc.getSelectedFile(); f = fc.getSelectedFile();
createGrid(f); createGrid(f);
} else {
System.out.println("No file was selected.");
} }
} }
@@ -179,6 +185,7 @@ public class Nav extends JPanel {
*/ */
private void openFile(String filename) { private void openFile(String filename) {
File f = new File("resources/" + filename + ".sdku"); 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()) { if(f == null || !f.exists() || f.isDirectory() || !f.canRead()) {
System.out.println( System.out.println(
"The file " + filename + "The file " + filename +
@@ -192,6 +199,7 @@ public class Nav extends JPanel {
return; return;
} }
// Otherwise, open the file and populate the grid.
System.out.println("Opening the file " + filename + ".sdku."); System.out.println("Opening the file " + filename + ".sdku.");
createGrid(f); createGrid(f);
} }
@@ -241,21 +249,31 @@ public class Nav extends JPanel {
*/ */
private void createGrid(File f) { private void createGrid(File f) {
try (Scanner in = new Scanner(f)) { try (Scanner in = new Scanner(f)) {
// Create a new 9x9 grid of Cells.
Cell[][] grid = new Cell[9][9]; Cell[][] grid = new Cell[9][9];
// Read in the file line by line.
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {
// Read in the line and create a new Cell for each character.
String line = in.nextLine(); String line = in.nextLine();
for(int j = 0; j < 9; j++) { for(int j = 0; j < 9; j++) {
// If the character is a period, the cell is empty.
if(line.charAt(j) == '.') { if(line.charAt(j) == '.') {
grid[i][j] = new Cell(i, j, 0); grid[i][j] = new Cell(i, j, 0);
continue; continue;
} }
// Otherwise, the character is a digit.
int value = Character.getNumericValue(line.charAt(j)); int value = Character.getNumericValue(line.charAt(j));
grid[i][j] = new Cell(i, j, value); grid[i][j] = new Cell(i, j, value);
} }
} }
this.grid = grid; this.grid = grid;
// If the file is not found, print an error message and return.
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
String filename = f.getName(); String filename = f.getName();
System.err.println("An error occurred while reading the file " + System.err.println("An error occurred while reading the file " +
+65 -39
View File
@@ -1,35 +1,40 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList; 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 { public class SudokuChecker {
private Cell[][] grid; private Cell[][] grid;
/** /**
* Create a new SudokuChecker object, initializing the grid to the given 9x9 grid of numbers. * Create a new SudokuChecker object, initializing the grid to the given
* This should either check each cell as the user inputs a value, or be used to check the validity of a * 9x9 grid of numbers. This should either check each cell as the user
* puzzle when the user requests it. * 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 * @param grid
*/ */
public SudokuChecker(Cell[][] grid) { public SudokuChecker(Cell[][] grid) {
this.grid = grid; this.grid = grid;
solve(); 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()); * Get the solution to the Sudoku puzzle.
} *
System.out.println(); * @return Cell[][]
} */
public Cell[][] getSolution() {
return grid;
} }
/** /**
@@ -39,7 +44,10 @@ public class SudokuChecker {
* @param b * @param b
* @return * @return
*/ */
private ArrayList<Integer> intersection(ArrayList<Integer> a, ArrayList<Integer> b) { private ArrayList<Integer> intersection(
ArrayList<Integer> a,
ArrayList<Integer> b
) {
ArrayList<Integer> intersection = new ArrayList<Integer>(); ArrayList<Integer> intersection = new ArrayList<Integer>();
for(int i = 0; i < a.size(); i++) { for(int i = 0; i < a.size(); i++) {
if(b.contains(a.get(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. * Determine what numbers are available to be placed in the given cell of
* This is effectively an intersection of the numbers available in the row, column, and box of the cell. * the grid. This is effectively an intersection of the numbers available
* in the row, column, and box of the cell.
* *
* @param row * @param row
* @param col * @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) { private void getAvailableNumbers(int row, int col) {
ArrayList<Integer> intersection = intersection(getRowRemainingNumbers(row), getColRemainingNumbers(col)); ArrayList<Integer> intersection = intersection(
intersection = intersection(intersection, getBoxRemainingNumbers(row, col)); getRowRemainingNumbers(row),
getColRemainingNumbers(col)
);
intersection = intersection(
intersection,
getBoxRemainingNumbers(row, col)
);
if(intersection.size() == 0) { if(intersection.size() == 0) {
return; return;
@@ -77,13 +94,16 @@ public class SudokuChecker {
availableNumbers.add(intersection.get(i)); 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. * Update the possible values for cells in the same row, column, and box
* This should always be called once a cell's value has been set, to remove that value from the possible values of other cells. * 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 row
* @param col * @param col
@@ -112,9 +132,9 @@ public class SudokuChecker {
int boxCol = col / 3; int boxCol = col / 3;
for(int i = 0; i < 3; i++) { for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) { for(int j = 0; j < 3; j++) {
if(grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == 0) { if(grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == 0)
grid[boxRow * 3 + i][boxCol * 3 + j].removePossibleValue(value); 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. * 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 * @param list
* @return * @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 * @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. // Check that every cell has a value between 1 and 9.
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) { for(int j = 0; j < 9; j++) {
if(grid[i][j].getValue() < 1 || grid[i][j].getValue() > 9) { 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() + ".");
return false; return false;
}
} }
} }
@@ -283,7 +304,10 @@ public class SudokuChecker {
} }
} }
if(!isValidSet(box)) { 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; 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 * @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) { private boolean isValidSet(int[] set) {
boolean[] found = new boolean[9]; boolean[] found = new boolean[9];
@@ -311,4 +337,4 @@ public class SudokuChecker {
} }
return true; return true;
} }
} }
+3 -18
View File
@@ -11,22 +11,6 @@ import javax.swing.JFileChooser;
public class FileChooser extends JFileChooser { public class FileChooser extends JFileChooser {
private Settings s; 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 * Create a new FileChooser with the default directory set to the user's
* Sudoku directory. * Sudoku directory.
@@ -70,7 +54,8 @@ public class FileChooser extends JFileChooser {
public boolean accept(File f) { public boolean accept(File f) {
String name = f.getName().toLowerCase(); String name = f.getName().toLowerCase();
if(name.length() > 5) { 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; return false;
} }
@@ -79,4 +64,4 @@ public class FileChooser extends JFileChooser {
return "Sudoku Puzzle Files (*.sdku)"; return "Sudoku Puzzle Files (*.sdku)";
} }
} }
} }
+66 -39
View File
@@ -1,6 +1,5 @@
package gui; package gui;
import java.util.Scanner;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -12,13 +11,30 @@ import java.awt.FontFormatException;
* A class solely to store user settings relating to default behaviors, * A class solely to store user settings relating to default behaviors,
* difficulty, filepaths, and more. * 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 { public class Settings {
private final String os; 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 int newFileProperties;
private String defaultDirectory;
// The font to be used throughout the application.
private Font font; private Font font;
/** /**
@@ -33,11 +49,13 @@ public class Settings {
public Settings() { public Settings() {
os = System.getProperty("os.name").toLowerCase(); os = System.getProperty("os.name").toLowerCase();
if(os.startsWith("windows")) if(os.startsWith("windows"))
defaultDirectory = System.getProperty("user.home") + "\\AppData\\Local\\Sudoku\\"; appDirectory = System.getProperty("user.home") +
"\\AppData\\Local\\Sudoku\\";
else 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()) { if (!dir.exists() || !dir.isDirectory()) {
dir.mkdirs(); dir.mkdirs();
@@ -46,6 +64,31 @@ public class Settings {
readSettings(); 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 * New File Properties is a configurable setting, where the following
* values are allowable: * values are allowable:
@@ -69,32 +112,6 @@ public class Settings {
updateSettingsFile(); 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 * The Font is a configurable setting, where the user can specify a
* font to be used throughout the application. * font to be used throughout the application.
@@ -134,6 +151,7 @@ public class Settings {
* settings file. * settings file.
*/ */
private void defaultSettings() { private void defaultSettings() {
defaultDirectory = appDirectory + "puzzles";
newFileProperties = 0; newFileProperties = 0;
// Load the font from the system directory. // Load the font from the system directory.
@@ -154,8 +172,8 @@ public class Settings {
* to populate the Settings file. * to populate the Settings file.
*/ */
private void readSettings() { private void readSettings() {
File settingsFile = new File(defaultDirectory + "/settings"); File settingsFile = new File(appDirectory + "/settings.json");
if (!settingsFile.exists()) { if (!settingsFile.exists() && !settingsFile.isDirectory()) {
defaultSettings(); defaultSettings();
return; return;
} }
@@ -172,7 +190,9 @@ public class Settings {
// Load the font from the system directory. // Load the font from the system directory.
String fontFilepath; String fontFilepath;
if(os.startsWith("windows")) 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")) else if(os.startsWith("mac"))
fontFilepath = "/Library/Fonts/" + fontName + ".ttf"; fontFilepath = "/Library/Fonts/" + fontName + ".ttf";
else else
@@ -180,13 +200,20 @@ public class Settings {
// Register the font with the GraphicsEnvironment. // Register the font with the GraphicsEnvironment.
try { try {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsEnvironment ge = GraphicsEnvironment.
ge.registerFont(Font.createFont(Font.TRUETYPE_FONT, new File(fontFilepath))); getLocalGraphicsEnvironment();
ge.registerFont(Font.createFont(
Font.TRUETYPE_FONT,
new File(fontFilepath)
));
return 0; return 0;
} catch(IOException | FontFormatException e) { } 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; return 1;
} }
} }
} }