Merge pull request #5 from SLCC-Programming-Club/nav-gui-impl
GUI implementation
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
/bin/
|
||||
.classpath
|
||||
.project
|
||||
+49
-20
@@ -1,29 +1,44 @@
|
||||
// GUI imports
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
|
||||
// Project imports
|
||||
import gui.*;
|
||||
import gui.backend.*;
|
||||
|
||||
// Unused imports
|
||||
//import java.awt.event.KeyEvent;
|
||||
//import java.awt.event.KeyListener;
|
||||
|
||||
/**
|
||||
* 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
|
||||
private Nav nav;
|
||||
private Board board;
|
||||
|
||||
// Data fields
|
||||
private Settings settings;
|
||||
private SudokuChecker checker;
|
||||
private Settings s;
|
||||
private SudokuChecker sc;
|
||||
|
||||
/**
|
||||
* Create a new App object, initializing the GUI and internal logic.
|
||||
*/
|
||||
public App() {
|
||||
super("Sudoku");
|
||||
settings = new Settings();
|
||||
nav = new Nav(settings, false);
|
||||
s = new Settings();
|
||||
initSetup();
|
||||
nav = new Nav(s, false);
|
||||
sc = new SudokuChecker(nav.getLoadedGrid());
|
||||
board = new Board(s, nav.getLoadedGrid(), sc);
|
||||
|
||||
nav.setBoard(board);
|
||||
nav.setChecker(sc);
|
||||
|
||||
add(createApp());
|
||||
}
|
||||
|
||||
@@ -31,30 +46,43 @@ 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));
|
||||
setPreferredSize(s.getDimension());
|
||||
setSize(getPreferredSize());
|
||||
setResizable(false);
|
||||
setResizable(s.getResizable());
|
||||
setLocationRelativeTo(null);
|
||||
|
||||
// Set the look and feel for the app to be cross-platform.
|
||||
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 initSetup()."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the root JPanel holding all other GUI components.
|
||||
* Create the Root JPanel for the Sudoku App. This will contain all other
|
||||
* GUI components.
|
||||
*
|
||||
* @return JPanel
|
||||
* Settings must be passed since, in the future, the user may be able to
|
||||
* customize the layout of the app.
|
||||
*
|
||||
* @param settings
|
||||
* @return Root
|
||||
*/
|
||||
private JPanel createApp() {
|
||||
// TODO: Create the root JPanel holding all other GUI components.
|
||||
JPanel root = new JPanel();
|
||||
private Root createApp() {
|
||||
Root root = new Root(s);
|
||||
root.setLayout(new BoxLayout(root, BoxLayout.Y_AXIS));
|
||||
root.add(nav);
|
||||
root.add(board);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -63,7 +91,7 @@ public class App extends JFrame {
|
||||
*/
|
||||
public static void cli() {
|
||||
Nav nav = new Nav(new Settings(), true);
|
||||
SudokuChecker check = new SudokuChecker(nav.getLoadedGrid());
|
||||
new SudokuChecker(nav.getLoadedGrid());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +102,8 @@ 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);
|
||||
|
||||
-216
@@ -1,216 +0,0 @@
|
||||
import java.util.Scanner;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JButton;
|
||||
|
||||
/**
|
||||
* The Nav class represents the Navigation bar at the top of the JFrame window.
|
||||
*
|
||||
* The Nav class is responsible for basic File IO operations. The most basic
|
||||
* functionality is the following:
|
||||
* - creating a new, completely empty .sdku file for a user to create
|
||||
* their own puzzle with;
|
||||
* - open an existing .sdku file in the user's file system (a standard
|
||||
* or default location is TBD);
|
||||
* - save the currently open .sdku file with any changes the user has made.
|
||||
*
|
||||
* In the future, the Nav class would ideally incorporate additional elements,
|
||||
* such as the difficulty of the current problem, providing hints, displaying
|
||||
* the length of time since a puzzle was started, etc.
|
||||
*/
|
||||
public class Nav extends JPanel {
|
||||
private Settings s;
|
||||
private File f;
|
||||
private Cell[][] grid;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* file I/O behavior is dictated by the values in the Settings object.
|
||||
*/
|
||||
public Nav() {
|
||||
super();
|
||||
System.out.println("Welcome to the Sudoku Solver!");
|
||||
|
||||
Scanner in = new Scanner(System.in);
|
||||
System.out.print(
|
||||
"Enter the name of the input file (do not " +
|
||||
"include the file extension): "
|
||||
);
|
||||
String inputFilename = in.nextLine();
|
||||
in.close();
|
||||
|
||||
openFile(inputFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Nav object for use in the command-line. This is intended
|
||||
* for debugging rather than practical use.
|
||||
*
|
||||
* @param Settings
|
||||
*/
|
||||
public Nav(Settings s, boolean cli) {
|
||||
super();
|
||||
this.s = s;
|
||||
|
||||
if(cli) {
|
||||
System.out.println("Welcome to the Sudoku Solver!");
|
||||
|
||||
Scanner in = new Scanner(System.in);
|
||||
System.out.print(
|
||||
"Enter the name of the input file (do not " +
|
||||
"include the file extension): "
|
||||
);
|
||||
String inputFilename = in.nextLine();
|
||||
in.close();
|
||||
|
||||
openFile(inputFilename);
|
||||
} else {
|
||||
// TODO: Create the GUI for the Nav bar.
|
||||
createGUI();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the GUI for the Nav bar.
|
||||
*
|
||||
* This method is called when the Nav object is created in the App
|
||||
* class if the program is not being run in the command-line for
|
||||
* debugging purposes.
|
||||
*/
|
||||
private void createGUI() {
|
||||
JComboBox<String> fileOptions = new JComboBox<String>();
|
||||
fileOptions.addItem("New");
|
||||
fileOptions.addItem("Open");
|
||||
fileOptions.addItem("Save");
|
||||
fileOptions.addItem("Exit");
|
||||
|
||||
JLabel elapsedTime = new JLabel("Elapsed Time: 00:00:00");
|
||||
|
||||
JButton solve = new JButton("Solve");
|
||||
|
||||
add(fileOptions);
|
||||
add(elapsedTime);
|
||||
add(solve);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the user's selection from the JComboBox, perform the appropriate
|
||||
* action.
|
||||
*
|
||||
* @param String
|
||||
*/
|
||||
private void newFile() {
|
||||
int defaultOption = s.getNewFileProperties();
|
||||
|
||||
// See Settings.getNewFileProperties for possible values and
|
||||
// the expected behavior of each value.
|
||||
switch(defaultOption) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
default:
|
||||
}
|
||||
// TODO: Create a new .sdku file in the default directory.
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an input filename, open the file and populate the grid.
|
||||
*
|
||||
* @param String
|
||||
*/
|
||||
private void openFile(String filename) {
|
||||
File f = new File("resources/" + filename + ".sdku");
|
||||
if(f == null || !f.exists() || f.isDirectory() || !f.canRead()) {
|
||||
System.out.println(
|
||||
"The file " + filename +
|
||||
".sdku does not exist or cannot be read."
|
||||
);
|
||||
|
||||
System.out.println("The exact path to the file is: " +
|
||||
f.getAbsolutePath()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
createGrid(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the currently open .sdku file with any changes the user has made.
|
||||
* This needs to pull the current state of the grid from the Board JPanel.
|
||||
*/
|
||||
private void saveFile() {
|
||||
// TODO: Get the current Cell[][] from the Board JPanel
|
||||
// TODO: Write to a file.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently loaded Cell[][] grid.
|
||||
*
|
||||
* @return the grid
|
||||
*/
|
||||
public Cell[][] getLoadedGrid() {
|
||||
return grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the loaded grid with the current version with any changes the
|
||||
* user has made.
|
||||
*
|
||||
* For future auto-save functionality, this could simply be called after
|
||||
* each change the user makes on the Sudoku board.
|
||||
*
|
||||
* In the future, an optimization could be made where a different data
|
||||
* structure is used to pass only the changes the player has made within
|
||||
* the last x minutes, for eg. {row, col, value, noteValues[]}. Given
|
||||
* the size of a standard Sudoku board however, this may be an irrelevant
|
||||
* and unnecessary optimization.
|
||||
*
|
||||
* @param Cell[][]
|
||||
*/
|
||||
public void updateLoadedGrid(Cell[][] grid) {
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when a File is opened or created. This will populate the
|
||||
* Cell[][] grid with values read in from the file.
|
||||
*
|
||||
* @param File
|
||||
*/
|
||||
private void createGrid(File f) {
|
||||
try (Scanner in = new Scanner(f)) {
|
||||
Cell[][] grid = new Cell[9][9];
|
||||
for(int i = 0; i < 9; i++) {
|
||||
String line = in.nextLine();
|
||||
for(int j = 0; j < 9; j++) {
|
||||
if(line.charAt(j) == '.') {
|
||||
grid[i][j] = new Cell(i, j, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
int value = Character.getNumericValue(line.charAt(j));
|
||||
grid[i][j] = new Cell(i, j, value);
|
||||
}
|
||||
}
|
||||
|
||||
this.grid = grid;
|
||||
} catch (FileNotFoundException e) {
|
||||
String filename = f.getName();
|
||||
System.err.println("An error occurred while reading the file " +
|
||||
filename + ".sdku."
|
||||
);
|
||||
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import java.util.Scanner;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
public class Settings {
|
||||
private int newFileProperties;
|
||||
private String defaultDirectory;
|
||||
|
||||
/**
|
||||
* Create a new Settings object, initializing the default settings
|
||||
* or reading in the settings from a file if it exists.
|
||||
*/
|
||||
public Settings() {
|
||||
// TODO: Read in a file from the master default directory and populate
|
||||
// settings, effectively toggles. If the file does not exist,
|
||||
// populate it with the following values (possibly in .json).
|
||||
newFileProperties = 0;
|
||||
defaultDirectory = "~/AppData/Local/sudoku/";
|
||||
}
|
||||
|
||||
/**
|
||||
* New File Properties is a configurable setting, where the following
|
||||
* values are allowable:
|
||||
* 0 - Load the last opened .sdku file, default behavior
|
||||
* 1 - Create a new, blank .sdku file
|
||||
* 2 - Create a new, random .sdku file
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public int getNewFileProperties() {
|
||||
return newFileProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 value will be "~/.config/sudoku/" on MacOS and Linux.
|
||||
* On Windows, the value will be "~/AppData/Local/sudoku/".
|
||||
*
|
||||
* NOTE: This does not change the location that the settings file may
|
||||
* be located in. This must be located at {TBD}.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getDefaultDirectory() {
|
||||
return defaultDirectory;
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
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 also responsible for solving a given Sudoku puzzle.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given two arrays of numbers, return the intersection of the two arrays.
|
||||
*
|
||||
* @param a
|
||||
* @param b
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> intersection(ArrayList<Integer> a, ArrayList<Integer> b) {
|
||||
ArrayList<Integer> intersection = new ArrayList<Integer>();
|
||||
for(int i = 0; i < a.size(); i++) {
|
||||
if(b.contains(a.get(i))) {
|
||||
intersection.add(a.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
return intersection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private void getAvailableNumbers(int row, int col) {
|
||||
ArrayList<Integer> intersection = intersection(getRowRemainingNumbers(row), getColRemainingNumbers(col));
|
||||
intersection = intersection(intersection, getBoxRemainingNumbers(row, col));
|
||||
|
||||
if(intersection.size() == 0) {
|
||||
return;
|
||||
} else if(intersection.size() == 1) {
|
||||
int value = intersection.get(0);
|
||||
|
||||
grid[row][col].setValue(value);
|
||||
updatePossibleValues(row, col);
|
||||
return;
|
||||
} else {
|
||||
// Join the arrays and find the intersection of the three arrays.
|
||||
ArrayList<Integer> availableNumbers = new ArrayList<Integer>();
|
||||
for(int i = 0; i < intersection.size(); i++) {
|
||||
availableNumbers.add(intersection.get(i));
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param row
|
||||
* @param col
|
||||
*/
|
||||
private void updatePossibleValues(int row, int col) {
|
||||
int value = grid[row][col].getValue();
|
||||
|
||||
for(int i = 0; i < 9; i++) {
|
||||
if(grid[row][i].getValue() == 0) {
|
||||
grid[row][i].removePossibleValue(value);
|
||||
if(grid[row][i].getPossibleValues().length == 1) {
|
||||
grid[row][i].setValue(grid[row][i].getPossibleValues()[0]);
|
||||
updatePossibleValues(row, i);
|
||||
}
|
||||
}
|
||||
if(grid[i][col].getValue() == 0) {
|
||||
grid[i][col].removePossibleValue(value);
|
||||
if(grid[i][col].getPossibleValues().length == 1) {
|
||||
grid[i][col].setValue(grid[i][col].getPossibleValues()[0]);
|
||||
updatePossibleValues(i, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int boxRow = row / 3;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Solve the Sudoku puzzle.
|
||||
*/
|
||||
public void solve() {
|
||||
// Continue until a valid solution is reached.
|
||||
while(!isValidSolution()) {
|
||||
for(int i = 0; i < 9; i++) {
|
||||
for(int j = 0; j < 9; j++) {
|
||||
if(grid[i][j].getValue() == 0) {
|
||||
getAvailableNumbers(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/**
|
||||
* Get any number between 1 and 9 that is not in the row.
|
||||
*
|
||||
* @param row
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> getRowRemainingNumbers(int row) {
|
||||
ArrayList<Integer> remainingNumbers = new ArrayList<Integer>();
|
||||
for(int i = 1; i <= 9; i++) {
|
||||
boolean found = false;
|
||||
for(int j = 0; j < 9; j++) {
|
||||
if(grid[row][j].getValue() == i) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
remainingNumbers.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
return remainingNumbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any number between 1 and 9 that is not in the column.
|
||||
*
|
||||
* @param col
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> getColRemainingNumbers(int col) {
|
||||
ArrayList<Integer> remainingNumbers = new ArrayList<Integer>();
|
||||
for(int i = 1; i <= 9; i++) {
|
||||
boolean found = false;
|
||||
for(int j = 0; j < 9; j++) {
|
||||
if(grid[j][col].getValue() == i) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
remainingNumbers.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
return remainingNumbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any number between 1 and 9 that is not in the box.
|
||||
*
|
||||
* A box is a 3x3 subgrid of the 9x9 grid.
|
||||
*
|
||||
* @param box
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> getBoxRemainingNumbers(int row, int col) {
|
||||
ArrayList<Integer> remainingNumbers = new ArrayList<Integer>();
|
||||
int boxRow = row / 3;
|
||||
int boxCol = col / 3;
|
||||
for(int i = 1; i <= 9; i++) {
|
||||
boolean found = false;
|
||||
for(int j = 0; j < 3; j++) {
|
||||
for(int k = 0; k < 3; k++) {
|
||||
if(grid[boxRow * 3 + j][boxCol * 3 + k].getValue() == i) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
remainingNumbers.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
return remainingNumbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param list
|
||||
* @return
|
||||
*/
|
||||
private int[] arrayListToArray(ArrayList<Integer> list) {
|
||||
int[] array = new int[list.size()];
|
||||
for(int i = 0; i < list.size(); i++) {
|
||||
array[i] = list.get(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return true if the grid is a valid Sudoku puzzle, and false otherwise
|
||||
*/
|
||||
private boolean isValidSolution() {
|
||||
// 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() + ".");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that every row contains the numbers 1-9 exactly once.
|
||||
for(int i = 0; i < 9; i++) {
|
||||
int[] row = new int[9];
|
||||
for(int j = 0; j < 9; j++) {
|
||||
row[j] = grid[i][j].getValue();
|
||||
}
|
||||
if(!isValidSet(row)) {
|
||||
System.out.println("Row " + i + " is invalid.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that every column contains the numbers 1-9 exactly once.
|
||||
for(int i = 0; i < 9; i++) {
|
||||
int[] col = new int[9];
|
||||
for(int j = 0; j < 9; j++) {
|
||||
col[j] = grid[j][i].getValue();
|
||||
}
|
||||
if(!isValidSet(col)) {
|
||||
System.out.println("Column " + i + " is invalid.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that every 3x3 subgrid contains the numbers 1-9 exactly once.
|
||||
for(int i = 0; i < 3; i++) {
|
||||
for(int j = 0; j < 3; j++) {
|
||||
int[] box = new int[9];
|
||||
for(int k = 0; k < 3; k++) {
|
||||
for(int l = 0; l < 3; l++) {
|
||||
box[k * 3 + l] = grid[i * 3 + k][j * 3 + l].getValue();
|
||||
}
|
||||
}
|
||||
if(!isValidSet(box)) {
|
||||
System.out.println("Box at row " + i + " and column " + j + " is invalid.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private boolean isValidSet(int[] set) {
|
||||
boolean[] found = new boolean[9];
|
||||
for(int i = 0; i < 9; i++) {
|
||||
if(set[i] < 1 || set[i] > 9) {
|
||||
return false;
|
||||
} else if(found[set[i] - 1]) {
|
||||
return false;
|
||||
} else {
|
||||
found[set[i] - 1] = true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,323 @@
|
||||
package gui;
|
||||
|
||||
// GUI imports
|
||||
import javax.swing.JPanel;
|
||||
import java.awt.GridLayout;
|
||||
|
||||
// Event & action imports
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
|
||||
// Processing & backend imports
|
||||
import gui.backend.Cell;
|
||||
import gui.backend.Settings;
|
||||
import gui.backend.SudokuChecker;
|
||||
|
||||
/**
|
||||
* The Board class represents the Sudoku board in the GUI.
|
||||
*
|
||||
* It contains all other related GUI components, and is the main JPanel for
|
||||
* the board. Accessible information from the Board includes:
|
||||
* - the current state of the board, including filled values and notes;
|
||||
*
|
||||
* TODO: Future features:
|
||||
* - the current difficulty;
|
||||
* - the current time since the puzzle was started;
|
||||
* - the current hints available; and
|
||||
* - the current number of mistakes.
|
||||
*/
|
||||
public class Board extends JPanel {
|
||||
private Settings s;
|
||||
private Cell[][] grid;
|
||||
private Cell[][] solvedGrid;
|
||||
private CellGUI[][] gridGUI;
|
||||
private SudokuChecker sc;
|
||||
private CellGUI selected;
|
||||
|
||||
/**
|
||||
* Create a new Board object.
|
||||
*
|
||||
* Board requires a Settings object for styling, sizing, and for different
|
||||
* toggleable features.
|
||||
*
|
||||
* A 2D Cell array is required to abstract the data away from the Board,
|
||||
* and the GUI in general.
|
||||
*
|
||||
* @param s
|
||||
* @param grid
|
||||
*/
|
||||
public Board(Settings s, Cell[][] grid) {
|
||||
super(new GridLayout(9, 9));
|
||||
this.s = s;
|
||||
this.grid = grid;
|
||||
solvedGrid = new SudokuChecker(Cell.copyGrid(grid)).getSolution();
|
||||
|
||||
style();
|
||||
createBoard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Board object.
|
||||
*
|
||||
* Board requires a Settings object for styling, sizing, and for different
|
||||
* toggleable features.
|
||||
*
|
||||
* A 2D Cell array is required to abstract the data away from the Board,
|
||||
* and the GUI in general.
|
||||
*
|
||||
* A SudokuChecker is required if Settings.getAutoCheckValues() is true.
|
||||
*
|
||||
* @param s
|
||||
* @param grid
|
||||
* @param sc
|
||||
*/
|
||||
public Board(Settings s, Cell[][] grid, SudokuChecker sc) {
|
||||
super(new GridLayout(9, 9));
|
||||
this.s = s;
|
||||
this.grid = grid;
|
||||
this.sc = sc;
|
||||
solvedGrid = new SudokuChecker(Cell.copyGrid(grid)).getSolution();
|
||||
|
||||
style();
|
||||
createBoard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the grid of the Board.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Cell[][] getGrid() {
|
||||
return grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the grid of the Board to the given grid.
|
||||
*
|
||||
* Intended to be used when loading a new puzzle.
|
||||
*
|
||||
* @param grid
|
||||
*/
|
||||
public void setGrid(Cell[][] grid) {
|
||||
this.grid = grid;
|
||||
gridGUI = null;
|
||||
selected = null;
|
||||
|
||||
removeAll();
|
||||
createBoard();
|
||||
repaint();
|
||||
revalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the Board Panel with the appropriate styling.
|
||||
*/
|
||||
private void style() {
|
||||
setBackground(s.getTheme().getPrimaryBackground());
|
||||
setForeground(s.getTheme().getPrimaryText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Board Panel with the appropriate cells.
|
||||
*
|
||||
* Every CellGUI object is created with the appropriate Cell object and
|
||||
* Settings object. The CellGUI objects are then added to the Board Panel.
|
||||
*
|
||||
* Each CellGUI object is also given a MouseListener to handle user input,
|
||||
* and a KeyListener to handle keyboard input if/when the cell is selected.
|
||||
*/
|
||||
private void createBoard() {
|
||||
if(s.getAutoFillNotes()) grid = sc.getPossibleValues(grid);
|
||||
gridGUI = new CellGUI[9][9];
|
||||
|
||||
for(int i = 0; i < 9; i++) {
|
||||
for(int j = 0; j < 9; j++) {
|
||||
// Create a new CellGUI object with the appropriate Cell object.
|
||||
CellGUI cell = new CellGUI(grid[i][j], s);
|
||||
|
||||
// Add a MouseListener to the cell.
|
||||
cell.addMouseListener(createMouseListener(cell));
|
||||
|
||||
// Add the CellGUI object to the Board Panel.
|
||||
gridGUI[i][j] = cell;
|
||||
add(gridGUI[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the given cell and highlight all cells in the same row, column,
|
||||
* and box.
|
||||
*
|
||||
* Intended to be used in conjunction with a MouseListener.
|
||||
*
|
||||
* @param cell
|
||||
*/
|
||||
private void select(Cell cell) {
|
||||
// Select the CellGUI objects in the same row and column, but not in
|
||||
// the same box.
|
||||
for(int n = 0; n < 9; n++) {
|
||||
int row = cell.getRow();
|
||||
int col = cell.getCol();
|
||||
boolean colInSameBox =
|
||||
(gridGUI[n][col].cell.getBox() ==
|
||||
cell.getBox());
|
||||
|
||||
boolean rowInSameBox =
|
||||
(gridGUI[row][n].cell.getBox() ==
|
||||
cell.getBox());
|
||||
|
||||
// Handling edge cases where the column or row are in the box.
|
||||
if(colInSameBox && !rowInSameBox) {
|
||||
gridGUI[row][n].select();
|
||||
continue;
|
||||
} else if(!colInSameBox && rowInSameBox) {
|
||||
gridGUI[n][col].select();
|
||||
continue;
|
||||
|
||||
// Skip the cell if it is in the same box.
|
||||
} else if(colInSameBox && rowInSameBox) continue;
|
||||
|
||||
gridGUI[n][col].select();
|
||||
gridGUI[row][n].select();
|
||||
}
|
||||
|
||||
// Select every cell in the box.
|
||||
int boxRow = cell.getRow() / 3;
|
||||
int boxCol = cell.getCol() / 3;
|
||||
for(int n = 0; n < 3; n++) {
|
||||
for(int m = 0; m < 3; m++) {
|
||||
int row = boxRow * 3 + n;
|
||||
int col = boxCol * 3 + m;
|
||||
|
||||
// Skip the user-selected cell for clearer highlighting.
|
||||
if(row == cell.getRow() && col == cell.getCol()) continue;
|
||||
|
||||
gridGUI[row][col].select();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MouseListener for CellGUI objects.
|
||||
*
|
||||
* This MouseListener listens for mouse clicks, and selects the cell
|
||||
* if it is clicked. If the cell is already selected, it is deselected.
|
||||
*
|
||||
* Additionally, adds hover highlighting for the cell.
|
||||
*
|
||||
* mouseClicked() is implemented for cell selection functionality.
|
||||
* mouseEntered() and mouseExited() are implemented for hover functionality.
|
||||
* Other methods are not implemented.
|
||||
*
|
||||
* @param cell
|
||||
* @return MouseListener
|
||||
*/
|
||||
private MouseListener createMouseListener(CellGUI cell) {
|
||||
return new MouseListener() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
// Left click
|
||||
if(e.getButton() == 1) {
|
||||
// Deselect the already selected cell.
|
||||
if(selected != null) select(selected.cell);
|
||||
|
||||
// Deselect the cell if it is already selected.
|
||||
if(selected == cell) {
|
||||
selected = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the cell and add a KeyListener for input.
|
||||
select(cell.cell);
|
||||
selected = cell;
|
||||
selected.requestFocusInWindow();
|
||||
selected.addKeyListener(createKeyListener());
|
||||
|
||||
// Right click
|
||||
} else if(e.getButton() == 3) {
|
||||
cell.setNoteMode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {}
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {}
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
cell.select();
|
||||
}
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
cell.select();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a KeyListener for CellGUI objects.
|
||||
*
|
||||
* This KeyListener listens for key releases, and updates the
|
||||
* selected cell with additional possible values or a new value.
|
||||
*
|
||||
* Only keyReleased is implemented, as keyTyped does not allow for
|
||||
* certain features regarding key codes.
|
||||
*
|
||||
* @return KeyListener
|
||||
*/
|
||||
private KeyListener createKeyListener() {
|
||||
return new KeyListener() {
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e) {}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
// If a CellGUI is not selected, do nothing.
|
||||
if(selected == null) return;
|
||||
|
||||
char key = e.getKeyChar();
|
||||
Cell c = selected.cell;
|
||||
|
||||
// If the Cell is an initial value read from the starting file
|
||||
// then skip it.
|
||||
if(c.isInitValue()) return;
|
||||
|
||||
// Backspace support for removing values.
|
||||
if(e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
|
||||
selected.removeValue();
|
||||
selected.repaint();
|
||||
selected.revalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only allow digits to be entered.
|
||||
if(!Character.isDigit(key)) return;
|
||||
|
||||
// Enter the value from the keyboard.
|
||||
if(!selected.isInNotesMode()) {
|
||||
|
||||
// Set the value of the cell and check if it is correct.
|
||||
if(s.getAutoCheckValues()) {
|
||||
int row = c.getRow();
|
||||
int col = c.getCol();
|
||||
int expected = solvedGrid[row][col].getValue();
|
||||
selected.setValue(Character.getNumericValue(key), expected);
|
||||
|
||||
// Set the value of the cell.
|
||||
} else selected.setValue(Character.getNumericValue(key));
|
||||
|
||||
// Add the possible value to the cell.
|
||||
} else selected.addPossibleValue(Character.getNumericValue(key));
|
||||
|
||||
selected.repaint();
|
||||
selected.revalidate();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package gui;
|
||||
|
||||
// GUI imports
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.LineBorder;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridLayout;
|
||||
|
||||
// Processing & backend imports
|
||||
import gui.backend.Cell;
|
||||
import gui.backend.Settings;
|
||||
import gui.backend.Theme;
|
||||
|
||||
/**
|
||||
* GUI representation of a single cell in the Sudoku grid.
|
||||
*/
|
||||
class CellGUI extends JPanel {
|
||||
// GUI fields
|
||||
private GridLayout noteLayout = new GridLayout(3, 3);
|
||||
private GridLayout valueLayout = new GridLayout(0, 1);
|
||||
private JPanel internalPanel = new JPanel(valueLayout);
|
||||
private JLabel valueLabel;
|
||||
private Label[] notesLabels = new Label[9];
|
||||
|
||||
// GUI data fields read from a Settings object.
|
||||
private Dimension size;
|
||||
private Theme theme;
|
||||
private Font font;
|
||||
|
||||
// Data fields
|
||||
Cell cell;
|
||||
private boolean noteMode; // true to display notes, or display value
|
||||
private boolean selected = false; // true if the cell is selected
|
||||
private boolean incorrect = false; // true if the cell has an invalid value
|
||||
|
||||
/**
|
||||
* Create a new CellGUI object with the given single cell.
|
||||
*
|
||||
* Start blank cells in notes mode if startInNotesOrValueMode is true.
|
||||
*
|
||||
* @param cell
|
||||
* @param startInNotesOrValueMode
|
||||
*/
|
||||
public CellGUI(Cell cell, Settings s) {
|
||||
super();
|
||||
this.cell = cell;
|
||||
|
||||
// Set the initial state of the cell based off the settings.
|
||||
noteMode = s.getCellGUIStartMode();
|
||||
theme = s.getTheme();
|
||||
font = s.getFont();
|
||||
selected = false;
|
||||
size = s.getCellDimensions();
|
||||
|
||||
// Set the layout and size of the cell.
|
||||
setFocusable(true);
|
||||
setLayout(valueLayout);
|
||||
valueLabel = new Label("", theme, font);
|
||||
setSize(size);
|
||||
internalPanel.setSize(size);
|
||||
defaultStyle();
|
||||
|
||||
// Populate the cell with the appropriate value or notes.
|
||||
if(cell.getValue() == 0) {
|
||||
internalPanel.setLayout(noteLayout);
|
||||
generateNotes(true);
|
||||
} else {
|
||||
internalPanel.setLayout(valueLayout);
|
||||
valueLabel.setText(Integer.toString(cell.getValue()));
|
||||
internalPanel.add(valueLabel);
|
||||
}
|
||||
add(internalPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the underlying Cell object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public int getValue() {
|
||||
return cell.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the underlying Cell object.
|
||||
*
|
||||
* This implementation is to be used when Settings.getAutoCheckValues
|
||||
* is false. It will assume that every value the user enters is incorrect,
|
||||
* and thus the underlying List of possible values is never cleared.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setValue(int value) {
|
||||
if(!selected) return;
|
||||
|
||||
cell.setValue(value, false);
|
||||
// Update the GUI with the actual Cell value (unchanged if invalid)
|
||||
valueLabel.setText(Integer.toString(cell.getValue()));
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the underlying Cell object.
|
||||
*
|
||||
* This implementation is to be used when Settings.getAutoCheckValues
|
||||
* is true. It allows for incorrect values to be highlighted. And the
|
||||
* underlying List of possible values to be cleared if the entered
|
||||
* value is correct.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setValue(int value, int expected) {
|
||||
if(!selected) return;
|
||||
|
||||
cell.setValue(value, value == expected);
|
||||
valueLabel.setText(Integer.toString(cell.getValue()));
|
||||
|
||||
if(value != expected) {
|
||||
incorrect = true;
|
||||
valueLabel.setText(Integer.toString(value));
|
||||
errorStyle();
|
||||
} else refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the value of the underlying Cell object.
|
||||
*/
|
||||
public void removeValue() {
|
||||
if(!selected) return;
|
||||
|
||||
cell.setValue(0, false);
|
||||
if(!cell.isInitValue())
|
||||
valueLabel.setText("");
|
||||
|
||||
incorrect = false;
|
||||
highlightedStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a possible value to the cell.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void addPossibleValue(int value) {
|
||||
if(!selected) return;
|
||||
|
||||
cell.addPossibleValue(value);
|
||||
internalPanel.removeAll();
|
||||
internalPanel.setLayout(noteLayout);
|
||||
noteMode = true;
|
||||
generateNotes(true);
|
||||
highlightedStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a possible value from the cell.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public void removePossibleValue(int value) {
|
||||
cell.removePossibleValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of the cell in notes mode.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isInNotesMode() {
|
||||
return noteMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the mode of the cell between value and note, including the
|
||||
* layout and visual content of the cell.
|
||||
*/
|
||||
public void setNoteMode() {
|
||||
if(cell.isInitValue()) return;
|
||||
|
||||
if(noteMode) {
|
||||
internalPanel.removeAll();
|
||||
internalPanel.setLayout(valueLayout);
|
||||
internalPanel.add(valueLabel);
|
||||
} else {
|
||||
internalPanel.setLayout(noteLayout);
|
||||
generateNotes(true);
|
||||
}
|
||||
|
||||
refresh();
|
||||
noteMode = !noteMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the cell, and style it for error, highlight, or default.
|
||||
*/
|
||||
public void select() {
|
||||
selected = !selected;
|
||||
if(incorrect) {
|
||||
errorStyle();
|
||||
return;
|
||||
} else if(!selected)
|
||||
defaultStyle();
|
||||
else highlightedStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the CellGUI with the primary colors from the Theme.
|
||||
*
|
||||
* @see Theme.java
|
||||
*/
|
||||
private void defaultStyle() {
|
||||
setBackground(theme.getPrimaryBackground());
|
||||
setForeground(theme.getPrimaryText());
|
||||
internalPanel.setBackground(theme.getPrimaryBackground());
|
||||
internalPanel.setForeground(theme.getPrimaryText());
|
||||
valueLabel.setForeground(theme.getPrimaryText());
|
||||
setBorder(new LineBorder(theme.getPrimaryBorder(), 2));
|
||||
|
||||
for(int i = 0; i < 9; i++) {
|
||||
if(notesLabels[i] != null) {
|
||||
notesLabels[i].setBackground(theme.getPrimaryBackground());
|
||||
notesLabels[i].setForeground(theme.getPrimaryText());
|
||||
}
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the CellGUI with the secondary colors from the Theme.
|
||||
*
|
||||
* @see Theme.java
|
||||
*/
|
||||
private void highlightedStyle() {
|
||||
setBackground(theme.getSecondaryBackground());
|
||||
setForeground(theme.getSecondaryText());
|
||||
internalPanel.setBackground(theme.getSecondaryBackground());
|
||||
internalPanel.setForeground(theme.getSecondaryText());
|
||||
valueLabel.setForeground(theme.getSecondaryText());
|
||||
setBorder(new LineBorder(theme.getSecondaryBorder(), 2));
|
||||
|
||||
for(int i = 0; i < 9; i++) {
|
||||
if(notesLabels[i] != null) {
|
||||
notesLabels[i].setBackground(theme.getSecondaryBackground());
|
||||
notesLabels[i].setForeground(theme.getSecondaryText());
|
||||
}
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the CellGUI with the error colors from the Theme.
|
||||
*
|
||||
* @see Theme.java
|
||||
*/
|
||||
private void errorStyle() {
|
||||
valueLabel.setForeground(theme.getErrorText());
|
||||
internalPanel.setBackground(theme.getErrorBackground());
|
||||
setBorder(new LineBorder(theme.getErrorBorder(), 2));
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to repaint and revalidate the internal panel and CellGUI.
|
||||
*/
|
||||
private void refresh() {
|
||||
internalPanel.repaint();
|
||||
internalPanel.revalidate();
|
||||
repaint();
|
||||
revalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the GUI components for the cell's notes.
|
||||
*
|
||||
* If autoFill is true, then the CellGUI will automatically switch to
|
||||
* noteMode.
|
||||
*
|
||||
* @param autoFill
|
||||
*/
|
||||
private void generateNotes(boolean autoFill) {
|
||||
int[] possibleValues = cell.getPossibleValues();
|
||||
|
||||
// Prepare the internalPanel for noteMode.
|
||||
if(autoFill) internalPanel.removeAll();
|
||||
|
||||
// Handle cases where there are no possible values stored.
|
||||
if(possibleValues.length == 0) {
|
||||
for(int i = 0; i < 9; i++) {
|
||||
notesLabels[i] = new Label("", theme, font);
|
||||
|
||||
if(autoFill) internalPanel.add(notesLabels[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
// Add the noted possible values to the cell.
|
||||
for(int i = 1; i <= 9 && index < possibleValues.length; i++) {
|
||||
// Add the possible value if it exists.
|
||||
if(possibleValues[index] == i) {
|
||||
notesLabels[i - 1] = new Label(Integer.toString(i), theme, font);
|
||||
index++;
|
||||
|
||||
// Otherwise, add a blank value.
|
||||
} else
|
||||
notesLabels[i - 1] = new Label("", theme, font);
|
||||
|
||||
if(autoFill) internalPanel.add(notesLabels[i - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Font;
|
||||
|
||||
import javax.swing.JComboBox;
|
||||
|
||||
import gui.backend.Settings;
|
||||
import gui.backend.Theme;
|
||||
|
||||
/**
|
||||
* A custom JComboBox with the appropriate styling for the Sudoku app.
|
||||
*/
|
||||
public class ComboBox<E> extends JComboBox<E> {
|
||||
private Theme theme;
|
||||
private Font font;
|
||||
|
||||
/**
|
||||
* Create a new ComboBox with the default styling and without initial items.
|
||||
*/
|
||||
public ComboBox() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ComboBox(Settings s) {
|
||||
super();
|
||||
theme = s.getTheme();
|
||||
font = s.getFont();
|
||||
style();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the ComboBox with the appropriate styling.
|
||||
*/
|
||||
private void style() {
|
||||
setFont(font);
|
||||
setBackground(theme.getPrimaryBackground());
|
||||
setForeground(theme.getPrimaryText());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package gui;
|
||||
|
||||
// GUI imports
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
|
||||
// Processing & backend imports
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* A custom FileChooser with the appropriate styling for the Sudoku app.
|
||||
* Additionally, this FileChooser only allows .sdku files to be selected.
|
||||
*/
|
||||
public class FileChooser extends JFileChooser {
|
||||
/**
|
||||
* Create a new FileChooser with the default directory set to the user's
|
||||
* Sudoku directory.
|
||||
*
|
||||
* @param defaultDirectory
|
||||
* @param s
|
||||
*/
|
||||
public FileChooser(File defaultDirectory) {
|
||||
super(defaultDirectory);
|
||||
|
||||
for (File f: defaultDirectory.listFiles()) {
|
||||
ensureFileIsVisible(f);
|
||||
}
|
||||
|
||||
style();
|
||||
setupSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the FileChooser with the appropriate styling.
|
||||
*/
|
||||
private void style() {
|
||||
// TODO: Style the FileChooser.
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the FileChooser with the appropriate settings.
|
||||
*/
|
||||
private void setupSettings() {
|
||||
setFileHidingEnabled(false);
|
||||
setFileSelectionMode(JFileChooser.FILES_ONLY);
|
||||
setFileFilter(new Filter());
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom FileFilter to only allow .sdku files to be selected.
|
||||
*/
|
||||
private class Filter extends FileFilter {
|
||||
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 false;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return "Sudoku Puzzle Files (*.sdku)";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Font;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import gui.backend.Theme;
|
||||
|
||||
/**
|
||||
* The Note class represents a single note of a cell in the Sudoku board.
|
||||
*
|
||||
* This is the GUI abstraction for the underlying Cell object, and is used to
|
||||
* display and manage the note or value for a cell.
|
||||
*/
|
||||
public class Label extends JLabel {
|
||||
private Theme t;
|
||||
private Font f;
|
||||
|
||||
/**
|
||||
* Create a new Note object with the given text and theme.
|
||||
*
|
||||
* @param text
|
||||
* @param t
|
||||
*/
|
||||
public Label(String text, Theme t, Font f) {
|
||||
super(text, SwingConstants.CENTER);
|
||||
this.t = t;
|
||||
this.f = f;
|
||||
style();
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the Note component with the current theme.
|
||||
*/
|
||||
private void style() {
|
||||
setBackground(t.getPrimaryBackground());
|
||||
setForeground(t.getPrimaryText());
|
||||
setFont(f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
package gui;
|
||||
|
||||
// File IO imports
|
||||
import java.util.Scanner;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
// GUI imports
|
||||
import javax.swing.JPanel;
|
||||
|
||||
// Processing & backend imports
|
||||
import gui.backend.Cell;
|
||||
import gui.backend.Settings;
|
||||
import gui.backend.SudokuChecker;
|
||||
|
||||
// Unused imports
|
||||
//import javax.swing.JLabel;
|
||||
//import javax.swing.JButton;
|
||||
|
||||
/**
|
||||
* The Nav class represents the Navigation bar at the top of the JFrame window.
|
||||
*
|
||||
* The Nav class is responsible for basic File IO operations. The most basic
|
||||
* functionality is the following:
|
||||
* - open an existing .sdku file in the user's file system (a standard
|
||||
* or default location is TBD);
|
||||
*
|
||||
* NOTE: Needed features
|
||||
* - default open state from Settings
|
||||
* - creating a new, completely empty .sdku file for a user to create
|
||||
* their own puzzle with;
|
||||
* - save the currently open .sdku file with any changes the user has made;
|
||||
*
|
||||
* NOTE: Future features
|
||||
* - auto-save
|
||||
* - display elapsed time;
|
||||
* - open a settings GUI interface;
|
||||
* - display the number of mistakes made;
|
||||
* - display how many hints have been used;
|
||||
* - button to globally enable noteMode in CellGUI objects contained in
|
||||
* the Board; and
|
||||
* - toggleable number pad instead of using keyboard inputs.
|
||||
*/
|
||||
public class Nav extends JPanel {
|
||||
@SuppressWarnings("unused")
|
||||
private SudokuChecker sc;
|
||||
|
||||
private Settings s;
|
||||
|
||||
private File f;
|
||||
private Board b;
|
||||
private Cell[][] grid;
|
||||
|
||||
/**
|
||||
* Create a new Nav in a command-line interface. This is intended for
|
||||
* debugging rather than practical use.
|
||||
*
|
||||
* Additionally, even for debugging, this should not be used since default
|
||||
* file I/O behavior is dictated by the values in the Settings object.
|
||||
*
|
||||
* @Deprecated
|
||||
*/
|
||||
public Nav() {
|
||||
super();
|
||||
System.out.println("Welcome to the Sudoku Solver!");
|
||||
|
||||
Scanner in = new Scanner(System.in);
|
||||
System.out.print(
|
||||
"Enter the name of the input file (do not " +
|
||||
"include the file extension): "
|
||||
);
|
||||
String inputFilename = in.nextLine();
|
||||
in.close();
|
||||
|
||||
openFile(inputFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Nav object for use in the command-line. This is intended
|
||||
* for debugging rather than practical use.
|
||||
*
|
||||
* @param Settings
|
||||
*/
|
||||
public Nav(Settings s, boolean cli) {
|
||||
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!");
|
||||
|
||||
Scanner in = new Scanner(System.in);
|
||||
System.out.print(
|
||||
"Enter the name of the input file (do not " +
|
||||
"include the file extension): "
|
||||
);
|
||||
String inputFilename = in.nextLine();
|
||||
in.close();
|
||||
|
||||
openFile(inputFilename);
|
||||
|
||||
// Otherwise, create the GUI for the Nav bar.
|
||||
} else {
|
||||
createGUI();
|
||||
|
||||
// TODO: Need to implement the default file options in Settings.
|
||||
switch(s.getDefaultOpenState()) {
|
||||
case 0:
|
||||
openFile("input");
|
||||
break;
|
||||
case 1:
|
||||
openFile("medium");
|
||||
break;
|
||||
case 2:
|
||||
openFile("hard");
|
||||
break;
|
||||
default:
|
||||
System.out.println("Invalid default file option.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Board object for the Nav bar.
|
||||
*
|
||||
* This is necessary for the Nav bar to be able to interact with the
|
||||
* Board object and update the grid with file I/O operations. This
|
||||
* shouldn't be necessary to change once the program is running.
|
||||
*
|
||||
* @param b
|
||||
*/
|
||||
public void setBoard(Board b) {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SudokuChecker object for the Nav bar.
|
||||
*
|
||||
* This is necessary for the Nav bar to be able to interact with the
|
||||
* SudokuChecker object for the solve button.
|
||||
*
|
||||
* In the future, the SudokuChecker object is also used for hints.
|
||||
*
|
||||
* This shouldn't be necessary to change once the program is running.
|
||||
*/
|
||||
public void setChecker(SudokuChecker sc) {
|
||||
this.sc = sc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the Nav with the primary colors from Theme.
|
||||
*/
|
||||
private void style() {
|
||||
setBackground(s.getTheme().getPrimaryBackground());
|
||||
setForeground(s.getTheme().getPrimaryText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the GUI for the Nav bar.
|
||||
*
|
||||
* This method is called when the Nav object is created in the App
|
||||
* class and if the program is not being run in the command-line for
|
||||
* debugging purposes.
|
||||
*/
|
||||
private void createGUI() {
|
||||
style();
|
||||
|
||||
/*
|
||||
// TODO: Make custom Label class.
|
||||
JLabel elapsedTime = new JLabel("Elapsed Time: 00:00:00");
|
||||
elapsedTime.setFont(s.getFont());
|
||||
|
||||
// TODO: Make custom Button class.
|
||||
JButton solve = new JButton("Solve");
|
||||
solve.setFont(s.getFont());
|
||||
*/
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the ComboBox for the file options.
|
||||
*
|
||||
* @return ComboBox<String>
|
||||
*/
|
||||
private ComboBox<String> createFileOptions() {
|
||||
// Create a new ComboBox with the default styling.
|
||||
ComboBox<String> fileOptions = new ComboBox<>(s);
|
||||
fileOptions.addItem("New");
|
||||
fileOptions.addItem("Open");
|
||||
fileOptions.addItem("Save");
|
||||
fileOptions.addItem("Save As");
|
||||
fileOptions.addItem("Exit");
|
||||
|
||||
fileOptions.addActionListener(e -> {
|
||||
int selected = fileOptions.getSelectedIndex();
|
||||
switch(selected) {
|
||||
case 0:
|
||||
newFile();
|
||||
break;
|
||||
case 1:
|
||||
openFile();
|
||||
break;
|
||||
case 2:
|
||||
saveFile(false);
|
||||
break;
|
||||
case 3:
|
||||
saveFile(true);
|
||||
break;
|
||||
case 4:
|
||||
System.exit(0);
|
||||
break;
|
||||
default:
|
||||
System.out.println("Invalid selection.");
|
||||
}
|
||||
});
|
||||
return fileOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the user's selection from the ComboBox, perform the appropriate
|
||||
* action.
|
||||
*
|
||||
* See Settings.getNewFileProperties() for more information on the expected
|
||||
* actions.
|
||||
*
|
||||
* @see Settings.java
|
||||
*/
|
||||
private void newFile() {
|
||||
int defaultOption = s.getNewFileProperties();
|
||||
|
||||
switch(defaultOption) {
|
||||
case 0:
|
||||
f = new File("new.sdku");
|
||||
try {
|
||||
f.createNewFile();
|
||||
|
||||
// Create a new, blank .sdku file.
|
||||
for(int i = 0; i < 9; i++)
|
||||
for(int j = 0; j < 9; j++)
|
||||
grid[i][j] = new Cell(i, j, 0);
|
||||
|
||||
// Update the Board with the new, blank .sdku puzzle.
|
||||
b.setGrid(grid);
|
||||
|
||||
// Save the file if auto-save is on.
|
||||
if(s.getAutoSave()) saveFile(true);
|
||||
} catch (IOException e1) {
|
||||
System.err.println("Unable to create new file " + f.getName());
|
||||
// TODO: Add a dialog box GUI to notify.
|
||||
|
||||
// Open the default, pre-downloaded default.sdku file.
|
||||
openFile(s.getDefaultDirectory() + "default");
|
||||
}
|
||||
case 1:
|
||||
case 2:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an existing .sdku file in the user's file system, selected using
|
||||
* a GUI.
|
||||
*
|
||||
* Create the Grid and update the Board with the loaded Grid.
|
||||
*/
|
||||
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());
|
||||
|
||||
// Custom FileChooser class for style and to ensure the user can only
|
||||
// select .sdku files.
|
||||
FileChooser fc = new FileChooser(defaultDirectory);
|
||||
int result = fc.showOpenDialog(this);
|
||||
|
||||
// If the user selects a file, open it.
|
||||
if(result == FileChooser.APPROVE_OPTION) {
|
||||
f = fc.getSelectedFile();
|
||||
createGrid(f);
|
||||
}
|
||||
b.setGrid(grid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an input filename, open the file and populate the grid.
|
||||
*
|
||||
* This implementation is for use only in the command-line interface.
|
||||
*
|
||||
* @param String
|
||||
*/
|
||||
private void openFile(String filename) {
|
||||
//file path is not working for everyone had to append src/ to run
|
||||
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 +
|
||||
".sdku does not exist or cannot be read."
|
||||
);
|
||||
|
||||
System.out.println("The exact path to the file is: " +
|
||||
f.getAbsolutePath()
|
||||
);
|
||||
|
||||
grid = new Cell[9][9];
|
||||
for(int i = 0; i < 9; i++)
|
||||
for(int j = 0; j < 9; j++)
|
||||
grid[i][j] = new Cell(i, j, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, open the file and populate the grid.
|
||||
createGrid(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the currently open .sdku file with any changes the user has made.
|
||||
* This needs to pull the current state of the grid from the Board JPanel.
|
||||
*
|
||||
* If called from 'Save' option, then saveAs should be false.
|
||||
* If called from 'Save As' option, then saveAs should be true.
|
||||
*
|
||||
* 'Save' will simply overwrite the existing file that was opened,
|
||||
* while 'Save As' while prompt the user to select or create a file.
|
||||
*
|
||||
* 'Save As' functionality is the default behavior for new .sdku files
|
||||
* created. Therefore, if the filename is 'new.sdku', then saveAs will
|
||||
* be made true.
|
||||
*
|
||||
* @param boolean
|
||||
*/
|
||||
private void saveFile(boolean saveAs) {
|
||||
if(f.getName().equals("new.sdku"))
|
||||
saveAs = true;
|
||||
// TODO: Get the current Cell[][] from the Board JPanel
|
||||
// TODO: Write to a file.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently loaded Cell[][] grid.
|
||||
*
|
||||
* @return the grid
|
||||
*/
|
||||
public Cell[][] getLoadedGrid() {
|
||||
return grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the loaded grid with the current version with any changes the
|
||||
* user has made.
|
||||
*
|
||||
* If the user has enabled auto-save in Settings, then save the file.
|
||||
*
|
||||
* In the future, an optimization could be made where a different data
|
||||
* structure is used to pass only the changes the player has made within
|
||||
* the last x minutes, for eg. {row, col, value, noteValues[]}. Given
|
||||
* the size of a standard Sudoku board however, this may be an irrelevant
|
||||
* and unnecessary optimization.
|
||||
*
|
||||
* @param Cell[][]
|
||||
*/
|
||||
public void updateLoadedGrid(Cell[][] grid) {
|
||||
this.grid = grid;
|
||||
if(s.getAutoSave()) saveFile(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when a File is opened or created. This will populate the
|
||||
* Cell[][] grid with values read in from the file.
|
||||
*
|
||||
* @param File
|
||||
*/
|
||||
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) {
|
||||
newFile();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package gui;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import gui.backend.Settings;
|
||||
import gui.backend.Theme;
|
||||
|
||||
/**
|
||||
* The Root class represents the root JPanel for the Sudoku app.
|
||||
*
|
||||
* It contains all other GUI components, and is the main JPanel for the app.
|
||||
* To create a new Root object, a Settings object must be passed in for
|
||||
* styling purposes.
|
||||
*/
|
||||
public class Root extends JPanel {
|
||||
private Theme theme;
|
||||
|
||||
/**
|
||||
* Create a new Root object with the default styling from settings.
|
||||
*
|
||||
* @param s
|
||||
*/
|
||||
public Root(Settings settings) {
|
||||
super();
|
||||
this.theme = settings.getTheme();
|
||||
style();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the Root Panel with the appropriate styling.
|
||||
*/
|
||||
private void style() {
|
||||
setBackground(theme.getPrimaryBackground());
|
||||
setForeground(theme.getPrimaryText());
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
package gui.backend;
|
||||
/**
|
||||
* A Cell represents a single cell in the Sudoku grid.
|
||||
* This helper class is used to store the row, column, value, and possible values for a cell.
|
||||
@@ -10,6 +11,8 @@ public class Cell {
|
||||
private int col;
|
||||
private int box;
|
||||
private int value;
|
||||
private boolean initValue;
|
||||
|
||||
private List possibleValues;
|
||||
|
||||
/**
|
||||
@@ -40,6 +43,8 @@ public class Cell {
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
this.value = value;
|
||||
initValue = value != 0;
|
||||
|
||||
setBox();
|
||||
possibleValues = new List();
|
||||
}
|
||||
@@ -65,10 +70,16 @@ public class Cell {
|
||||
*
|
||||
* The value must be between 1 and 9, inclusive.
|
||||
*
|
||||
* Additionally if the value is incorrect, then do not clear the
|
||||
* possible values.
|
||||
*
|
||||
* @param value
|
||||
* @param isCorrect
|
||||
*/
|
||||
public void setValue(int value) {
|
||||
possibleValues.clear();
|
||||
public void setValue(int value, boolean isCorrect) {
|
||||
if(initValue) return;
|
||||
if(isCorrect) possibleValues.clear();
|
||||
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@@ -81,6 +92,24 @@ public class Cell {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the row of the cell.
|
||||
*
|
||||
* @return row number
|
||||
*/
|
||||
public int getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column of the cell.
|
||||
*
|
||||
* @return column number
|
||||
*/
|
||||
public int getCol() {
|
||||
return col;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the box a cell is in.
|
||||
*
|
||||
@@ -98,7 +127,10 @@ public class Cell {
|
||||
* @param value
|
||||
*/
|
||||
public void addPossibleValue(int value) {
|
||||
if(initValue) return;
|
||||
|
||||
if(possibleValues.contains(value)) {
|
||||
removePossibleValue(value);
|
||||
return;
|
||||
} else if(value < 1 || value > 9) {
|
||||
return;
|
||||
@@ -113,6 +145,8 @@ public class Cell {
|
||||
* @param possibleValues
|
||||
*/
|
||||
public void setPossibleValues(int[] possibleValues) {
|
||||
if(initValue) return;
|
||||
|
||||
this.possibleValues = new List(possibleValues);
|
||||
}
|
||||
|
||||
@@ -132,7 +166,8 @@ public class Cell {
|
||||
/**
|
||||
* Get a list of possible values for the cell.
|
||||
*
|
||||
* This list is a copy of the cell's possible values, and is sorted in ascending order.
|
||||
* This list is a copy of the cell's possible values, and is sorted
|
||||
* in ascending order.
|
||||
*
|
||||
* @return list of possible values for the cell
|
||||
*/
|
||||
@@ -140,6 +175,28 @@ public class Cell {
|
||||
return possibleValues.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cell has an initial value.
|
||||
*
|
||||
* An initial value is a value that is set when the cell is created,
|
||||
* and cannot be changed.
|
||||
* @return
|
||||
*/
|
||||
public boolean isInitValue() {
|
||||
return initValue;
|
||||
}
|
||||
|
||||
public static Cell[][] copyGrid(Cell[][] grid) {
|
||||
Cell[][] newGrid = new Cell[9][9];
|
||||
for(int row = 0; row < 9; row++) {
|
||||
for(int col = 0; col < 9; col++) {
|
||||
newGrid[row][col] = new Cell(row, col, grid[row][col].getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return newGrid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the cell's row and column, set the box number for the cell.
|
||||
*/
|
||||
@@ -150,10 +207,12 @@ public class Cell {
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to store the possible values for a cell in a sorted list.
|
||||
* The list is sorted in ascending order, and the values are stored in a linked list.
|
||||
* This class is used to store the possible values for a cell in a
|
||||
* sorted list. The list is sorted in ascending order, and the values are
|
||||
* stored in a linked list.
|
||||
*
|
||||
* The List class also contains a method to add a value to the list, and a method to get the list of values as an array.
|
||||
* The List class also contains a method to add a value to the list,
|
||||
* and a method to get the list of values as an array.
|
||||
*/
|
||||
private class List {
|
||||
|
||||
@@ -176,20 +235,6 @@ public class Cell {
|
||||
min = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new List with the given initial value.
|
||||
*
|
||||
* By default, the head and tail are the same, the size is 1, and the max and min are the initial value.
|
||||
* @param initValue
|
||||
*/
|
||||
public List(int initValue) {
|
||||
head = new Value(initValue);
|
||||
tail = head;
|
||||
size = 1;
|
||||
max = initValue;
|
||||
min = initValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new List with the given initial values.
|
||||
*
|
||||
@@ -0,0 +1,543 @@
|
||||
package gui.backend;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
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 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 puzzles directory for .sdku files;
|
||||
* - New file Properties;
|
||||
* - Font;
|
||||
* - Window size;
|
||||
* - Window resizing (experimental);
|
||||
* - Cell start mode, meaning if a cell is blank and selected, input
|
||||
* is either going to the notes or entering a value;
|
||||
* - Application default open behavior;
|
||||
* - Theme, specifically by providing a default.theme file formatted for JSON;
|
||||
* - Auto-fill notes with possible values;
|
||||
* - Auto-check user-entered values with the correct values;
|
||||
* - Auto-save and auto-save frequency;
|
||||
*/
|
||||
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;
|
||||
|
||||
// The font to be used throughout the application.
|
||||
private Font font;
|
||||
|
||||
// The default dimension of the application window.
|
||||
private Dimension dimension;
|
||||
|
||||
// The default dimension of the cells in the application window.
|
||||
private Dimension cellDimension;
|
||||
|
||||
// Resizable setting
|
||||
private boolean resizable;
|
||||
|
||||
// Cell GUI Start Mode
|
||||
private boolean cellGUIStartMode;
|
||||
|
||||
// Default open state for the application.
|
||||
private int defaultOpenState;
|
||||
|
||||
// Theme object storing the colors for the GUI components.
|
||||
private Theme theme;
|
||||
|
||||
// Auto-fill notes
|
||||
private boolean autoFillNotes;
|
||||
|
||||
// Auto check values
|
||||
private boolean autoCheckValues;
|
||||
|
||||
// Auto-save whenever a new note or value is changed.
|
||||
private boolean autoSave;
|
||||
|
||||
// Auto-save frequency
|
||||
private int autoSaveFrequency;
|
||||
|
||||
/**
|
||||
* Create a new Settings object, initializing the default settings
|
||||
* or reading in the settings from settings file if it exists.
|
||||
*
|
||||
* While the defaultDirectory can be modified, it will not change where
|
||||
* the settings file is located. The settings file is always located in
|
||||
* either "~/.config/sudoku/" or "%APPDATA%/Local/Sudoku/", depending
|
||||
* on the user's operating system.
|
||||
*
|
||||
* The settings file is settings.json.
|
||||
*/
|
||||
public Settings() {
|
||||
os = System.getProperty("os.name").toLowerCase();
|
||||
if(os.startsWith("windows"))
|
||||
appDirectory = System.getProperty("user.home") +
|
||||
"\\AppData\\Local\\Sudoku\\";
|
||||
else
|
||||
appDirectory = System.getProperty("user.home") +
|
||||
"/.config/sudoku/";
|
||||
|
||||
File dir = new File(appDirectory);
|
||||
|
||||
// If the app directory does not exist, create it and populate a
|
||||
// settings.json file with the default settings.
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
dir.mkdirs();
|
||||
defaultSettings();
|
||||
updateSettingsFile();
|
||||
|
||||
} else readSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default settings for the application and write them to the
|
||||
* settings file.
|
||||
*/
|
||||
private void defaultSettings() {
|
||||
// Setup the default directory for .sdku puzzle files.
|
||||
defaultDirectory = appDirectory + "puzzles";
|
||||
File dir = new File(defaultDirectory);
|
||||
|
||||
// Create the puzzles directory.
|
||||
if(!dir.exists() || !dir.isDirectory())
|
||||
dir.mkdirs();
|
||||
|
||||
// Load the font from the system directory.
|
||||
if(loadFont("HackNerdFont-Regular") == 0)
|
||||
font = new Font("Hack Nerd Font", Font.PLAIN, 16);
|
||||
|
||||
// If the font does not exist, use Arial as the default font.
|
||||
else
|
||||
font = new Font("Arial", Font.PLAIN, 16);
|
||||
|
||||
// Set other default settings.
|
||||
newFileProperties = 0;
|
||||
dimension = new Dimension(600, 800);
|
||||
resizable = false;
|
||||
cellGUIStartMode = true;
|
||||
defaultOpenState = 0;
|
||||
theme = new Theme(new File(appDirectory + "default.theme"));
|
||||
autoFillNotes = false;
|
||||
autoCheckValues = true;
|
||||
autoSave = false;
|
||||
autoSaveFrequency = 0;
|
||||
setCellDimensions();
|
||||
|
||||
// Write the default settings to the settings.json file.
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open and write the settings to the settings file.
|
||||
*
|
||||
* This method is called whenever a setting is updated.
|
||||
*/
|
||||
private void updateSettingsFile() {
|
||||
// TODO: Write the settings to the settings file.
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the settings from the settings file.
|
||||
*
|
||||
* If the settings file does not exist, the default settings will be used
|
||||
* to populate the Settings file.
|
||||
*/
|
||||
private void readSettings() {
|
||||
File settingsFile = new File(appDirectory + "/settings.json");
|
||||
if (!settingsFile.exists() && !settingsFile.isDirectory()) {
|
||||
defaultSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Read the settings from the settings file.
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the font from the resources folder in the user's system directory.
|
||||
*
|
||||
* @return int 0 if successful, 1 if the file does not exist.
|
||||
*/
|
||||
private int loadFont(String fontName) {
|
||||
// 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";
|
||||
else if(os.startsWith("mac"))
|
||||
fontFilepath = "/Library/Fonts/" + fontName + ".ttf";
|
||||
else {
|
||||
fontName = "HackNerdFontMono-Regular";
|
||||
fontFilepath = "/usr/share/fonts/TTF/" + fontName + ".ttf";
|
||||
}
|
||||
|
||||
// Register the font with the GraphicsEnvironment.
|
||||
try {
|
||||
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.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
* 0 - Create a new, blank .sdku file
|
||||
* 1 - Create a new, random .sdku file
|
||||
*
|
||||
* Integer is being used instead of boolean since additional properties
|
||||
* may be added in the future.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public int getNewFileProperties() {
|
||||
return newFileProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the new file properties and write them to the settings file.
|
||||
*
|
||||
* @param newFileProperties
|
||||
*/
|
||||
public void setNewFileProperties(int newFileProperties) {
|
||||
this.newFileProperties = newFileProperties;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Font is a configurable setting, where the user can specify a
|
||||
* font to be used throughout the application.
|
||||
*
|
||||
* Fonts are loaded from the system directory. In Windows, the font
|
||||
* is loaded from C:\Windows\Fonts\. In MacOS, the font is loaded
|
||||
* from /Library/Fonts/. In Linux, the font is loaded from
|
||||
* /usr/share/fonts/.
|
||||
*
|
||||
* @return Font
|
||||
*/
|
||||
public Font getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the font and write it to the settings file.
|
||||
*
|
||||
* @param font
|
||||
*/
|
||||
public void setFont(Font font) {
|
||||
this.font = font;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default dimension of the application window.
|
||||
*
|
||||
* The user can specify both the starting width and height of the app,
|
||||
* and the app will remember the last used dimensions as well.
|
||||
*
|
||||
* @return Dimension
|
||||
*/
|
||||
public Dimension getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default dimension and write it to the settings file.
|
||||
*
|
||||
* @param dimension
|
||||
*/
|
||||
public void setDimension(Dimension dimension) {
|
||||
this.dimension = dimension;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The resizable setting for the application window.
|
||||
*
|
||||
* By default, the application window is not resizable and is considered
|
||||
* an experimental feature. Dynamic layout is not yet supported.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean getResizable() {
|
||||
return resizable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resizable setting and write it to the settings file.
|
||||
*
|
||||
* @param resizable
|
||||
*/
|
||||
public void setResizable(boolean resizable) {
|
||||
this.resizable = resizable;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Cell GUI Start Mode is a configurable setting, where the user can
|
||||
* specify whether the cell GUI should start in notes mode or value mode.
|
||||
*
|
||||
* By default, the cell GUI starts in value mode, ie. returns false.
|
||||
* Set to true to start in notes mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean getCellGUIStartMode() {
|
||||
return cellGUIStartMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cell GUI start mode and write it to the settings file.
|
||||
*
|
||||
* @param cellGUIStartMode
|
||||
*/
|
||||
public void setCellGUIStartMode(boolean cellGUIStartMode) {
|
||||
this.cellGUIStartMode = cellGUIStartMode;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default open state for the application. The following values
|
||||
* are accepted:
|
||||
* 0 - Open the default easy.sdku file, default behavior;
|
||||
* 1 - Open the last opened .sdku file;
|
||||
* 2 - Open a new, blank .sdku file; and
|
||||
* 3 - Open a new, random .sdku file.
|
||||
*
|
||||
* NOTE: The value 2 is not yet supported.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getDefaultOpenState() {
|
||||
return defaultOpenState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default open state for the application and write it to the
|
||||
* settings file.
|
||||
*
|
||||
* @param defaultOpenState
|
||||
*/
|
||||
public void setDefaultOpenState(int defaultOpenState) {
|
||||
this.defaultOpenState = defaultOpenState;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Theme object stores the colors for the GUI components.
|
||||
*
|
||||
* The available colors for customization are:
|
||||
* - primaryBackground
|
||||
* - secondaryBackground
|
||||
* - primaryText
|
||||
* - secondaryText
|
||||
* - primaryHighlight
|
||||
* - secondaryHighlight
|
||||
* - primaryBorder
|
||||
* - secondaryBorder
|
||||
* - primaryButton
|
||||
* - secondaryButton
|
||||
* - primaryButtonHighlight
|
||||
* - secondaryButtonHighlight
|
||||
* - primaryButtonBorder
|
||||
* - secondaryButtonBorder
|
||||
*
|
||||
* @see Theme
|
||||
* @return
|
||||
*/
|
||||
public Theme getTheme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the theme and write it to the settings file.
|
||||
*
|
||||
* @param theme
|
||||
*/
|
||||
public void setTheme(Theme theme) {
|
||||
this.theme = theme;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Auto-fill notes setting is a configurable setting, where the user
|
||||
* can specify whether notes should be automatically filled in when a
|
||||
* value is placed in a cell.
|
||||
*
|
||||
* By default, the auto-fill notes setting is set to false.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean getAutoFillNotes() {
|
||||
return autoFillNotes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the auto-fill notes setting and write it to the settings file.
|
||||
*
|
||||
* @param autoFillNotes
|
||||
*/
|
||||
public void setAutoFillNotes(boolean autoFillNotes) {
|
||||
this.autoFillNotes = autoFillNotes;
|
||||
setCellGUIStartMode(autoFillNotes);
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Auto-check values setting is a configurable setting, where the user
|
||||
* can specify whether values should be automatically checked when placed
|
||||
* in a cell.
|
||||
*
|
||||
* By default, the auto-check values setting is set to false.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean getAutoCheckValues() {
|
||||
return autoCheckValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the auto-check values setting and write it to the settings file.
|
||||
*
|
||||
* @param autoCheckValues
|
||||
*/
|
||||
public void setAutoCheckValues(boolean autoCheckValues) {
|
||||
this.autoCheckValues = autoCheckValues;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default dimensions of the cells in the application window.
|
||||
*
|
||||
* The default dimensions are set to 50x50 pixels.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Dimension getCellDimensions() {
|
||||
return cellDimension;
|
||||
}
|
||||
|
||||
private void setCellDimensions() {
|
||||
int height;
|
||||
int width;
|
||||
|
||||
if(dimension.height < 500) height = 50;
|
||||
else height = dimension.height / 10;
|
||||
|
||||
if(dimension.width < 500) width = 50;
|
||||
else width = dimension.width / 10;
|
||||
|
||||
cellDimension = new Dimension(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-save is a user-configurable setting to automatically save the
|
||||
* puzzle anytime they make a change. Additionally, auto-save will
|
||||
* result in the file being automatically saved every 60 seconds, to
|
||||
* preserve the elapsed time tracking.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean getAutoSave() {
|
||||
return autoSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the auto-save feature to a specified state.
|
||||
*
|
||||
* @param autoSave
|
||||
*/
|
||||
public void setAutoSave(boolean autoSave) {
|
||||
this.autoSave = autoSave;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The auto save frequnecy, in seconds.
|
||||
*
|
||||
* By default, auto-save will save whenever the user makes changes on the
|
||||
* puzzle. However, since elapsed time and other future features are not
|
||||
* user-controlled events, the frequency is used to specify in a separate
|
||||
* thread how often the .sdku file should be saved.
|
||||
*
|
||||
* For performance and realistic necessity for preserving this kind of
|
||||
* information, the frequency should (not required) be following:
|
||||
* - 0: off;
|
||||
* - 5: 5 second intervals;
|
||||
* - 30: 30 second intervals;
|
||||
* - 60: 60 second intervals.
|
||||
*
|
||||
* If auto-save is off, then this value is 0 automatically. When auto-save
|
||||
* is turned on, by default the value is 30.
|
||||
*
|
||||
* @return auto-save frequency
|
||||
*/
|
||||
public int getAutoSaveFrequency() {
|
||||
return autoSaveFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the auto-save frequency, in seconds.
|
||||
*
|
||||
* @param autoSaveFrequency
|
||||
*/
|
||||
public void setAutoSaveFrequency(int autoSaveFrequency) {
|
||||
this.autoSaveFrequency = autoSaveFrequency;
|
||||
updateSettingsFile();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
package gui.backend;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The SudokuChecker class is responsible for calculating the solution to 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;
|
||||
private Cell[][] origGrid;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param grid
|
||||
*/
|
||||
public SudokuChecker(Cell[][] grid) {
|
||||
this.grid = grid;
|
||||
|
||||
// Create a copy of the original grid to be used for resetting the
|
||||
// grid to its original state.
|
||||
origGrid = new Cell[9][9];
|
||||
for (int i = 0; i < 9; i++) {
|
||||
for (int j = 0; j < 9; j++) {
|
||||
origGrid[i][j] = new Cell(i, j, grid[i][j].getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given value can be placed in the given cell of the grid.
|
||||
*
|
||||
* False means that the value is incorrect, and true means that the value is
|
||||
* correct.
|
||||
*
|
||||
* @param row
|
||||
* @param col
|
||||
* @param value
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean checkValue(int row, int col, int value) {
|
||||
// Check the row
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (grid[row][i].getValue() == value && i != col)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the column
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (grid[i][col].getValue() == value && i != row)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the box
|
||||
int boxRow = row / 3;
|
||||
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() == value
|
||||
&& (boxRow * 3 + i != row || boxCol * 3 + j != col)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the possible values for each cell in the Sudoku puzzle.
|
||||
*
|
||||
* Intended to be used for auto-filling in possible values in the GUI.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Cell[][] getPossibleValues(Cell[][] grid) {
|
||||
for (int row = 0; row < grid.length; row++) {
|
||||
for (int col = 0; col < grid[row].length; col++) {
|
||||
if (grid[row][col].getValue() != 0)
|
||||
continue;
|
||||
|
||||
// Replace intersection with union?
|
||||
ArrayList<Integer> intersection = intersection(getRowRemainingNumbers(row),
|
||||
getColRemainingNumbers(col));
|
||||
|
||||
intersection = intersection(intersection, getBoxRemainingNumbers(row, col));
|
||||
|
||||
// Join the arrays and find the intersection of the three arrays
|
||||
ArrayList<Integer> availableNumbers = new ArrayList<Integer>();
|
||||
for (int i = 0; i < intersection.size(); i++) {
|
||||
availableNumbers.add(intersection.get(i));
|
||||
}
|
||||
|
||||
grid[row][col].setPossibleValues(arrayListToArray(availableNumbers));
|
||||
}
|
||||
}
|
||||
|
||||
this.grid = grid;
|
||||
return grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the solution to the Sudoku puzzle.
|
||||
*
|
||||
* @return Cell[][]
|
||||
*/
|
||||
public Cell[][] getSolution() {
|
||||
solve();
|
||||
return grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given two arrays of numbers, return the intersection of the two arrays.
|
||||
*
|
||||
* @param a
|
||||
* @param b
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> intersection(ArrayList<Integer> a, ArrayList<Integer> b) {
|
||||
ArrayList<Integer> intersection = new ArrayList<Integer>();
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
if (b.contains(a.get(i)))
|
||||
intersection.add(a.get(i));
|
||||
}
|
||||
|
||||
return intersection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private void getAvailableNumbers(int row, int col) {
|
||||
ArrayList<Integer> intersection = intersection(getRowRemainingNumbers(row), getColRemainingNumbers(col));
|
||||
|
||||
intersection = intersection(intersection, getBoxRemainingNumbers(row, col));
|
||||
|
||||
if (intersection.size() == 0)
|
||||
return;
|
||||
else if (intersection.size() == 1) {
|
||||
int value = intersection.get(0);
|
||||
|
||||
grid[row][col].setValue(value, true);
|
||||
updatePossibleValues(row, col);
|
||||
return;
|
||||
} else {
|
||||
// Join the arrays and find the intersection of the three arrays.
|
||||
ArrayList<Integer> availableNumbers = new ArrayList<Integer>();
|
||||
for (int i = 0; i < intersection.size(); i++)
|
||||
availableNumbers.add(intersection.get(i));
|
||||
|
||||
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.
|
||||
*
|
||||
* @param row
|
||||
* @param col
|
||||
*/
|
||||
private void updatePossibleValues(int row, int col) {
|
||||
int value = grid[row][col].getValue();
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (grid[row][i].getValue() == 0) {
|
||||
grid[row][i].removePossibleValue(value);
|
||||
if (grid[row][i].getPossibleValues().length == 1) {
|
||||
grid[row][i].setValue(grid[row][i].getPossibleValues()[0], true);
|
||||
updatePossibleValues(row, i);
|
||||
}
|
||||
}
|
||||
if (grid[i][col].getValue() == 0) {
|
||||
grid[i][col].removePossibleValue(value);
|
||||
if (grid[i][col].getPossibleValues().length == 1) {
|
||||
grid[i][col].setValue(grid[i][col].getPossibleValues()[0], true);
|
||||
updatePossibleValues(i, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int boxRow = row / 3;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Solve the Sudoku puzzle.
|
||||
*
|
||||
* @param grid
|
||||
*/
|
||||
private boolean solve() {
|
||||
return solve(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded method that solves the Sudoku puzzle moving from the position given to the end
|
||||
*
|
||||
* @param row
|
||||
* @param col
|
||||
* @return
|
||||
*/
|
||||
private boolean solve(int row, int col) {
|
||||
int nextCol = (col + 1) % 9;
|
||||
int nextRow = (nextCol == 0) ? row + 1 : row;
|
||||
// Base case - progressed past the last row and column.
|
||||
if (row == 9) {
|
||||
// grid.printTable();
|
||||
return true;
|
||||
}
|
||||
// If the cell has a value it is skipped.
|
||||
if (grid[row][col].getValue() != 0) {
|
||||
solve(nextRow, nextCol);
|
||||
} else {
|
||||
// An empty cell prompts a generation of possible numbers
|
||||
List<Integer> possibleNumbers = getAllRemainingNumbers(row, col);
|
||||
if (possibleNumbers.size() == 0) // If there are no available numbers the solve() returns false
|
||||
return false;
|
||||
// each possible number is given in ascending order
|
||||
for (Integer el : possibleNumbers) {
|
||||
grid[row][col].setValue(el.intValue(), true);
|
||||
|
||||
// here is the check to see if the next possible number needs to be tested
|
||||
if (solve(nextRow, nextCol))
|
||||
return true;
|
||||
// reset the cell so that it is not assumed to be solved after failing the
|
||||
// current tested values }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This may be redundant but I needed an method that could be called to get a list of all
|
||||
* remaining numbers after eliminating 1-9 by standard Sudoku rules.
|
||||
* This method calls the 3 methods already in this class to build a HashSet of
|
||||
* known values which are used to verify which numbers remain as possible solutions.
|
||||
* @param row
|
||||
* @param col
|
||||
* @return ArrayList<Integer> results;
|
||||
*/
|
||||
private ArrayList<Integer> getAllRemainingNumbers(int row, int col) {
|
||||
HashSet<Integer> nums = new HashSet<>();
|
||||
ArrayList<Integer> results = new ArrayList<>();
|
||||
nums.addAll(getColRemainingNumbers(col));
|
||||
nums.addAll(getRowRemainingNumbers(row));
|
||||
nums.addAll(getBoxRemainingNumbers(row, col));
|
||||
for (int i = 1; i <= 9; i++) {
|
||||
if (!nums.contains(i)) {
|
||||
results.add(i);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any number between 1 and 9 that is not in the row.
|
||||
*
|
||||
* @param row
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> getRowRemainingNumbers(int row) {
|
||||
ArrayList<Integer> remainingNumbers = new ArrayList<Integer>();
|
||||
for (int i = 1; i <= 9; i++) {
|
||||
boolean found = false;
|
||||
for (int j = 0; j < 9; j++) {
|
||||
if (grid[row][j].getValue() == i) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
remainingNumbers.add(i);
|
||||
}
|
||||
|
||||
return remainingNumbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any number between 1 and 9 that is not in the column.
|
||||
*
|
||||
* @param col
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> getColRemainingNumbers(int col) {
|
||||
ArrayList<Integer> remainingNumbers = new ArrayList<Integer>();
|
||||
for (int i = 1; i <= 9; i++) {
|
||||
boolean found = false;
|
||||
for (int j = 0; j < 9; j++) {
|
||||
if (grid[j][col].getValue() == i) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
remainingNumbers.add(i);
|
||||
}
|
||||
|
||||
return remainingNumbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any number between 1 and 9 that is not in the box.
|
||||
*
|
||||
* A box is a 3x3 subgrid of the 9x9 grid.
|
||||
*
|
||||
* @param box
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> getBoxRemainingNumbers(int row, int col) {
|
||||
ArrayList<Integer> remainingNumbers = new ArrayList<Integer>();
|
||||
int boxRow = row / 3;
|
||||
int boxCol = col / 3;
|
||||
for (int i = 1; i <= 9; i++) {
|
||||
boolean found = false;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int k = 0; k < 3; k++) {
|
||||
if (grid[boxRow * 3 + j][boxCol * 3 + k].getValue() == i) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
remainingNumbers.add(i);
|
||||
}
|
||||
|
||||
return remainingNumbers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param list
|
||||
* @return
|
||||
*/
|
||||
private int[] arrayListToArray(ArrayList<Integer> list) {
|
||||
int[] array = new int[list.size()];
|
||||
for (int i = 0; i < list.size(); i++)
|
||||
array[i] = list.get(i);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return true if the grid is a valid Sudoku puzzle, and false otherwise
|
||||
*/
|
||||
private boolean isValidSolution() {
|
||||
// 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)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that every row contains the numbers 1-9 exactly once.
|
||||
for (int i = 0; i < 9; i++) {
|
||||
int[] row = new int[9];
|
||||
for (int j = 0; j < 9; j++)
|
||||
row[j] = grid[i][j].getValue();
|
||||
|
||||
if (!isValidSet(row)) {
|
||||
System.out.println("Row " + i + " is invalid.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that every column contains the numbers 1-9 exactly once.
|
||||
for (int i = 0; i < 9; i++) {
|
||||
int[] col = new int[9];
|
||||
for (int j = 0; j < 9; j++)
|
||||
col[j] = grid[j][i].getValue();
|
||||
|
||||
if (!isValidSet(col)) {
|
||||
System.out.println("Column " + i + " is invalid.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that every 3x3 subgrid contains the numbers 1-9 exactly once.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
int[] box = new int[9];
|
||||
for (int k = 0; k < 3; k++) {
|
||||
for (int l = 0; l < 3; l++)
|
||||
box[k * 3 + l] = grid[i * 3 + k][j * 3 + l].getValue();
|
||||
}
|
||||
if (!isValidSet(box)) {
|
||||
System.out.println("Box at row " + i + " and column " + j + " is invalid.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private boolean isValidSet(int[] set) {
|
||||
boolean[] found = new boolean[9];
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (set[i] < 1 || set[i] > 9)
|
||||
return false;
|
||||
|
||||
else if (found[set[i] - 1])
|
||||
return false;
|
||||
|
||||
else
|
||||
found[set[i] - 1] = true;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
package gui.backend;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* The Theme class represents the color scheme for the GUI components.
|
||||
*
|
||||
* The Theme class reads a .theme file and sets the colors for the GUI components.
|
||||
* .theme files are .json files for standardized data processing.
|
||||
*
|
||||
* The Theme class provides the following color customizations:
|
||||
* - primaryBackground
|
||||
* - secondaryBackground
|
||||
* - primaryText
|
||||
* - secondaryText
|
||||
* - primaryHighlight
|
||||
* - secondaryHighlight
|
||||
* - primaryBorder
|
||||
* - secondaryBorder
|
||||
* - primaryButton
|
||||
* - secondaryButton
|
||||
* - primaryButtonHighlight
|
||||
* - secondaryButtonHighlight
|
||||
* - primaryButtonBorder
|
||||
* - secondaryButtonBorder
|
||||
*/
|
||||
public class Theme {
|
||||
private Color primaryBackground;
|
||||
private Color secondaryBackground;
|
||||
private Color primaryText;
|
||||
private Color secondaryText;
|
||||
private Color primaryHighlight;
|
||||
private Color secondaryHighlight;
|
||||
private Color primaryBorder;
|
||||
private Color secondaryBorder;
|
||||
private Color primaryButton;
|
||||
private Color secondaryButton;
|
||||
private Color primaryButtonHighlight;
|
||||
private Color secondaryButtonHighlight;
|
||||
private Color primaryButtonBorder;
|
||||
private Color secondaryButtonBorder;
|
||||
private Color errorBackground;
|
||||
private Color errorText;
|
||||
private Color errorBorder;
|
||||
|
||||
/**
|
||||
* Constructs a new Theme object with the given theme file.
|
||||
* This object provides the colors for the GUI components.
|
||||
*
|
||||
* @param theme the theme file
|
||||
*/
|
||||
public Theme(File file) {
|
||||
readTheme(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the .theme file and sets the colors for the GUI components.
|
||||
* Once complete, sets the theme for the GUI components.
|
||||
*/
|
||||
private void readTheme(File file) {
|
||||
// TODO: Read the .theme file
|
||||
setTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the theme for the GUI components.
|
||||
*/
|
||||
private void setTheme() {
|
||||
primaryBackground = Color.decode("#4A245E");
|
||||
secondaryBackground = Color.decode("#DADFF7");
|
||||
primaryText = Color.decode("#DBABF7");
|
||||
secondaryText = Color.decode("#4A245E");
|
||||
primaryBorder = Color.decode("#A76BCA");
|
||||
secondaryBorder = Color.decode("#DBABF7");
|
||||
errorBackground = Color.decode("#FF0000");
|
||||
errorText = Color.decode("#FFFFFF");
|
||||
errorBorder = Color.decode("#FF0000");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the primary background color.
|
||||
*
|
||||
* @return the primary background color
|
||||
*/
|
||||
public Color getPrimaryBackground() {
|
||||
return primaryBackground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the primary background color.
|
||||
*
|
||||
* @param primaryBackground the primary background color
|
||||
*/
|
||||
public void setPrimaryBackground(Color primaryBackground) {
|
||||
this.primaryBackground = primaryBackground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the secondary background color.
|
||||
*
|
||||
* @return the secondary background color
|
||||
*/
|
||||
public Color getSecondaryBackground() {
|
||||
return secondaryBackground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the secondary background color.
|
||||
*
|
||||
* @param secondaryBackground the secondary background color
|
||||
*/
|
||||
public void setSecondaryBackground(Color secondaryBackground) {
|
||||
this.secondaryBackground = secondaryBackground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the primary text color.
|
||||
*
|
||||
* @return the primary text color
|
||||
*/
|
||||
public Color getPrimaryText() {
|
||||
return primaryText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the primary text color.
|
||||
*
|
||||
* @param primaryText the primary text color
|
||||
*/
|
||||
public void setPrimaryText(Color primaryText) {
|
||||
this.primaryText = primaryText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the secondary text color.
|
||||
*
|
||||
* @return the secondary text color
|
||||
*/
|
||||
public Color getSecondaryText() {
|
||||
return secondaryText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the secondary text color.
|
||||
*
|
||||
* @param secondaryText the secondary text color
|
||||
*/
|
||||
public void setSecondaryText(Color secondaryText) {
|
||||
this.secondaryText = secondaryText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the primary highlight color.
|
||||
*
|
||||
* @return the primary highlight color
|
||||
*/
|
||||
public Color getPrimaryHighlight() {
|
||||
return primaryHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the primary highlight color.
|
||||
*
|
||||
* @param primaryHighlight the primary highlight color
|
||||
*/
|
||||
public void setPrimaryHighlight(Color primaryHighlight) {
|
||||
this.primaryHighlight = primaryHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the secondary highlight color.
|
||||
*
|
||||
* @return the secondary highlight color
|
||||
*/
|
||||
public Color getSecondaryHighlight() {
|
||||
return secondaryHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the secondary highlight color.
|
||||
*
|
||||
* @param secondaryHighlight the secondary highlight color
|
||||
*/
|
||||
public void setSecondaryHighlight(Color secondaryHighlight) {
|
||||
this.secondaryHighlight = secondaryHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the primary border color.
|
||||
*
|
||||
* @return the primary border color
|
||||
*/
|
||||
public Color getPrimaryBorder() {
|
||||
return primaryBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the primary border color.
|
||||
*
|
||||
* @param primaryBorder the primary border color
|
||||
*/
|
||||
public void setPrimaryBorder(Color primaryBorder) {
|
||||
this.primaryBorder = primaryBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the secondary border color.
|
||||
*
|
||||
* @return the secondary border color
|
||||
*/
|
||||
public Color getSecondaryBorder() {
|
||||
return secondaryBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the secondary border color.
|
||||
*
|
||||
* @param secondaryBorder the secondary border color
|
||||
*/
|
||||
public void setSecondaryBorder(Color secondaryBorder) {
|
||||
this.secondaryBorder = secondaryBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the primary button color.
|
||||
*
|
||||
* @return the primary button color
|
||||
*/
|
||||
public Color getPrimaryButton() {
|
||||
return primaryButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the primary button color.
|
||||
*
|
||||
* @param primaryButton the primary button color
|
||||
*/
|
||||
public void setPrimaryButton(Color primaryButton) {
|
||||
this.primaryButton = primaryButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the secondary button color.
|
||||
*
|
||||
* @return the secondary button color
|
||||
*/
|
||||
public Color getSecondaryButton() {
|
||||
return secondaryButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the secondary button color.
|
||||
*
|
||||
* @param secondaryButton the secondary button color
|
||||
*/
|
||||
public void setSecondaryButton(Color secondaryButton) {
|
||||
this.secondaryButton = secondaryButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the primary button highlight color.
|
||||
*
|
||||
* @return the primary button highlight color
|
||||
*/
|
||||
public Color getPrimaryButtonHighlight() {
|
||||
return primaryButtonHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the primary button highlight color.
|
||||
*
|
||||
* @param primaryButtonHighlight the primary button highlight color
|
||||
*/
|
||||
public void setPrimaryButtonHighlight(Color primaryButtonHighlight) {
|
||||
this.primaryButtonHighlight = primaryButtonHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the secondary button highlight color.
|
||||
*
|
||||
* @return the secondary button highlight color
|
||||
*/
|
||||
public Color getSecondaryButtonHighlight() {
|
||||
return secondaryButtonHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the secondary button highlight color.
|
||||
*
|
||||
* @param secondaryButtonHighlight the secondary button highlight color
|
||||
*/
|
||||
public void setSecondaryButtonHighlight(Color secondaryButtonHighlight) {
|
||||
this.secondaryButtonHighlight = secondaryButtonHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the primary button border color.
|
||||
*
|
||||
* @return the primary button border color
|
||||
*/
|
||||
public Color getPrimaryButtonBorder() {
|
||||
return primaryButtonBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the primary button border color.
|
||||
*
|
||||
* @param primaryButtonBorder the primary button border color
|
||||
*/
|
||||
public void setPrimaryButtonBorder(Color primaryButtonBorder) {
|
||||
this.primaryButtonBorder = primaryButtonBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the secondary button border color.
|
||||
*
|
||||
* @return the secondary button border color
|
||||
*/
|
||||
public Color getSecondaryButtonBorder() {
|
||||
return secondaryButtonBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the secondary button border color.
|
||||
*
|
||||
* @param secondaryButtonBorder the secondary button border color
|
||||
*/
|
||||
public void setSecondaryButtonBorder(Color secondaryButtonBorder) {
|
||||
this.secondaryButtonBorder = secondaryButtonBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error background color.
|
||||
*
|
||||
* @return the error background color
|
||||
*/
|
||||
public Color getErrorBackground() {
|
||||
return errorBackground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error background color.
|
||||
*
|
||||
* @param errorBackground the error background color
|
||||
*/
|
||||
public void setErrorBackground(Color errorBackground) {
|
||||
this.errorBackground = errorBackground;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error text color.
|
||||
*
|
||||
* @return the error text color
|
||||
*/
|
||||
public Color getErrorText() {
|
||||
return errorText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error text color.
|
||||
*
|
||||
* @param errorText the error text color
|
||||
*/
|
||||
public void setErrorText(Color errorText) {
|
||||
this.errorText = errorText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error border color.
|
||||
*
|
||||
* @return the error border color
|
||||
*/
|
||||
public Color getErrorBorder() {
|
||||
return errorBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error border color.
|
||||
*
|
||||
* @param errorBorder the error border color
|
||||
*/
|
||||
public void setErrorBorder(Color errorBorder) {
|
||||
this.errorBorder = errorBorder;
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -2,9 +2,8 @@ function Run {
|
||||
param(
|
||||
[string]$flag
|
||||
)
|
||||
javac *.java
|
||||
java App $flag
|
||||
rm *.class
|
||||
javac -d bin *.java
|
||||
java -cp bin App $flag
|
||||
}
|
||||
|
||||
if ($args.Length -eq 1) {
|
||||
|
||||
Regular → Executable
+30
-9
@@ -1,11 +1,32 @@
|
||||
run() {
|
||||
javac *.java
|
||||
java App $1
|
||||
rm *.class
|
||||
function run() {
|
||||
if [[ $# -eq 0 ]]; then
|
||||
# Compile the program
|
||||
javac -d bin *.java
|
||||
java -cp bin App
|
||||
else
|
||||
case $1 in
|
||||
"-c" | "--cli")
|
||||
# Run the program in the CLI
|
||||
# Compile the program
|
||||
javac -d bin *.java
|
||||
java -cp bin App -c
|
||||
;;
|
||||
|
||||
"-b" | "--build")
|
||||
# Compile the program
|
||||
javac -d bin *.java
|
||||
;;
|
||||
|
||||
"-j" | "--jar")
|
||||
# Create a JAR file
|
||||
jar cfe sudoku.jar App -C bin .
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Invalid argument"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
if($# -eq 1) then
|
||||
FLAG=$1
|
||||
fi
|
||||
|
||||
run $FLAG
|
||||
run $@
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user