wrote thorough documentation and made minor fixes for highlighting/selection

This commit is contained in:
Josh Ashton
2024-03-25 13:10:31 -06:00
parent 0409a8f41d
commit 600c215b7d
12 changed files with 625 additions and 376 deletions
+4 -2
View File
@@ -3,13 +3,15 @@ import javax.swing.BoxLayout;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.UIManager; import javax.swing.UIManager;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
// Project imports // Project imports
import gui.*; import gui.*;
import gui.backend.*; 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 * Runner class for the Sudoku App, managing the GUI and coordinating
* internal logic. * internal logic.
+178 -88
View File
@@ -1,17 +1,16 @@
package gui; package gui;
import java.awt.Dimension; // GUI imports
import javax.swing.JPanel;
import java.awt.GridLayout; import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.event.KeyAdapter; // Event & action imports
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.KeyListener; import java.awt.event.KeyListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import javax.swing.JPanel; // Processing & backend imports
import javax.swing.Box;
import gui.backend.Cell; import gui.backend.Cell;
import gui.backend.Settings; import gui.backend.Settings;
import gui.backend.SudokuChecker; import gui.backend.SudokuChecker;
@@ -22,6 +21,8 @@ import gui.backend.SudokuChecker;
* It contains all other related GUI components, and is the main JPanel for * It contains all other related GUI components, and is the main JPanel for
* the board. Accessible information from the Board includes: * the board. Accessible information from the Board includes:
* - the current state of the board, including filled values and notes; * - the current state of the board, including filled values and notes;
*
* TODO: Future features:
* - the current difficulty; * - the current difficulty;
* - the current time since the puzzle was started; * - the current time since the puzzle was started;
* - the current hints available; and * - the current hints available; and
@@ -36,9 +37,41 @@ public class Board extends JPanel {
private CellGUI selected; 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 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) { public Board(Settings s, Cell[][] grid, SudokuChecker sc) {
super(new GridLayout(9, 9)); super(new GridLayout(9, 9));
@@ -51,48 +84,6 @@ public class Board extends JPanel {
createBoard(); createBoard();
} }
private KeyListener createKeyListener() {
return new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {}
@Override
public void keyPressed(KeyEvent e) {}
@Override
public void keyReleased(KeyEvent e) {
char key = e.getKeyChar();
if(selected.cell.isInitValue()) return;
// Backspace key code is 0
if(e.getKeyCode() == e.VK_BACK_SPACE) {
selected.removeValue();
selected.repaint();
selected.revalidate();
return;
}
if(!Character.isDigit(key)) return;
if(!selected.isInNotesMode()) {
if(s.getAutoCheckValues()) {
int row = selected.cell.getRow();
int col = selected.cell.getCol();
int expected = solvedGrid[row][col].getValue();
selected.setValue(Character.getNumericValue(key), expected);
} else {
selected.setValue(Character.getNumericValue(key));
}
} else {
selected.addPossibleValue(Character.getNumericValue(key));
}
selected.repaint();
selected.revalidate();
}
};
}
/** /**
* Get the grid of the Board. * Get the grid of the Board.
* *
@@ -137,6 +128,8 @@ public class Board extends JPanel {
* @param cell * @param cell
*/ */
private void select(Cell 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++) { for(int n = 0; n < 9; n++) {
int row = cell.getRow(); int row = cell.getRow();
int col = cell.getCol(); int col = cell.getCol();
@@ -148,18 +141,22 @@ public class Board extends JPanel {
(gridGUI[row][n].cell.getBox() == (gridGUI[row][n].cell.getBox() ==
cell.getBox()); cell.getBox());
// Handling edge cases where the column or row are in the box.
if(colInSameBox && !rowInSameBox) { if(colInSameBox && !rowInSameBox) {
gridGUI[row][n].select(); gridGUI[row][n].select();
continue; continue;
} else if(!colInSameBox && rowInSameBox) { } else if(!colInSameBox && rowInSameBox) {
gridGUI[n][col].select(); gridGUI[n][col].select();
continue; continue;
// Skip the cell if it is in the same box.
} else if(colInSameBox && rowInSameBox) continue; } else if(colInSameBox && rowInSameBox) continue;
gridGUI[n][col].select(); gridGUI[n][col].select();
gridGUI[row][n].select(); gridGUI[row][n].select();
} }
// Select every cell in the box.
int boxRow = cell.getRow() / 3; int boxRow = cell.getRow() / 3;
int boxCol = cell.getCol() / 3; int boxCol = cell.getCol() / 3;
for(int n = 0; n < 3; n++) { for(int n = 0; n < 3; n++) {
@@ -177,6 +174,12 @@ public class Board extends JPanel {
/** /**
* Create the Board Panel with the appropriate cells. * 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() { private void createBoard() {
if(s.getAutoFillNotes()) grid = sc.getPossibleValues(grid); if(s.getAutoFillNotes()) grid = sc.getPossibleValues(grid);
@@ -184,50 +187,137 @@ public class Board extends JPanel {
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) { for(int j = 0; j < 9; j++) {
CellGUI cell = new CellGUI( // Create a new CellGUI object with the appropriate Cell object.
grid[i][j], CellGUI cell = new CellGUI(grid[i][j], s);
s
);
cell.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
// Left click
if(e.getButton() == 1) {
if(selected != null) select(selected.cell);
// Deselect the cell if it is already selected.
if(selected == cell) {
selected = null;
return;
}
select(cell.cell); // Add a MouseListener to the cell.
selected = cell; cell.addMouseListener(createMouseListener(cell));
selected.requestFocusInWindow();
selected.addKeyListener(createKeyListener());
} 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();
}
});
// Add the CellGUI object to the Board Panel.
gridGUI[i][j] = cell; gridGUI[i][j] = cell;
add(gridGUI[i][j]); add(gridGUI[i][j]);
} }
} }
} }
/**
* 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) {
if(selected == null) cell.select();
}
@Override
public void mouseExited(MouseEvent e) {
if(selected == null) 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();
}
};
}
} }
+65 -56
View File
@@ -1,14 +1,14 @@
package gui; package gui;
import java.awt.BorderLayout; // GUI imports
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Font; import java.awt.Font;
import java.awt.GridLayout; import java.awt.GridLayout;
import javax.swing.JLabel; // Processing & backend imports
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
import gui.backend.Cell; import gui.backend.Cell;
import gui.backend.Settings; import gui.backend.Settings;
import gui.backend.Theme; import gui.backend.Theme;
@@ -18,20 +18,22 @@ import gui.backend.Theme;
*/ */
class CellGUI extends JPanel { class CellGUI extends JPanel {
// GUI fields // GUI fields
private JPanel internalPanel = new JPanel(new BorderLayout()); private GridLayout noteLayout = new GridLayout(3, 3);
private GridLayout noteLayout = new GridLayout(3, 3); private GridLayout valueLayout = new GridLayout(0, 1);
private GridLayout valueLayout = new GridLayout(0, 1); private JPanel internalPanel = new JPanel(valueLayout);
private Dimension size; private JLabel valueLabel;
private JLabel valueLabel; private Label[] notesLabels = new Label[9];
private Note[] notesLabels = new Note[9];
// GUI data fields read from a Settings object.
private Dimension size;
private Theme theme; private Theme theme;
private Font font; private Font font;
private boolean incorrect;
// Data fields // Data fields
Cell cell; Cell cell;
private boolean noteMode; // true to display notes, or display value private boolean noteMode; // true to display notes, or display value
private boolean selected = false; // true if the cell is selected 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. * Create a new CellGUI object with the given single cell.
@@ -52,9 +54,10 @@ class CellGUI extends JPanel {
selected = false; selected = false;
size = s.getCellDimensions(); size = s.getCellDimensions();
// Set the layout and size of the cell.
setFocusable(true); setFocusable(true);
setLayout(valueLayout); setLayout(valueLayout);
valueLabel = new Note("", theme, font); valueLabel = new Label("", theme, font);
setSize(size); setSize(size);
internalPanel.setSize(size); internalPanel.setSize(size);
defaultStyle(); defaultStyle();
@@ -71,25 +74,6 @@ class CellGUI extends JPanel {
add(internalPanel); add(internalPanel);
} }
/**
* Get the size of the cell.
*/
public Dimension getSize() {
return size;
}
/**
* Set the size of the cell.
*/
public void setSize(Dimension size) {
this.size = size;
super.setSize(size);
internalPanel.setSize(size);
valueLabel.setSize(size);
for(int i = 0; i < 9; i++)
if(notesLabels[i] != null) notesLabels[i].setSize(size);
}
/** /**
* Get the value of the underlying Cell object. * Get the value of the underlying Cell object.
* *
@@ -105,11 +89,12 @@ class CellGUI extends JPanel {
* @param value * @param value
*/ */
public void setValue(int value) { public void setValue(int value) {
if(!selected) return;
cell.setValue(value); cell.setValue(value);
// Update the GUI with the actual Cell value (unchanged if invalid) // Update the GUI with the actual Cell value (unchanged if invalid)
valueLabel.setText(Integer.toString(cell.getValue())); valueLabel.setText(Integer.toString(cell.getValue()));
internalPanel.repaint(); refresh();
internalPanel.revalidate();
} }
/** /**
@@ -128,10 +113,7 @@ class CellGUI extends JPanel {
if(value != expected) { if(value != expected) {
incorrect = true; incorrect = true;
errorStyle(); errorStyle();
} } else refresh();
internalPanel.repaint();
internalPanel.revalidate();
} }
/** /**
@@ -146,9 +128,6 @@ class CellGUI extends JPanel {
incorrect = false; incorrect = false;
highlightedStyle(); highlightedStyle();
internalPanel.repaint();
internalPanel.revalidate();
} }
/** /**
@@ -204,10 +183,7 @@ class CellGUI extends JPanel {
generateNotes(true); generateNotes(true);
} }
internalPanel.repaint(); refresh();
internalPanel.revalidate();
repaint();
revalidate();
noteMode = !noteMode; noteMode = !noteMode;
} }
@@ -219,8 +195,6 @@ class CellGUI extends JPanel {
selected = !selected; selected = !selected;
if(incorrect) { if(incorrect) {
errorStyle(); errorStyle();
repaint();
revalidate();
return; return;
} }
@@ -228,13 +202,12 @@ class CellGUI extends JPanel {
defaultStyle(); defaultStyle();
else highlightedStyle(); else highlightedStyle();
repaint();
revalidate();
} }
/** /**
* Style the cell with the appropriate colors from the theme. * Style the CellGUI with the primary colors from the Theme.
*
* @see Theme.java
*/ */
private void defaultStyle() { private void defaultStyle() {
setBackground(theme.getPrimaryBackground()); setBackground(theme.getPrimaryBackground());
@@ -250,10 +223,14 @@ class CellGUI extends JPanel {
notesLabels[i].setForeground(theme.getPrimaryText()); notesLabels[i].setForeground(theme.getPrimaryText());
} }
} }
refresh();
} }
/** /**
* Style the cell with the appropriate colors from the theme. * Style the CellGUI with the secondary colors from the Theme.
*
* @see Theme.java
*/ */
private void highlightedStyle() { private void highlightedStyle() {
setBackground(theme.getSecondaryBackground()); setBackground(theme.getSecondaryBackground());
@@ -269,10 +246,14 @@ class CellGUI extends JPanel {
notesLabels[i].setForeground(theme.getSecondaryText()); notesLabels[i].setForeground(theme.getSecondaryText());
} }
} }
refresh();
} }
/** /**
* Style the cell with the appropriate colors from the theme. * Style the CellGUI with the error colors from the Theme.
*
* @see Theme.java
*/ */
private void errorStyle() { private void errorStyle() {
setBackground(theme.getErrorBackground()); setBackground(theme.getErrorBackground());
@@ -288,18 +269,42 @@ class CellGUI extends JPanel {
notesLabels[i].setForeground(theme.getErrorText()); notesLabels[i].setForeground(theme.getErrorText());
} }
} }
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. * Create the GUI components for the cell's notes.
* *
* If autoFill is true, then the CellGUI will automatically switch to
* noteMode.
*
* @param autoFill * @param autoFill
*/ */
private void generateNotes(boolean autoFill) { private void generateNotes(boolean autoFill) {
int[] possibleValues = cell.getPossibleValues(); int[] possibleValues = cell.getPossibleValues();
// Prepare the internalPanel for noteMode.
if(autoFill) {
internalPanel.removeAll();
noteMode = true;
}
// Handle cases where there are no possible values stored.
if(possibleValues.length == 0) { if(possibleValues.length == 0) {
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {
notesLabels[i] = new Note("", theme, font); notesLabels[i] = new Label("", theme, font);
if(autoFill) internalPanel.add(notesLabels[i]); if(autoFill) internalPanel.add(notesLabels[i]);
} }
return; return;
@@ -308,11 +313,15 @@ class CellGUI extends JPanel {
int index = 0; int index = 0;
// Add the noted possible values to the cell. // Add the noted possible values to the cell.
for(int i = 1; i <= 9 && index < possibleValues.length; i++) { for(int i = 1; i <= 9 && index < possibleValues.length; i++) {
// Add the possible value if it exists.
if(possibleValues[index] == i) { if(possibleValues[index] == i) {
notesLabels[i - 1] = new Note(Integer.toString(i), theme, font); notesLabels[i - 1] = new Label(Integer.toString(i), theme, font);
index++; index++;
// Otherwise, add a blank value.
} else } else
notesLabels[i - 1] = new Note("", theme, font); notesLabels[i - 1] = new Label("", theme, font);
if(autoFill) internalPanel.add(notesLabels[i - 1]); if(autoFill) internalPanel.add(notesLabels[i - 1]);
} }
} }
+10 -4
View File
@@ -1,14 +1,18 @@
package gui; package gui;
import java.awt.Font;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import gui.backend.Settings; import gui.backend.Settings;
import gui.backend.Theme;
/** /**
* A custom JComboBox with the appropriate styling for the Sudoku app. * A custom JComboBox with the appropriate styling for the Sudoku app.
*/ */
public class ComboBox<E> extends JComboBox<E> { 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. * 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) { public ComboBox(Settings s) {
super(); super();
this.s = s; theme = s.getTheme();
font = s.getFont();
style(); style();
} }
@@ -27,7 +32,8 @@ public class ComboBox<E> extends JComboBox<E> {
* Set up the ComboBox with the appropriate styling. * Set up the ComboBox with the appropriate styling.
*/ */
private void style() { private void style() {
// TODO: Style the ComboBox. setFont(font);
setFont(s.getFont()); setBackground(theme.getPrimaryBackground());
setForeground(theme.getPrimaryText());
} }
} }
+5 -9
View File
@@ -1,19 +1,17 @@
package gui; package gui;
import java.io.File; // GUI imports
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileFilter;
import gui.backend.Settings; // Processing & backend imports
import java.io.File;
import javax.swing.JFileChooser;
/** /**
* A custom FileChooser with the appropriate styling for the Sudoku app. * A custom FileChooser with the appropriate styling for the Sudoku app.
* Additionally, this FileChooser only allows .sdku files to be selected. * Additionally, this FileChooser only allows .sdku files to be selected.
*/ */
public class FileChooser extends JFileChooser { public class FileChooser extends JFileChooser {
private Settings s;
/** /**
* Create a new FileChooser with the default directory set to the user's * Create a new FileChooser with the default directory set to the user's
* Sudoku directory. * Sudoku directory.
@@ -21,9 +19,8 @@ public class FileChooser extends JFileChooser {
* @param defaultDirectory * @param defaultDirectory
* @param s * @param s
*/ */
public FileChooser(File defaultDirectory, Settings s) { public FileChooser(File defaultDirectory) {
super(defaultDirectory); super(defaultDirectory);
this.s = s;
for (File f: defaultDirectory.listFiles()) { for (File f: defaultDirectory.listFiles()) {
ensureFileIsVisible(f); ensureFileIsVisible(f);
@@ -38,7 +35,6 @@ public class FileChooser extends JFileChooser {
*/ */
private void style() { private void style() {
// TODO: Style the FileChooser. // TODO: Style the FileChooser.
setFont(s.getFont());
} }
/** /**
+3 -3
View File
@@ -11,9 +11,9 @@ import gui.backend.Theme;
* The Note class represents a single note of a cell in the Sudoku board. * 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 * This is the GUI abstraction for the underlying Cell object, and is used to
* display and manage the note for a cell. * display and manage the note or value for a cell.
*/ */
public class Note extends JLabel { public class Label extends JLabel {
private Theme t; private Theme t;
private Font f; private Font f;
@@ -23,7 +23,7 @@ public class Note extends JLabel {
* @param text * @param text
* @param t * @param t
*/ */
public Note(String text, Theme t, Font f) { public Label(String text, Theme t, Font f) {
super(text, SwingConstants.CENTER); super(text, SwingConstants.CENTER);
this.t = t; this.t = t;
this.f = f; this.f = f;
+106 -38
View File
@@ -1,36 +1,53 @@
package gui; package gui;
// File IO imports
import java.util.Scanner; import java.util.Scanner;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
// GUI imports
import javax.swing.JPanel; import javax.swing.JPanel;
// Processing & backend imports
import gui.backend.Cell; import gui.backend.Cell;
import gui.backend.Settings; import gui.backend.Settings;
import gui.backend.SudokuChecker; import gui.backend.SudokuChecker;
import javax.swing.JLabel; // Unused imports
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 represents the Navigation bar at the top of the JFrame window.
* *
* The Nav class is responsible for basic File IO operations. The most basic * The Nav class is responsible for basic File IO operations. The most basic
* functionality is the following: * 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 * - open an existing .sdku file in the user's file system (a standard
* or default location is TBD); * 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, * NOTE: Needed features
* such as the difficulty of the current problem, providing hints, displaying * - default open state from Settings
* the length of time since a puzzle was started, etc. * - 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 { public class Nav extends JPanel {
private Settings s; @SuppressWarnings("unused")
private SudokuChecker sc; private SudokuChecker sc;
private Settings s;
private File f; private File f;
private Board b; private Board b;
private Cell[][] grid; private Cell[][] grid;
@@ -41,6 +58,8 @@ public class Nav extends JPanel {
* *
* Additionally, even for debugging, this should not be used since default * Additionally, even for debugging, this should not be used since default
* file I/O behavior is dictated by the values in the Settings object. * file I/O behavior is dictated by the values in the Settings object.
*
* @Deprecated
*/ */
public Nav() { public Nav() {
super(); super();
@@ -120,13 +139,19 @@ public class Nav extends JPanel {
* Set the SudokuChecker object for the Nav bar. * Set the SudokuChecker object for the Nav bar.
* *
* This is necessary for the Nav bar to be able to interact with the * This is necessary for the Nav bar to be able to interact with the
* SudokuChecker object for the solve button. This shouldn't be necessary * SudokuChecker object for the solve button.
* to change once the program is running. *
* 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) { public void setChecker(SudokuChecker sc) {
this.sc = sc; this.sc = sc;
} }
/**
* Style the Nav with the primary colors from Theme.
*/
private void style() { private void style() {
setBackground(s.getTheme().getPrimaryBackground()); setBackground(s.getTheme().getPrimaryBackground());
setForeground(s.getTheme().getPrimaryText()); setForeground(s.getTheme().getPrimaryText());
@@ -136,21 +161,27 @@ public class Nav extends JPanel {
* Create the GUI for the Nav bar. * Create the GUI for the Nav bar.
* *
* This method is called when the Nav object is created in the App * 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. * debugging purposes.
*/ */
private void createGUI() { private void createGUI() {
style(); style();
/*
// TODO: Make custom Label class.
JLabel elapsedTime = new JLabel("Elapsed Time: 00:00:00"); 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"); 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 // Add the ComboBox for the file options, the elapsed time label, and
// the solve button to the Nav Panel. // the solve button to the Nav Panel.
add(createFileOptions()); add(createFileOptions());
add(elapsedTime); //add(elapsedTime);
add(solve); //add(solve);
} }
/** /**
@@ -164,6 +195,7 @@ public class Nav extends JPanel {
fileOptions.addItem("New"); fileOptions.addItem("New");
fileOptions.addItem("Open"); fileOptions.addItem("Open");
fileOptions.addItem("Save"); fileOptions.addItem("Save");
fileOptions.addItem("Save As");
fileOptions.addItem("Exit"); fileOptions.addItem("Exit");
fileOptions.addActionListener(e -> { fileOptions.addActionListener(e -> {
@@ -176,39 +208,67 @@ public class Nav extends JPanel {
openFile(); openFile();
break; break;
case 2: case 2:
saveFile(); saveFile(false);
break; break;
case 3: case 3:
saveFile(true);
break;
case 4:
System.exit(0); System.exit(0);
break; break;
default: default:
System.out.println("Invalid selection."); System.out.println("Invalid selection.");
} }
}); });
return fileOptions; 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. * action.
*
* See Settings.getNewFileProperties() for more information on the expected
* actions.
*
* @see Settings.java
*/ */
private void newFile() { private void newFile() {
int defaultOption = s.getNewFileProperties(); int defaultOption = s.getNewFileProperties();
// See Settings.getNewFileProperties for possible values and
// the expected behavior of each value.
switch(defaultOption) { switch(defaultOption) {
case 0: 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 1:
case 2: case 2:
default: 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() { private void openFile() {
// No error handling is needed here, since the Settings class ensures // No error handling is needed here, since the Settings class ensures
@@ -217,7 +277,7 @@ public class Nav extends JPanel {
// Custom FileChooser class for style and to ensure the user can only // Custom FileChooser class for style and to ensure the user can only
// select .sdku files. // select .sdku files.
FileChooser fc = new FileChooser(defaultDirectory, s); FileChooser fc = new FileChooser(defaultDirectory);
int result = fc.showOpenDialog(this); int result = fc.showOpenDialog(this);
// If the user selects a file, open it. // If the user selects a file, open it.
@@ -263,8 +323,22 @@ public class Nav extends JPanel {
/** /**
* Save the currently open .sdku file with any changes the user has made. * 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. * 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: Get the current Cell[][] from the Board JPanel
// TODO: Write to a file. // TODO: Write to a file.
} }
@@ -282,9 +356,8 @@ public class Nav extends JPanel {
* Update the loaded grid with the current version with any changes the * Update the loaded grid with the current version with any changes the
* user has made. * user has made.
* *
* For future auto-save functionality, this could simply be called after * If the user has enabled auto-save in Settings, then save the file.
* each change the user makes on the Sudoku board. *
*
* In the future, an optimization could be made where a different data * 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 * structure is used to pass only the changes the player has made within
* the last x minutes, for eg. {row, col, value, noteValues[]}. Given * the last x minutes, for eg. {row, col, value, noteValues[]}. Given
@@ -295,6 +368,7 @@ public class Nav extends JPanel {
*/ */
public void updateLoadedGrid(Cell[][] grid) { public void updateLoadedGrid(Cell[][] grid) {
this.grid = grid; this.grid = grid;
if(s.getAutoSave()) saveFile(true);
} }
/** /**
@@ -331,13 +405,7 @@ public class Nav extends JPanel {
// If the file is not found, print an error message and return. // If the file is not found, print an error message and return.
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
String filename = f.getName(); newFile();
System.err.println("An error occurred while reading the file " +
filename + ".sdku."
);
e.printStackTrace();
grid = new Cell[9][9];
return; return;
} }
} }
+6 -6
View File
@@ -1,9 +1,9 @@
package gui; package gui;
import java.awt.LayoutManager;
import javax.swing.JPanel; import javax.swing.JPanel;
import gui.backend.Settings; import gui.backend.Settings;
import gui.backend.Theme;
/** /**
* The Root class represents the root JPanel for the Sudoku app. * The Root class represents the root JPanel for the Sudoku app.
@@ -13,17 +13,16 @@ import gui.backend.Settings;
* styling purposes. * styling purposes.
*/ */
public class Root extends JPanel { public class Root extends JPanel {
private Settings s; private Theme theme;
/** /**
* Create a new Root object with the default styling from settings. * Create a new Root object with the default styling from settings.
* *
* @param s * @param s
*/ */
public Root(Settings s) { public Root(Settings settings) {
super(); super();
this.theme = settings.getTheme();
this.s = s;
style(); style();
} }
@@ -31,6 +30,7 @@ public class Root extends JPanel {
* Set up the Root Panel with the appropriate styling. * Set up the Root Panel with the appropriate styling.
*/ */
private void style() { private void style() {
// TODO: Style the Root Panel. setBackground(theme.getPrimaryBackground());
setForeground(theme.getPrimaryText());
} }
} }
-14
View File
@@ -230,20 +230,6 @@ public class Cell {
max = 0; max = 0;
min = 0; 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. * Create a new List with the given initial values.
+166 -85
View File
@@ -18,10 +18,18 @@ import java.awt.FontFormatException;
* system. * system.
* *
* (Currently) Configurable settings include: * (Currently) Configurable settings include:
* - Default Directory * - Default puzzles directory for .sdku files;
* - New File Properties * - New file Properties;
* - Font * - 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 { public class Settings {
private final String os; private final String os;
@@ -62,6 +70,12 @@ public class Settings {
// Auto check values // Auto check values
private boolean autoCheckValues; 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 * Create a new Settings object, initializing the default settings
* or reading in the settings from settings file if it exists. * or reading in the settings from settings file if it exists.
@@ -70,6 +84,8 @@ public class Settings {
* the settings file is located. The settings file is always located in * the settings file is located. The settings file is always located in
* either "~/.config/sudoku/" or "%APPDATA%/Local/Sudoku/", depending * either "~/.config/sudoku/" or "%APPDATA%/Local/Sudoku/", depending
* on the user's operating system. * on the user's operating system.
*
* The settings file is settings.json.
*/ */
public Settings() { public Settings() {
os = System.getProperty("os.name").toLowerCase(); os = System.getProperty("os.name").toLowerCase();
@@ -81,12 +97,114 @@ public class Settings {
"/.config/sudoku/"; "/.config/sudoku/";
File dir = new File(appDirectory); 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()) { if (!dir.exists() || !dir.isDirectory()) {
dir.mkdirs(); dir.mkdirs();
defaultSettings(); 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 = false;
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
fontFilepath = "/usr/share/fonts/" + 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();
} }
/** /**
@@ -117,9 +235,11 @@ public class Settings {
/** /**
* New File Properties is a configurable setting, where the following * New File Properties is a configurable setting, where the following
* values are allowable: * values are allowable:
* 0 - Load the last opened .sdku file, default behavior * 0 - Create a new, blank .sdku file
* 1 - Create a new, blank .sdku file * 1 - Create a new, random .sdku file
* 2 - Create a new, random .sdku file *
* Integer is being used instead of boolean since additional properties
* may be added in the future.
* *
* @return int * @return int
*/ */
@@ -364,97 +484,58 @@ public class Settings {
} }
/** /**
* Open and write the settings to the settings file. * 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.
* *
* This method is called whenever a setting is updated. * @return boolean
*/ */
private void updateSettingsFile() { public boolean getAutoSave() {
// TODO: Write the settings to the settings file. return autoSave;
} }
/** /**
* Set the default settings for the application and write them to the * Set the auto-save feature to a specified state.
* settings file. *
* @param autoSave
*/ */
private void defaultSettings() { public void setAutoSave(boolean autoSave) {
// Setup the default directory for .sdku puzzle files. this.autoSave = autoSave;
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, 16);
// If the font does not exist, use Arial as the default font.
else
font = new Font("Arial", Font.PLAIN, 16);
dimension = new Dimension(600, 800);
resizable = false;
cellGUIStartMode = false;
defaultOpenState = 0;
theme = new Theme(new File(appDirectory + "default.theme"));
autoFillNotes = false;
autoCheckValues = true;
setCellDimensions();
updateSettingsFile(); updateSettingsFile();
} }
/** /**
* Read the settings from the settings file. * The auto save frequnecy, in seconds.
* *
* If the settings file does not exist, the default settings will be used * By default, auto-save will save whenever the user makes changes on the
* to populate the Settings file. * 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
*/ */
private void readSettings() { public int getAutoSaveFrequency() {
File settingsFile = new File(appDirectory + "/settings.json"); return autoSaveFrequency;
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. * Set the auto-save frequency, in seconds.
* *
* @return int 0 if successful, 1 if the file does not exist. * @param autoSaveFrequency
*/ */
private int loadFont(String fontName) { public void setAutoSaveFrequency(int autoSaveFrequency) {
// Load the font from the system directory. this.autoSaveFrequency = autoSaveFrequency;
String fontFilepath; updateSettingsFile();
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";
// 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;
}
} }
} }
+77 -57
View File
@@ -12,6 +12,8 @@ import java.util.ArrayList;
*/ */
public class SudokuChecker { public class SudokuChecker {
private Cell[][] grid; private Cell[][] grid;
private Cell[][] origGrid;
/** /**
* Create a new SudokuChecker object, initializing the grid to the given * Create a new SudokuChecker object, initializing the grid to the given
@@ -19,13 +21,60 @@ public class SudokuChecker {
* inputs a value, or be used to check the validity of a puzzle when the * inputs a value, or be used to check the validity of a puzzle when the
* user requests it. * user requests it.
* *
* TODO: Currently, this class is not used in the GUI. It is only used in
* the command-line interface.
*
* @param grid * @param grid
*/ */
public SudokuChecker(Cell[][] grid) { public SudokuChecker(Cell[][] grid) {
this.grid = grid; this.grid = grid;
// Create a copy of the original grid to be used for resetting the
// grid to its original state.
origGrid = new Cell[9][9];
for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) {
origGrid[i][j] = new Cell(i, j, grid[i][j].getValue());
}
}
}
/**
* Check if the given value can be placed in the given cell of the grid.
*
* False means that the value is incorrect, and true means that the value
* is correct.
*
* @param row
* @param col
* @param value
* @return boolean
*/
public boolean checkValue(int row, int col, int value) {
// Check the row
for(int i = 0; i < 9; i++) {
if(grid[row][i].getValue() == value && i != col)
return false;
}
// Check the column
for(int i = 0; i < 9; i++) {
if(grid[i][col].getValue() == value && i != row)
return false;
}
// Check the box
int boxRow = row / 3;
int boxCol = col / 3;
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
if(
grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == value &&
(boxRow * 3 + i != row || boxCol * 3 + j != col)
) {
return false;
}
}
}
return true;
} }
/** /**
@@ -91,39 +140,13 @@ public class SudokuChecker {
) { ) {
ArrayList<Integer> intersection = new ArrayList<Integer>(); ArrayList<Integer> intersection = new ArrayList<Integer>();
for(int i = 0; i < a.size(); i++) { for(int i = 0; i < a.size(); i++) {
if(b.contains(a.get(i))) { if(b.contains(a.get(i)))
intersection.add(a.get(i)); intersection.add(a.get(i));
}
} }
return intersection; return intersection;
} }
/**
* Determine what numbers are available to be placed in the given cell of
* the grid. This is effectively an union of the numbers available in the
* row, column, and box of the cell.
*
* @param row
* @param col
*/
private ArrayList<Integer> union(
ArrayList<Integer> a,
ArrayList<Integer> b
) {
ArrayList<Integer> union = new ArrayList<Integer>();
for(int i = 0; i < a.size(); i++) {
union.add(a.get(i));
}
for(int i = 0; i < b.size(); i++) {
if(!union.contains(b.get(i))) {
union.add(b.get(i));
}
}
return union;
}
/** /**
* Determine what numbers are available to be placed in the given cell of * Determine what numbers are available to be placed in the given cell of
* the grid. This is effectively an intersection of the numbers available * the grid. This is effectively an intersection of the numbers available
@@ -145,9 +168,9 @@ public class SudokuChecker {
getBoxRemainingNumbers(row, col) getBoxRemainingNumbers(row, col)
); );
if(intersection.size() == 0) { if(intersection.size() == 0)
return; return;
} else if(intersection.size() == 1) { else if(intersection.size() == 1) {
int value = intersection.get(0); int value = intersection.get(0);
grid[row][col].setValue(value); grid[row][col].setValue(value);
@@ -156,9 +179,8 @@ public class SudokuChecker {
} else { } else {
// Join the arrays and find the intersection of the three arrays. // Join the arrays and find the intersection of the three arrays.
ArrayList<Integer> availableNumbers = new ArrayList<Integer>(); ArrayList<Integer> availableNumbers = new ArrayList<Integer>();
for(int i = 0; i < intersection.size(); i++) { for(int i = 0; i < intersection.size(); i++)
availableNumbers.add(intersection.get(i)); availableNumbers.add(intersection.get(i));
}
grid[row][col].setPossibleValues( grid[row][col].setPossibleValues(
arrayListToArray(availableNumbers) arrayListToArray(availableNumbers)
@@ -213,9 +235,8 @@ public class SudokuChecker {
while(!isValidSolution()) { while(!isValidSolution()) {
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) { for(int j = 0; j < 9; j++) {
if(grid[i][j].getValue() == 0) { if(grid[i][j].getValue() == 0)
getAvailableNumbers(i, j); getAvailableNumbers(i, j);
}
} }
} }
} }
@@ -239,9 +260,7 @@ public class SudokuChecker {
break; break;
} }
} }
if(!found) { if(!found) remainingNumbers.add(i);
remainingNumbers.add(i);
}
} }
return remainingNumbers; return remainingNumbers;
@@ -263,9 +282,8 @@ public class SudokuChecker {
break; break;
} }
} }
if(!found) { if(!found)
remainingNumbers.add(i); remainingNumbers.add(i);
}
} }
return remainingNumbers; return remainingNumbers;
@@ -293,9 +311,8 @@ public class SudokuChecker {
} }
} }
} }
if(!found) { if(!found)
remainingNumbers.add(i); remainingNumbers.add(i);
}
} }
return remainingNumbers; return remainingNumbers;
@@ -312,9 +329,9 @@ public class SudokuChecker {
*/ */
private int[] arrayListToArray(ArrayList<Integer> list) { private int[] arrayListToArray(ArrayList<Integer> list) {
int[] array = new int[list.size()]; int[] array = new int[list.size()];
for(int i = 0; i < list.size(); i++) { for(int i = 0; i < list.size(); i++)
array[i] = list.get(i); array[i] = list.get(i);
}
return array; return array;
} }
@@ -331,17 +348,19 @@ public class SudokuChecker {
// Check that every cell has a value between 1 and 9. // Check that every cell has a value between 1 and 9.
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) { for(int j = 0; j < 9; j++) {
if(grid[i][j].getValue() < 1 || grid[i][j].getValue() > 9) if(
return false; grid[i][j].getValue() < 1 ||
grid[i][j].getValue() > 9
) return false;
} }
} }
// Check that every row contains the numbers 1-9 exactly once. // Check that every row contains the numbers 1-9 exactly once.
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {
int[] row = new int[9]; int[] row = new int[9];
for(int j = 0; j < 9; j++) { for(int j = 0; j < 9; j++)
row[j] = grid[i][j].getValue(); row[j] = grid[i][j].getValue();
}
if(!isValidSet(row)) { if(!isValidSet(row)) {
System.out.println("Row " + i + " is invalid."); System.out.println("Row " + i + " is invalid.");
return false; return false;
@@ -351,9 +370,9 @@ public class SudokuChecker {
// Check that every column contains the numbers 1-9 exactly once. // Check that every column contains the numbers 1-9 exactly once.
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {
int[] col = new int[9]; int[] col = new int[9];
for(int j = 0; j < 9; j++) { for(int j = 0; j < 9; j++)
col[j] = grid[j][i].getValue(); col[j] = grid[j][i].getValue();
}
if(!isValidSet(col)) { if(!isValidSet(col)) {
System.out.println("Column " + i + " is invalid."); System.out.println("Column " + i + " is invalid.");
return false; return false;
@@ -365,9 +384,8 @@ public class SudokuChecker {
for(int j = 0; j < 3; j++) { for(int j = 0; j < 3; j++) {
int[] box = new int[9]; int[] box = new int[9];
for(int k = 0; k < 3; k++) { for(int k = 0; k < 3; k++) {
for(int l = 0; l < 3; l++) { for(int l = 0; l < 3; l++)
box[k * 3 + l] = grid[i * 3 + k][j * 3 + l].getValue(); box[k * 3 + l] = grid[i * 3 + k][j * 3 + l].getValue();
}
} }
if(!isValidSet(box)) { if(!isValidSet(box)) {
System.out.println( System.out.println(
@@ -393,13 +411,15 @@ public class SudokuChecker {
private boolean isValidSet(int[] set) { private boolean isValidSet(int[] set) {
boolean[] found = new boolean[9]; boolean[] found = new boolean[9];
for(int i = 0; i < 9; i++) { for(int i = 0; i < 9; i++) {
if(set[i] < 1 || set[i] > 9) { if(set[i] < 1 || set[i] > 9)
return false; return false;
} else if(found[set[i] - 1]) {
else if(found[set[i] - 1])
return false; return false;
} else {
else
found[set[i] - 1] = true; found[set[i] - 1] = true;
}
} }
return true; return true;
} }
+5 -14
View File
@@ -6,7 +6,8 @@ import java.io.File;
/** /**
* The Theme class represents the color scheme for the GUI components. * 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. * 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: * The Theme class provides the following color customizations:
* - primaryBackground * - primaryBackground
@@ -24,16 +25,7 @@ import java.io.File;
* - primaryButtonBorder * - primaryButtonBorder
* - secondaryButtonBorder * - secondaryButtonBorder
*/ */
import java.awt.Color;
import java.io.File;
/**
* The Theme class represents a theme for the GUI components.
* It provides colors for various GUI elements.
*/
public class Theme { public class Theme {
private File theme;
private Color primaryBackground; private Color primaryBackground;
private Color secondaryBackground; private Color secondaryBackground;
private Color primaryText; private Color primaryText;
@@ -58,16 +50,15 @@ public class Theme {
* *
* @param theme the theme file * @param theme the theme file
*/ */
public Theme(File theme) { public Theme(File file) {
this.theme = theme; readTheme(file);
readTheme();
} }
/** /**
* Reads the .theme file and sets the colors for the GUI components. * Reads the .theme file and sets the colors for the GUI components.
* Once complete, sets the theme for the GUI components. * Once complete, sets the theme for the GUI components.
*/ */
private void readTheme() { private void readTheme(File file) {
// TODO: Read the .theme file // TODO: Read the .theme file
setTheme(); setTheme();
} }