From e1d79f4e3796d6e34a639293ef2cc8aa69207e88 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Fri, 22 Mar 2024 08:32:09 -0600 Subject: [PATCH 01/16] basic board implementation, connected to file inputs from user. additionally expanded available settings and created structure for theming. --- src/App.java | 17 +- src/gui/Board.java | 165 ++++++++++++++++- src/gui/Nav.java | 39 +++- src/gui/backend/Settings.java | 81 +++++++-- src/gui/backend/Theme.java | 333 ++++++++++++++++++++++++++++++++++ 5 files changed, 614 insertions(+), 21 deletions(-) create mode 100644 src/gui/backend/Theme.java diff --git a/src/App.java b/src/App.java index 321b190..254f12a 100644 --- a/src/App.java +++ b/src/App.java @@ -1,13 +1,12 @@ +// GUI imports 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.*; + /** * Runner class for the Sudoku App, managing the GUI and coordinating * internal logic. @@ -15,6 +14,7 @@ import java.awt.BorderLayout; public class App extends JFrame { // GUI fields private Nav nav; + private Board board; // Data fields private Settings s; @@ -25,8 +25,10 @@ public class App extends JFrame { public App() { super("Sudoku"); s = new Settings(); - nav = new Nav(s, false); initSetup(); + nav = new Nav(s, false); + board = new Board(s, nav.getLoadedGrid()); + nav.setBoard(board); add(createApp()); } @@ -68,6 +70,7 @@ public class App extends JFrame { private Root createApp() { Root root = new Root(s); root.add(nav); + root.add(board); return root; } diff --git a/src/gui/Board.java b/src/gui/Board.java index 01f64fc..ee1b030 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -1,8 +1,10 @@ package gui; import java.awt.GridLayout; +import java.awt.LayoutManager; import javax.swing.JPanel; +import javax.swing.JLabel; import gui.backend.Settings; @@ -20,6 +22,7 @@ import gui.backend.Settings; public class Board extends JPanel { private Settings s; private Cell[][] grid; + private Cell selected; /** * Create a new Board object with the default styling from settings. @@ -31,6 +34,31 @@ public class Board extends JPanel { this.s = s; this.grid = grid; 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; + removeAll(); + createBoard(); + repaint(); + revalidate(); } /** @@ -41,6 +69,139 @@ public class Board extends JPanel { } private void createBoard() { - // TODO: Create the board with the given grid. + for(int i = 0; i < 9; i++) { + for(int j = 0; j < 9; j++) { + CellGUI cell = new CellGUI(grid[i][j], s.getCellGUIStartMode()); + add(cell); + } + } } -} + + /** + * GUI representation of a single cell in the Sudoku grid. + */ + private class CellGUI extends JPanel { + private static GridLayout noteLayout = new GridLayout(3, 3); + private static GridLayout valueLayout = new GridLayout(1, 1); + + private Cell cell; + private JLabel valueLabel; + private JLabel[] notesLabels; + private boolean notes; // true for notes, false for value + private boolean startInNotesOrValueMode; + + /** + * 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, boolean startInNotesOrValueMode) { + super(); + this.cell = cell; + this.startInNotesOrValueMode = startInNotesOrValueMode; + + // Populate the cell with the appropriate value or notes. + if(cell.getValue() == 0) { + generateNotes(); + + if(startInNotesOrValueMode) { + setLayout(noteLayout); + notes = true; + } else { + setLayout(valueLayout); + notes = false; + valueLabel = new JLabel(""); + add(valueLabel); + } + } else { + valueLabel = new JLabel(Integer.toString(cell.getValue())); + add(valueLabel); + } + } + + /** + * Get the value of the underlying Cell object. + * + * @return int + */ + public int getValue() { + return cell.getValue(); + } + + /** + * Add a possible value to the cell. + * + * @param value + */ + public void addPossibleValue(int value) { + cell.addPossibleValue(value); + } + + /** + * Remove a possible value from the cell. + * + * @return + */ + public void removePossibleValue(int value) { + cell.removePossibleValue(value); + } + + /** + * Get the possible values of the underlying Cell object. + * + * @return int[] + */ + public int[] getPossibleValues() { + return cell.getPossibleValues(); + } + + /** + * Toggle the mode of the cell between value and note, including the + * layout of the cell. + */ + public void setNoteMode() { + if(notes) { + removeAll(); + setLayout(valueLayout); + repaint(); + revalidate(); + + add(valueLabel); + } else { + removeAll(); + setLayout(noteLayout); + repaint(); + revalidate(); + + generateNotes(); + } + + notes = !notes; + } + + /** + * Create the GUI components for the cell. + */ + private void generateNotes() { + if(cell.getPossibleValues().length == 0) { + notesLabels = new JLabel[9]; + return; + } + + // Add the noted possible values to the cell. + int index = 0; + for(int i = 1; i <= 9; i++) { + if(cell.getPossibleValues()[index] == i) { + notesLabels[index] = new JLabel(Integer.toString(i)); + index++; + } else { + notesLabels[index] = new JLabel(""); + } + add(notesLabels[index]); + } + } + } +} \ No newline at end of file diff --git a/src/gui/Nav.java b/src/gui/Nav.java index ed0b07c..8d8f63f 100644 --- a/src/gui/Nav.java +++ b/src/gui/Nav.java @@ -27,6 +27,7 @@ import javax.swing.JButton; public class Nav extends JPanel { private Settings s; private File f; + private Board b; private Cell[][] grid; /** @@ -77,8 +78,37 @@ 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; } /** @@ -174,6 +204,7 @@ public class Nav extends JPanel { f = fc.getSelectedFile(); createGrid(f); } + b.setGrid(grid); } /** @@ -196,6 +227,11 @@ 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; } @@ -281,6 +317,7 @@ public class Nav extends JPanel { ); e.printStackTrace(); + grid = new Cell[9][9]; return; } } diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index 3ccc88a..764213e 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -44,6 +44,12 @@ public class Settings { // Resizable setting private boolean resizable; + // Cell GUI Start Mode + private boolean cellGUIStartMode; + + // Default open state for the application. + private int defaultOpenState; + /** * Create a new Settings object, initializing the default settings * or reading in the settings from settings file if it exists. @@ -174,28 +180,78 @@ 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(); - } + } + + /** + * The Cell GUI Start Mode is a configurable setting, where the user can + * specify whether the cell GUI should start in notes mode or value mode. + * + * By default, the cell GUI starts in value mode, ie. returns false. + * Set to true to start in notes mode. + * + * @return boolean + */ + public boolean getCellGUIStartMode() { + return cellGUIStartMode; + } + + /** + * Set the cell GUI start mode and write it to the settings file. + * + * @param cellGUIStartMode + */ + public void setCellGUIStartMode(boolean cellGUIStartMode) { + this.cellGUIStartMode = cellGUIStartMode; + updateSettingsFile(); + } + + /** + * The default open state for the application. The following values + * are accepted: + * 0 - Open the default easy.sdku file, default behavior; + * 1 - Open the last opened .sdku file; + * 2 - Open a new, blank .sdku file; and + * 3 - Open a new, random .sdku file. + * + * NOTE: The value 2 is not yet supported. + * + * @return + */ + public int getDefaultOpenState() { + return defaultOpenState; + } + + /** + * Set the default open state for the application and write it to the + * settings file. + * + * @param defaultOpenState + */ + public void setDefaultOpenState(int defaultOpenState) { + this.defaultOpenState = defaultOpenState; + updateSettingsFile(); + } /** * Open and write the settings to the settings file. * * This method is called whenever a setting is updated. */ - private void updateSettingsFile() { + private void updateSettingsFile() { // TODO: Write the settings to the settings file. - } + } /** * Set the default settings for the application and write them to the @@ -221,6 +277,9 @@ public class Settings { dimension = new Dimension(750, 550); resizable = false; + cellGUIStartMode = false; + defaultOpenState = 0; + updateSettingsFile(); } diff --git a/src/gui/backend/Theme.java b/src/gui/backend/Theme.java new file mode 100644 index 0000000..c848bd7 --- /dev/null +++ b/src/gui/backend/Theme.java @@ -0,0 +1,333 @@ +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. + * + * The Theme class provides the following color customizations: + * - primaryBackground + * - secondaryBackground + * - primaryText + * - secondaryText + * - primaryHighlight + * - secondaryHighlight + * - primaryBorder + * - secondaryBorder + * - primaryButton + * - secondaryButton + * - primaryButtonHighlight + * - secondaryButtonHighlight + * - primaryButtonBorder + * - 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 { + 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; + + /** + * 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 theme) { + this.theme = theme; + readTheme(); + } + + /** + * Reads the .theme file and sets the colors for the GUI components. + * Once complete, sets the theme for the GUI components. + */ + private void readTheme() { + // TODO: Read the .theme file + setTheme(); + } + + /** + * Sets the theme for the GUI components. + */ + private void setTheme() { + primaryBackground = Color.WHITE; + secondaryBackground = Color.LIGHT_GRAY; + primaryText = Color.BLACK; + secondaryText = Color.DARK_GRAY; + } + + /** + * 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; + } + + private File theme; +} From 37442180400ac85630d3722fe9aa6008c6ee33b0 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Fri, 22 Mar 2024 11:23:28 -0600 Subject: [PATCH 02/16] added built-in check to the Cell data class to ensure initial values cannot be overridden by the user (even accidentally). --- src/App.java | 9 ++- src/gui/Board.java | 105 +++++++++++++++++++---------- src/gui/Cell.java | 12 +++- src/gui/Nav.java | 19 ++++++ src/gui/Note.java | 35 ++++++++++ src/gui/Root.java | 1 + src/gui/backend/Settings.java | 67 ++++++++++++++++++ src/gui/backend/SudokuChecker.java | 40 ++++++++++- src/gui/backend/Theme.java | 5 +- 9 files changed, 253 insertions(+), 40 deletions(-) create mode 100644 src/gui/Note.java diff --git a/src/App.java b/src/App.java index 254f12a..e792983 100644 --- a/src/App.java +++ b/src/App.java @@ -1,4 +1,5 @@ // GUI imports +import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.UIManager; import java.awt.BorderLayout; @@ -18,6 +19,7 @@ public class App extends JFrame { // Data fields private Settings s; + private SudokuChecker sc; /** * Create a new App object, initializing the GUI and internal logic. @@ -27,8 +29,12 @@ public class App extends JFrame { s = new Settings(); initSetup(); nav = new Nav(s, false); - board = new Board(s, nav.getLoadedGrid()); + sc = new SudokuChecker(nav.getLoadedGrid()); + board = new Board(s, nav.getLoadedGrid(), sc); + nav.setBoard(board); + nav.setChecker(sc); + add(createApp()); } @@ -69,6 +75,7 @@ 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); diff --git a/src/gui/Board.java b/src/gui/Board.java index ee1b030..23ce8e0 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -4,9 +4,12 @@ import java.awt.GridLayout; import java.awt.LayoutManager; import javax.swing.JPanel; +import javax.swing.border.LineBorder; import javax.swing.JLabel; import gui.backend.Settings; +import gui.backend.SudokuChecker; +import gui.backend.Theme; /** * The Board class represents the Sudoku board in the GUI. @@ -22,6 +25,7 @@ import gui.backend.Settings; public class Board extends JPanel { private Settings s; private Cell[][] grid; + private SudokuChecker sc; private Cell selected; /** @@ -29,10 +33,11 @@ public class Board extends JPanel { * * @param s */ - public Board(Settings s, Cell[][] grid) { + public Board(Settings s, Cell[][] grid, SudokuChecker sc) { super(new GridLayout(9, 9)); this.s = s; this.grid = grid; + this.sc = sc; style(); createBoard(); } @@ -65,13 +70,19 @@ public class Board extends JPanel { * 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. + */ private void createBoard() { + if(s.getAutoFillNotes()) grid = sc.getPossibleValues(grid); + for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { - CellGUI cell = new CellGUI(grid[i][j], s.getCellGUIStartMode()); + CellGUI cell = new CellGUI(grid[i][j], s.getCellGUIStartMode() || s.getAutoFillNotes(), s.getTheme()); add(cell); } } @@ -81,14 +92,15 @@ public class Board extends JPanel { * GUI representation of a single cell in the Sudoku grid. */ private class CellGUI extends JPanel { - private static GridLayout noteLayout = new GridLayout(3, 3); - private static GridLayout valueLayout = new GridLayout(1, 1); + private GridLayout noteLayout = new GridLayout(3, 3); + private GridLayout valueLayout = new GridLayout(1, 1); + private Theme theme; private Cell cell; private JLabel valueLabel; - private JLabel[] notesLabels; + private Note[] notesLabels = new Note[9]; private boolean notes; // true for notes, false for value - private boolean startInNotesOrValueMode; + private boolean cellGUIStartMode; /** * Create a new CellGUI object with the given single cell. @@ -98,24 +110,18 @@ public class Board extends JPanel { * @param cell * @param startInNotesOrValueMode */ - public CellGUI(Cell cell, boolean startInNotesOrValueMode) { + public CellGUI(Cell cell, boolean notes, Theme theme) { super(); this.cell = cell; - this.startInNotesOrValueMode = startInNotesOrValueMode; + this.notes = notes; + this.theme = theme; + style(); // Populate the cell with the appropriate value or notes. if(cell.getValue() == 0) { - generateNotes(); - - if(startInNotesOrValueMode) { - setLayout(noteLayout); - notes = true; - } else { - setLayout(valueLayout); - notes = false; - valueLabel = new JLabel(""); - add(valueLabel); - } + notes = !notes; + valueLabel = new JLabel(""); + setNoteMode(); } else { valueLabel = new JLabel(Integer.toString(cell.getValue())); add(valueLabel); @@ -131,6 +137,25 @@ public class Board extends JPanel { return cell.getValue(); } + /** + * Set the value of the underlying Cell object. + * + * @param value + */ + public void setValue(int value) { + cell.setValue(value); + valueLabel.setText(Integer.toString(value)); + } + + /** + * Set the possible values of the underlying Cell object. + * + * @param value + */ + public void setPossibleValues(int[] values) { + cell.setPossibleValues(values); + } + /** * Add a possible value to the cell. * @@ -163,44 +188,54 @@ public class Board extends JPanel { * layout of the cell. */ public void setNoteMode() { + removeAll(); if(notes) { - removeAll(); setLayout(valueLayout); - repaint(); - revalidate(); add(valueLabel); } else { - removeAll(); setLayout(noteLayout); - repaint(); - revalidate(); generateNotes(); + for(int i = 0; i < 9; i++) + add(notesLabels[i]); } + repaint(); + revalidate(); notes = !notes; } + /** + * Style the cell with the appropriate colors from the theme. + */ + private void style() { + setBackground(theme.getPrimaryBackground()); + setForeground(theme.getPrimaryText()); + setBorder(new LineBorder(theme.getPrimaryBorder(), 2)); + } + /** * Create the GUI components for the cell. */ private void generateNotes() { if(cell.getPossibleValues().length == 0) { - notesLabels = new JLabel[9]; + for(int i = 0; i < 9; i++) { + notesLabels[i] = new Note("", theme); + add(notesLabels[i]); + } return; } + // Add the noted possible values to the cell. - int index = 0; for(int i = 1; i <= 9; i++) { - if(cell.getPossibleValues()[index] == i) { - notesLabels[index] = new JLabel(Integer.toString(i)); - index++; - } else { - notesLabels[index] = new JLabel(""); - } - add(notesLabels[index]); + if(cell.getPossibleValues()[i - 1] == i) + notesLabels[i - 1] = new Note(Integer.toString(i), theme); + else + notesLabels[i - 1] = new Note("", theme); + + add(notesLabels[i - 1]); } } } diff --git a/src/gui/Cell.java b/src/gui/Cell.java index 5f72672..d8c5539 100644 --- a/src/gui/Cell.java +++ b/src/gui/Cell.java @@ -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(); } @@ -69,6 +73,8 @@ public class Cell { * @param value */ public void setValue(int value) { + if(initValue) return; + possibleValues.clear(); this.value = value; } @@ -99,6 +105,8 @@ public class Cell { * @param value */ public void addPossibleValue(int value) { + if(initValue) return; + if(possibleValues.contains(value)) { return; } else if(value < 1 || value > 9) { @@ -114,6 +122,8 @@ public class Cell { * @param possibleValues */ public void setPossibleValues(int[] possibleValues) { + if(initValue) return; + this.possibleValues = new List(possibleValues); } @@ -140,7 +150,7 @@ public class Cell { public int[] getPossibleValues() { return possibleValues.toArray(); } - + /** * Given the cell's row and column, set the box number for the cell. */ diff --git a/src/gui/Nav.java b/src/gui/Nav.java index 8d8f63f..f15d0d4 100644 --- a/src/gui/Nav.java +++ b/src/gui/Nav.java @@ -6,6 +6,7 @@ import java.io.FileNotFoundException; import javax.swing.JPanel; import gui.backend.Settings; +import gui.backend.SudokuChecker; import javax.swing.JLabel; import javax.swing.JButton; @@ -26,6 +27,7 @@ import javax.swing.JButton; */ public class Nav extends JPanel { private Settings s; + private SudokuChecker sc; private File f; private Board b; private Cell[][] grid; @@ -111,6 +113,22 @@ public class Nav extends JPanel { 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. This shouldn't be necessary + * to change once the program is running. + */ + public void setChecker(SudokuChecker sc) { + this.sc = sc; + } + + private void style() { + setBackground(s.getTheme().getPrimaryBackground()); + setForeground(s.getTheme().getPrimaryText()); + } + /** * Create the GUI for the Nav bar. * @@ -119,6 +137,7 @@ public class Nav extends JPanel { * debugging purposes. */ private void createGUI() { + style(); JLabel elapsedTime = new JLabel("Elapsed Time: 00:00:00"); elapsedTime.setFont(s.getFont()); // TODO: Make custom Label class. JButton solve = new JButton("Solve"); diff --git a/src/gui/Note.java b/src/gui/Note.java new file mode 100644 index 0000000..79f057b --- /dev/null +++ b/src/gui/Note.java @@ -0,0 +1,35 @@ +package gui; + +import javax.swing.JLabel; + +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 for a cell. + */ +public class Note extends JLabel { + private Theme t; + + /** + * Create a new Note object with the given text and theme. + * + * @param text + * @param t + */ + public Note(String text, Theme t) { + super(text); + this.t = t; + style(); + } + + /** + * Style the Note component with the current theme. + */ + private void style() { + setBackground(t.getPrimaryBackground()); + setForeground(t.getPrimaryText()); + } +} diff --git a/src/gui/Root.java b/src/gui/Root.java index 376a45d..c24104d 100644 --- a/src/gui/Root.java +++ b/src/gui/Root.java @@ -1,5 +1,6 @@ package gui; +import java.awt.LayoutManager; import javax.swing.JPanel; import gui.backend.Settings; diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index 764213e..778b620 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -50,6 +50,12 @@ public class Settings { // 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; + /** * Create a new Settings object, initializing the default settings * or reading in the settings from settings file if it exists. @@ -244,6 +250,65 @@ public class Settings { updateSettingsFile(); } + /** + * The Theme object stores the colors for the GUI components. + * + * The available colors for customization are: + * - primaryBackground + * - secondaryBackground + * - primaryText + * - secondaryText + * - primaryHighlight + * - secondaryHighlight + * - primaryBorder + * - secondaryBorder + * - primaryButton + * - secondaryButton + * - primaryButtonHighlight + * - secondaryButtonHighlight + * - primaryButtonBorder + * - secondaryButtonBorder + * + * @see Theme + * @return + */ + public Theme getTheme() { + return theme; + } + + /** + * Set the theme and write it to the settings file. + * + * @param theme + */ + public void setTheme(Theme theme) { + this.theme = theme; + updateSettingsFile(); + } + + /** + * The Auto-fill notes setting is a configurable setting, where the user + * can specify whether notes should be automatically filled in when a + * value is placed in a cell. + * + * By default, the auto-fill notes setting is set to false. + * + * @return boolean + */ + public boolean getAutoFillNotes() { + return autoFillNotes; + } + + /** + * Set the auto-fill notes setting and write it to the settings file. + * + * @param autoFillNotes + */ + public void setAutoFillNotes(boolean autoFillNotes) { + this.autoFillNotes = autoFillNotes; + updateSettingsFile(); + } + /** * Open and write the settings to the settings file. * @@ -279,6 +344,8 @@ public class Settings { resizable = false; cellGUIStartMode = false; defaultOpenState = 0; + theme = new Theme(new File(appDirectory + "default.theme")); + autoFillNotes = true; updateSettingsFile(); } diff --git a/src/gui/backend/SudokuChecker.java b/src/gui/backend/SudokuChecker.java index 10af2a6..7cf8c86 100644 --- a/src/gui/backend/SudokuChecker.java +++ b/src/gui/backend/SudokuChecker.java @@ -28,7 +28,44 @@ public class SudokuChecker { */ public SudokuChecker(Cell[][] grid) { this.grid = grid; - solve(); + } + + /** + * 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; + + ArrayList intersection = intersection( + getRowRemainingNumbers(row), + getColRemainingNumbers(col) + ); + + intersection = intersection( + intersection, + getBoxRemainingNumbers(row, col) + ); + + // Join the arrays and find the intersection of the three arrays + ArrayList availableNumbers = new ArrayList(); + for(int i = 0; i < intersection.size(); i++) { + availableNumbers.add(intersection.get(i)); + } + + grid[row][col].setPossibleValues( + arrayListToArray(availableNumbers) + ); + } + } + + return grid; } /** @@ -37,6 +74,7 @@ public class SudokuChecker { * @return Cell[][] */ public Cell[][] getSolution() { + solve(); return grid; } diff --git a/src/gui/backend/Theme.java b/src/gui/backend/Theme.java index c848bd7..7dad6d4 100644 --- a/src/gui/backend/Theme.java +++ b/src/gui/backend/Theme.java @@ -71,10 +71,11 @@ public class Theme { * Sets the theme for the GUI components. */ private void setTheme() { - primaryBackground = Color.WHITE; + primaryBackground = Color.LIGHT_GRAY; secondaryBackground = Color.LIGHT_GRAY; - primaryText = Color.BLACK; + primaryText = Color.DARK_GRAY; secondaryText = Color.DARK_GRAY; + primaryBorder = Color.BLACK; } /** From 5e3c4151a64abb64a979981a243fba804146e3bf Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Fri, 22 Mar 2024 11:26:36 -0600 Subject: [PATCH 03/16] added built-in check to the Cell data class to ensure initial values cannot be overridden by the user (even accidentally). --- src/gui/Board.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/gui/Board.java b/src/gui/Board.java index 23ce8e0..4dcd212 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -99,8 +99,7 @@ public class Board extends JPanel { private Cell cell; private JLabel valueLabel; private Note[] notesLabels = new Note[9]; - private boolean notes; // true for notes, false for value - private boolean cellGUIStartMode; + private boolean noteMode; // true to display notes, or display value /** * Create a new CellGUI object with the given single cell. @@ -110,16 +109,16 @@ public class Board extends JPanel { * @param cell * @param startInNotesOrValueMode */ - public CellGUI(Cell cell, boolean notes, Theme theme) { + public CellGUI(Cell cell, boolean noteMode, Theme theme) { super(); this.cell = cell; - this.notes = notes; + this.noteMode = noteMode; this.theme = theme; style(); // Populate the cell with the appropriate value or notes. if(cell.getValue() == 0) { - notes = !notes; + noteMode = !noteMode; // Flip for use with setNoteMode() valueLabel = new JLabel(""); setNoteMode(); } else { @@ -186,10 +185,12 @@ public class Board extends JPanel { /** * Toggle the mode of the cell between value and note, including the * layout of the cell. + * + * If the current mode is value, switch to note mode, and vice versa. */ public void setNoteMode() { removeAll(); - if(notes) { + if(noteMode) { setLayout(valueLayout); add(valueLabel); @@ -199,11 +200,13 @@ public class Board extends JPanel { generateNotes(); for(int i = 0; i < 9; i++) add(notesLabels[i]); + + System.out.println("Displaying notes."); } repaint(); revalidate(); - notes = !notes; + noteMode = !noteMode; } /** From fa3dbf32e213fff8e004db3b479fb80f52dc4122 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Sat, 23 Mar 2024 22:15:15 -0600 Subject: [PATCH 04/16] added toggle-able notes and and basic button functionality to cell's --- src/gui/Board.java | 81 +++++++++++++++++++----------- src/gui/Nav.java | 1 - src/gui/Note.java | 3 +- src/gui/backend/Settings.java | 5 +- src/gui/backend/SudokuChecker.java | 29 ++++++++++- 5 files changed, 85 insertions(+), 34 deletions(-) diff --git a/src/gui/Board.java b/src/gui/Board.java index 4dcd212..446d490 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -1,9 +1,12 @@ package gui; +import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.LayoutManager; +import java.awt.event.MouseListener; import javax.swing.JPanel; +import javax.swing.SwingConstants; import javax.swing.border.LineBorder; import javax.swing.JLabel; @@ -83,6 +86,21 @@ public class Board extends JPanel { for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { CellGUI cell = new CellGUI(grid[i][j], s.getCellGUIStartMode() || s.getAutoFillNotes(), s.getTheme()); + cell.addMouseListener(new MouseListener() { + @Override + public void mouseClicked(java.awt.event.MouseEvent e) { + cell.setNoteMode(); + } + @Override + public void mousePressed(java.awt.event.MouseEvent e) {} + @Override + public void mouseReleased(java.awt.event.MouseEvent e) {} + @Override + public void mouseEntered(java.awt.event.MouseEvent e) {} + @Override + public void mouseExited(java.awt.event.MouseEvent e) {} + + }); add(cell); } } @@ -92,13 +110,16 @@ public class Board extends JPanel { * GUI representation of a single cell in the Sudoku grid. */ private class CellGUI extends JPanel { - private GridLayout noteLayout = new GridLayout(3, 3); - private GridLayout valueLayout = new GridLayout(1, 1); - private Theme theme; - - private Cell cell; + // GUI fields + private JPanel internalPanel = new JPanel(new BorderLayout()); + private GridLayout noteLayout = new GridLayout(3, 3); + private GridLayout valueLayout = new GridLayout(0, 1); private JLabel valueLabel; private Note[] notesLabels = new Note[9]; + private Theme theme; + + // Data fields + private Cell cell; private boolean noteMode; // true to display notes, or display value /** @@ -112,19 +133,22 @@ public class Board extends JPanel { public CellGUI(Cell cell, boolean noteMode, Theme theme) { super(); this.cell = cell; - this.noteMode = noteMode; + this.noteMode = !noteMode; // Flip for use with setNoteMode() this.theme = theme; + setLayout(valueLayout); style(); // Populate the cell with the appropriate value or notes. if(cell.getValue() == 0) { - noteMode = !noteMode; // Flip for use with setNoteMode() valueLabel = new JLabel(""); - setNoteMode(); } else { - valueLabel = new JLabel(Integer.toString(cell.getValue())); - add(valueLabel); + valueLabel = new JLabel( + Integer.toString(cell.getValue()), + SwingConstants.CENTER + ); } + setNoteMode(); + add(internalPanel); } /** @@ -143,7 +167,8 @@ public class Board extends JPanel { */ public void setValue(int value) { cell.setValue(value); - valueLabel.setText(Integer.toString(value)); + // Update the GUI with the actual Cell value (unchanged if invalid) + valueLabel.setText(Integer.toString(cell.getValue())); } /** @@ -189,23 +214,20 @@ public class Board extends JPanel { * If the current mode is value, switch to note mode, and vice versa. */ public void setNoteMode() { - removeAll(); + internalPanel.removeAll(); if(noteMode) { - setLayout(valueLayout); - - add(valueLabel); + internalPanel.setLayout(valueLayout); + internalPanel.add(valueLabel); } else { - setLayout(noteLayout); - + internalPanel.setLayout(noteLayout); generateNotes(); - for(int i = 0; i < 9; i++) - add(notesLabels[i]); - - System.out.println("Displaying notes."); } + internalPanel.repaint(); + internalPanel.revalidate(); repaint(); revalidate(); + noteMode = !noteMode; } @@ -222,23 +244,24 @@ public class Board extends JPanel { * Create the GUI components for the cell. */ private void generateNotes() { - if(cell.getPossibleValues().length == 0) { + int[] possibleValues = cell.getPossibleValues(); + if(possibleValues.length == 0) { for(int i = 0; i < 9; i++) { notesLabels[i] = new Note("", theme); - add(notesLabels[i]); + internalPanel.add(notesLabels[i]); } return; } - + int index = 0; // Add the noted possible values to the cell. - for(int i = 1; i <= 9; i++) { - if(cell.getPossibleValues()[i - 1] == i) + for(int i = 1; i <= 9 && index < possibleValues.length; i++) { + if(possibleValues[index] == i) { notesLabels[i - 1] = new Note(Integer.toString(i), theme); - else + index++; + } else notesLabels[i - 1] = new Note("", theme); - - add(notesLabels[i - 1]); + internalPanel.add(notesLabels[i - 1]); } } } diff --git a/src/gui/Nav.java b/src/gui/Nav.java index f15d0d4..35fa451 100644 --- a/src/gui/Nav.java +++ b/src/gui/Nav.java @@ -255,7 +255,6 @@ public class Nav extends JPanel { } // Otherwise, open the file and populate the grid. - System.out.println("Opening the file " + filename + ".sdku."); createGrid(f); } diff --git a/src/gui/Note.java b/src/gui/Note.java index 79f057b..42e70d9 100644 --- a/src/gui/Note.java +++ b/src/gui/Note.java @@ -1,6 +1,7 @@ package gui; import javax.swing.JLabel; +import javax.swing.SwingConstants; import gui.backend.Theme; @@ -20,7 +21,7 @@ public class Note extends JLabel { * @param t */ public Note(String text, Theme t) { - super(text); + super(text, SwingConstants.CENTER); this.t = t; style(); } diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index 778b620..b3b7e49 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -306,6 +306,7 @@ public class Settings { */ public void setAutoFillNotes(boolean autoFillNotes) { this.autoFillNotes = autoFillNotes; + setCellGUIStartMode(autoFillNotes); updateSettingsFile(); } @@ -340,9 +341,9 @@ public class Settings { else font = new Font("Arial", Font.PLAIN, 12); - dimension = new Dimension(750, 550); + dimension = new Dimension(1000, 1000); resizable = false; - cellGUIStartMode = false; + cellGUIStartMode = true; defaultOpenState = 0; theme = new Theme(new File(appDirectory + "default.theme")); autoFillNotes = true; diff --git a/src/gui/backend/SudokuChecker.java b/src/gui/backend/SudokuChecker.java index 7cf8c86..be25be7 100644 --- a/src/gui/backend/SudokuChecker.java +++ b/src/gui/backend/SudokuChecker.java @@ -40,9 +40,10 @@ public class SudokuChecker { 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) + if(grid[row][col].getValue() != 0) continue; + // Replace intersection with union? ArrayList intersection = intersection( getRowRemainingNumbers(row), getColRemainingNumbers(col) @@ -65,6 +66,7 @@ public class SudokuChecker { } } + this.grid = grid; return grid; } @@ -99,6 +101,31 @@ public class SudokuChecker { 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 union( + ArrayList a, + ArrayList b + ) { + ArrayList union = new ArrayList(); + 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 * the grid. This is effectively an intersection of the numbers available From 1afd170feac07105a20eac06b127866cc2758bf1 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Sun, 24 Mar 2024 12:10:06 -0600 Subject: [PATCH 05/16] added deselection support, though still buggy. additionally, altered default theme for easier viewing and for aesthetics --- src/gui/Board.java | 106 ++++++++++++++++++++++++++++++++-- src/gui/Cell.java | 40 +++++++++++-- src/gui/backend/Settings.java | 2 +- src/gui/backend/Theme.java | 11 ++-- 4 files changed, 144 insertions(+), 15 deletions(-) diff --git a/src/gui/Board.java b/src/gui/Board.java index 446d490..52840cb 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -8,6 +8,7 @@ import java.awt.event.MouseListener; import javax.swing.JPanel; import javax.swing.SwingConstants; import javax.swing.border.LineBorder; +import javax.swing.Box; import javax.swing.JLabel; import gui.backend.Settings; @@ -28,8 +29,10 @@ import gui.backend.Theme; public class Board extends JPanel { private Settings s; private Cell[][] grid; + private CellGUI[][] gridGUI; + private Box[][] boxGUI = new Box[3][3]; private SudokuChecker sc; - private Cell selected; + private CellGUI selected; /** * Create a new Board object with the default styling from settings. @@ -82,14 +85,72 @@ public class Board extends JPanel { */ private void createBoard() { if(s.getAutoFillNotes()) grid = sc.getPossibleValues(grid); + gridGUI = new CellGUI[9][9]; for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { - CellGUI cell = new CellGUI(grid[i][j], s.getCellGUIStartMode() || s.getAutoFillNotes(), s.getTheme()); + CellGUI cell = new CellGUI( + grid[i][j], + s.getCellGUIStartMode(), + s.getTheme() + ); cell.addMouseListener(new MouseListener() { @Override public void mouseClicked(java.awt.event.MouseEvent e) { - cell.setNoteMode(); + // If a cell is already selected, deselect it and + // the row and column. + if(selected != null) { + int boxRow = selected.cell.getRow() / 3; + int boxCol = selected.cell.getCol() / 3; + + for(int n = 0; n < 9; n++) { + int row = selected.cell.getRow(); + int col = selected.cell.getCol(); + + if( + (row == selected.cell.getRow() && n == selected.cell.getCol()) || + (col == selected.cell.getCol() && n == selected.cell.getRow()) + ) continue; + + gridGUI[n][col].select(); + gridGUI[row][n].select(); + } + + for(int n = 0; n < 3; n++) { + for(int m = 0; m < 3; m++) { + int row = boxRow * 3 + n; + int col = boxCol * 3 + m; + if(row == selected.cell.getRow() || col == selected.cell.getCol()) continue; + + gridGUI[row][col].select(); + } + } + } + + // Graphically highlight the row and column + // of the selected cell. + for(int n = 0; n < 9; n++) { + gridGUI[n][cell.cell.getCol()].select(); + gridGUI[cell.cell.getRow()][n].select(); + } + + selected = cell; + int boxRow = selected.cell.getRow() / 3; + int boxCol = selected.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; + if(row == selected.cell.getRow() || col == selected.cell.getCol()) continue; + + if( + (row == selected.cell.getRow() && n == selected.cell.getCol()) || + (col == selected.cell.getCol() && n == selected.cell.getRow()) + ) continue; + + gridGUI[row][col].select(); + } + } } @Override public void mousePressed(java.awt.event.MouseEvent e) {} @@ -101,7 +162,8 @@ public class Board extends JPanel { public void mouseExited(java.awt.event.MouseEvent e) {} }); - add(cell); + gridGUI[i][j] = cell; + add(gridGUI[i][j]); } } } @@ -121,6 +183,7 @@ public class Board extends JPanel { // Data fields private Cell cell; private boolean noteMode; // true to display notes, or display value + private boolean selected = false; // true if the cell is selected /** * Create a new CellGUI object with the given single cell. @@ -135,19 +198,25 @@ public class Board extends JPanel { this.cell = cell; this.noteMode = !noteMode; // Flip for use with setNoteMode() this.theme = theme; + selected = false; setLayout(valueLayout); style(); // Populate the cell with the appropriate value or notes. if(cell.getValue() == 0) { valueLabel = new JLabel(""); + valueLabel.setForeground(theme.getPrimaryText()); + internalPanel.setLayout(noteLayout); + generateNotes(); } else { valueLabel = new JLabel( Integer.toString(cell.getValue()), SwingConstants.CENTER ); + valueLabel.setForeground(theme.getPrimaryText()); + internalPanel.setLayout(valueLayout); + internalPanel.add(valueLabel); } - setNoteMode(); add(internalPanel); } @@ -214,6 +283,8 @@ public class Board extends JPanel { * If the current mode is value, switch to note mode, and vice versa. */ public void setNoteMode() { + if(cell.isInitValue()) return; + internalPanel.removeAll(); if(noteMode) { internalPanel.setLayout(valueLayout); @@ -231,12 +302,37 @@ public class Board extends JPanel { noteMode = !noteMode; } + public void select() { + selected = !selected; + + if(!selected) { + setBackground(theme.getPrimaryBackground()); + setForeground(theme.getPrimaryText()); + internalPanel.setBackground(theme.getPrimaryBackground()); + internalPanel.setForeground(theme.getPrimaryText()); + valueLabel.setForeground(theme.getPrimaryText()); + setBorder(new LineBorder(theme.getPrimaryBorder(), 2)); + } else { + setBackground(theme.getSecondaryBackground()); + setForeground(theme.getSecondaryText()); + internalPanel.setBackground(theme.getSecondaryBackground()); + internalPanel.setForeground(theme.getSecondaryText()); + valueLabel.setForeground(theme.getPrimaryText()); + setBorder(new LineBorder(theme.getSecondaryBorder(), 2)); + } + + repaint(); + revalidate(); + } + /** * Style the cell with the appropriate colors from the theme. */ private void style() { setBackground(theme.getPrimaryBackground()); setForeground(theme.getPrimaryText()); + internalPanel.setBackground(theme.getPrimaryBackground()); + internalPanel.setForeground(theme.getPrimaryText()); setBorder(new LineBorder(theme.getPrimaryBorder(), 2)); } diff --git a/src/gui/Cell.java b/src/gui/Cell.java index d8c5539..a36a202 100644 --- a/src/gui/Cell.java +++ b/src/gui/Cell.java @@ -88,6 +88,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. * @@ -143,7 +161,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 */ @@ -151,6 +170,17 @@ 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; + } + /** * Given the cell's row and column, set the box number for the cell. */ @@ -161,10 +191,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 { diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index b3b7e49..2d2994b 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -343,7 +343,7 @@ public class Settings { dimension = new Dimension(1000, 1000); resizable = false; - cellGUIStartMode = true; + cellGUIStartMode = false; defaultOpenState = 0; theme = new Theme(new File(appDirectory + "default.theme")); autoFillNotes = true; diff --git a/src/gui/backend/Theme.java b/src/gui/backend/Theme.java index 7dad6d4..3005331 100644 --- a/src/gui/backend/Theme.java +++ b/src/gui/backend/Theme.java @@ -71,11 +71,12 @@ public class Theme { * Sets the theme for the GUI components. */ private void setTheme() { - primaryBackground = Color.LIGHT_GRAY; - secondaryBackground = Color.LIGHT_GRAY; - primaryText = Color.DARK_GRAY; - secondaryText = Color.DARK_GRAY; - primaryBorder = Color.BLACK; + primaryBackground = Color.decode("#4A245E"); + secondaryBackground = Color.decode("#DADFF7"); + primaryText = Color.decode("#DBABF7"); + secondaryText = Color.decode("#4A245E"); + primaryBorder = Color.decode("#A76BCA"); + secondaryBorder = Color.decode("#DBABF7"); } /** From 0f4b78be2f522791eec525eade28652ea0e8ac9f Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Sun, 24 Mar 2024 12:11:51 -0600 Subject: [PATCH 06/16] added deselection support, though still buggy. additionally, altered default theme for easier viewing and for aesthetics --- src/gui/Board.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/gui/Board.java b/src/gui/Board.java index 52840cb..12e231f 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -107,10 +107,8 @@ public class Board extends JPanel { int row = selected.cell.getRow(); int col = selected.cell.getCol(); - if( - (row == selected.cell.getRow() && n == selected.cell.getCol()) || - (col == selected.cell.getCol() && n == selected.cell.getRow()) - ) continue; + if((n == selected.cell.getCol()) || (n == selected.cell.getRow())) + continue; gridGUI[n][col].select(); gridGUI[row][n].select(); @@ -143,11 +141,6 @@ public class Board extends JPanel { int col = boxCol * 3 + m; if(row == selected.cell.getRow() || col == selected.cell.getCol()) continue; - if( - (row == selected.cell.getRow() && n == selected.cell.getCol()) || - (col == selected.cell.getCol() && n == selected.cell.getRow()) - ) continue; - gridGUI[row][col].select(); } } From e821ffa24ba2793a3459a6623734713e009fb9e9 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Sun, 24 Mar 2024 13:45:09 -0600 Subject: [PATCH 07/16] fixed bug relating to highlighting rows and columns. --- src/gui/Board.java | 117 ++++++++++++++++------------------ src/gui/backend/Settings.java | 2 +- 2 files changed, 57 insertions(+), 62 deletions(-) diff --git a/src/gui/Board.java b/src/gui/Board.java index 12e231f..a108e49 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -80,6 +80,53 @@ public class Board extends JPanel { setForeground(s.getTheme().getPrimaryText()); } + /** + * 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) { + 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()); + + if(colInSameBox && !rowInSameBox) { + gridGUI[row][n].select(); + continue; + } else if(!colInSameBox && rowInSameBox) { + gridGUI[n][col].select(); + continue; + } else if(colInSameBox && rowInSameBox) continue; + + gridGUI[n][col].select(); + gridGUI[row][n].select(); + } + + 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 the Board Panel with the appropriate cells. */ @@ -96,54 +143,10 @@ public class Board extends JPanel { ); cell.addMouseListener(new MouseListener() { @Override - public void mouseClicked(java.awt.event.MouseEvent e) { - // If a cell is already selected, deselect it and - // the row and column. - if(selected != null) { - int boxRow = selected.cell.getRow() / 3; - int boxCol = selected.cell.getCol() / 3; - - for(int n = 0; n < 9; n++) { - int row = selected.cell.getRow(); - int col = selected.cell.getCol(); - - if((n == selected.cell.getCol()) || (n == selected.cell.getRow())) - continue; - - gridGUI[n][col].select(); - gridGUI[row][n].select(); - } - - for(int n = 0; n < 3; n++) { - for(int m = 0; m < 3; m++) { - int row = boxRow * 3 + n; - int col = boxCol * 3 + m; - if(row == selected.cell.getRow() || col == selected.cell.getCol()) continue; - - gridGUI[row][col].select(); - } - } - } - - // Graphically highlight the row and column - // of the selected cell. - for(int n = 0; n < 9; n++) { - gridGUI[n][cell.cell.getCol()].select(); - gridGUI[cell.cell.getRow()][n].select(); - } - + public void mouseClicked(java.awt.event.MouseEvent e) { + if(selected != null) select(selected.cell); + select(cell.cell); selected = cell; - int boxRow = selected.cell.getRow() / 3; - int boxCol = selected.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; - if(row == selected.cell.getRow() || col == selected.cell.getCol()) continue; - - gridGUI[row][col].select(); - } - } } @Override public void mousePressed(java.awt.event.MouseEvent e) {} @@ -193,20 +196,15 @@ public class Board extends JPanel { this.theme = theme; selected = false; setLayout(valueLayout); + valueLabel = new JLabel("", SwingConstants.CENTER); style(); // Populate the cell with the appropriate value or notes. if(cell.getValue() == 0) { - valueLabel = new JLabel(""); - valueLabel.setForeground(theme.getPrimaryText()); internalPanel.setLayout(noteLayout); generateNotes(); } else { - valueLabel = new JLabel( - Integer.toString(cell.getValue()), - SwingConstants.CENTER - ); - valueLabel.setForeground(theme.getPrimaryText()); + valueLabel.setText(Integer.toString(cell.getValue())); internalPanel.setLayout(valueLayout); internalPanel.add(valueLabel); } @@ -249,6 +247,7 @@ public class Board extends JPanel { */ public void addPossibleValue(int value) { cell.addPossibleValue(value); + setNoteMode(); } /** @@ -299,18 +298,13 @@ public class Board extends JPanel { selected = !selected; if(!selected) { - setBackground(theme.getPrimaryBackground()); - setForeground(theme.getPrimaryText()); - internalPanel.setBackground(theme.getPrimaryBackground()); - internalPanel.setForeground(theme.getPrimaryText()); - valueLabel.setForeground(theme.getPrimaryText()); - setBorder(new LineBorder(theme.getPrimaryBorder(), 2)); + style(); } else { setBackground(theme.getSecondaryBackground()); setForeground(theme.getSecondaryText()); internalPanel.setBackground(theme.getSecondaryBackground()); internalPanel.setForeground(theme.getSecondaryText()); - valueLabel.setForeground(theme.getPrimaryText()); + valueLabel.setForeground(theme.getSecondaryText()); setBorder(new LineBorder(theme.getSecondaryBorder(), 2)); } @@ -326,6 +320,7 @@ public class Board extends JPanel { setForeground(theme.getPrimaryText()); internalPanel.setBackground(theme.getPrimaryBackground()); internalPanel.setForeground(theme.getPrimaryText()); + valueLabel.setForeground(theme.getPrimaryText()); setBorder(new LineBorder(theme.getPrimaryBorder(), 2)); } diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index 2d2994b..5621090 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -346,7 +346,7 @@ public class Settings { cellGUIStartMode = false; defaultOpenState = 0; theme = new Theme(new File(appDirectory + "default.theme")); - autoFillNotes = true; + autoFillNotes = false; updateSettingsFile(); } From 56f4fed7bcfcb9dfe4a1dd2377a0954107f4ad85 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Sun, 24 Mar 2024 15:44:33 -0600 Subject: [PATCH 08/16] added keyboard input for cells when not in noteMode. noteMode is toggled with a right mouse click, and cells can be selected with left click --- src/App.java | 2 + src/gui/Board.java | 266 +++++++---------------------- src/gui/CellGUI.java | 224 ++++++++++++++++++++++++ src/gui/Nav.java | 55 ++++-- src/gui/{ => backend}/Cell.java | 2 +- src/gui/backend/Settings.java | 2 +- src/gui/backend/SudokuChecker.java | 2 - 7 files changed, 331 insertions(+), 222 deletions(-) create mode 100644 src/gui/CellGUI.java rename src/gui/{ => backend}/Cell.java (99%) diff --git a/src/App.java b/src/App.java index e792983..9ef280a 100644 --- a/src/App.java +++ b/src/App.java @@ -3,6 +3,8 @@ import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.UIManager; import java.awt.BorderLayout; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; // Project imports import gui.*; diff --git a/src/gui/Board.java b/src/gui/Board.java index a108e49..39acf2b 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -1,19 +1,19 @@ package gui; -import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.LayoutManager; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.JPanel; -import javax.swing.SwingConstants; -import javax.swing.border.LineBorder; import javax.swing.Box; -import javax.swing.JLabel; +import gui.backend.Cell; import gui.backend.Settings; import gui.backend.SudokuChecker; -import gui.backend.Theme; /** * The Board class represents the Sudoku board in the GUI. @@ -30,7 +30,6 @@ public class Board extends JPanel { private Settings s; private Cell[][] grid; private CellGUI[][] gridGUI; - private Box[][] boxGUI = new Box[3][3]; private SudokuChecker sc; private CellGUI selected; @@ -48,6 +47,36 @@ public class Board extends JPanel { createBoard(); } + private KeyListener createKeyListener() { + return new KeyListener() { + @Override + public void keyTyped(KeyEvent e) { + char key = e.getKeyChar(); + if(!Character.isDigit(key)) return; + if(selected.cell.isInitValue()) return; + + if(e.getKeyCode() == e.VK_BACK_SPACE) { + if(selected.isInNotesMode()) { + System.out.println("Remove all notes."); + return; + } else { + selected.setValue(0); + return; + } + } + + if(!selected.isInNotesMode()) + selected.setValue(Character.getNumericValue(key)); + } + + @Override + public void keyPressed(KeyEvent e) {} + + @Override + public void keyReleased(KeyEvent e) {} + }; + } + /** * Get the grid of the Board. * @@ -66,6 +95,9 @@ public class Board extends JPanel { */ public void setGrid(Cell[][] grid) { this.grid = grid; + gridGUI = null; + selected = null; + removeAll(); createBoard(); repaint(); @@ -143,210 +175,44 @@ public class Board extends JPanel { ); cell.addMouseListener(new MouseListener() { @Override - public void mouseClicked(java.awt.event.MouseEvent e) { - if(selected != null) select(selected.cell); - select(cell.cell); - selected = cell; + 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); + selected = cell; + selected.requestFocusInWindow(); + selected.addKeyListener(createKeyListener()); + } else if(e.getButton() == 3) { + cell.setNoteMode(); + return; + } } @Override - public void mousePressed(java.awt.event.MouseEvent e) {} + public void mousePressed(MouseEvent e) {} @Override - public void mouseReleased(java.awt.event.MouseEvent e) {} + public void mouseReleased(MouseEvent e) {} @Override - public void mouseEntered(java.awt.event.MouseEvent e) {} + public void mouseEntered(MouseEvent e) { + cell.select(); + } @Override - public void mouseExited(java.awt.event.MouseEvent e) {} + public void mouseExited(MouseEvent e) { + cell.select(); + } }); + gridGUI[i][j] = cell; add(gridGUI[i][j]); } } } - - /** - * GUI representation of a single cell in the Sudoku grid. - */ - private class CellGUI extends JPanel { - // GUI fields - private JPanel internalPanel = new JPanel(new BorderLayout()); - private GridLayout noteLayout = new GridLayout(3, 3); - private GridLayout valueLayout = new GridLayout(0, 1); - private JLabel valueLabel; - private Note[] notesLabels = new Note[9]; - private Theme theme; - - // Data fields - private Cell cell; - private boolean noteMode; // true to display notes, or display value - private boolean selected = false; // true if the cell is selected - - /** - * 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, boolean noteMode, Theme theme) { - super(); - this.cell = cell; - this.noteMode = !noteMode; // Flip for use with setNoteMode() - this.theme = theme; - selected = false; - setLayout(valueLayout); - valueLabel = new JLabel("", SwingConstants.CENTER); - style(); - - // Populate the cell with the appropriate value or notes. - if(cell.getValue() == 0) { - internalPanel.setLayout(noteLayout); - generateNotes(); - } else { - valueLabel.setText(Integer.toString(cell.getValue())); - internalPanel.setLayout(valueLayout); - 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. - * - * @param value - */ - public void setValue(int value) { - cell.setValue(value); - // Update the GUI with the actual Cell value (unchanged if invalid) - valueLabel.setText(Integer.toString(cell.getValue())); - } - - /** - * Set the possible values of the underlying Cell object. - * - * @param value - */ - public void setPossibleValues(int[] values) { - cell.setPossibleValues(values); - } - - /** - * Add a possible value to the cell. - * - * @param value - */ - public void addPossibleValue(int value) { - cell.addPossibleValue(value); - setNoteMode(); - } - - /** - * Remove a possible value from the cell. - * - * @return - */ - public void removePossibleValue(int value) { - cell.removePossibleValue(value); - } - - /** - * Get the possible values of the underlying Cell object. - * - * @return int[] - */ - public int[] getPossibleValues() { - return cell.getPossibleValues(); - } - - /** - * Toggle the mode of the cell between value and note, including the - * layout of the cell. - * - * If the current mode is value, switch to note mode, and vice versa. - */ - public void setNoteMode() { - if(cell.isInitValue()) return; - - internalPanel.removeAll(); - if(noteMode) { - internalPanel.setLayout(valueLayout); - internalPanel.add(valueLabel); - } else { - internalPanel.setLayout(noteLayout); - generateNotes(); - } - - internalPanel.repaint(); - internalPanel.revalidate(); - repaint(); - revalidate(); - - noteMode = !noteMode; - } - - public void select() { - selected = !selected; - - if(!selected) { - style(); - } else { - setBackground(theme.getSecondaryBackground()); - setForeground(theme.getSecondaryText()); - internalPanel.setBackground(theme.getSecondaryBackground()); - internalPanel.setForeground(theme.getSecondaryText()); - valueLabel.setForeground(theme.getSecondaryText()); - setBorder(new LineBorder(theme.getSecondaryBorder(), 2)); - } - - repaint(); - revalidate(); - } - - /** - * Style the cell with the appropriate colors from the theme. - */ - private void style() { - setBackground(theme.getPrimaryBackground()); - setForeground(theme.getPrimaryText()); - internalPanel.setBackground(theme.getPrimaryBackground()); - internalPanel.setForeground(theme.getPrimaryText()); - valueLabel.setForeground(theme.getPrimaryText()); - setBorder(new LineBorder(theme.getPrimaryBorder(), 2)); - } - - /** - * Create the GUI components for the cell. - */ - private void generateNotes() { - int[] possibleValues = cell.getPossibleValues(); - if(possibleValues.length == 0) { - for(int i = 0; i < 9; i++) { - notesLabels[i] = new Note("", theme); - 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++) { - if(possibleValues[index] == i) { - notesLabels[i - 1] = new Note(Integer.toString(i), theme); - index++; - } else - notesLabels[i - 1] = new Note("", theme); - internalPanel.add(notesLabels[i - 1]); - } - } - } } \ No newline at end of file diff --git a/src/gui/CellGUI.java b/src/gui/CellGUI.java new file mode 100644 index 0000000..4a7c418 --- /dev/null +++ b/src/gui/CellGUI.java @@ -0,0 +1,224 @@ +package gui; + +import java.awt.BorderLayout; +import java.awt.GridLayout; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.border.LineBorder; + +import gui.backend.Cell; +import gui.backend.Theme; + +/** + * GUI representation of a single cell in the Sudoku grid. + */ +class CellGUI extends JPanel { + // GUI fields + private JPanel internalPanel = new JPanel(new BorderLayout()); + private GridLayout noteLayout = new GridLayout(3, 3); + private GridLayout valueLayout = new GridLayout(0, 1); + private JLabel valueLabel; + private Note[] notesLabels = new Note[9]; + private Theme theme; + + // Data fields + Cell cell; + private boolean noteMode; // true to display notes, or display value + private boolean selected = false; // true if the cell is selected + + /** + * 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, boolean noteMode, Theme theme) { + super(); + this.cell = cell; + this.noteMode = !noteMode; // Flip for use with setNoteMode() + this.theme = theme; + selected = false; + setFocusable(true); + setLayout(valueLayout); + valueLabel = new JLabel("", SwingConstants.CENTER); + style(); + + // Populate the cell with the appropriate value or notes. + if(cell.getValue() == 0) { + internalPanel.setLayout(noteLayout); + generateNotes(); + } else { + valueLabel.setText(Integer.toString(cell.getValue())); + internalPanel.setLayout(valueLayout); + 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. + * + * @param value + */ + public void setValue(int value) { + cell.setValue(value); + // Update the GUI with the actual Cell value (unchanged if invalid) + valueLabel.setText(Integer.toString(cell.getValue())); + internalPanel.repaint(); + internalPanel.revalidate(); + } + + /** + * Set the possible values of the underlying Cell object. + * + * @param value + */ + public void setPossibleValues(int[] values) { + cell.setPossibleValues(values); + } + + /** + * Add a possible value to the cell. + * + * @param value + */ + public void addPossibleValue(int value) { + cell.addPossibleValue(value); + setNoteMode(); + } + + /** + * Remove a possible value from the cell. + * + * @return + */ + public void removePossibleValue(int value) { + cell.removePossibleValue(value); + } + + /** + * Get the possible values of the underlying Cell object. + * + * @return int[] + */ + public int[] getPossibleValues() { + return cell.getPossibleValues(); + } + + /** + * 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 of the cell. + * + * If the current mode is value, switch to note mode, and vice versa. + */ + public void setNoteMode() { + if(cell.isInitValue()) return; + + internalPanel.removeAll(); + if(noteMode) { + internalPanel.setLayout(valueLayout); + internalPanel.add(valueLabel); + } else { + internalPanel.setLayout(noteLayout); + generateNotes(); + } + + internalPanel.repaint(); + internalPanel.revalidate(); + repaint(); + revalidate(); + + noteMode = !noteMode; + } + + public void select() { + selected = !selected; + + if(!selected) { + style(); + } else { + 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()); + } + } + } + + repaint(); + revalidate(); + } + + /** + * Style the cell with the appropriate colors from the theme. + */ + private void style() { + 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()); + } + } + } + + /** + * Create the GUI components for the cell. + */ + private void generateNotes() { + int[] possibleValues = cell.getPossibleValues(); + if(possibleValues.length == 0) { + for(int i = 0; i < 9; i++) { + notesLabels[i] = new Note("", theme); + 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++) { + if(possibleValues[index] == i) { + notesLabels[i - 1] = new Note(Integer.toString(i), theme); + index++; + } else + notesLabels[i - 1] = new Note("", theme); + internalPanel.add(notesLabels[i - 1]); + } + } +} \ No newline at end of file diff --git a/src/gui/Nav.java b/src/gui/Nav.java index 35fa451..cc6a348 100644 --- a/src/gui/Nav.java +++ b/src/gui/Nav.java @@ -1,10 +1,13 @@ package gui; import java.util.Scanner; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; import java.io.File; import java.io.FileNotFoundException; import javax.swing.JPanel; +import gui.backend.Cell; import gui.backend.Settings; import gui.backend.SudokuChecker; @@ -163,25 +166,41 @@ public class Nav extends JPanel { fileOptions.addItem("Save"); fileOptions.addItem("Exit"); - // Add an ActionListener to the ComboBox to handle the user's selection. - fileOptions.addActionListener(e -> { - String selected = (String) fileOptions.getSelectedItem(); - switch(selected) { - case "New": - newFile(); - break; - case "Open": - openFile(); - break; - case "Save": - saveFile(); - break; - case "Exit": - System.exit(0); - break; - default: - System.out.println("Invalid selection."); + fileOptions.addMouseListener(new MouseListener() { + @Override + public void mouseClicked(MouseEvent e) { + int selected = fileOptions.getSelectedIndex(); + switch(selected) { + case 0: + newFile(); + break; + case 1: + openFile(); + break; + case 2: + saveFile(); + break; + case 3: + System.exit(0); + break; + default: + System.out.println("Invalid selection."); + } } + + @Override + public void mousePressed(MouseEvent e) {} + + @Override + public void mouseReleased(MouseEvent e) {} + + @Override + public void mouseEntered(MouseEvent e) { + fileOptions.showPopup(); + } + + @Override + public void mouseExited(MouseEvent e) {} }); return fileOptions; diff --git a/src/gui/Cell.java b/src/gui/backend/Cell.java similarity index 99% rename from src/gui/Cell.java rename to src/gui/backend/Cell.java index a36a202..6ba91d6 100644 --- a/src/gui/Cell.java +++ b/src/gui/backend/Cell.java @@ -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. diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index 5621090..2d2994b 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -346,7 +346,7 @@ public class Settings { cellGUIStartMode = false; defaultOpenState = 0; theme = new Theme(new File(appDirectory + "default.theme")); - autoFillNotes = false; + autoFillNotes = true; updateSettingsFile(); } diff --git a/src/gui/backend/SudokuChecker.java b/src/gui/backend/SudokuChecker.java index be25be7..e2641ae 100644 --- a/src/gui/backend/SudokuChecker.java +++ b/src/gui/backend/SudokuChecker.java @@ -1,8 +1,6 @@ package gui.backend; import java.util.ArrayList; -import gui.Cell; - /** * The SudokuChecker class is responsible for calculating the solution to * a given Sudoku puzzle. From bf5a54d500d7c280784ae04746cc9d59f653314e Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Sun, 24 Mar 2024 17:00:55 -0600 Subject: [PATCH 09/16] added backspace support and basic error checking in settings and gui. 2 major bugs: empty cells always start in note mode, and the sizing is not constant. --- src/gui/Board.java | 57 +++++++++----- src/gui/CellGUI.java | 141 +++++++++++++++++++++++++++++----- src/gui/backend/Cell.java | 12 +++ src/gui/backend/Settings.java | 57 +++++++++++++- src/gui/backend/Theme.java | 62 ++++++++++++++- 5 files changed, 286 insertions(+), 43 deletions(-) diff --git a/src/gui/Board.java b/src/gui/Board.java index 39acf2b..ea636c8 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -1,5 +1,6 @@ package gui; +import java.awt.Dimension; import java.awt.GridLayout; import java.awt.LayoutManager; import java.awt.event.KeyAdapter; @@ -29,6 +30,7 @@ import gui.backend.SudokuChecker; public class Board extends JPanel { private Settings s; private Cell[][] grid; + private Cell[][] solvedGrid; private CellGUI[][] gridGUI; private SudokuChecker sc; private CellGUI selected; @@ -43,6 +45,8 @@ public class Board extends JPanel { this.s = s; this.grid = grid; this.sc = sc; + solvedGrid = new SudokuChecker(Cell.copyGrid(grid)).getSolution(); + style(); createBoard(); } @@ -50,30 +54,42 @@ public class Board extends JPanel { private KeyListener createKeyListener() { return new KeyListener() { @Override - public void keyTyped(KeyEvent e) { - char key = e.getKeyChar(); - if(!Character.isDigit(key)) return; - if(selected.cell.isInitValue()) return; - - if(e.getKeyCode() == e.VK_BACK_SPACE) { - if(selected.isInNotesMode()) { - System.out.println("Remove all notes."); - return; - } else { - selected.setValue(0); - return; - } - } - - if(!selected.isInNotesMode()) - selected.setValue(Character.getNumericValue(key)); - } + public void keyTyped(KeyEvent e) {} @Override public void keyPressed(KeyEvent e) {} @Override - public void keyReleased(KeyEvent e) {} + 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(); + } }; } @@ -171,7 +187,8 @@ public class Board extends JPanel { CellGUI cell = new CellGUI( grid[i][j], s.getCellGUIStartMode(), - s.getTheme() + s.getTheme(), + s.getCellDimensions() ); cell.addMouseListener(new MouseListener() { @Override diff --git a/src/gui/CellGUI.java b/src/gui/CellGUI.java index 4a7c418..953ce07 100644 --- a/src/gui/CellGUI.java +++ b/src/gui/CellGUI.java @@ -1,6 +1,7 @@ package gui; import java.awt.BorderLayout; +import java.awt.Dimension; import java.awt.GridLayout; import javax.swing.JLabel; @@ -19,9 +20,11 @@ class CellGUI extends JPanel { private JPanel internalPanel = new JPanel(new BorderLayout()); private GridLayout noteLayout = new GridLayout(3, 3); private GridLayout valueLayout = new GridLayout(0, 1); + private Dimension size; private JLabel valueLabel; private Note[] notesLabels = new Note[9]; private Theme theme; + private boolean incorrect; // Data fields Cell cell; @@ -36,7 +39,7 @@ class CellGUI extends JPanel { * @param cell * @param startInNotesOrValueMode */ - public CellGUI(Cell cell, boolean noteMode, Theme theme) { + public CellGUI(Cell cell, boolean noteMode, Theme theme, Dimension size) { super(); this.cell = cell; this.noteMode = !noteMode; // Flip for use with setNoteMode() @@ -45,7 +48,9 @@ class CellGUI extends JPanel { setFocusable(true); setLayout(valueLayout); valueLabel = new JLabel("", SwingConstants.CENTER); - style(); + this.size = size; + setSize(this.size); + defaultStyle(); // Populate the cell with the appropriate value or notes. if(cell.getValue() == 0) { @@ -59,6 +64,25 @@ class CellGUI extends JPanel { 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. * @@ -81,6 +105,45 @@ class CellGUI extends JPanel { internalPanel.revalidate(); } + /** + * Set the value of the underlying Cell object. + * + * This implementation allows for incorrect values to be highlighted. + * + * @param value + */ + public void setValue(int value, int expected) { + if(!selected) return; + + cell.setValue(value); + valueLabel.setText(Integer.toString(cell.getValue())); + + if(value != expected) { + incorrect = true; + errorStyle(); + } + + internalPanel.repaint(); + internalPanel.revalidate(); + } + + /** + * Remove the value of the underlying Cell object. + */ + public void removeValue() { + if(!selected) return; + + cell.setValue(0); + if(!cell.isInitValue()) + valueLabel.setText(""); + + incorrect = false; + highlightedStyle(); + + internalPanel.repaint(); + internalPanel.revalidate(); + } + /** * Set the possible values of the underlying Cell object. * @@ -96,8 +159,13 @@ class CellGUI extends JPanel { * @param value */ public void addPossibleValue(int value) { + if(!selected) return; + cell.addPossibleValue(value); - setNoteMode(); + internalPanel.removeAll(); + internalPanel.setLayout(noteLayout); + generateNotes(); + highlightedStyle(); } /** @@ -155,24 +223,17 @@ class CellGUI extends JPanel { public void select() { selected = !selected; - - if(!selected) { - style(); - } else { - setBackground(theme.getSecondaryBackground()); - setForeground(theme.getSecondaryText()); - internalPanel.setBackground(theme.getSecondaryBackground()); - internalPanel.setForeground(theme.getSecondaryText()); - valueLabel.setForeground(theme.getSecondaryText()); - setBorder(new LineBorder(theme.getSecondaryBorder(), 2)); + if(incorrect) { + errorStyle(); + repaint(); + revalidate(); + return; + } - for(int i = 0; i < 9; i++) { - if(notesLabels[i] != null) { - notesLabels[i].setBackground(theme.getSecondaryBackground()); - notesLabels[i].setForeground(theme.getSecondaryText()); - } - } - } + if(!selected) + defaultStyle(); + + else highlightedStyle(); repaint(); revalidate(); @@ -181,7 +242,7 @@ class CellGUI extends JPanel { /** * Style the cell with the appropriate colors from the theme. */ - private void style() { + private void defaultStyle() { setBackground(theme.getPrimaryBackground()); setForeground(theme.getPrimaryText()); internalPanel.setBackground(theme.getPrimaryBackground()); @@ -197,6 +258,44 @@ class CellGUI extends JPanel { } } + /** + * Style the cell with the appropriate colors from the theme. + */ + 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()); + } + } + } + + /** + * Style the cell with the appropriate colors from the theme. + */ + private void errorStyle() { + setBackground(theme.getErrorBackground()); + setForeground(theme.getErrorText()); + internalPanel.setBackground(theme.getErrorBackground()); + internalPanel.setForeground(theme.getErrorText()); + valueLabel.setForeground(theme.getErrorText()); + setBorder(new LineBorder(theme.getErrorBorder(), 2)); + + for(int i = 0; i < 9; i++) { + if(notesLabels[i] != null) { + notesLabels[i].setBackground(theme.getErrorBackground()); + notesLabels[i].setForeground(theme.getErrorText()); + } + } + } + /** * Create the GUI components for the cell. */ diff --git a/src/gui/backend/Cell.java b/src/gui/backend/Cell.java index 6ba91d6..c77cabc 100644 --- a/src/gui/backend/Cell.java +++ b/src/gui/backend/Cell.java @@ -126,6 +126,7 @@ public class Cell { if(initValue) return; if(possibleValues.contains(value)) { + removePossibleValue(value); return; } else if(value < 1 || value > 9) { return; @@ -181,6 +182,17 @@ public class Cell { 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. */ diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index 2d2994b..c2c3469 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -41,6 +41,9 @@ 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; @@ -56,6 +59,9 @@ public class Settings { // Auto-fill notes private boolean autoFillNotes; + // Auto check values + private boolean autoCheckValues; + /** * Create a new Settings object, initializing the default settings * or reading in the settings from settings file if it exists. @@ -310,6 +316,53 @@ public class Settings { 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); + } + /** * Open and write the settings to the settings file. * @@ -346,7 +399,9 @@ public class Settings { cellGUIStartMode = false; defaultOpenState = 0; theme = new Theme(new File(appDirectory + "default.theme")); - autoFillNotes = true; + autoFillNotes = false; + autoCheckValues = true; + setCellDimensions(); updateSettingsFile(); } diff --git a/src/gui/backend/Theme.java b/src/gui/backend/Theme.java index 3005331..4ea4e29 100644 --- a/src/gui/backend/Theme.java +++ b/src/gui/backend/Theme.java @@ -32,6 +32,8 @@ import java.io.File; * It provides colors for various GUI elements. */ public class Theme { + private File theme; + private Color primaryBackground; private Color secondaryBackground; private Color primaryText; @@ -46,6 +48,9 @@ public class Theme { 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. @@ -77,6 +82,9 @@ public class Theme { secondaryText = Color.decode("#4A245E"); primaryBorder = Color.decode("#A76BCA"); secondaryBorder = Color.decode("#DBABF7"); + errorBackground = Color.decode("#FF0000"); + errorText = Color.decode("#FFFFFF"); + errorBorder = Color.decode("#FF0000"); } /** @@ -331,5 +339,57 @@ public class Theme { this.secondaryButtonBorder = secondaryButtonBorder; } - private File theme; + /** + * 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; + } } From 0409a8f41d2795ef57b55238377831e23e4d2e24 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Sun, 24 Mar 2024 18:46:26 -0600 Subject: [PATCH 10/16] starting optimizations and refactoring --- src/gui/Board.java | 4 +-- src/gui/CellGUI.java | 68 +++++++++++++++++------------------ src/gui/Nav.java | 51 +++++++++----------------- src/gui/Note.java | 7 +++- src/gui/backend/Settings.java | 6 ++-- 5 files changed, 59 insertions(+), 77 deletions(-) diff --git a/src/gui/Board.java b/src/gui/Board.java index ea636c8..bd2522f 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -186,9 +186,7 @@ public class Board extends JPanel { for(int j = 0; j < 9; j++) { CellGUI cell = new CellGUI( grid[i][j], - s.getCellGUIStartMode(), - s.getTheme(), - s.getCellDimensions() + s ); cell.addMouseListener(new MouseListener() { @Override diff --git a/src/gui/CellGUI.java b/src/gui/CellGUI.java index 953ce07..7f7e39e 100644 --- a/src/gui/CellGUI.java +++ b/src/gui/CellGUI.java @@ -2,14 +2,15 @@ package gui; import java.awt.BorderLayout; import java.awt.Dimension; +import java.awt.Font; import java.awt.GridLayout; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.SwingConstants; import javax.swing.border.LineBorder; import gui.backend.Cell; +import gui.backend.Settings; import gui.backend.Theme; /** @@ -24,6 +25,7 @@ class CellGUI extends JPanel { private JLabel valueLabel; private Note[] notesLabels = new Note[9]; private Theme theme; + private Font font; private boolean incorrect; // Data fields @@ -39,26 +41,31 @@ class CellGUI extends JPanel { * @param cell * @param startInNotesOrValueMode */ - public CellGUI(Cell cell, boolean noteMode, Theme theme, Dimension size) { + public CellGUI(Cell cell, Settings s) { super(); this.cell = cell; - this.noteMode = !noteMode; // Flip for use with setNoteMode() - this.theme = theme; + + // 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(); + setFocusable(true); setLayout(valueLayout); - valueLabel = new JLabel("", SwingConstants.CENTER); - this.size = size; - setSize(this.size); + valueLabel = new Note("", 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(); + generateNotes(s.getAutoFillNotes()); } else { - valueLabel.setText(Integer.toString(cell.getValue())); internalPanel.setLayout(valueLayout); + valueLabel.setText(Integer.toString(cell.getValue())); internalPanel.add(valueLabel); } add(internalPanel); @@ -144,15 +151,6 @@ class CellGUI extends JPanel { internalPanel.revalidate(); } - /** - * Set the possible values of the underlying Cell object. - * - * @param value - */ - public void setPossibleValues(int[] values) { - cell.setPossibleValues(values); - } - /** * Add a possible value to the cell. * @@ -164,7 +162,8 @@ class CellGUI extends JPanel { cell.addPossibleValue(value); internalPanel.removeAll(); internalPanel.setLayout(noteLayout); - generateNotes(); + noteMode = true; + generateNotes(true); highlightedStyle(); } @@ -177,15 +176,6 @@ class CellGUI extends JPanel { cell.removePossibleValue(value); } - /** - * Get the possible values of the underlying Cell object. - * - * @return int[] - */ - public int[] getPossibleValues() { - return cell.getPossibleValues(); - } - /** * Get the status of the cell in notes mode. * @@ -210,7 +200,8 @@ class CellGUI extends JPanel { internalPanel.add(valueLabel); } else { internalPanel.setLayout(noteLayout); - generateNotes(); + noteMode = true; + generateNotes(true); } internalPanel.repaint(); @@ -221,6 +212,9 @@ class CellGUI extends JPanel { noteMode = !noteMode; } + /** + * Select the cell, and style it for error, highlight, or default. + */ public void select() { selected = !selected; if(incorrect) { @@ -297,14 +291,16 @@ class CellGUI extends JPanel { } /** - * Create the GUI components for the cell. + * Create the GUI components for the cell's notes. + * + * @param autoFill */ - private void generateNotes() { + private void generateNotes(boolean autoFill) { int[] possibleValues = cell.getPossibleValues(); if(possibleValues.length == 0) { for(int i = 0; i < 9; i++) { - notesLabels[i] = new Note("", theme); - internalPanel.add(notesLabels[i]); + notesLabels[i] = new Note("", theme, font); + if(autoFill) internalPanel.add(notesLabels[i]); } return; } @@ -313,11 +309,11 @@ class CellGUI extends JPanel { // Add the noted possible values to the cell. for(int i = 1; i <= 9 && index < possibleValues.length; i++) { if(possibleValues[index] == i) { - notesLabels[i - 1] = new Note(Integer.toString(i), theme); + notesLabels[i - 1] = new Note(Integer.toString(i), theme, font); index++; } else - notesLabels[i - 1] = new Note("", theme); - internalPanel.add(notesLabels[i - 1]); + notesLabels[i - 1] = new Note("", theme, font); + if(autoFill) internalPanel.add(notesLabels[i - 1]); } } } \ No newline at end of file diff --git a/src/gui/Nav.java b/src/gui/Nav.java index cc6a348..a363e12 100644 --- a/src/gui/Nav.java +++ b/src/gui/Nav.java @@ -166,41 +166,24 @@ public class Nav extends JPanel { fileOptions.addItem("Save"); fileOptions.addItem("Exit"); - fileOptions.addMouseListener(new MouseListener() { - @Override - public void mouseClicked(MouseEvent e) { - int selected = fileOptions.getSelectedIndex(); - switch(selected) { - case 0: - newFile(); - break; - case 1: - openFile(); - break; - case 2: - saveFile(); - break; - case 3: - System.exit(0); - break; - default: - System.out.println("Invalid selection."); - } + fileOptions.addActionListener(e -> { + int selected = fileOptions.getSelectedIndex(); + switch(selected) { + case 0: + newFile(); + break; + case 1: + openFile(); + break; + case 2: + saveFile(); + break; + case 3: + System.exit(0); + break; + default: + System.out.println("Invalid selection."); } - - @Override - public void mousePressed(MouseEvent e) {} - - @Override - public void mouseReleased(MouseEvent e) {} - - @Override - public void mouseEntered(MouseEvent e) { - fileOptions.showPopup(); - } - - @Override - public void mouseExited(MouseEvent e) {} }); return fileOptions; diff --git a/src/gui/Note.java b/src/gui/Note.java index 42e70d9..947bc55 100644 --- a/src/gui/Note.java +++ b/src/gui/Note.java @@ -1,5 +1,7 @@ package gui; +import java.awt.Font; + import javax.swing.JLabel; import javax.swing.SwingConstants; @@ -13,6 +15,7 @@ import gui.backend.Theme; */ public class Note extends JLabel { private Theme t; + private Font f; /** * Create a new Note object with the given text and theme. @@ -20,9 +23,10 @@ public class Note extends JLabel { * @param text * @param t */ - public Note(String text, Theme t) { + public Note(String text, Theme t, Font f) { super(text, SwingConstants.CENTER); this.t = t; + this.f = f; style(); } @@ -32,5 +36,6 @@ public class Note extends JLabel { private void style() { setBackground(t.getPrimaryBackground()); setForeground(t.getPrimaryText()); + setFont(f); } } diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index c2c3469..545aeac 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -388,13 +388,13 @@ public class Settings { // Load the font from the system directory. if(loadFont("HackNerdFont-Regular") == 0) - font = new Font("Hack Nerd Font", Font.PLAIN, 12); + 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, 12); + font = new Font("Arial", Font.PLAIN, 16); - dimension = new Dimension(1000, 1000); + dimension = new Dimension(600, 800); resizable = false; cellGUIStartMode = false; defaultOpenState = 0; From 600c215b7d2e348874e6aaf910217dce339b23db Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Mon, 25 Mar 2024 13:10:31 -0600 Subject: [PATCH 11/16] wrote thorough documentation and made minor fixes for highlighting/selection --- src/App.java | 6 +- src/gui/Board.java | 266 +++++++++++++++++++---------- src/gui/CellGUI.java | 121 +++++++------ src/gui/ComboBox.java | 14 +- src/gui/FileChooser.java | 14 +- src/gui/{Note.java => Label.java} | 6 +- src/gui/Nav.java | 144 +++++++++++----- src/gui/Root.java | 12 +- src/gui/backend/Cell.java | 14 -- src/gui/backend/Settings.java | 251 ++++++++++++++++++--------- src/gui/backend/SudokuChecker.java | 134 ++++++++------- src/gui/backend/Theme.java | 19 +-- 12 files changed, 625 insertions(+), 376 deletions(-) rename src/gui/{Note.java => Label.java} (85%) diff --git a/src/App.java b/src/App.java index 9ef280a..594cbba 100644 --- a/src/App.java +++ b/src/App.java @@ -3,13 +3,15 @@ import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.UIManager; import java.awt.BorderLayout; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; // 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. diff --git a/src/gui/Board.java b/src/gui/Board.java index bd2522f..6376136 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -1,17 +1,16 @@ package gui; -import java.awt.Dimension; +// GUI imports +import javax.swing.JPanel; 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.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; -import javax.swing.JPanel; -import javax.swing.Box; - +// Processing & backend imports import gui.backend.Cell; import gui.backend.Settings; 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 * 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 @@ -36,9 +37,41 @@ public class Board extends JPanel { 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)); @@ -51,48 +84,6 @@ public class Board extends JPanel { 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. * @@ -137,6 +128,8 @@ public class Board extends JPanel { * @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(); @@ -148,18 +141,22 @@ public class Board extends JPanel { (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++) { @@ -177,6 +174,12 @@ public class Board extends JPanel { /** * Create the Board Panel with the appropriate cells. + * + * Every CellGUI object is created with the appropriate Cell object and + * Settings object. The CellGUI objects are then added to the Board Panel. + * + * Each CellGUI object is also given a MouseListener to handle user input, + * and a KeyListener to handle keyboard input if/when the cell is selected. */ private void createBoard() { if(s.getAutoFillNotes()) grid = sc.getPossibleValues(grid); @@ -184,50 +187,137 @@ public class Board extends JPanel { for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { - CellGUI cell = new CellGUI( - grid[i][j], - 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; - } + // Create a new CellGUI object with the appropriate Cell object. + CellGUI cell = new CellGUI(grid[i][j], s); - select(cell.cell); - selected = 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 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]); } } } + + /** + * 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(); + } + }; + } } \ No newline at end of file diff --git a/src/gui/CellGUI.java b/src/gui/CellGUI.java index 7f7e39e..62c765d 100644 --- a/src/gui/CellGUI.java +++ b/src/gui/CellGUI.java @@ -1,14 +1,14 @@ 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.Font; import java.awt.GridLayout; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.border.LineBorder; - +// Processing & backend imports import gui.backend.Cell; import gui.backend.Settings; import gui.backend.Theme; @@ -18,20 +18,22 @@ import gui.backend.Theme; */ class CellGUI extends JPanel { // GUI fields - private JPanel internalPanel = new JPanel(new BorderLayout()); - private GridLayout noteLayout = new GridLayout(3, 3); - private GridLayout valueLayout = new GridLayout(0, 1); - private Dimension size; - private JLabel valueLabel; - private Note[] notesLabels = new Note[9]; + 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; - private boolean incorrect; // 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. @@ -52,9 +54,10 @@ class CellGUI extends JPanel { selected = false; size = s.getCellDimensions(); + // Set the layout and size of the cell. setFocusable(true); setLayout(valueLayout); - valueLabel = new Note("", theme, font); + valueLabel = new Label("", theme, font); setSize(size); internalPanel.setSize(size); defaultStyle(); @@ -71,25 +74,6 @@ class CellGUI extends JPanel { 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. * @@ -105,11 +89,12 @@ class CellGUI extends JPanel { * @param value */ public void setValue(int value) { + if(!selected) return; + cell.setValue(value); // Update the GUI with the actual Cell value (unchanged if invalid) valueLabel.setText(Integer.toString(cell.getValue())); - internalPanel.repaint(); - internalPanel.revalidate(); + refresh(); } /** @@ -128,10 +113,7 @@ class CellGUI extends JPanel { if(value != expected) { incorrect = true; errorStyle(); - } - - internalPanel.repaint(); - internalPanel.revalidate(); + } else refresh(); } /** @@ -146,9 +128,6 @@ class CellGUI extends JPanel { incorrect = false; highlightedStyle(); - - internalPanel.repaint(); - internalPanel.revalidate(); } /** @@ -204,10 +183,7 @@ class CellGUI extends JPanel { generateNotes(true); } - internalPanel.repaint(); - internalPanel.revalidate(); - repaint(); - revalidate(); + refresh(); noteMode = !noteMode; } @@ -219,8 +195,6 @@ class CellGUI extends JPanel { selected = !selected; if(incorrect) { errorStyle(); - repaint(); - revalidate(); return; } @@ -228,13 +202,12 @@ class CellGUI extends JPanel { defaultStyle(); 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() { setBackground(theme.getPrimaryBackground()); @@ -250,10 +223,14 @@ class CellGUI extends JPanel { 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() { setBackground(theme.getSecondaryBackground()); @@ -269,10 +246,14 @@ class CellGUI extends JPanel { 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() { setBackground(theme.getErrorBackground()); @@ -288,18 +269,42 @@ class CellGUI extends JPanel { 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. * + * 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(); + noteMode = true; + } + + // Handle cases where there are no possible values stored. if(possibleValues.length == 0) { 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]); } return; @@ -308,11 +313,15 @@ class CellGUI extends JPanel { 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 Note(Integer.toString(i), theme, font); + notesLabels[i - 1] = new Label(Integer.toString(i), theme, font); index++; + + // Otherwise, add a blank value. } else - notesLabels[i - 1] = new Note("", theme, font); + notesLabels[i - 1] = new Label("", theme, font); + if(autoFill) internalPanel.add(notesLabels[i - 1]); } } diff --git a/src/gui/ComboBox.java b/src/gui/ComboBox.java index fec2702..75d4d9d 100644 --- a/src/gui/ComboBox.java +++ b/src/gui/ComboBox.java @@ -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 extends JComboBox { - 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 extends JComboBox { public ComboBox(Settings s) { super(); - this.s = s; + theme = s.getTheme(); + font = s.getFont(); style(); } @@ -27,7 +32,8 @@ public class ComboBox extends JComboBox { * 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()); } } diff --git a/src/gui/FileChooser.java b/src/gui/FileChooser.java index 184cf81..d14cb62 100644 --- a/src/gui/FileChooser.java +++ b/src/gui/FileChooser.java @@ -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()); } /** diff --git a/src/gui/Note.java b/src/gui/Label.java similarity index 85% rename from src/gui/Note.java rename to src/gui/Label.java index 947bc55..bf1a587 100644 --- a/src/gui/Note.java +++ b/src/gui/Label.java @@ -11,9 +11,9 @@ 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 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 Font f; @@ -23,7 +23,7 @@ public class Note extends JLabel { * @param text * @param t */ - public Note(String text, Theme t, Font f) { + public Label(String text, Theme t, Font f) { super(text, SwingConstants.CENTER); this.t = t; this.f = f; diff --git a/src/gui/Nav.java b/src/gui/Nav.java index a363e12..364017b 100644 --- a/src/gui/Nav.java +++ b/src/gui/Nav.java @@ -1,36 +1,53 @@ package gui; + +// File IO imports import java.util.Scanner; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; 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; -import javax.swing.JLabel; -import javax.swing.JButton; +// Unused imports +//import javax.swing.JLabel; +//import javax.swing.JButton; + /** * The Nav class represents the Navigation bar at the top of the JFrame window. * * The Nav class is responsible for basic File IO operations. The most basic * functionality is the following: - * - 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 { - private Settings s; + @SuppressWarnings("unused") private SudokuChecker sc; + + private Settings s; + private File f; private Board b; private Cell[][] grid; @@ -41,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(); @@ -120,13 +139,19 @@ public class Nav extends JPanel { * 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. This shouldn't be necessary - * to change once the program is running. + * 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()); @@ -136,21 +161,27 @@ public class Nav extends JPanel { * 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); } /** @@ -164,6 +195,7 @@ public class Nav extends JPanel { fileOptions.addItem("New"); fileOptions.addItem("Open"); fileOptions.addItem("Save"); + fileOptions.addItem("Save As"); fileOptions.addItem("Exit"); fileOptions.addActionListener(e -> { @@ -176,39 +208,67 @@ public class Nav extends JPanel { openFile(); break; case 2: - saveFile(); + saveFile(false); break; case 3: + saveFile(true); + break; + case 4: System.exit(0); break; default: System.out.println("Invalid selection."); } }); - return fileOptions; } /** - * Given the user's selection from the 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 @@ -217,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. @@ -263,8 +323,22 @@ public class Nav extends JPanel { /** * 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. } @@ -282,9 +356,8 @@ 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 * 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) { 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. } catch (FileNotFoundException e) { - String filename = f.getName(); - System.err.println("An error occurred while reading the file " + - filename + ".sdku." - ); - - e.printStackTrace(); - grid = new Cell[9][9]; + newFile(); return; } } diff --git a/src/gui/Root.java b/src/gui/Root.java index c24104d..2f80f41 100644 --- a/src/gui/Root.java +++ b/src/gui/Root.java @@ -1,9 +1,9 @@ package gui; -import java.awt.LayoutManager; import javax.swing.JPanel; import gui.backend.Settings; +import gui.backend.Theme; /** * The Root class represents the root JPanel for the Sudoku app. @@ -13,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(); } @@ -31,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()); } } diff --git a/src/gui/backend/Cell.java b/src/gui/backend/Cell.java index c77cabc..8f2f9a2 100644 --- a/src/gui/backend/Cell.java +++ b/src/gui/backend/Cell.java @@ -230,20 +230,6 @@ public class Cell { max = 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. diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index 545aeac..a48b5a1 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -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; @@ -62,6 +70,12 @@ public class Settings { // 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. @@ -70,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(); @@ -81,12 +97,114 @@ 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 = 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 * 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 */ @@ -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() { - // TODO: Write the settings to the settings file. + public boolean getAutoSave() { + return autoSave; } /** - * Set the default settings for the application and write them to the - * settings file. + * Set the auto-save feature to a specified state. + * + * @param autoSave */ - 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, 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(); - + public void setAutoSave(boolean autoSave) { + this.autoSave = autoSave; 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 - * to populate the Settings file. + * 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 */ - 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 getAutoSaveFrequency() { + return autoSaveFrequency; + } /** - * 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) { - // 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; - } + public void setAutoSaveFrequency(int autoSaveFrequency) { + this.autoSaveFrequency = autoSaveFrequency; + updateSettingsFile(); } } \ No newline at end of file diff --git a/src/gui/backend/SudokuChecker.java b/src/gui/backend/SudokuChecker.java index e2641ae..89c8ff5 100644 --- a/src/gui/backend/SudokuChecker.java +++ b/src/gui/backend/SudokuChecker.java @@ -12,6 +12,8 @@ import java.util.ArrayList; */ public class SudokuChecker { private Cell[][] grid; + private Cell[][] origGrid; + /** * 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 * 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; + + // 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 intersection = new ArrayList(); 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)); - } } 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 union( - ArrayList a, - ArrayList b - ) { - ArrayList union = new ArrayList(); - 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 * the grid. This is effectively an intersection of the numbers available @@ -145,9 +168,9 @@ public class SudokuChecker { getBoxRemainingNumbers(row, col) ); - if(intersection.size() == 0) { + if(intersection.size() == 0) return; - } else if(intersection.size() == 1) { + else if(intersection.size() == 1) { int value = intersection.get(0); grid[row][col].setValue(value); @@ -156,9 +179,8 @@ public class SudokuChecker { } else { // Join the arrays and find the intersection of the three arrays. ArrayList availableNumbers = new ArrayList(); - for(int i = 0; i < intersection.size(); i++) { + for(int i = 0; i < intersection.size(); i++) availableNumbers.add(intersection.get(i)); - } grid[row][col].setPossibleValues( arrayListToArray(availableNumbers) @@ -213,9 +235,8 @@ public class SudokuChecker { while(!isValidSolution()) { for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { - if(grid[i][j].getValue() == 0) { + if(grid[i][j].getValue() == 0) getAvailableNumbers(i, j); - } } } } @@ -239,9 +260,7 @@ public class SudokuChecker { break; } } - if(!found) { - remainingNumbers.add(i); - } + if(!found) remainingNumbers.add(i); } return remainingNumbers; @@ -263,9 +282,8 @@ public class SudokuChecker { break; } } - if(!found) { + if(!found) remainingNumbers.add(i); - } } return remainingNumbers; @@ -293,9 +311,8 @@ public class SudokuChecker { } } } - if(!found) { + if(!found) remainingNumbers.add(i); - } } return remainingNumbers; @@ -312,9 +329,9 @@ public class SudokuChecker { */ private int[] arrayListToArray(ArrayList list) { 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); - } + return array; } @@ -331,17 +348,19 @@ public class SudokuChecker { // Check that every cell has a value between 1 and 9. for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { - if(grid[i][j].getValue() < 1 || grid[i][j].getValue() > 9) - return false; + 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++) { + 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; @@ -351,9 +370,9 @@ public class SudokuChecker { // 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++) { + 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; @@ -365,9 +384,8 @@ public class SudokuChecker { 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++) { + 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( @@ -393,13 +411,15 @@ public class SudokuChecker { private boolean isValidSet(int[] set) { boolean[] found = new boolean[9]; for(int i = 0; i < 9; i++) { - if(set[i] < 1 || set[i] > 9) { + if(set[i] < 1 || set[i] > 9) return false; - } else if(found[set[i] - 1]) { + + else if(found[set[i] - 1]) return false; - } else { + + else found[set[i] - 1] = true; - } + } return true; } diff --git a/src/gui/backend/Theme.java b/src/gui/backend/Theme.java index 4ea4e29..eae087f 100644 --- a/src/gui/backend/Theme.java +++ b/src/gui/backend/Theme.java @@ -6,7 +6,8 @@ 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. + * 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 @@ -24,16 +25,7 @@ import java.io.File; * - primaryButtonBorder * - 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 { - private File theme; - private Color primaryBackground; private Color secondaryBackground; private Color primaryText; @@ -58,16 +50,15 @@ public class Theme { * * @param theme the theme file */ - public Theme(File theme) { - this.theme = theme; - readTheme(); + 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() { + private void readTheme(File file) { // TODO: Read the .theme file setTheme(); } From bf011ea75baf3f4544b1e491e9f5f4524cd360d2 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Mon, 25 Mar 2024 14:06:03 -0600 Subject: [PATCH 12/16] fixed error checking bug. note, unexpected behavior occurs when incorrect values are entered and checkValues is true. needs more testing/additional fixes --- src/gui/Board.java | 60 +++++++++++++++--------------- src/gui/CellGUI.java | 48 +++++++++--------------- src/gui/backend/Cell.java | 8 +++- src/gui/backend/Settings.java | 2 +- src/gui/backend/SudokuChecker.java | 6 +-- 5 files changed, 58 insertions(+), 66 deletions(-) diff --git a/src/gui/Board.java b/src/gui/Board.java index 6376136..7823814 100644 --- a/src/gui/Board.java +++ b/src/gui/Board.java @@ -119,6 +119,34 @@ public class Board extends JPanel { setForeground(s.getTheme().getPrimaryText()); } + /** + * Create the Board Panel with the appropriate cells. + * + * Every CellGUI object is created with the appropriate Cell object and + * Settings object. The CellGUI objects are then added to the Board Panel. + * + * Each CellGUI object is also given a MouseListener to handle user input, + * and a KeyListener to handle keyboard input if/when the cell is selected. + */ + private void createBoard() { + if(s.getAutoFillNotes()) grid = sc.getPossibleValues(grid); + gridGUI = new CellGUI[9][9]; + + for(int i = 0; i < 9; i++) { + for(int j = 0; j < 9; j++) { + // Create a new CellGUI object with the appropriate Cell object. + CellGUI cell = new CellGUI(grid[i][j], s); + + // Add a MouseListener to the cell. + cell.addMouseListener(createMouseListener(cell)); + + // Add the CellGUI object to the Board Panel. + gridGUI[i][j] = cell; + add(gridGUI[i][j]); + } + } + } + /** * Select the given cell and highlight all cells in the same row, column, * and box. @@ -172,34 +200,6 @@ public class Board extends JPanel { } } - /** - * Create the Board Panel with the appropriate cells. - * - * Every CellGUI object is created with the appropriate Cell object and - * Settings object. The CellGUI objects are then added to the Board Panel. - * - * Each CellGUI object is also given a MouseListener to handle user input, - * and a KeyListener to handle keyboard input if/when the cell is selected. - */ - private void createBoard() { - if(s.getAutoFillNotes()) grid = sc.getPossibleValues(grid); - gridGUI = new CellGUI[9][9]; - - for(int i = 0; i < 9; i++) { - for(int j = 0; j < 9; j++) { - // Create a new CellGUI object with the appropriate Cell object. - CellGUI cell = new CellGUI(grid[i][j], s); - - // Add a MouseListener to the cell. - cell.addMouseListener(createMouseListener(cell)); - - // Add the CellGUI object to the Board Panel. - gridGUI[i][j] = cell; - add(gridGUI[i][j]); - } - } - } - /** * Create a MouseListener for CellGUI objects. * @@ -248,11 +248,11 @@ public class Board extends JPanel { public void mouseReleased(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) { - if(selected == null) cell.select(); + cell.select(); } @Override public void mouseExited(MouseEvent e) { - if(selected == null) cell.select(); + cell.select(); } }; } diff --git a/src/gui/CellGUI.java b/src/gui/CellGUI.java index 62c765d..9c33445 100644 --- a/src/gui/CellGUI.java +++ b/src/gui/CellGUI.java @@ -65,7 +65,7 @@ class CellGUI extends JPanel { // Populate the cell with the appropriate value or notes. if(cell.getValue() == 0) { internalPanel.setLayout(noteLayout); - generateNotes(s.getAutoFillNotes()); + generateNotes(noteMode); } else { internalPanel.setLayout(valueLayout); valueLabel.setText(Integer.toString(cell.getValue())); @@ -86,12 +86,16 @@ class CellGUI extends JPanel { /** * 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); + cell.setValue(value, false); // Update the GUI with the actual Cell value (unchanged if invalid) valueLabel.setText(Integer.toString(cell.getValue())); refresh(); @@ -100,18 +104,22 @@ class CellGUI extends JPanel { /** * Set the value of the underlying Cell object. * - * This implementation allows for incorrect values to be highlighted. + * 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); + cell.setValue(value, value == expected); valueLabel.setText(Integer.toString(cell.getValue())); if(value != expected) { incorrect = true; + valueLabel.setText(Integer.toString(value)); errorStyle(); } else refresh(); } @@ -122,7 +130,7 @@ class CellGUI extends JPanel { public void removeValue() { if(!selected) return; - cell.setValue(0); + cell.setValue(0, true); if(!cell.isInitValue()) valueLabel.setText(""); @@ -166,25 +174,21 @@ class CellGUI extends JPanel { /** * Toggle the mode of the cell between value and note, including the - * layout of the cell. - * - * If the current mode is value, switch to note mode, and vice versa. + * layout and visual content of the cell. */ public void setNoteMode() { if(cell.isInitValue()) return; - internalPanel.removeAll(); if(noteMode) { + internalPanel.removeAll(); internalPanel.setLayout(valueLayout); internalPanel.add(valueLabel); } else { internalPanel.setLayout(noteLayout); - noteMode = true; generateNotes(true); } refresh(); - noteMode = !noteMode; } @@ -196,11 +200,8 @@ class CellGUI extends JPanel { if(incorrect) { errorStyle(); return; - } - - if(!selected) + } else if(!selected) defaultStyle(); - else highlightedStyle(); } @@ -256,20 +257,10 @@ class CellGUI extends JPanel { * @see Theme.java */ private void errorStyle() { - setBackground(theme.getErrorBackground()); - setForeground(theme.getErrorText()); - internalPanel.setBackground(theme.getErrorBackground()); - internalPanel.setForeground(theme.getErrorText()); valueLabel.setForeground(theme.getErrorText()); + internalPanel.setBackground(theme.getErrorBackground()); setBorder(new LineBorder(theme.getErrorBorder(), 2)); - for(int i = 0; i < 9; i++) { - if(notesLabels[i] != null) { - notesLabels[i].setBackground(theme.getErrorBackground()); - notesLabels[i].setForeground(theme.getErrorText()); - } - } - refresh(); } @@ -295,10 +286,7 @@ class CellGUI extends JPanel { int[] possibleValues = cell.getPossibleValues(); // Prepare the internalPanel for noteMode. - if(autoFill) { - internalPanel.removeAll(); - noteMode = true; - } + if(autoFill) internalPanel.removeAll(); // Handle cases where there are no possible values stored. if(possibleValues.length == 0) { diff --git a/src/gui/backend/Cell.java b/src/gui/backend/Cell.java index 8f2f9a2..040ed19 100644 --- a/src/gui/backend/Cell.java +++ b/src/gui/backend/Cell.java @@ -70,12 +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) { + public void setValue(int value, boolean isCorrect) { if(initValue) return; + if(isCorrect) possibleValues.clear(); - possibleValues.clear(); this.value = value; } diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index a48b5a1..5a15c92 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -133,7 +133,7 @@ public class Settings { newFileProperties = 0; dimension = new Dimension(600, 800); resizable = false; - cellGUIStartMode = false; + cellGUIStartMode = true; defaultOpenState = 0; theme = new Theme(new File(appDirectory + "default.theme")); autoFillNotes = false; diff --git a/src/gui/backend/SudokuChecker.java b/src/gui/backend/SudokuChecker.java index 89c8ff5..623134f 100644 --- a/src/gui/backend/SudokuChecker.java +++ b/src/gui/backend/SudokuChecker.java @@ -173,7 +173,7 @@ public class SudokuChecker { else if(intersection.size() == 1) { int value = intersection.get(0); - grid[row][col].setValue(value); + grid[row][col].setValue(value, true); updatePossibleValues(row, col); return; } else { @@ -203,14 +203,14 @@ public class SudokuChecker { 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]); + 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]); + grid[i][col].setValue(grid[i][col].getPossibleValues()[0], true); updatePossibleValues(i, col); } } From 414bf3c10434762ead8cdc219b041f8167f50700 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Tue, 26 Mar 2024 15:27:19 -0600 Subject: [PATCH 13/16] cleaned up codebase and comments, and fixed unexpected behavior relating to incorrect values --- src/gui/CellGUI.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/CellGUI.java b/src/gui/CellGUI.java index 9c33445..2c30d5a 100644 --- a/src/gui/CellGUI.java +++ b/src/gui/CellGUI.java @@ -65,7 +65,7 @@ class CellGUI extends JPanel { // Populate the cell with the appropriate value or notes. if(cell.getValue() == 0) { internalPanel.setLayout(noteLayout); - generateNotes(noteMode); + generateNotes(true); } else { internalPanel.setLayout(valueLayout); valueLabel.setText(Integer.toString(cell.getValue())); @@ -130,7 +130,7 @@ class CellGUI extends JPanel { public void removeValue() { if(!selected) return; - cell.setValue(0, true); + cell.setValue(0, false); if(!cell.isInitValue()) valueLabel.setText(""); From 579a791d6c85b0d20487803b5f10790a61f2fdba Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Mon, 1 Apr 2024 11:24:00 -0600 Subject: [PATCH 14/16] fixed linux/macos run script. added additional cli options to create jar, and redirected compiled classes to new bin directory --- src/bin/App.class | Bin 0 -> 2593 bytes src/bin/gui/Board$1.class | Bin 0 -> 1480 bytes src/bin/gui/Board$2.class | Bin 0 -> 1669 bytes src/bin/gui/Board.class | Bin 0 -> 3173 bytes src/bin/gui/CellGUI.class | Bin 0 -> 4836 bytes src/bin/gui/ComboBox.class | Bin 0 -> 903 bytes src/bin/gui/FileChooser$Filter.class | Bin 0 -> 882 bytes src/bin/gui/FileChooser.class | Bin 0 -> 883 bytes src/bin/gui/Label.class | Bin 0 -> 739 bytes src/bin/gui/Nav.class | Bin 0 -> 6126 bytes src/bin/gui/Root.class | Bin 0 -> 624 bytes src/bin/gui/backend/Cell$List$Value.class | Bin 0 -> 593 bytes src/bin/gui/backend/Cell$List.class | Bin 0 -> 2427 bytes src/bin/gui/backend/Cell.class | Bin 0 -> 2203 bytes src/bin/gui/backend/Settings.class | Bin 0 -> 5620 bytes src/bin/gui/backend/SudokuChecker.class | Bin 0 -> 5720 bytes src/bin/gui/backend/Theme.class | Bin 0 -> 3614 bytes src/run.ps1 | 9 ++--- src/run.sh | 41 ++++++++++++++++------ src/sudoku.jar | Bin 0 -> 26064 bytes 20 files changed, 33 insertions(+), 17 deletions(-) create mode 100644 src/bin/App.class create mode 100644 src/bin/gui/Board$1.class create mode 100644 src/bin/gui/Board$2.class create mode 100644 src/bin/gui/Board.class create mode 100644 src/bin/gui/CellGUI.class create mode 100644 src/bin/gui/ComboBox.class create mode 100644 src/bin/gui/FileChooser$Filter.class create mode 100644 src/bin/gui/FileChooser.class create mode 100644 src/bin/gui/Label.class create mode 100644 src/bin/gui/Nav.class create mode 100644 src/bin/gui/Root.class create mode 100644 src/bin/gui/backend/Cell$List$Value.class create mode 100644 src/bin/gui/backend/Cell$List.class create mode 100644 src/bin/gui/backend/Cell.class create mode 100644 src/bin/gui/backend/Settings.class create mode 100644 src/bin/gui/backend/SudokuChecker.class create mode 100644 src/bin/gui/backend/Theme.class mode change 100644 => 100755 src/run.sh create mode 100644 src/sudoku.jar diff --git a/src/bin/App.class b/src/bin/App.class new file mode 100644 index 0000000000000000000000000000000000000000..d0657de31c1d9859c8592a4ca160004ed0e8966f GIT binary patch literal 2593 zcmaJ@`BxKH7`+35f%rhg1^33SCZUj4tX6T4;1(5$LS3pIk_V0?GjTEjwRW#v?7mmK z*xmMro>P13;Hl^M2lS8X>3uV!Bp%vx;LY2<@4N4QTmJs%uX_M?;@2kBX=uz=^7eQo ziU=BXGz#cw($Id+m@qD+%9ExwmfGL%7)8meTTIJzw`rIkcM!Qg4ub$F>aF{C4^ zVcuB9OpO@1acSjKS?RhwD@V}`p)|K>sE;RxnlT$~I_3zpV=lLLmP#75vW5kjnZ$de zygy%Hfs$`l7^t{X(y>s3t^^Jl6H#N*s0pL@UJ}dbgM_E`Uuo`PLw2VnNV;gyy-{Y8h;*KXyo~$vNmW2Xw$FNp`ifibI z&vYX|loaU1V;btpISor{^aS1sArN9W5i5whY{SV%(S^sA{wFl-{f|e_Wc7b61b})3 z*5gSH4I|2ohFQLZZDOYb7*nypVXwH#_EB)vY@q~|I(6(Ja9?}B4(MK;Dc>+v^5ot`iskB`z*_O22 z-b4_lwVOKjkmTz49yYDKJ(;$xvTIl_qjfg+3GBxK4GRfCp5It%77zBrQ^ta*1W{L?Wi&~EfjEl zO5hn~sEvkd#1!%~0?#U)I&~^lDvw75avfvKDL|C4uvBLdx&gr(^p<@-(a_shZZqxF0IQ|TnkkK9ZxdeB(9`EQwiBB) z-LtWrf*vb{*kc8PPmL0K%(VCy*1dS5JBpX_ijG$WUQ=iTWVOoawURPbuynl6OjSig zB~KJr@TRI6Z?V`tl#5}x_G|gu50SjfzHmRTui{;$;60{CRb(-LT*I^&!psY0Wk3yS z<#ay{J{I^yMTV*dLuT1z*filY7QL=q6kp()j_U$n;;UK~{pP~fBhOci0y{)a>p47f zPUe{ACz|jLi&a-wu3##A-wJ%^_ZqI5sA)UT2%ViFxI>lVh;)tyG24=L*{BX0rEnc7 z62B_uCj)O)2SE|= z0KI}1j^A_j8yYzS&L`WoDby$LqWSm~X5B_>(!)G1JB}y+L~H#N7Tv~Du48Jw)Wgat zJbD`m?sWM#!d-q6Q%K#$I<>+7bsjc^(}ai3NvhrEVb}028rARv$0VX0V+67SbFdQg zu?lP0;x=+Tg0(n>IMX?a68lq~>c1iM8>r_R=ub9!*h?T8B0uEeaFX^~J!Cx$D|073 zoDR1P4>GBFFz=vv2j!a(nLlu${RIy%-J;eWY+|#RpjL%z9R$Z761E;~q;d&15ZOj_ zv+eX?D_2{1Kh1GFS3A(hUhpF&bktqeF~J`;s)K}Jk5P>R+jSRLe7>geMyB&Ft{(4< zOyM05?{_MeJbW;$CS;A1-zgAqw1DCu8v=t^ge+M)N;ZaA3K?UeiLG?La^*7Rw9t># zz371Nw7_Fkw6~HzuR8Ka6IlYGFJGgNZtyMRbe*+$ulM&E^4LHj{G|AyPBv^n`C}K0qo#Lz!k-765 LqBMHbm;2>ky~KTW literal 0 HcmV?d00001 diff --git a/src/bin/gui/Board$1.class b/src/bin/gui/Board$1.class new file mode 100644 index 0000000000000000000000000000000000000000..b11d5a106ca408f517ae86dec9d8084afd4f0857 GIT binary patch literal 1480 zcma)6+j9$182_C#vQ4tl(9#xFG)PmV?w7bWQVmHNb(!&CW}7_|%XXLDle#>){3D)t z?o1mq9z1&R4|wok%;3Rg24jpF2H!aw4K;&@?D_8B@4MyExBCwO96&jW5Lz^Zb<9JA zAzrOn*&)~P%+yAPh&X5YsX-E#X5~5Q(W)bg7(?r%VW%qGwiy!2Bv16Alf_&NIuaV@ z>u5ugA$i`IG_tngRI^9N&-03)*t;#q5_=h1(*37mScrBF9Xb|aF+;a9H!h1TpX83n zmff1q3zB4rR=F6e3E?`*wHQSgx-~4(u~Z_8&H^QLKDQ|sH>2pmat*yYR$yhL$Ock| zaDacI!ar_QE^x=pN-Y)9g+3iAtR}cYEQUqtKMiF8Jy@$Fjedp>k6)^BUktmInqPEI zTaM{oR$$M_hyyy-VLiiwipLGXkMOG{%NN|?9>el}}nC34! zhTinwTmy)kb!?IIi2GcOx`LNolgpsnWR*z`I~bPD$*+3fM7T64U#&>)w~_asqF&}H zAVXWpa`2jaeTw=H~(YF~{G`bF`eJv?3#}Pb;k# z9h!dLHVC~>D;lEXVc3pPGAYjtpqH+b4p#o!uoErVMbuh(yHb)Fm;uYIn}L?OhxinU z7;^ND&=ee{*F9+QMDaBHBB1>xJ6$V`Jxqw^s;B-P-i zv8;|&4Tc`=4s^+}8FWrbLdu#(k|T{&P^dcAoNkcvsUuUzhP!B&86&q5m)3P`nv(cK z3ja1Fh<=JzJVPIzVK4L$4MG)>=yuFRe zJ1|>hw~9>NncJdIWH$6~v(IFfpEXMnCtKDU0N>gS8dLHw(D0RP4*s{tcW5-QFbWFi XFb>fhroW6498r=kl<18$@)G$2Ek8=e literal 0 HcmV?d00001 diff --git a/src/bin/gui/Board$2.class b/src/bin/gui/Board$2.class new file mode 100644 index 0000000000000000000000000000000000000000..b263b6276a0e0d4d81203445670ffcbf3c352b03 GIT binary patch literal 1669 zcmah}-BS}+6#pFvS+co6Kq3{P+9-f2Y$<&)s~0wKLtB-Fxr({GH!9cYpi))vo~N;YQGgb`2qc z4ulEO%D$Pp>lki%WRei}w@h#3I*W5H`HYTEL^S9EQ3yh8+o&2T%djh{hsAAK^4V?H zv`zmup*@i-M9_^M4KaaU^bsPhK!i?DTI?Xpgsvb#Mq1Y5<1DA>M_j{cfdRZn7zk1r zPyLjvO50B@%Yy~AA?Ow9^C`1sxIwl|1cNxMVMyQ{&Ns?wL?whu9G}n^WKn*MPL+(V;oMouFUF;V@zONF%)&> zu2YqPtCC+8xTfTqEB6f3_Lc0qzz0gEyRvFnX4&wijwwuQxFL|nhlEqdJgagI=8bFC zOz)mqF`0@`B9mlHgSaX1u_~fVAzpQS>8&s<)rMJtTi~{kM>zc!33=)Jrd{!vh&jw_ zxGRuRG5Svu(KKQPnfCd8-^pyr(oRs9rx;uixUbjdCE4*hjqR?MsVuN`#j4l#$F?jo_F@$lDu!Kq|fqNd5<&rhbN@uS(B<;CMWGB5N}h8OvaF9yh&>{}6<)K(Uzm ztMHE|#BUS^Yl^%Z=CzGieQf+E(Adw=4*5hV@i)vXJEF|{4&HtjV}Zms{8g@J_*Jg- zZ;w7UsQkZKoXxhHX7mW{&^X#3V@IGPbiG07aJ+_7w;JaooLRkE!~4(b^2@Q#A2Hm} z)sSrMCR)4H;m|Lbe9rbQ(MQj*UO&v#aOWAOIpV!^h`)>Jj=Ewg9-54IOxCcP4mbQb z%%ga?hQ}{j8I`Dp&t9PSgiQ^fH%>L2gc0zh2rVGabPXa(Lx|BioTZDnL?f7>%Sh7| z%+V;8D1l{~K%P=O?~~Z2DR?xE1G<6lDUBcKCSKAkUej&7p*#4U=3sK{UTD$3FhCkE kjB0;kDb~fE-VRdlz~VK;_k9<8futMX@~U$KR0H|>2ZHf#NB{r; literal 0 HcmV?d00001 diff --git a/src/bin/gui/Board.class b/src/bin/gui/Board.class new file mode 100644 index 0000000000000000000000000000000000000000..94b6029a28d2c25e40a7efb198142b3e23c84432 GIT binary patch literal 3173 zcma)8{c~JZ8Gg=w-R|b5*`(Q&G)ZYokz~_!A;kc08`?x$(k7vhG#Dz_o9r>!_GWL~ zy_=6JC>n&KD2M{B{!r>i)EN~VvSF-NodF#kXZ+C_{TKWPU@G-_@7+yyG0vFGo^$p+ z@8|P=oc+s(zrF$B7`|^Hps?e-wP2;JMK^UKpUn(g%TCdaK_O%!jEF+ya5k59`xU~S zLqlB?F>FKBKum7y6q-bODOFg^=4MhSPg^-{E3|bEZz%N@j#xQsM(5LA6H(M-yMYE1 zaWpDKXNuX>fMew|3aX&cGAx8CYx;uDWm03>b*Z?J7O5R3nvtMmGxSrT`Rv&m-a&2K zX#+bIT5D7kGtPzL;H;*`d}g zrljC5+gc&mDT%2^kBNgquba+m%hevPqULTB_n=oHP?%d0?UK1ZjpMoJc zpExt5&`^$9AR@uzy5GbD_yhqfXj@Oant;`NiYrXw&tVfs#Ghzh&p8YFm~9L7K@&%X zYUK62mCdo*>M>wqP-wBdUa;(JhTg~UDI7O&!o)+M6V27hsuTw86Uy;fJx3H0on1Ad zdE|5qr!Z__#Kb5bR%onNQxm1M8_Ne1(wg{8`xD?Z8O`MY|ZWO&vp^%-j^@L>?b-_TN0cN^RVlC$*hAC(%(a$M# zch=w}9O)WL1zz^6L_BLEi}M^6ep!t;#eyEr7F?avd4(QdTP5#RnCS(bb5k|!L~shW zi8-+F<5nh9bE~ko3dF6%eTU{vd>%9%qylNw>NL&GJ2{$7SNSpMRERPFToXksaIg`# zN1a6v&>^{4GO=8_U;?A`^b01wh^IJnroHLVRD+U-n+Sf%#Fs0A1J06!_!Sdhm3of& z?P1{Aayq=8+<#9DD|k-Q@w_)KB~jjupN9=BCk(t$BiI|mC455!Ut~r3$oqm_6C0`c zukpFn%o6_4pp#+aGz@2RdbBt61C<$U`4jkFSt{Bd9&PhS9hgfB31Wkj*u~@n4i|{7zPsaz&R*wOdLmb z!5z^OC;P}8%CUY2ZL3hw1?**=Z{xEVK|o$6o&xgRcsob1w{t9dJ7=l4a{zLW^05r# zn_R!eZyBD0-0w|ZhnkE>?n5^RZi*HUA?;awxomNc7DMEI;D1}}SwrgoF}u%$V1#Bz zY4%~7Jxy00!QB|6Gvl})XK3`>*k3l?!CldGIQcf3-sHjeKH?Fd;Q9*oa1~s}iD!WO z9s!~$sn&2vmiriYT4Lv?pPyr!82HZ2GP*2cI8Uc+?BL+oi5%MC;C9SYBj3TYg(LhX z&U7T{jakAcOE_7=>CJEBB|NsNe!G{Z1#HJ6e+?{Am``U})n5syKw9)QJ${)U^O%w_ zOL$TOkj%aZE5JB{huh-OYd9G^(k}bq>zJAp&bHq6UQw`)%xF)tr-YdjE|74D7d>O5 zP)@xiIFGFa)atEwR}|NhxES!N;2`RFUSS%Z#hrK#J$Rn=@B)VLBA=j_a279Pmb4u2 zTzrTBVC9BeZ=zn^grPn_{|AIGz)|#FKQi?Xeq))dNZHwI-6a(Ixh(dD#G&@yM5y;| zTrA;f5}&>bBeaT8uun34sDs{aaTiI1FQX+9xq`+DR|(Io;%Fj#1v?WFL9e3PySPgP z>f|D_ie1%rRq`GrPl|gH`-nIgvHOF4p+qPmQ1^usVII=$k=aD(TwkPvd-z)y;f< zh`3T@+2(V<50gYc&wQoJ6}*r2EA1A$j+Mz|_d34z(f0f&?_b4hW&XYF25Fw?bzGWE luH)+;VOMc7yiSGyF5?P!nUj}To8RNPiRTsW@^{JK{sSXg>3skI literal 0 HcmV?d00001 diff --git a/src/bin/gui/CellGUI.class b/src/bin/gui/CellGUI.class new file mode 100644 index 0000000000000000000000000000000000000000..d6c250ababb2714ea90d62a213f9bb417a54c5f7 GIT binary patch literal 4836 zcma)AX?RrC8GcV@l1U~f5V8Sgsj?=+GD0Y+1VKQd!LZa}M{6gUOBk5U&^r@0wb}~e zZWlp`v{JN9X{nW#Oi&iBwu)%6wXIg|YHRzWdmr0APfJVR@7$YNfcEK+x#ymH&iR)2 zd%y2H$?1PTeGVncXycxy1ZB^vJvE?gRpTQP;AnbCMOHA}(M z*tEijVuecC77TAm1sB-SNPBo&q9^5t4}K9-s!-V2*4DJ51Z5cF!%&TK3{xoW>WK#D zS+UrHH>D6w6Z zL}-h~2XF-~<L3^1h29#D{k50l(ks$z>kl@_93Z}LJwnQB5mMkW12DhC@__9pb>tx zxn#&i%#SVD>ccjT?YQau%3~mK{9a~lC7-GZ!Is+5?y$XW zE-O!$o#=@Zz>3@)o0o_sY{SF@8js;|@_^_u-2geWH0tR*M8u;5ex9 z+K_9-RP%pe1OpA_$9kJ|Sdo*s%HLs%Pu*TO#O?g($;NlZF zBKg|S^t!-CgKHTEG>+maC)u523JuQY^Rzmv#}5_JE@K<(+_)&b$(@%v+;$V9 zOcKi!Ag9rll8iESW<^f#_2HKa1!9vwl<2WLt@%-@@Y?N@lZ8|4>1A{r(0uZ8!nXps zXPA-q1v9cvo00wBjO;IFWJfe3I~hkvCpUBUS~Kz@U`FA2Vc})sMN(HcsaEV`0FX#^QhoOXE5Uu3KHPoU7C^n_B*WDOMTY1=^ z@nb&Y2H7CIs3z7mXuxnx!bnWVXteSIbrmirrp?588kY0!asyX7(88uQgI%bVbzvs% zCHfCy7VFw%2a^gN67luEkHNvl0d8xajer%!e)?Zx~dh zqz^hFAr*L62X+RZ1g}C8!e@-mM003(4;+2OKshL_`H4aUQ}Ie z-J`f9;1zDrlG=<7clN=@aORjY4keVGq$xeT9B(Fpw-Wem1bjO~a1%kinc&@GOdE&7 z;&V(E!A~)-h@m>i)9#!@mr_AGfS($?NOG*F?Skm!TJE}-7h`r8n%H$G;T~g`7X`(&)WY8BVispyBr;QSwbrKbDbC5m z-7@g!@K};Y$V^~z6px}xP-HQ7vnuQ%J0D`49>#d=Wsdg}q(^e&#qR6s9_#8Zp@Jsr zd{~@uNF)UoWNI-U=a-*hs(YlKqkvS%BhBY??hu!Lp271lMab4wBAxK`+i-0M<_NMw-@@SY1CK`GLxzyMS8D3O1PDS3=3cbuvA z4B7E4RG+{b$PM9y;wTI$6VHB;G5_A>9%xKq-I zOMbq0I()q#EH5%7r>OBI^5HZa@XIV3uOPrqHyLLJt!cl&lp$r3u)Gq}CL!lRP z2gI5gX*`m~=iS#Yr12$w6ug{;uViOjJH(k}IM+D)Dog1TQlmM2vTy+3NF(i;SzBIm z08{z3p?2yE811|`o2cmN+&N_&+o4q7xib=zYz(FoM6isN;tkZW?~cKnB+XmQ%(oGu zpKF%jP8A`@Qr)ad$RYk& z;A)!9Q8Wz~*=d|{hVN0(5BU3Gwir0VhQg=HDR3AnR0V1jTa^5fM!W$A#NWg+xcs#pDLYrheud;*fYyJ-COM~FL?~n z`iUnGpmSpV&$v{I)N1P*pB*V&?{nehBqjT4D%y&(H z*!7S62}82(dai6R#HzK^3{ptz$Z*^OXDB*j*X-K8FT(4aZ6PJqju=voke$zBD2TOM zJ6QxpX)BE-|A`N<|85 z9A&IC#6Lx#+%-ssn%LyHqkcXyk{1J^GTR(?Q6citj67exR;b2sA3HjBIUe94$=~=x zH5ws52JX-fE{(|K>@qiB9mx%K{*XC@hXb1{{oGSz%&2= literal 0 HcmV?d00001 diff --git a/src/bin/gui/FileChooser$Filter.class b/src/bin/gui/FileChooser$Filter.class new file mode 100644 index 0000000000000000000000000000000000000000..3d83038775a7d8e75ff8c93fcf9a73b0b5330bbc GIT binary patch literal 882 zcmZuvT~8B16g|^zyJcBmK|tk0Ye8(GQV|qQjY>+4O(7s$0JgAcAqB%k+C~OhhQguy!Tnf~ zCw|ziyz>L@9S9+LRCz^vdKHftvd{d`SI-%YVrkDp4nrm^8^f>}Y~7&Zi==?T>~gi^ z9&?7tVyTgw1unI2sfZY=7Dh22J+%&P1h#kz3b#x2~Yj{JS>22xjD68ffn$URjp z?dyBqu~EPRiSp&PA5mOHpArT8IoxHKYsDRL6z6y2)6;wCR4wf>q-&x>vm0ypA>WCQ+dO*fwgZYdO|A|^XV;BrxMT{+&bpq* zdx~LvpnqTjX=fN|Dc3t{y1haDDEYF@r5E`<<%^KPYKb`Vw8gr+hJnOY?FmC}hfDQZ zNM&M!1YHqJzSqN$Tnbz%xfXMAG32pCKbU?D!VHSEmgs99EQx29zk_|F0K+n^Swdg~ zE3{^4>o^oB<36prN>CSraBZazvyYKxxsQq5XB5iGrqjpt#7rM^XRs(Ee+D!C1!?2l zIH#gi!Z1S|z+*CV6AqqW0Z*|?SV5H#u#x@^GHcX$N&JlB_JDkrP^&X07QYf)7W{%# XJ)l?$Yj~L8Ivx>Pl(mF)qD%b+X_&ij literal 0 HcmV?d00001 diff --git a/src/bin/gui/FileChooser.class b/src/bin/gui/FileChooser.class new file mode 100644 index 0000000000000000000000000000000000000000..941d85f4a7c92a2c2f56c31a8d400ec3d344d477 GIT binary patch literal 883 zcmZ8fO>fgc5Pj>|aqGHH`jMn5O(CVUj+4f;APy8&LJ^2mNiRW>fK!?*#SrYu>+o^m z#4q8(nM*2x)E~fK;SX@4%-VrMEO~Z!=FQtTGyDD5*K+{(aod1`ln&K|hBU*<%if#b zJ6F8*{1f-^p*Q4>7eOF+$dJD0`CfFuuwb_&%Jl+Q0`@Ci12dS_VVIbM$zc8)W|$p% zB9cfUb1TpO&o*Eoqa$l#K?=y6jJ%lk@4ZXh7o(6%f~M$t!aE*vnY3hL894?`MDK^3 zA!SP;778X-q<>!UXmr})QAD)}8Cf-P1#1k;6p?g~ya9zC_&s7AFsSy^gkjypRb0Eo zb@-6?BQNmV!GJDO?PjH`V}l_-C3cfoBOV$kV^cQt265P}DNx)m3)?1aR1%p|Op?m5 zopkrl44zY{pNt@GO9CSf($R?JWLlojk4CSLdH7gXLX@4T*MHUSoyKP!b!zKW*BOjX zFbex|TN$!a!KCALIX#BCBQBygm$WoDv+4UhYz%us$T6l98C0i5??-+q&?Jy1>(J=~ zdDFD+I@L3<>KJ;vc7Bk$qvq7oC**25b?>g`6tywtKdS8W#SdqUMf&q!C`mz;>^gL^ z1tKkC4XfCo)kX<5irl36)Z(aefh;r~mW~-6JB$H8DM^t}W1^)HVMPZ_4s1q9n z6fKklEIcAqA0pp(WM7R4rHdd8^lL)H>6pAfl7>geOw@em_R6raWI+flV}(#2P6B^c zN}5pXm=Aq<^;Lxfzx!E@RnwHL3ap_@pihL#lvrMCza53TX>-0Vu#N_ynCRP)BIF&n zXJZ3TENlvFVVkh_A2XpoRQh8ajAeZLmNkcQGzkYhnd8niTG1$qGm%|^Jv=2WCrUqr z*Jh)dj;83T_%ng$MtNnf(p5Lw^m`z1cu&=iVl`DZ8Arf{N3dALjAy!;H?#jN|nSB{e;w#k-vLHLknK>c$xvc{KA~}BeYMIB^ z$lly2jzKu#=oRuAZrwZ1|3uz9xkK4Iy+g%IQTxsS!YSJZifp$~!Zwz$gH`OJ&YR7d x6V9kw91)}TJnsj}DITZTnYsHZUKqEM;%H7<&6E$A@Q_K4n9^Z_Gp5MF{|99HpdbJM literal 0 HcmV?d00001 diff --git a/src/bin/gui/Nav.class b/src/bin/gui/Nav.class new file mode 100644 index 0000000000000000000000000000000000000000..8ed673a7d360097ac3b7445e4e7dd06662fa955b GIT binary patch literal 6126 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 literal 0 HcmV?d00001 diff --git a/src/bin/gui/Root.class b/src/bin/gui/Root.class new file mode 100644 index 0000000000000000000000000000000000000000..d827603c5c17c09868426fcdb198dcc950b38909 GIT binary patch literal 624 zcmZuv$xZ@65Pi+!$k-r@2)KjWFeJ{&c)*ybi3dXrQR01&4vxZ1GBkppWujx^!4L4G zjMWHny;Q2Y>%CX?y5HVkUI84Uq9cT`h6qO#Qw-@F`_3L(y@BJlt<$>giVnlnzT-Ob zfFW#@FLg{Kra|YJ0cV(N_Z_QgU)>6~WnBm<3D{$pZVTDC7G1%RHOjT`O{3##3<=C? zNOGi*R{cbC?s<|SDnD_Ve^5H1<~Xuy)M!uMcZ7}{@)`;p3s_`G{ZUdQ3R(A^uI=9+ z(V%VL>$@#lmF800TJ}I%$6m+ttBQV^V+E@W@t%<1+cU=RZskP|MUIkEPJF2}#84`# z4UWwT)rseeiE_dCvD(D7YWb2Oa_qIpwAq^DinD&VDg1`r?9hpR;r0D1apI^VrmWgg z)1yd2A$m(jk*uUFM0pfj6t}6`rCQym%x5sO5%Rc-l3m?=VA9^%`5F)`WD10Avu+?>?{gD1v4-)jy59mil zch;w*Vqwmm!<}>PoY~K>_YVM9I8H%B%z$nojszh)5ew0Dggb8D4JL9J5E2)*YX_Hv zSgqbSk%Va=Wnlvrp*Rlh=0FUm(j7J1(s8O?+YhRJ;e?V9U#yTi>R8%B23dmc$`_U` zcmFxtV(HrN4XsYS&m!&lh#fLr+m*d=Hjv(<7&y#iZ)NZ_AKeR2%w!-vpJ4bh=tZ8D zKQvbZx0G<|VIF!zd2K5(Q(fsyMM6mSq#xYO{lGwxZPte;SRL1uUfU7AFMR`tg#3C_ zlnxKCLgIL$m{8$tG=2Dd)%pE9|sW@P-4t6 OQ$_(tj7hF4(7pjlUw1tK literal 0 HcmV?d00001 diff --git a/src/bin/gui/backend/Cell$List.class b/src/bin/gui/backend/Cell$List.class new file mode 100644 index 0000000000000000000000000000000000000000..820c9d6aaed05ebdefaa140b53b052577b223430 GIT binary patch literal 2427 zcmah~U2ha+6n@U`wzIoT+okk_c5PjtK)Y;r0g(?E6uMBUw5706tmUKA?MR32&NREz zhMJgop+CWBywNl!jR_ZGN=eWVz2U-Julxflek3O94I$L$on1hFlmv_%#G{RH0fBxn|57sk~udO`RUUX69T$^(~HY(0FymN{t)2Dbt=vWz2lOqu(mgCopLm6N0w>b>DXk8~GVCtzy;cs8fLgu3_cX z#zr0W*u;&J^^Pf!KDF7Xqe*T03&wS|-mIggyteEx*HIlWppEa138Dr%`?`iTyeNo1 z2R@8;Y*DPY(ynv;MAHluqFslM?Ov9)dEHfxJ9TuTOQ4lZcaVF6*nfnma4pB#r6Zy8 zu9{T>gpfo^!)_gWu=gqHvvVcaERc^g!_bZWUQsM3HB_{0H|rKnqmT|_pP*LB9kWU? zvoP(>CBxW{0~!wMID}UOo7VAT&eJMN-e%y=a%8XE3~Sj}(_O>$Y>dM?j({a^E*Mj0 z#<6pTd% zoa&7(^(L5eY<8SYFx4$@u_U>?X%q$FtTR*0nZ1_EEu>mHsZ^xPpjmQHI;y)PJ&xm+ z+@djE&b<`GIX02k)iF`!upL&#j=7U&EYAuh>#8vv#s$2oVMIwwz>X&;&9Y~mDKk~B zKw0fxBj-9rCj9a08dVI-s3#43B5#&D`Wx+9OtSDzGYM+pdqN?gof7%@qjm@f(tQ! z!l|FAdymhfoSq^}1^Hck5s^<2kAIE!k$ChrVu~a;HN`Wact#aZKAIQsVU%wgKF5yo z<|1lVxouN!HH+9r&jpMr)8=_>^;}oX1`;av5+fuCw17iuIgp^&Jv6Cd5%K6eHmK>q z!t(tU$H8v3MaM+9@*ZK@fib4{M455Q3@k$<`vxId@nMVju|on#N;US2hO`86N0Nu(BkP!HcD!b$b=%YN{Pf{C1_lk zE9$F^EBgGv@X&O0S?l-V8Qcd`dflHvFGIKQnu=#*X$xemTZsAVftWO=|&7xZJG z0)Cz}mBtE<&2tZp3XRHE1#VP?`NMx>1+R&{4jJOfJA;s%sn!tlXw!mv<2$=|5sD?%O z#g%mTqO;By^<>w(u)~)#>uR>1%1EMA z$`pxIic%?%6N$!1ZrN3+8&7B5Gb@)D-Tbse?-gpMifn}m=Zd#Bc|cquJulodmMn9! z6=25m(ke0;$+2@}!meOfQDm7B^(6S;7Co#=l*sFOuKc&^bRo5KnUplA?Z0wkET3Lh zuoIOGNmkla2v6l!@=NY$S~P~F%e&;2C{&+u3&jan94MH0Qz2U3r@HIfJ>^y^n|1Re z8K+Q?9yO%TsY3tC1`P5g0s)SETAcah?dZs6(vfYeBb!x6_C8}BNP;nruW)r52KK-GFJwHc(@4B4Xb|<1DwEH_)<9h(e0vex{VsAXx}8)FRGDdVqhent0SB{+I}Z z$m3;(4h;VW6IV#()2R+oBVd!JgQ<=(RVVwl3vD=tb{yx}gI9?-4BsPG zVweaj#iz*lh}+w1pX}?}PJ6YD(*UWht*o}km2__5a0S)Kb$jlhk;Qs;1C_X|2Y9FBm2r-CWGyE30(neY$cJ`|ttp`$HVTN9=%G z+NA)DU?eP7oTqoC+=IMU$-U=sZr#BhJC|8+D^Fswm!*8ocHtY#$ z-WKrrPQB0V)LXj6FZc$(ld{GZXyc#`X0Jka7AVf=$?69ejR znCc-8KGe?hf3;UrA03uw&_c0hAk)WC6Ug-2(ZD;g3mZu8d`?RK{mP`j@lpMbz4(JC dXLUJqm}itPTq73n7UvCm9>m*>3*5Z`-+$4ibOZnZ literal 0 HcmV?d00001 diff --git a/src/bin/gui/backend/Settings.class b/src/bin/gui/backend/Settings.class new file mode 100644 index 0000000000000000000000000000000000000000..1a8bf5ffec2ee5aa47dcae4f1e63ef3531af0986 GIT binary patch literal 5620 zcmaJ^d0-r675{xXx|?*;B-=LFra;irrrS14!K09(mb7VuXPVD9Rz1f(I1vK&7BnIhv#fMZptLyzvAiYX*=29x@;z? z8g-}-AY@=J=1t#L>u_7o>2yY{beri~3iFzMHmO1bjtk&51{Po;W%@IAq}PlNS;<(W z+wwfxb`{DTK0-5H2x1Y#9I=xzXT%kMPB3sHnh2NcnQ716VS57#^Znx46~t>5sxq#X zZW(YAR){z+HgK{abX>w5vf7+v)bwNrzHz&x=ZdpS4K$0VhM7vOv(tp$Nsor0usDF_ zBx~esC(>@m349nUC1WkG;>325IK{v!oXP}R`)t=^piJN{VZ7eJ8-y|2b_6e!d+)zy@Xqr+qmFJ5F41TjZ9USm5#MLNpIy=t3MMr(_*v3z}a}4LNM-_n)xaK zbOf+Tp+xiyG$6)M-JZ8I*?yA13`k3xOY8WA{hN5Hn21Vz{O610MA>+FP;bZsYDjXgL| zd@;FKbKY{)Kn$$%s#so*@JyL6Xt#>teggxrDV(-kd%xKmr=r>=oFM~o;nYQ~cznY- z9o%%lNF{LLq0bIM9mz_S84pHUQ_ooxe_)O0x(S0e>a+|5uV2VJf{TeF~z z7}y7%`$|ug6y|o$*hzc+P6O`}-)hW^=Mb-Wm*ZLbP+VZ(-NLCAPTPPL9olZjGg;ny z47^u(LE&|q!#a}p8F;@&K2K~#N=eqcme)oc3gEdRRA6z4!CWp! z^$La6KE*JzPxv&RAgccqTa>-R;il*cj-;O62OWrKDnoiM$E zL>2Fi&lvbDZXr@mhQ7@&c(#SNg2%hTG83&K1O>d?@VNj!Z{V=ByMo0cLAL6YsKt4N z8t_E}U&8H_%q6+F*m;1PcV&z7P6KzzxvG%^Q7D8ep20B#<5EMj1}wXOz!TC@178+W z<%k`V&R@~SlOt`wPFh`=M6Z?JmOX4WJT}pxO=e2}58$4>$NEYwAV4a!ye`{Q3#~8f z^GxbI%684{By8s$>;(+dhgs3g&P8CTnoVJSapCBElS|!KOE8rU@o}o_G!*Ypptn+v z+Z6s!^-(F70+}r#yW!TU&Lnz8e+3dbmD3Ye1XQ6dO#`Zk8srYTLCLMK%%|J1ThL}s zq@meILfow5HfxJp5lK`lA%${MMQw>~JtsGfSf2~500yrCnT%vZX#@&lvb+H&<_7TuGo*Ufa>_70vqyKD zp6*P{8Orno@q0E$6={h&$v(S3;%agvh^GjgN@e!%k6YZ~8D66*cW<}o-X6rCcp6lM zVd%-KWzcn!LHtEwWvQu6b~Np{PM_DaBRgL8NCmn*s>SpAf_Rp3Mmp`@w3!~&x3(L} zasIB*5Xrddh&y1Wtw^6}<$3?4Apsy|dIL>PUz0GKl8(ohOfq&xlWK}Nma8dx7;34V zg@0A!-*`TN7o_9A$T>^*GJQ_%c5LFUoe9jSDBS$Ch!Ztwl&?Yrd7qPWbb96VqgT#MdgTP8S9V;za!AoD zM-shq`p_%K1-)_{&?_&0z4D^hEAMu$Cvqz0*9xDL{C7Wx5%Fg*JOLFB1&*R>9JOPZ zA3lQlp~i6>e?8`fLno0wiPr&RSaJyUdBQSHxEtY}6kjrd6`jk+5xE}KTuwWPxm@13 zBRr1N`CD@c0WNR3L;G?!pQpm$a~Fq)-TX-C!6M!jD{&rY<|sCxpEKhCxiy!%|_sNmyfHacmN2iKA^fN84sN8q7KxY9GhO zgJ=lfjn18+uJXFN32Yfd_aV&9l6DkG;qo!;ByrEq`m%HD%bO^7ZII{1*j>6wqhQG$nXnsnAI|hrPz-ZIKU5( z3$T#`+_{|B_TXY9aS2>bXZvs&xgWrFxSW&76}TB!;t;Ol<$pD9=YVnq*J=c3qv9Br z61IxQTH?eHkz6?}pgAQ!@j*edBrM=Yit&RaC$>9RH78F6cbN`O=l>vTv-uA)n$z!? z3a5dE2Tm^axP@hSi1l0*yQ{OG#aYuTtthtid<2i3eyi9>f-Wj|KdYj${rh8s{Iw ztN`k^2PmuXHKs9p+>Ylea8}akw%hqqjPaxV_jQ`giV6@?!qwsBEZuxvWaZ}&f8Z&C z^E9gP3^nehLo-e6JX~Vpc?x}lewUhPlxohEUXcm9TB-O!q{^uAEqvR@M0ZIPs*Xb6 z!FOkvU=-bcCKk}dLYio##{GD}$3$8N=*=`K0g!dX`-DbHc;aSz9t4sO>|R~v$mg!K|d2cG_i*!_EO`AzUfYs znn+OeNB-$f_?Z}?iG4INO1&TBCpp)Oi*~p)7uQher}$Y;bXw64`*1Z({ zWx>`|IJ9+Ox*2^2)dMv1Aj6Sj{FQI@-cgF!!wi5Q(o~QO_);EXIm~CXBlI4@k$Uyq z&8Vt7mDlB*9I384z9#!Rk>{RS;7*WxroM<4HR?;KsNXK66{trrS3Qao)MHqs9>*H> z1Ul4{x>Fgbn0t&Eh2%khe(j6Vg{2rhOVQu>V|1Y(MlTSf7m3kJ)O*CYa4#w`QC3gU zNBs*|u1S4G7WJvJg{UlRL{-@$V)x|kI!+h-|6R*`uPX_d|E&MsGon< Y&%f&D-}Lhzc!}E8)cgIK@De21kOTuPl3)r%#Xtf`5Ft^>lB{82vzzX2BG3nj zsgGJ8)wW710zt(Yu_IQNimloiZLMu>$5v--tF>c0oldpwO#dqK=i!Jf!7V6i7_w*CEb0|nr-3EosoD~O>19QVrO6D_6WO4fzh3eb_pi6w>Rg` zYK+8U^=_DIr|~8V)i$0)GTOb8)7_Xr87`jgLlGvK@S~VhyCdn1;aFcp%hRMDO;NI! zP|)(GrlyLGK5iT^QHnA_F=ww&+`2x}6Q&aJ?lpZq+ak%7U`lyYMYGnmFCC55EKerG zyPKn_biGc&Lu;?-)(EEAVuw!nh%d%{%z%=U;AOF3K(G zS0r{_d_K)iP4h2zBPf`dM=>8N;82$;6V=L)Mk=}|q6X%hxB|5_Cv7G04;v;21oYQJ zLD2{{ny7VmB&n8%Of1HgTpsSCxBa#Oau-*$DT~#asK-)zH=MQghJ-e|QYXKG+O>Nd zOnd>$czi07UYAIvqT6B-jR3mQS>A4Ye1(Zd1z=|?5$o4_U1Q=}C77o(5l_?oDIXT# zIulJAM)aHBL;+~EiDtBLCQq=jM^B`=X;(tC2sfJ8gw0fk=Dv6{qr5`t>_NMUn{cyWd^AOYqFt?t7;&l)#vs(@ z+-jmji8YbiEbk9TV_}-bhPP65n~6@)A?|(^ENgEennrupcA*CREhf6LUEs?er;wm+ z+vdd%>~te$q6a)ahaXyZr_zxgN|fkRxUpOqP1LMQ(zdPXWF*{EKY=9EYvPL_A588E z?~F7i;+^62jnVXW-lfvvc$!|k2y?!cJM!VzTbYGHE?8_THOD%tB9kZ@( zsLvN?`Df;(m;6%&Rm0F4&8D9=zxvboEB^vF?hzEUHwmi$2VEITZrrC9(h0uSL|?Kq za&1)kZNjKbRjp`^$X=01q*Li+xVI&e-k#`6dGHXUl-Xm3%!sCDMB|L4Xjiod-zFI_ zE%f!oFMjf{U=9g;MmRkqsn><$T{AlKOdapS0UrkOT{j+8z&t3JJ;JU$DzP(CV?oo3 zm}|qG=|pn3;E`N{wI^BvU&q5@k<{$w1ha6j?dn{N=OAL#!VSB7BYCE`=ghZifqEMh zdQ*Fg#&9gw8cj#)nG2iZ@kp{U7EYxiDK`$&7?zchTh3ti844@74f`#DWdR%%8F>; zY9^&}2WtI>5~l1h#`ulWLvT8d4;V_Q(epT~bYxy2XI{V+3mK)nI2|FE-{l&@YDdWJ zck^bcI>B$aw)loUD z!O{$-2Fo&-mch(ABUoYtOWB*FdoQA5^C?u;S=K5sGMG1nk`iZ$;dfB^g|$v2wr0%555PmCp{Frwzrt-$V~@rlH!1mUfzH3#-VjG*bsEc|V^< z2_b?dY#R{8O6J}T*oiK-I}k?_3G}hu#XY{ty#|;e@8-xp?#!@IW>VPlpGSpT|Itd* z|AVo^wi>|K?C6-omQaK@n89)d0wt+vN2)ghaDRin0#)VmssgH?_;j*eY#V5e#$Tm2 z&+K`Xej2JKgR2jqsLJYOEv3NvZqrozQ-?xiX2!zA2K(H}q!?-$`At;$$9&S4BS zkjs>d@8IMD+{u3yJeK$}j^k>RyMw`iawYw#T)8G#6%6oRIdV-EB^!isLbb+W%&Br@ zy|*%CSdmrA*rM30^rAH(_c z)(|y3N{%{$@pzJ&K1CLIl7#UzW8*jt`wX4_JzR-rS*M<01^OJ3e-ay!!8V*?Qpm9I zd4cqBnqGSWci~6cIul`>#bvZkg>9rW#8QE(QEl5Cw|GSSSs_Ut4Z~b<%o?>gWe|4fCgZBx|_hb_OYzL+H z9L9@$f*U@;8iIL*!4?k(C0|=C&(XyW{(yQ0W!oS^j->&W1}g1jRi+dSSS&xt$W=(+ zqS@xd0ilX-AG`3g5&dTAWb$`1;zz{a$JF*8)bXFh+{b+1`-p7(ZyQf;W+abhjuJ~K zm0J1w%&Us~XV|J(TWg0$i=lGnQ7j{-bf731r(3f5op$!{8X@BtyaOs)KJXiZFz1#1 z0AbCXDxl}QF zBNzoDrCM-BbT(bwMWIznwp2LrxmBP7f&W8=T2+A1?H=({g<`5OnJP@73MEuwDpic)3B0rT4d(uR)e1cY&Fgh9X^_>Kg+-)abl>+4LO0UJ%<;DLM}x5BjGdMo)zy&a!d z+VL68iBIDQCgyYty^rgD&k-Hj7`ccAm`m=_r;qKBXMfMCamPKQagJg9pJo~TcyiEXNQ)L>}zz? zma=hb9Ie%H{;nqPH-b!@ly`!XxAZjjWY1E%AJ$?&lv6ei5#5lR>F+I!oUNE59hfU& z)W|mCXbTqe*;UewYh*iCNfhg42Rhi^A~9mA2YV#W*ZOYkmlR!=#{JTVhov7+uzgx~ z;rp^1FUubMn9qMId-101!`pHjekc3!q1=v-WI%j!yA;ZPDV95An%pUsa*tHWK3O1p zWr;kXVQ~{mvh~$$yZAf{A^VsXR7jnE;`GzT&hp!oJy&7-3@S9bi!F>3Ui|)IXMsBz zt&mY&SyoL8BI literal 0 HcmV?d00001 diff --git a/src/bin/gui/backend/Theme.class b/src/bin/gui/backend/Theme.class new file mode 100644 index 0000000000000000000000000000000000000000..e137ef8df2a171eaf65d88e0828e877aca26d27f GIT binary patch literal 3614 zcmZvdO;j6I6vyxI8N%Z$Km}`23Z*b8spYe#(k4Kle3dqcU#&KfF@%Ps4oPXPdg?j4 z)TJ)n>du~{)l<)L_svW)^YS={xs!YU_xJ95bLY-4|2_GMh~o5N znChs$nHmH&QjH>T+3p7F`1m@>}Lf%$JyOSShtV~dM?5U(42xUcrM9hmC5D_8G|K+A3WOseGC4gyJCj(5v+Q|6FG!hJY0=JDHbl`}~$O?cr1zlxHUuE5dOb>{z3A)ar?f}O*J+5I}&?_A8RUnSSpoRt6Z5`{mg0)#b z+_miO^ll#g*ORqMTeTe(8LeqTXwm8#ZMWOP)qWk@!Hxk=)ixUfplAc_)l&FD;;pH! z=Quk3OxNY7Ij-(1r*ej}^S$DI-d;b^@AqSUey+ef0@+q*rfQYmX-E9jiqntuoPM5s z>Or3C3i3Q>faf~J3QNIxAm7$nr|^_cLkKWsqVtpc%;C6Ckozp)2&al=d(Yx&Z%|lH_X(chG>BKN z4&R(ETrrJtSI2WzT8vC-F(ReKq>&awMOw^6X)yt%#Wax?vqM_U328A5q{aI#E#7Ns zF%YH2ypt9a4)zQg_~;2-cks(ZJzQVd{GJSR{4q5@!Y_kf#kHO4ah;|HnxPh&g$`do zH*XM@^BT>^)K*R5?<&zHq+G@wK6kGCGyF0Txo$=}f1t>B)MrMXkv9WoWKiA=d7%+A zGA2FaUTDIMyeK`BW@JX*%z2(zb#0TvSRsj0mnlLk*z6S=rd51e*06zfRJ8%`CSAky zH0U*Y-L35uEZ5d(eoKAd;KtcKmrghbgWjMwapluGjcxD-o6K=+p}MrQ?OQn4G?X{A z=m8a-gY>2zJ)oj(ppT$`ON$;-(GMVfTaO-6(H{f-3G}zL=n)nDDWqvVdPGHk4)hn$ z?`qNWD*8)E_w?v_75x>^UqheKqT?$15u}zL9aqs$fc_EsceLnn75y`$Sv`7OMgI!) zZ_w{+(Gx2AcSv)3^n{B31L!}YKhUBVRrFtw-qxcRRrKFL{{wwqi(XdI|3Z4GM=vXA zqmCMldgu#U^s0h3S|Kg!(W@%D2k2ht?`Y8*Dw?OjyL$A7iXH*_EcCV(y``d~kRIvL zTPk`E=nK%7wCI$Ijze13qf;t+73ekS?`qMLDmn$}u^v6CqHh3w6Z(5v^qh*eAib|g n&#CAF&_(Fq)1qT4`WVs&dbICTA?LO65a^Gfe;*UQjz0JwQi!2k literal 0 HcmV?d00001 diff --git a/src/run.ps1 b/src/run.ps1 index f1f6007..96c3ae1 100644 --- a/src/run.ps1 +++ b/src/run.ps1 @@ -2,15 +2,12 @@ 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) { $flag = $args[0] } -Run $flag \ No newline at end of file +Run $flag diff --git a/src/run.sh b/src/run.sh old mode 100644 new mode 100755 index 70f0d5b..bb3119f --- a/src/run.sh +++ b/src/run.sh @@ -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 \ No newline at end of file +run $@ diff --git a/src/sudoku.jar b/src/sudoku.jar new file mode 100644 index 0000000000000000000000000000000000000000..9100b81f37888d0321d2f33118fdc11a87405243 GIT binary patch literal 26064 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 literal 0 HcmV?d00001 From 7900d5bc2f2c44bbcbe8729ea5ee49fec32349b9 Mon Sep 17 00:00:00 2001 From: Edwin Casady Date: Mon, 1 Apr 2024 13:51:22 -0600 Subject: [PATCH 15/16] updated solving method was added --- .gitignore | 3 + src/gui/Nav.java | 3 +- src/gui/backend/SudokuChecker.java | 785 +++++++++++++++-------------- 3 files changed, 413 insertions(+), 378 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa968c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/bin/ +.classpath +.project diff --git a/src/gui/Nav.java b/src/gui/Nav.java index 364017b..05405ba 100644 --- a/src/gui/Nav.java +++ b/src/gui/Nav.java @@ -296,7 +296,8 @@ public class Nav extends JPanel { * @param String */ private void openFile(String filename) { - File f = new File("resources/" + filename + ".sdku"); + //file path is not working for everyone had to append src/ to run + File f = new File("src/resources/" + filename + ".sdku"); // If the file does not exist, print an error message and return. if(f == null || !f.exists() || f.isDirectory() || !f.canRead()) { System.out.println( diff --git a/src/gui/backend/SudokuChecker.java b/src/gui/backend/SudokuChecker.java index 623134f..bd7d772 100644 --- a/src/gui/backend/SudokuChecker.java +++ b/src/gui/backend/SudokuChecker.java @@ -1,426 +1,457 @@ package gui.backend; + import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + + /** - * The SudokuChecker class is responsible for calculating the solution to - * a given Sudoku puzzle. + * 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[][] origGrid; + private Cell[][] grid; + private Cell[][] origGrid; - - /** - * Create a new SudokuChecker object, initializing the grid to the given - * 9x9 grid of numbers. This should either check each cell as the user - * inputs a value, or be used to check the validity of a puzzle when the - * user requests it. - * - * @param grid - */ - public SudokuChecker(Cell[][] grid) { - this.grid = grid; + /** + * Create a new SudokuChecker object, initializing the grid to the given 9x9 + * grid of numbers. This should either check each cell as the user inputs a + * value, or be used to check the validity of a puzzle when the user requests + * it. + * + * @param grid + */ + public SudokuChecker(Cell[][] grid) { + this.grid = grid; - // Create a copy of the original grid to be used for resetting the - // grid to its original state. - origGrid = new Cell[9][9]; - for(int i = 0; i < 9; i++) { - for(int j = 0; j < 9; j++) { - origGrid[i][j] = new Cell(i, j, grid[i][j].getValue()); - } - } - } + // 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 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 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; - } - } - } + // 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; - } + return true; + } - /** - * Get the possible values for each cell in the Sudoku puzzle. - * - * Intended to be used for auto-filling in possible values in the GUI. - * - * @return - */ - public Cell[][] getPossibleValues(Cell[][] grid) { - for(int row = 0; row < grid.length; row++) { - for(int col = 0; col < grid[row].length; col++) { - if(grid[row][col].getValue() != 0) - continue; + /** + * Get the possible values for each cell in the Sudoku puzzle. + * + * Intended to be used for auto-filling in possible values in the GUI. + * + * @return + */ + public Cell[][] getPossibleValues(Cell[][] grid) { + for (int row = 0; row < grid.length; row++) { + for (int col = 0; col < grid[row].length; col++) { + if (grid[row][col].getValue() != 0) + continue; - // Replace intersection with union? - ArrayList intersection = intersection( - getRowRemainingNumbers(row), - getColRemainingNumbers(col) - ); + // Replace intersection with union? + ArrayList intersection = intersection(getRowRemainingNumbers(row), + getColRemainingNumbers(col)); - intersection = intersection( - intersection, - getBoxRemainingNumbers(row, col) - ); + intersection = intersection(intersection, getBoxRemainingNumbers(row, col)); - // Join the arrays and find the intersection of the three arrays - ArrayList availableNumbers = new ArrayList(); - for(int i = 0; i < intersection.size(); i++) { - availableNumbers.add(intersection.get(i)); - } + // Join the arrays and find the intersection of the three arrays + ArrayList availableNumbers = new ArrayList(); + for (int i = 0; i < intersection.size(); i++) { + availableNumbers.add(intersection.get(i)); + } - grid[row][col].setPossibleValues( - arrayListToArray(availableNumbers) - ); - } - } + grid[row][col].setPossibleValues(arrayListToArray(availableNumbers)); + } + } - this.grid = grid; - return grid; - } + this.grid = grid; + return grid; + } - /** - * Get the solution to the Sudoku puzzle. - * - * @return Cell[][] - */ - public Cell[][] getSolution() { - solve(); - return grid; - } + /** + * Get the solution to the Sudoku puzzle. + * + * @return Cell[][] + */ + public Cell[][] getSolution() { + solve(); + return grid; + } - /** - * Given two arrays of numbers, return the intersection of the two arrays. - * - * @param a - * @param b - * @return - */ - private ArrayList intersection( - ArrayList a, - ArrayList b - ) { - ArrayList intersection = new ArrayList(); - for(int i = 0; i < a.size(); i++) { - if(b.contains(a.get(i))) - intersection.add(a.get(i)); - } + /** + * Given two arrays of numbers, return the intersection of the two arrays. + * + * @param a + * @param b + * @return + */ + private ArrayList intersection(ArrayList a, ArrayList b) { + ArrayList intersection = new ArrayList(); + for (int i = 0; i < a.size(); i++) { + if (b.contains(a.get(i))) + 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 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 intersection = intersection( - getRowRemainingNumbers(row), - getColRemainingNumbers(col) - ); + /** + * 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 intersection = intersection(getRowRemainingNumbers(row), getColRemainingNumbers(col)); - intersection = intersection( - intersection, - getBoxRemainingNumbers(row, col) - ); + intersection = intersection(intersection, getBoxRemainingNumbers(row, col)); - if(intersection.size() == 0) - return; - else if(intersection.size() == 1) { - int value = intersection.get(0); + if (intersection.size() == 0) + return; + else if (intersection.size() == 1) { + int value = intersection.get(0); - grid[row][col].setValue(value, true); - updatePossibleValues(row, col); - return; - } else { - // Join the arrays and find the intersection of the three arrays. - ArrayList availableNumbers = new ArrayList(); - for(int i = 0; i < intersection.size(); i++) - availableNumbers.add(intersection.get(i)); + grid[row][col].setValue(value, true); + updatePossibleValues(row, col); + return; + } else { + // Join the arrays and find the intersection of the three arrays. + ArrayList availableNumbers = new ArrayList(); + for (int i = 0; i < intersection.size(); i++) + availableNumbers.add(intersection.get(i)); - grid[row][col].setPossibleValues( - arrayListToArray(availableNumbers) - ); - } - } + grid[row][col].setPossibleValues(arrayListToArray(availableNumbers)); + } + } - /** - * Update the possible values for cells in the same row, column, and box - * as the given cell. 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(); + /** + * Update the possible values for cells in the same row, column, and box as the + * given cell. This should always be called once a cell's value has been set, to + * remove that value from the possible values of other cells. + * + * @param row + * @param col + */ + private void updatePossibleValues(int row, int col) { + int value = grid[row][col].getValue(); - for(int i = 0; i < 9; i++) { - if(grid[row][i].getValue() == 0) { - grid[row][i].removePossibleValue(value); - if(grid[row][i].getPossibleValues().length == 1) { - grid[row][i].setValue(grid[row][i].getPossibleValues()[0], true); - updatePossibleValues(row, i); - } - } - if(grid[i][col].getValue() == 0) { - grid[i][col].removePossibleValue(value); - if(grid[i][col].getPossibleValues().length == 1) { - grid[i][col].setValue(grid[i][col].getPossibleValues()[0], true); - updatePossibleValues(i, col); - } - } - } + for (int i = 0; i < 9; i++) { + if (grid[row][i].getValue() == 0) { + grid[row][i].removePossibleValue(value); + if (grid[row][i].getPossibleValues().length == 1) { + grid[row][i].setValue(grid[row][i].getPossibleValues()[0], true); + updatePossibleValues(row, i); + } + } + if (grid[i][col].getValue() == 0) { + grid[i][col].removePossibleValue(value); + if (grid[i][col].getPossibleValues().length == 1) { + grid[i][col].setValue(grid[i][col].getPossibleValues()[0], true); + updatePossibleValues(i, col); + } + } + } - int boxRow = row / 3; - int boxCol = col / 3; - for(int i = 0; i < 3; i++) { - for(int j = 0; j < 3; j++) { - if(grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == 0) - grid[boxRow * 3 + i][boxCol * 3 + j]. - removePossibleValue(value); - } - } - } + int boxRow = row / 3; + int boxCol = col / 3; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (grid[boxRow * 3 + i][boxCol * 3 + j].getValue() == 0) + grid[boxRow * 3 + i][boxCol * 3 + j].removePossibleValue(value); + } + } + } - /** - * Solve the Sudoku puzzle. - */ - public void solve() { - // Continue until a valid solution is reached. - while(!isValidSolution()) { - for(int i = 0; i < 9; i++) { - for(int j = 0; j < 9; j++) { - if(grid[i][j].getValue() == 0) - getAvailableNumbers(i, j); - } - } - } - } + /** + * Solve the Sudoku puzzle. + * + * @param grid + */ + private boolean solve() { + return solve(0, 0); + } - // + /** + * Overloaded method that solves the Sudoku puzzle moving from the position given to the end + * + * @param row + * @param col + * @return + */ + private boolean solve(int row, int col) { + int nextCol = (col + 1) % 9; + int nextRow = (nextCol == 0) ? row + 1 : row; + // Base case - progressed past the last row and column. + if (row == 9) { +// grid.printTable(); + return true; + } + // If the cell has a value it is skipped. + if (grid[row][col].getValue() != 0) { + solve(nextRow, nextCol); + } else { + // An empty cell prompts a generation of possible numbers + List possibleNumbers = getAllRemainingNumbers(row, col); + if (possibleNumbers.size() == 0) // If there are no available numbers the solve() returns false + return false; + // each possible number is given in ascending order + for (Integer el : possibleNumbers) { + grid[row][col].setValue(el.intValue(), true); + + // here is the check to see if the next possible number needs to be tested + if (solve(nextRow, nextCol)) + return true; + // reset the cell so that it is not assumed to be solved after failing the + // current tested values } + } + } + return false; + } + + /** + * This may be redundant but I needed an method that could be called to get a list of all + * remaining numbers after eliminating 1-9 by standard Sudoku rules. + * This method calls the 3 methods already in this class to build a HashSet of + * known values which are used to verify which numbers remain as possible solutions. + * @param row + * @param col + * @return ArrayList results; + */ + private ArrayList getAllRemainingNumbers(int row, int col) { + HashSet nums = new HashSet<>(); + ArrayList 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 getRowRemainingNumbers(int row) { - ArrayList remainingNumbers = new ArrayList(); - 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); - } + /** + * Get any number between 1 and 9 that is not in the row. + * + * @param row + * @return + */ + private ArrayList getRowRemainingNumbers(int row) { + ArrayList remainingNumbers = new ArrayList(); + 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; - } + return remainingNumbers; + } - /** - * Get any number between 1 and 9 that is not in the column. - * - * @param col - * @return - */ - private ArrayList getColRemainingNumbers(int col) { - ArrayList remainingNumbers = new ArrayList(); - 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); - } + /** + * Get any number between 1 and 9 that is not in the column. + * + * @param col + * @return + */ + private ArrayList getColRemainingNumbers(int col) { + ArrayList remainingNumbers = new ArrayList(); + 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 getBoxRemainingNumbers(int row, int col) { - ArrayList remainingNumbers = new ArrayList(); - 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; + } - 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 getBoxRemainingNumbers(int row, int col) { + ArrayList remainingNumbers = new ArrayList(); + 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); + } - /** - * 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 list) { - int[] array = new int[list.size()]; - for(int i = 0; i < list.size(); i++) - array[i] = list.get(i); + return remainingNumbers; + } - 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(); + /** + * 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 list) { + int[] array = new int[list.size()]; + for (int i = 0; i < list.size(); i++) + array[i] = list.get(i); - if(!isValidSet(row)) { - System.out.println("Row " + i + " is invalid."); - return false; - } - } + return array; + } - // 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(); + /** + * 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; + } + } - if(!isValidSet(col)) { - System.out.println("Column " + i + " is invalid."); - 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(); - // 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; - } - } - } + if (!isValidSet(row)) { + System.out.println("Row " + i + " 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; + // 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(); - else if(found[set[i] - 1]) - return false; + if (!isValidSet(col)) { + System.out.println("Column " + i + " is invalid."); + return false; + } + } - else - found[set[i] - 1] = true; + // 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; - } + 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; + } } \ No newline at end of file From 1ce9c4c408cda5a02f3b7f77083e3c8d7d556c40 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Mon, 8 Apr 2024 10:33:44 -0600 Subject: [PATCH 16/16] bug fixes for gavldac's solve improvement --- src/bin/App.class | Bin 2593 -> 2593 bytes src/bin/gui/Board$1.class | Bin 1480 -> 1480 bytes src/bin/gui/Board$2.class | Bin 1669 -> 1669 bytes src/bin/gui/Board.class | Bin 3173 -> 3173 bytes src/bin/gui/CellGUI.class | Bin 4836 -> 4836 bytes src/bin/gui/ComboBox.class | Bin 903 -> 903 bytes src/bin/gui/FileChooser$Filter.class | Bin 882 -> 882 bytes src/bin/gui/FileChooser.class | Bin 883 -> 883 bytes src/bin/gui/Label.class | Bin 739 -> 739 bytes src/bin/gui/Nav.class | Bin 6126 -> 6126 bytes src/bin/gui/Root.class | Bin 624 -> 624 bytes src/bin/gui/backend/Cell$List$Value.class | Bin 593 -> 593 bytes src/bin/gui/backend/Cell$List.class | Bin 2427 -> 2427 bytes src/bin/gui/backend/Cell.class | Bin 2203 -> 2203 bytes src/bin/gui/backend/Settings.class | Bin 5620 -> 5661 bytes src/bin/gui/backend/SudokuChecker.class | Bin 5720 -> 6285 bytes src/bin/gui/backend/Theme.class | Bin 3614 -> 3614 bytes src/gui/Nav.java | 2 +- src/gui/backend/Settings.java | 8 +++++--- src/sudoku.jar | Bin 26064 -> 26374 bytes 20 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/bin/App.class b/src/bin/App.class index d0657de31c1d9859c8592a4ca160004ed0e8966f..7692a294b3f5cf77e1435d21a0c880a90b5373da 100644 GIT binary patch delta 17 ZcmZ1|vQUKM)W2Q(7#J9wHgZUE0RTOx1N^A3?xCL6oD57196Su1 z3|x#1Y$X|)#VQ7j4BTKPPWg#NDH9XQ8QC`8xWuHx%D~3JARquz3N#hSX5eSAWMF2n z0Iw=o!P+z50B X6N5FwvrOa58W) zGH{e7=BOm6=HxIk@Pd^%1KIAOo)Z(w8M!vzxWuFbRK~y{Ai%)Nzy>rGg!vgP8JHQY zfGl;ONz5P-E$yugj9NOAuQ3PIGj3rJKgJ-UwFM{w#6aE_2AORPN>C1vVqeC<1L84m zWf0#8Vt~XDia& diff --git a/src/bin/gui/Board$2.class b/src/bin/gui/Board$2.class index b263b6276a0e0d4d81203445670ffcbf3c352b03..b372525c31c1961a0dfdfc835bb7387d93b7095b 100644 GIT binary patch delta 17 YcmZqWZRO=S^>5cc1_lPFjU2UX06G~3>i_@% delta 17 YcmZqWZRO=S^>5cc1_lPljU2UX06G)}>Hq)$ diff --git a/src/bin/gui/Board.class b/src/bin/gui/Board.class index 94b6029a28d2c25e40a7efb198142b3e23c84432..c3a2388a3518a22b9357126a5b192d5f207ebe69 100644 GIT binary patch delta 17 ZcmaDV@l=B2)W2Q(7#J9wHgd%B002iR2B!c3 delta 17 ZcmaDV@l=B2)W2Q(7#J8FH*&=C002iM2BrW2 diff --git a/src/bin/gui/CellGUI.class b/src/bin/gui/CellGUI.class index d6c250ababb2714ea90d62a213f9bb417a54c5f7..2e70d97008ce208bc62ff0212a60c2aeb6a7d214 100644 GIT binary patch delta 17 ZcmaE&`b3rE)W2Q(7#J9wHgen%0su*q2Sfk> delta 17 ZcmaE&`b3rE)W2Q(7#J8FH*(w&0su*l2SWe= diff --git a/src/bin/gui/ComboBox.class b/src/bin/gui/ComboBox.class index b0e01cef05a04e5602ef3b05ea440baec5bd4714..ed7bc32f009ecf92183bc82879a796c84595124d 100644 GIT binary patch delta 17 YcmZo?Z)fK?^>5cc1_lPFjU4sN06C@w;s5{u delta 17 YcmZo?Z)fK?^>5cc1_lPljU4sN06C!r;Q#;t diff --git a/src/bin/gui/FileChooser$Filter.class b/src/bin/gui/FileChooser$Filter.class index 3d83038775a7d8e75ff8c93fcf9a73b0b5330bbc..ea0f5c23250eb9c7fd338a1f80abfd25ecfda5dd 100644 GIT binary patch delta 17 Zcmeyw_KA(-)W2Q(7#J9wHgaS!0{}+82B81| delta 17 Zcmeyw_KA(-)W2Q(7#J8FH*#b#0{}+32A}`{ diff --git a/src/bin/gui/FileChooser.class b/src/bin/gui/FileChooser.class index 941d85f4a7c92a2c2f56c31a8d400ec3d344d477..9c0486e775ea826e2f103b3df9a2803b66b60761 100644 GIT binary patch delta 17 Zcmey&_L+_2)W2Q(7#J9wHgaS$0{}+g2BZK0 delta 17 Zcmey&_L+_2)W2Q(7#J8FH*#b%0{}+b2BQD~ diff --git a/src/bin/gui/Label.class b/src/bin/gui/Label.class index 6787e315308dc5de1520a81a3df2df63b46b35ad..c3a15944146cbd6349ba39d0cd2002ebe8d33106 100644 GIT binary patch delta 17 ZcmaFN`k0mD)W2Q(7#J9wHgeo%0suv!2Jrv@ delta 17 ZcmaFN`k0mD)W2Q(7#J8FH*(x&0suvv2Jip? diff --git a/src/bin/gui/Nav.class b/src/bin/gui/Nav.class index 8ed673a7d360097ac3b7445e4e7dd06662fa955b..e9868061289c8391369f1d0bb650f576f7b0bd8f 100644 GIT binary patch delta 196 zcmWNKzYf6w7{u=ei7te+CX>Y?Iv`piqOl1R5++{2q85Wc5hRF^MuHl61hE-BfX6VI z`Uv8ies`D4ou_-c|NVQu09bc_Sv3;~Fo`&Iq?ksQ85D&U4b*6&$s9V&!xj$3PguZ| zB`jz;bG5EC5M&b}ww#ZeV%LdiIi(LDZKxbLE$v$!LE!{p&Jg7SF)oqd3I^Axa3dc( iw7Ex5a$7n>9x&!n_D49-F^IdO)O&o delta 196 zcmWNKI}QN>7=&jEiERj3DwRgzRjkA#5>|9Vg`nCKDA-CO9*IqmAX$mWDma4Z6b|4R zD%BoA{M9!znasFpH|>4@o-Y9U-CI@jarl@-j45c;kYxrXVTD;Vm_wU+7%adPj>Jz{ z#EfNF)a`}ZRGRR!g#bJDN7ZQ9;ZQ*tz{4R_nsztzt&gE_f*|LJaDgZ-B)CM5E7Z7_ kk1cxKp)a{9oe}q#@F4qR9C?N#`obfwJmE&?u?ZXe11*s(H~;_u diff --git a/src/bin/gui/Root.class b/src/bin/gui/Root.class index d827603c5c17c09868426fcdb198dcc950b38909..aa2a21642364f12c248c78d8181e2d43d93515ac 100644 GIT binary patch delta 17 Zcmeys@_~ip)W2Q(7#J9wHgaSz0RTol29^K- delta 17 Zcmeys@_~ip)W2Q(7#J8FH*#b!0RTog29*E+ diff --git a/src/bin/gui/backend/Cell$List$Value.class b/src/bin/gui/backend/Cell$List$Value.class index 2ffe7e9e3fb8d08377348a84117b0a93f018529a..cad821e834a0bf00f77178df8eecd143df929cc4 100644 GIT binary patch delta 17 Zcmcb}a*>7O)W2Q(7#J9wHgb3}0RTcD1~~u# delta 17 Zcmcb}a*>7O)W2Q(7#J8FH*$C~0RTc81~>o! diff --git a/src/bin/gui/backend/Cell$List.class b/src/bin/gui/backend/Cell$List.class index 820c9d6aaed05ebdefaa140b53b052577b223430..a8755a3ded719093f0fda9cc97a50b32b1aefa8d 100644 GIT binary patch delta 17 Zcmew@^jnDI)W2Q(7#J9wHgXhm0su)~2HF4s delta 17 Zcmew@^jnDI)W2Q(7#J8FH*yqn0su)_2H5}r diff --git a/src/bin/gui/backend/Cell.class b/src/bin/gui/backend/Cell.class index 82a77d78837324b22d554321272da90338dcbf7a..ccc6f9f6a472d0d8ce6a6c1ae34d1f1ca170067b 100644 GIT binary patch delta 17 ZcmbO&I9rh8)W2Q(7#J9wHgZhn002FI1~&iz delta 17 ZcmbO&I9rh8)W2Q(7#J8FH*!qo002FD1~vcy diff --git a/src/bin/gui/backend/Settings.class b/src/bin/gui/backend/Settings.class index 1a8bf5ffec2ee5aa47dcae4f1e63ef3531af0986..36b7744f9c4278b974193bfd85432515659b5d31 100644 GIT binary patch delta 2059 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 diff --git a/src/bin/gui/backend/SudokuChecker.class b/src/bin/gui/backend/SudokuChecker.class index b15e4ccc4b5691d09f2cf622b2bf3ae37ccbe68e..fd6b027a7573770983dff7b54398ece1126fc614 100644 GIT binary patch delta 2836 zcmaJ@3v5&875Vzbup|NQiNXjdL3@DF;Hwh$=0L6d{rp6}NJj&Xp zK-%$W3*8-$q_pK7TG9ulGbKO@YpY6KX{Al8rb^Q$P1~xfo3>7y)F>(Y?zICgpkJ3u!OekqbTiGA`R3kHtWU{n z(h$N17445{2!ruw{ZL1st`EfK2e;F72oViYY!WCD3IsVrX2XHVx{3v>Hw4$jYg7<0 z^NyvuKx}jrnNN!!vkRAFqtH@6oUq`}EDqLut?9 zc@-~cIDmut1!aCM(Gul>etj#0$kM(I%cG~!LV?M>tX$mj^*V?Sgj-BrTmRVE!XLMpUu)es)wA#X`SdYZUa)GA9D82Gc3$xxdId5!bB9I8Y8$b zyQFtyqLF4VXACNQFMF_pmM>4?Tr>s7#i5hbMUP?z*qPqg-q@i^_pOgQ>|7HUOpc70Tae53 z5;R=qu9u~Fg(Y>BgY6ntq7OmbKpelsPJDo$;X}NEkJtg1srd=s$FC^)6gT<%0-xa@ z_*@*gDe3rBa`0=Dk776nks+c-Gra8O`x@h+hj@DP4Sl`jl(si zJV<2+HTLBztK7~_wFO;pdX-h(P`u+5uiJSXHl-&?oIy->C(`{MrGR#uyU}E(d1bY& zJdYi0?R6SR-M4O94Tmje z4JXUy&rtCf*5_BO*RPR}zrl~cvqt}k$@rE%^Dor5g#t5`h%hl)_;cf_%neaPM*Y7eJ~(Ge|I(6;OnV)L)S$LFYOrQFYS z4^JT8T<-TKaBpfyVX3igSH{^qHn;N>ipo8<_pm*IC%TZHr=(uSHh0)q9e>DEj0Q~x z=VyyTG&p1!-Ajj8GEgdRjFwDPOBQBIHWqQ;EV*VJZdkuZm5Mf_9}DjE;|bbR^UYp3 ztbgM!*PGnMbLaput;D?kz#Yg+79*sHki~Gz2xLnM3gkYNODSPTqKfYm#fMt)V-|Jh zNjYaupSvJ4+4?65RdgF^{{KZ8Pm~EnnMjmsqSO#&5>X}-rJ5*Hh%%KZ6NobX2PpS* zHX8HlV&<0CWGqH^oi4u<%QUzd%R-_o!Z2w-mNX(?7Nby>@P9%R#z-?QFU3MxhDN?G zqx=zB!OC2T2zBEUAnNX{id1X{jWw8LqJEI&Yp7db()ZW9=*#5{ghZjrCT6USP@9;s zn3)|7mO(gGoRWS{xFlU?Xzc7zTUzS%+U(8lC%cgO4t9EbIc|}_e*Jp(*t>);CWW?~ z)=X+<8JhXM91o^yTaKsQm@Hi!{E(dteHRJdjSA^txVtc(vdOX+Gh`oT%YHP-b6Caa zqw)enK7bu^5Kr)YmmI=AIgA5x1V`j3&hU9&x^Pim!uxUzH+cTBoWN&t62FyG_)<>e zPjUv|$k`F%k~89#(~>LaWVpO0qvUldmy=Q@CuE|$MNTU`g>wuV^7`RnnGQf?5L%L& zyJQ2f56Rh%)b~-jAGzjgBX#^sV+<+1(=#V-gl&H5o-1Rc#S43{GOcWGl%c*(0GrzzG<#+<2|0ZD80-{eGPg4 E1>$gNTmS$7 delta 2249 zcmZuyYfx2H6#mw|mwWExaCu%3c_==L3jzW{ipcoLQ1JiXx}*(%&F$W-e<49&idB+*0=ZB zp7-S#*M;l7ZvmKsLnmAxTdM7Xh?eH2wni6xn6Be4j2AfWX$}PI{ec!23NS;*OeE0g z4+N{~n_B4^gK{0SF^9&M#;}oLGGEqca`2 zSI2$0U*H;iyD?~>*Ua=)JFyZEXlT;03IUZIQD}cq$3s}Hsv^d@gJ{*zrsH9(QBOyd zxF3v#1#6A{b*H|GSR|@l%drY_)HxZVuG$l<8lF(cv?9(s)Sp^Oz7oV#G(S9ChWlHF z`vYx8>NyTPEr_4S3RVUFz4NSUaAbL&cVRDH(6G;d+OPIIK9iT!L(WX;R{NaO-5xM| zJj!vsy5d}#-NEkM1}Dd%upyaG!{Ehmq#_-oF#@HW@8o(GSDmWWmG0hvjmF!66sUu) z-UUX4S%~F04lWeZw}|hJLk5bGkBOLp67CnXNt3aVN~p$E(mM@JjN6In*iVlGTpy3X2X^cubMg55Umy?v7Bgi$l~)@#x6o5CU7o= zA7$*(-FN^EoL3@%)o4a5=QWJ6p0T1w8D+|F#aa^x%i}cBzYJOze48vzA>>Rf<)g6e(PLj9_xqL2!V$nb(to?9n z@X5$)?8j!BEZD+->M)#vcvL8IQes(=%|K7iPs#V%Y$43bOil@5u5x*%-onX2SkXJ9z`POWO6zJJxude^79(T<2X;<37)5uZr<&iY`r#+<33c&M|btB*tL0mgNBNL&pWm;6pa(Bg*Sz z3g;sCK4Hs0Wh+0!27Hb^xWsb5z-jvS;%l76H+UD{()%*4-{C60mk9hI4qO%wzLj|V zWOgMT&VDsHdRT`rtUX*cnXvK)Or#R3_oB#Jk{s>qMwWF_iZQ1Sw#O{jX||`f%fjS; z>=KTbj@O&}8)MX{sxiN-HEv8CyCvi8V|?~Uac-R`>)v4n6MDHGKO&l7`pbw%`N|bgnae_Hk5wod?%Ixf<766Gk!Gekl8&I;J9;DA`2GL8Rm&R>mSl@-R&D zks$@hlW~k(i1|{4O0JjCzFdmYAQKUwZ%|5!dM-Y9ptfzqo5(APsC&G=in4hob3fn0 zTmlayq@2XhMvTlM(rk>Dd8R^iSo-1A@VH##(F(KmBxCUd6PAl*fm-Wb9o@JWF(=_q z?&WpElhva7y@j_J9|;?$Ce(D$GuH4-=|#I~wf30mdC63dvi02TXg9fnEF%-O)~2N(=K3VyCp?*$!#L zVLlv_H8>@Y;H@rDOiljbROzDmyPlk5;Akq&#OdR#>9kiwXkFgK-0cUkQF;U~^*WgxNOe_x? zH-Y)Nl6{Df{Zzz@RKo$J$w8{28<}z#`LvIhqh=~mi0Fr=VY|eO`@-jA#$S&;W c9MAQ048F%4pEAd1@FI6yv>xDH`ZALK24b7jP5=M^ diff --git a/src/bin/gui/backend/Theme.class b/src/bin/gui/backend/Theme.class index e137ef8df2a171eaf65d88e0828e877aca26d27f..314e3ba32464b0d59b62a7bad110366173d4d4df 100644 GIT binary patch delta 17 ZcmbOyGf#%&)W2Q(7#J9wHgbsZ0RTQV1>67t delta 17 ZcmbOyGf#%&)W2Q(7#J8FH*$#a0RTQQ1=|1s diff --git a/src/gui/Nav.java b/src/gui/Nav.java index 05405ba..db9d466 100644 --- a/src/gui/Nav.java +++ b/src/gui/Nav.java @@ -297,7 +297,7 @@ public class Nav extends JPanel { */ private void openFile(String filename) { //file path is not working for everyone had to append src/ to run - File f = new File("src/resources/" + filename + ".sdku"); + File f = new File("resources/" + filename + ".sdku"); // If the file does not exist, print an error message and return. if(f == null || !f.exists() || f.isDirectory() || !f.canRead()) { System.out.println( diff --git a/src/gui/backend/Settings.java b/src/gui/backend/Settings.java index 5a15c92..7831755 100644 --- a/src/gui/backend/Settings.java +++ b/src/gui/backend/Settings.java @@ -185,8 +185,10 @@ public class Settings { fontName + ".ttf"; else if(os.startsWith("mac")) fontFilepath = "/Library/Fonts/" + fontName + ".ttf"; - else - fontFilepath = "/usr/share/fonts/" + fontName + ".ttf"; + else { + fontName = "HackNerdFontMono-Regular"; + fontFilepath = "/usr/share/fonts/TTF/" + fontName + ".ttf"; + } // Register the font with the GraphicsEnvironment. try { @@ -538,4 +540,4 @@ public class Settings { this.autoSaveFrequency = autoSaveFrequency; updateSettingsFile(); } -} \ No newline at end of file +} diff --git a/src/sudoku.jar b/src/sudoku.jar index 9100b81f37888d0321d2f33118fdc11a87405243..83744b0d8407d1dbc3ad44885f13f53a92d512e2 100644 GIT binary patch delta 22492 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 delta 22168 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