Merge pull request #4 from SLCC-Programming-Club/board-gui-impl
GUI implementation
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
/bin/
|
||||
.classpath
|
||||
.project
|
||||
+21
-7
@@ -1,13 +1,17 @@
|
||||
// GUI imports
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import gui.Nav;
|
||||
import gui.Root;
|
||||
import gui.backend.Settings;
|
||||
import gui.backend.SudokuChecker;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
|
||||
// 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.
|
||||
@@ -15,9 +19,11 @@ import java.awt.BorderLayout;
|
||||
public class App extends JFrame {
|
||||
// GUI fields
|
||||
private Nav nav;
|
||||
private Board board;
|
||||
|
||||
// Data fields
|
||||
private Settings s;
|
||||
private SudokuChecker sc;
|
||||
|
||||
/**
|
||||
* Create a new App object, initializing the GUI and internal logic.
|
||||
@@ -25,8 +31,14 @@ public class App extends JFrame {
|
||||
public App() {
|
||||
super("Sudoku");
|
||||
s = new Settings();
|
||||
nav = new Nav(s, false);
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -67,7 +79,9 @@ public class App extends JFrame {
|
||||
*/
|
||||
private Root createApp() {
|
||||
Root root = new Root(s);
|
||||
root.setLayout(new BoxLayout(root, BoxLayout.Y_AXIS));
|
||||
root.add(nav);
|
||||
root.add(board);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
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.
+281
-4
@@ -1,10 +1,19 @@
|
||||
package gui;
|
||||
|
||||
// GUI imports
|
||||
import javax.swing.JPanel;
|
||||
import java.awt.GridLayout;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
// 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.
|
||||
@@ -12,6 +21,8 @@ import gui.backend.Settings;
|
||||
* 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
|
||||
@@ -20,27 +31,293 @@ import gui.backend.Settings;
|
||||
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 with the default styling from settings.
|
||||
* 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() {
|
||||
// TODO: Style the Board Panel.
|
||||
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() {
|
||||
// TODO: Create the board with the given grid.
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-4
@@ -1,14 +1,18 @@
|
||||
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 Settings s;
|
||||
private Theme theme;
|
||||
private Font font;
|
||||
|
||||
/**
|
||||
* Create a new ComboBox with the default styling and without initial items.
|
||||
@@ -19,7 +23,8 @@ public class ComboBox<E> extends JComboBox<E> {
|
||||
|
||||
public ComboBox(Settings s) {
|
||||
super();
|
||||
this.s = s;
|
||||
theme = s.getTheme();
|
||||
font = s.getFont();
|
||||
style();
|
||||
}
|
||||
|
||||
@@ -27,7 +32,8 @@ public class ComboBox<E> extends JComboBox<E> {
|
||||
* Set up the ComboBox with the appropriate styling.
|
||||
*/
|
||||
private void style() {
|
||||
// TODO: Style the ComboBox.
|
||||
setFont(s.getFont());
|
||||
setFont(font);
|
||||
setBackground(theme.getPrimaryBackground());
|
||||
setForeground(theme.getPrimaryText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
package gui;
|
||||
|
||||
import java.io.File;
|
||||
// GUI imports
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
|
||||
import gui.backend.Settings;
|
||||
|
||||
import javax.swing.JFileChooser;
|
||||
// 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 {
|
||||
private Settings s;
|
||||
|
||||
/**
|
||||
* Create a new FileChooser with the default directory set to the user's
|
||||
* Sudoku directory.
|
||||
@@ -21,9 +19,8 @@ public class FileChooser extends JFileChooser {
|
||||
* @param defaultDirectory
|
||||
* @param s
|
||||
*/
|
||||
public FileChooser(File defaultDirectory, Settings s) {
|
||||
public FileChooser(File defaultDirectory) {
|
||||
super(defaultDirectory);
|
||||
this.s = s;
|
||||
|
||||
for (File f: defaultDirectory.listFiles()) {
|
||||
ensureFileIsVisible(f);
|
||||
@@ -38,7 +35,6 @@ public class FileChooser extends JFileChooser {
|
||||
*/
|
||||
private void style() {
|
||||
// TODO: Style the FileChooser.
|
||||
setFont(s.getFont());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+164
-38
@@ -1,32 +1,55 @@
|
||||
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;
|
||||
|
||||
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.
|
||||
* 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;
|
||||
|
||||
/**
|
||||
@@ -35,6 +58,8 @@ public class Nav extends JPanel {
|
||||
*
|
||||
* 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();
|
||||
@@ -77,28 +102,86 @@ public class Nav extends JPanel {
|
||||
openFile(inputFilename);
|
||||
|
||||
// Otherwise, create the GUI for the Nav bar.
|
||||
} else createGUI();
|
||||
} 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 if the program is not being run in the command-line for
|
||||
* 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 Label class.
|
||||
elapsedTime.setFont(s.getFont());
|
||||
|
||||
// TODO: Make custom Button class.
|
||||
JButton solve = new JButton("Solve");
|
||||
solve.setFont(s.getFont()); // TODO: Make custom Button class.
|
||||
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);
|
||||
//add(elapsedTime);
|
||||
//add(solve);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,52 +195,80 @@ public class Nav extends JPanel {
|
||||
fileOptions.addItem("New");
|
||||
fileOptions.addItem("Open");
|
||||
fileOptions.addItem("Save");
|
||||
fileOptions.addItem("Save As");
|
||||
fileOptions.addItem("Exit");
|
||||
|
||||
// Add an ActionListener to the ComboBox to handle the user's selection.
|
||||
fileOptions.addActionListener(e -> {
|
||||
String selected = (String) fileOptions.getSelectedItem();
|
||||
int selected = fileOptions.getSelectedIndex();
|
||||
switch(selected) {
|
||||
case "New":
|
||||
case 0:
|
||||
newFile();
|
||||
break;
|
||||
case "Open":
|
||||
case 1:
|
||||
openFile();
|
||||
break;
|
||||
case "Save":
|
||||
saveFile();
|
||||
case 2:
|
||||
saveFile(false);
|
||||
break;
|
||||
case "Exit":
|
||||
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 JComboBox, perform the appropriate
|
||||
* 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();
|
||||
|
||||
// See Settings.getNewFileProperties for possible values and
|
||||
// the expected behavior of each value.
|
||||
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:
|
||||
}
|
||||
// TODO: Create a new .sdku file in the default directory.
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an existing .sdku file in the user's file system, using a GUI.
|
||||
* Open an existing .sdku file in the user's file system, 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
|
||||
@@ -166,7 +277,7 @@ public class Nav extends JPanel {
|
||||
|
||||
// Custom FileChooser class for style and to ensure the user can only
|
||||
// select .sdku files.
|
||||
FileChooser fc = new FileChooser(defaultDirectory, s);
|
||||
FileChooser fc = new FileChooser(defaultDirectory);
|
||||
int result = fc.showOpenDialog(this);
|
||||
|
||||
// If the user selects a file, open it.
|
||||
@@ -174,6 +285,7 @@ public class Nav extends JPanel {
|
||||
f = fc.getSelectedFile();
|
||||
createGrid(f);
|
||||
}
|
||||
b.setGrid(grid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,6 +296,7 @@ public class Nav extends JPanel {
|
||||
* @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()) {
|
||||
@@ -196,19 +309,37 @@ public class Nav extends JPanel {
|
||||
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.
|
||||
System.out.println("Opening the file " + filename + ".sdku.");
|
||||
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() {
|
||||
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.
|
||||
}
|
||||
@@ -226,8 +357,7 @@ public class Nav extends JPanel {
|
||||
* 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.
|
||||
* 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
|
||||
@@ -239,6 +369,7 @@ public class Nav extends JPanel {
|
||||
*/
|
||||
public void updateLoadedGrid(Cell[][] grid) {
|
||||
this.grid = grid;
|
||||
if(s.getAutoSave()) saveFile(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,12 +406,7 @@ public class Nav extends JPanel {
|
||||
|
||||
// If the file is not found, print an error message and return.
|
||||
} catch (FileNotFoundException e) {
|
||||
String filename = f.getName();
|
||||
System.err.println("An error occurred while reading the file " +
|
||||
filename + ".sdku."
|
||||
);
|
||||
|
||||
e.printStackTrace();
|
||||
newFile();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -3,6 +3,7 @@ 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.
|
||||
@@ -12,17 +13,16 @@ import gui.backend.Settings;
|
||||
* styling purposes.
|
||||
*/
|
||||
public class Root extends JPanel {
|
||||
private Settings s;
|
||||
private Theme theme;
|
||||
|
||||
/**
|
||||
* Create a new Root object with the default styling from settings.
|
||||
*
|
||||
* @param s
|
||||
*/
|
||||
public Root(Settings s) {
|
||||
public Root(Settings settings) {
|
||||
super();
|
||||
|
||||
this.s = s;
|
||||
this.theme = settings.getTheme();
|
||||
style();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ public class Root extends JPanel {
|
||||
* Set up the Root Panel with the appropriate styling.
|
||||
*/
|
||||
private void style() {
|
||||
// TODO: Style the Root Panel.
|
||||
setBackground(theme.getPrimaryBackground());
|
||||
setForeground(theme.getPrimaryText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package gui;
|
||||
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.
|
||||
@@ -11,6 +11,8 @@ public class Cell {
|
||||
private int col;
|
||||
private int box;
|
||||
private int value;
|
||||
private boolean initValue;
|
||||
|
||||
private List possibleValues;
|
||||
|
||||
/**
|
||||
@@ -41,6 +43,8 @@ public class Cell {
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
this.value = value;
|
||||
initValue = value != 0;
|
||||
|
||||
setBox();
|
||||
possibleValues = new List();
|
||||
}
|
||||
@@ -66,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;
|
||||
}
|
||||
|
||||
@@ -82,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.
|
||||
*
|
||||
@@ -99,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;
|
||||
@@ -114,6 +145,8 @@ public class Cell {
|
||||
* @param possibleValues
|
||||
*/
|
||||
public void setPossibleValues(int[] possibleValues) {
|
||||
if(initValue) return;
|
||||
|
||||
this.possibleValues = new List(possibleValues);
|
||||
}
|
||||
|
||||
@@ -133,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
|
||||
*/
|
||||
@@ -141,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.
|
||||
*/
|
||||
@@ -151,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 {
|
||||
|
||||
@@ -177,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.
|
||||
*
|
||||
+349
-84
@@ -18,10 +18,18 @@ import java.awt.FontFormatException;
|
||||
* system.
|
||||
*
|
||||
* (Currently) Configurable settings include:
|
||||
* - Default Directory
|
||||
* - New File Properties
|
||||
* - Font
|
||||
*
|
||||
* - 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;
|
||||
@@ -41,9 +49,33 @@ public class Settings {
|
||||
// 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.
|
||||
@@ -52,6 +84,8 @@ public class Settings {
|
||||
* 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();
|
||||
@@ -63,12 +97,116 @@ public class Settings {
|
||||
"/.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;
|
||||
}
|
||||
readSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,9 +237,11 @@ public class Settings {
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
*/
|
||||
@@ -174,105 +314,230 @@ public class Settings {
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean getResizable() {
|
||||
public boolean getResizable() {
|
||||
return resizable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resizable setting and write it to the settings file.
|
||||
*
|
||||
* @param resizable
|
||||
*/
|
||||
public void setResizable(boolean resizable) {
|
||||
/**
|
||||
* Set the resizable setting and write it to the settings file.
|
||||
*
|
||||
* @param resizable
|
||||
*/
|
||||
public void setResizable(boolean resizable) {
|
||||
this.resizable = resizable;
|
||||
updateSettingsFile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open and write the settings to the settings file.
|
||||
* 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.
|
||||
*
|
||||
* This method is called whenever a setting is updated.
|
||||
* By default, the cell GUI starts in value mode, ie. returns false.
|
||||
* Set to true to start in notes mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private void updateSettingsFile() {
|
||||
// TODO: Write the settings to the settings file.
|
||||
}
|
||||
public boolean getCellGUIStartMode() {
|
||||
return cellGUIStartMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default settings for the application and write them to the
|
||||
* settings file.
|
||||
* Set the cell GUI start mode and write it to the settings file.
|
||||
*
|
||||
* @param cellGUIStartMode
|
||||
*/
|
||||
private void defaultSettings() {
|
||||
// Setup the default directory for .sdku puzzle files.
|
||||
defaultDirectory = appDirectory + "puzzles";
|
||||
File dir = new File(defaultDirectory);
|
||||
|
||||
if(!dir.exists() || !dir.isDirectory())
|
||||
dir.mkdirs();
|
||||
|
||||
newFileProperties = 0;
|
||||
|
||||
// Load the font from the system directory.
|
||||
if(loadFont("HackNerdFont-Regular") == 0)
|
||||
font = new Font("Hack Nerd Font", Font.PLAIN, 12);
|
||||
|
||||
// If the font does not exist, use Arial as the default font.
|
||||
else
|
||||
font = new Font("Arial", Font.PLAIN, 12);
|
||||
|
||||
dimension = new Dimension(750, 550);
|
||||
resizable = false;
|
||||
public void setCellGUIStartMode(boolean cellGUIStartMode) {
|
||||
this.cellGUIStartMode = cellGUIStartMode;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the settings from the settings file.
|
||||
* 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.
|
||||
*
|
||||
* If the settings file does not exist, the default settings will be used
|
||||
* to populate the Settings file.
|
||||
* NOTE: The value 2 is not yet supported.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
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.
|
||||
}
|
||||
public int getDefaultOpenState() {
|
||||
return defaultOpenState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the font from the resources folder in the user's system directory.
|
||||
* Set the default open state for the application and write it to the
|
||||
* settings file.
|
||||
*
|
||||
* @return int 0 if successful, 1 if the file does not exist.
|
||||
* @param defaultOpenState
|
||||
*/
|
||||
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
|
||||
fontFilepath = "/usr/share/fonts/" + fontName + ".ttf";
|
||||
public void setDefaultOpenState(int defaultOpenState) {
|
||||
this.defaultOpenState = defaultOpenState;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
// Register the font with the GraphicsEnvironment.
|
||||
try {
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.
|
||||
getLocalGraphicsEnvironment();
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
ge.registerFont(Font.createFont(
|
||||
Font.TRUETYPE_FONT,
|
||||
new File(fontFilepath)
|
||||
));
|
||||
/**
|
||||
* Set the theme and write it to the settings file.
|
||||
*
|
||||
* @param theme
|
||||
*/
|
||||
public void setTheme(Theme theme) {
|
||||
this.theme = theme;
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
return 0;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
} catch(IOException | FontFormatException e) {
|
||||
System.out.println("Filepath of font not found: " +
|
||||
fontFilepath + " does not exist.");
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
+422
-308
@@ -1,343 +1,457 @@
|
||||
package gui.backend;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import gui.Cell;
|
||||
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.
|
||||
* 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.
|
||||
* 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[][] 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.
|
||||
*
|
||||
* 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();
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Get the solution to the Sudoku puzzle.
|
||||
*
|
||||
* @return Cell[][]
|
||||
*/
|
||||
public Cell[][] getSolution() {
|
||||
return 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
return intersection;
|
||||
}
|
||||
// Check the column
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (grid[i][col].getValue() == value && i != row)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
intersection = intersection(
|
||||
intersection,
|
||||
getBoxRemainingNumbers(row, col)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(intersection.size() == 0) {
|
||||
return;
|
||||
} else if(intersection.size() == 1) {
|
||||
int value = intersection.get(0);
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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));
|
||||
}
|
||||
// Replace intersection with union?
|
||||
ArrayList<Integer> intersection = intersection(getRowRemainingNumbers(row),
|
||||
getColRemainingNumbers(col));
|
||||
|
||||
grid[row][col].setPossibleValues(
|
||||
arrayListToArray(availableNumbers)
|
||||
);
|
||||
}
|
||||
}
|
||||
intersection = intersection(intersection, getBoxRemainingNumbers(row, col));
|
||||
|
||||
/**
|
||||
* 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();
|
||||
// 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));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
grid[row][col].setPossibleValues(arrayListToArray(availableNumbers));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.grid = grid;
|
||||
return grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 intersection;
|
||||
}
|
||||
|
||||
return remainingNumbers;
|
||||
}
|
||||
/**
|
||||
* 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));
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
intersection = intersection(intersection, getBoxRemainingNumbers(row, col));
|
||||
|
||||
return remainingNumbers;
|
||||
}
|
||||
if (intersection.size() == 0)
|
||||
return;
|
||||
else if (intersection.size() == 1) {
|
||||
int value = intersection.get(0);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
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));
|
||||
|
||||
return remainingNumbers;
|
||||
}
|
||||
grid[row][col].setPossibleValues(arrayListToArray(availableNumbers));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Solve the Sudoku puzzle.
|
||||
*
|
||||
* @param grid
|
||||
*/
|
||||
private boolean solve() {
|
||||
return solve(0, 0);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
|
||||
return 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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
-5
@@ -2,11 +2,8 @@ function Run {
|
||||
param(
|
||||
[string]$flag
|
||||
)
|
||||
javac *.java
|
||||
java App $flag
|
||||
Remove-Item *.class
|
||||
Remove-Item gui/*.class
|
||||
Remove-Item gui/backend/*.class
|
||||
javac -d bin *.java
|
||||
java -cp bin App $flag
|
||||
}
|
||||
|
||||
if ($args.Length -eq 1) {
|
||||
|
||||
Regular → Executable
+30
-11
@@ -1,13 +1,32 @@
|
||||
run() {
|
||||
javac *.java
|
||||
java App $1
|
||||
rm *.class
|
||||
rm gui/*.class
|
||||
rm gui/backend/*.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