diff --git a/Cell.java b/Cell.java index 9d98c1acd0f5e33bf2c04c8405f06492a08aada0..301ece1726f0f5de1802fe1079abbd668fd9779c 100644 --- a/Cell.java +++ b/Cell.java @@ -76,8 +76,8 @@ public class Cell extends Pane { for(Cell cell : cage.getCells()) { if((cell.getCellNumber() == getCellNumber() - 1 || cell.getCellNumber() == getCellNumber() + 1 || - cell.getCellNumber() == getCellNumber() - 8 || - cell.getCellNumber() == getCellNumber() + 8) && + cell.getCellNumber() == getCellNumber() - Window.n || + cell.getCellNumber() == getCellNumber() + Window.n) && cell.getCellNumber() != getCellNumber()) { return true; } @@ -92,5 +92,13 @@ public class Cell extends Pane { public int getCellNumber() { return cellNumber; } + + public void setValue(int x) { + getTextField().setText(String.valueOf(x)); + } + + public int getValue() { + return Integer.parseInt(getTextField().getText()); + } } diff --git a/GameScene.java b/GameScene.java index ffd89a1b194f48610c403a1f71ab024ed4734c37..2148c1e5a99905570fe79f26422af0f315be2cb6 100644 --- a/GameScene.java +++ b/GameScene.java @@ -16,27 +16,52 @@ import javafx.stage.Stage; import java.io.*; import java.util.ArrayList; +/** + * this class holds the main method and runs the application + * it creates the menu page where the user can select the size of + * the board and the way the user wants to input the board + * the class opens and reads a file and memorises the cages and their + * label (result and symbol) and the cells of the cells + * the class also provides methods for verifying if the input of the user + * can create a valid board for the game + * -> if a cell appears exactly once in just one cage + * -> if the cells are adjacent in a cage + */ + public class GameScene extends Application { - private ArrayList<Cage> cages = new ArrayList<Cage>(); - private Window window = new Window(); + static ArrayList<Cage> cages = new ArrayList<Cage>(); //memorises the cages from the input + private Window window = new Window(); //helps to display different stages when needed + /** + * main function which launches the application + */ public static void main(String[] args) { launch(args); } + /** + * overrides method for the application to start an application + * @param stage -> is the main stage where the menu is displayed + */ @Override public void start(Stage stage) { - FileChooser fileChooser = new FileChooser(); - VBox root = new VBox(20); + FileChooser fileChooser = new FileChooser(); //used to choose a file from the system + + VBox root = new VBox(20); //the root of the application where all elements will be added + + //set the style for the root + root.setStyle("-fx-background-color: SeaShell"); root.setPrefSize(800, 700); root.setAlignment(Pos.TOP_CENTER); root.setPadding(new Insets(30)); + //set a title of the content of the application Label greeting = new Label("Mathdoku"); greeting.setStyle("-fx-font-size: 70px; -fx-font-weight:bold;"); + //add the instructions of the game String instructions = "A player needs to fill the cells in an NxN square grid with the numbers 1 to N " + "(one number per cell), while adhering to the following constraints:" + '\n' + "- Each number must appear exactly once in each row." + '\n' + @@ -49,115 +74,145 @@ public class GameScene extends Application { " operator to the numbers in that cage. For - and รท, this can be done in any order." + '\n' + "Note: If a cage consists of a single cell, then no arithmetic operator is shown. " + "The label simply shows the number that must be in that cell."; - Label instruct = new Label("Instructions:" + '\n' + instructions); + //set properties and style for the instructions instruct.setWrapText(true); instruct.setStyle("-fx-font-size: 15px;"); instruct.setPadding(new Insets(40)); + /* create the grid pane which contains the choice box with functionalities: + -> select size for the board of the game + -> select way in which you want to read input + */ GridPane buttons = new GridPane(); buttons.setPadding(new Insets(20)); buttons.setHgap(10); buttons.setAlignment(Pos.TOP_CENTER); + //button which will change the stage to the one of Mathdoku game Button play = new Button("Play"); + + //set style and properties of the button play.setStyle("-fx-font-size: 20px; -fx-font-weight:bold; -fx-background-color: MediumSeaGreen;" + "-fx-border-color: black; -fx-border-width: 4 4 4 4;"); - play.setDisable(true); + play.setDisable(true); //disable the play button until the all the details will be inputted + //label to let the user know where to input the size Label size = new Label("Choose size: "); size.setStyle("-fx-font-size: 20px; -fx-font-weight:bold;"); + //the size of the board will be selected; the size is between 2x2 and 8x8 ChoiceBox cbSize = new ChoiceBox(); cbSize.setItems(FXCollections.observableArrayList( "2x2", "3x3", "4x4", "5x5", "6x6", "7x7", "8x8") ); + //set style and properties for the choice box cbSize.setStyle("-fx-font-size: 15px;"); cbSize.setPrefWidth(110); + //label to let the user know where to input the file or where to access the text area Label input = new Label("Input from: "); input.setStyle("-fx-font-size: 20px; -fx-font-weight:bold;"); + //the way of inputting will be selected: file or direct input in a special text area ChoiceBox cbInput = new ChoiceBox(); cbInput.setItems(FXCollections.observableArrayList( "file", "text area") ); + //set style and properties for the choice box cbInput.setStyle("-fx-font-size: 15px;"); cbInput.setPrefWidth(110); + //add all the buttons to the grid pane with the coordinates where to be placed buttons.add(size, 0, 0, 1, 1); buttons.add(cbSize, 1, 0, 1, 1); buttons.add(input, 2, 0, 1, 1); buttons.add(cbInput, 3, 0, 1, 1); + //disable the input button -> first the user has to select the size of the board input.setDisable(true); cbInput.setDisable(true); + /* + if the size of the board is selected, permit the user to input -> enable the input button + */ cbSize.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observableValue, String oldValue, String newValue) { - size.setDisable(true); + size.setDisable(true); //disable the input label for design input.setDisable(false); cbInput.setDisable(false); + play.setDisable(true); } }); + /* + * get the size of the board selected + * identify the way the input is wanted to be inputted: + -> file: + * use the file chooser to choose a file from the system + * if the file is null, don't permit the user to play + * if the file is not null, open it and use it as input + -> text area: + * input from the text area + */ cbInput.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observableValue, String oldValue, String newValue) { int n = Character.getNumericValue(cbSize.getSelectionModel().getSelectedItem().toString().charAt(0)); - //play.setDisable(false); - if(newValue.equals("file")) { - if(cbSize.getSelectionModel().getSelectedItem() != null) { + File file = fileChooser.showOpenDialog(stage); //memorise the choice of the user - File file = fileChooser.showOpenDialog(stage); - - if(file != null) { - try { - openFile(file, n, play); - } catch (IOException e) { - e.printStackTrace(); - } - } else { - play.setDisable(true); + //verify if the file is selected + if(file != null) { + try { + openFile(file, n, play); //open the file and use it as input for the game + } catch (IOException e) { + e.printStackTrace(); } + } else { + play.setDisable(true); //if the file is not selected, disable the play button } } else if(newValue.equals("text area")) { - directInput(n, play); + directInput(n, play); //input from text area if the text area is selected } - input.setDisable(true); + input.setDisable(true); //disable the input label for design } }); + /* + * play button to start the stage with the game + * enabled when the size of the board and the way of inputting are selected + */ play.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent actionEvent) { - stage.close(); + stage.close(); //close the stage String size = cbSize.getSelectionModel().getSelectedItem().toString(); - Stage newStage = window.mathdokuStage(Character.getNumericValue(size.charAt(0)), cages); - newStage.show(); + window.mathdokuStage(Character.getNumericValue(size.charAt(0)), + cages).show(); //show the Mathdoku game stage } }); - root.getChildren().addAll(greeting, instruct, buttons, play); + root.getChildren().addAll(greeting, instruct, buttons, play); //add the labels and buttons to the root + //create the scene with parent being the root Scene scene = new Scene(root); - stage.setScene(scene); + //set properties for the stage stage.setTitle("Welcome to Mathdoku!"); stage.setX(650); stage.setY(250); @@ -166,60 +221,93 @@ public class GameScene extends Application { stage.setMinHeight(700); stage.show(); - stage.setResizable(false); + stage.setResizable(false); //don't resize the menu window } - public boolean openFile(File file, int n, Button play) throws IOException { + /** + * this method opens an input file and creates the cages with the details provided + * after creating the cages, check if the input is correct and can create a logical game based + * on the the numbers provided + * @param file -> reads from the file with the data for the board + * @param n -> represents the size of the board game + * @param play -> the button which has to be enabled or disabled in different situations + * @return boolean -> depending on the input and if it can create a valid board for the game + * @throws IOException + */ + public void openFile(File file, int n, Button play) throws IOException { - BufferedReader buffer = new BufferedReader(new FileReader(file.getPath())); + BufferedReader buffer = new BufferedReader(new FileReader(file.getPath())); //extract the data from the file - String line = buffer.readLine(); + String line = buffer.readLine(); //read first line + //loop through all the lines using a buffer until it reaches the end of the file while(line != null) { - String str[] = line.split(" "); - Cage cage = new Cage(n); + String str[] = line.split(" "); //create an array of the strings splitting the line with regex " " + Cage cage = new Cage(n); //the line represents a new cage => create a new cage + //check if the first string from the array is just a number if(isNumeric(str[0])) { - cage.setResult(str[0]); - cage.addCell(Integer.parseInt(str[1])); + cage.setResult(str[0]); //the number represents the result of the cage + cage.addCell(Integer.parseInt(str[1])); //the cage of the line consists of only the second element in + // the array + //else, there are more cells in the cage } else { - String[] cells = str[1].split(","); + String[] cells = str[1].split(","); //create an array of strings that memorises all the cells + //add the cells to the cage for(String cell : cells) { cage.addCell(Integer.parseInt(cell)); } + //set the symbol and result of the cage cage.setSymbol(str[0].charAt(str[0].length() - 1)); cage.setResult(str[0].substring(0, str[0].length() - 1)); } - cage.getCells().get(0).setLabel(str[0]); - cage.setLabel(str[0]); - cages.add(cage); - line = buffer.readLine(); + cage.getCells().get(0).setLabel(str[0]); //set the label in on the board + cage.setLabel(str[0]); //set the label of the cage + cages.add(cage); //add the cage created to an array list of all the cages + + line = buffer.readLine(); //go to the next line of the file } + checkInput(n, play); + } + + public boolean checkInput(int n, Button play) throws IOException { try { + //check if the cells appear exactly in one cage if(!checkUniqueCells(n)) { cages = new ArrayList<Cage>(); play.setDisable(true); + //display an error window with message window.inputError("The cells are not part of exactly " + "one cage.").showAndWait(); return false; } + //if ArrayIndexOutOfBoundsException appears => + // => the input file provides data for a much larger board } catch (ArrayIndexOutOfBoundsException e) { cages = new ArrayList<Cage>(); play.setDisable(true); + //display an error window with message window.inputError("Some cells don't belong to the bord").showAndWait(); return false; } + if(checkAdjacent(n) != cages.size()) { + cages = new ArrayList<Cage>(); + play.setDisable(true); + window.inputError("The cells in the cage are not adjacent").showAndWait(); + return false; + } + play.setDisable(false); - return true; + return true; //return true if everything is correct } public boolean checkUniqueCells(int n) throws IOException { @@ -241,16 +329,37 @@ public class GameScene extends Application { return true; } - public boolean checkAdjacent() { + public int checkAdjacent(int n) { + + int[] paths = new int[n*n + 1]; + int count = 0; + + for(int i = 1; i <= n * n; i ++) { + paths[i] = i; + } for(Cage cage : cages) { for(Cell cell : cage.getCells()) { - if(!cell.isAdjacent(cage)) { - return false; + if(cell.hasUpNeighbour(cage)) { + paths[cell.getCellNumber()] = cell.getCellNumber() - n; + } + if(cell.hasLeftNeighbour(cage)) { + if(paths[cell.getCellNumber() - 1] != cell.getCellNumber() - 1) { + paths[cell.getCellNumber()] = cell.getCellNumber() - 1; + } else { + paths[cell.getCellNumber() - 1] = cell.getCellNumber(); + } } } } - return true; + + for(int i =1; i <= n * n; i ++) { + if(i == paths[i]) { + count++; + } + } + + return count; } public void directInput(int n, Button play) { diff --git a/Handler.java b/Handler.java index 975b2c7ca13c796bd1e9f3e7db8cc8450e3fbee2..155306f8224c840dea16017430831b647be2aa73 100644 --- a/Handler.java +++ b/Handler.java @@ -16,6 +16,7 @@ import javafx.scene.layout.GridPane; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; +import javafx.stage.Stage; import javafx.util.Duration; import java.util.ArrayList; @@ -24,10 +25,10 @@ public class Handler { private Window window = new Window(); private int n; - private int[][] cellValues; + static int[][] cellValues; private UndoRedo undoStack = new UndoRedo(); private UndoRedo redoStack = new UndoRedo(); - private ArrayList<Cage> cages; + public static ArrayList<Cage> cages; public Handler(int n, ArrayList<Cage> cages) { this.n = n; @@ -62,29 +63,31 @@ public class Handler { }); } - public void submitButton(Button submit) { + public void submitButton(Button submit, Stage stage) { submit.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent actionEvent) { - for(int i = 0; i < n; i ++) { - for(int j = 0; j < n; j ++) { - System.out.print(getCellValues()[i][j]); - } - System.out.println(); - } - - System.out.println(); + boolean completed = true; for(Cage cage : cages) { - System.out.print(cage.getResult() + " "); + if(!cage.isCompleted()) { + window.win("Complete de board!", "Don't stop now...").show(); + completed = false; + break; + } } - if(verify() && verifyCages()) { - win(); - } else { - window.win("Too bad...", "You lost the game"); + if(completed) { + if(verify() && verifyCages()) { + win(); + window.win("Congratulations!", "You are the master!").showAndWait(); + stage.close(); + } else { + window.win("Try again...", "...it's not correct.").show(); + } } + } }); } @@ -262,6 +265,16 @@ public class Handler { redoStack.root = null; } + public void solveBoard(Button solve, GridPane grid, int n) { + solve.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent actionEvent) { + Solver solver = new Solver(n, grid); + solver.backtracking(1, 1); + } + }); + } + public void setTransparent(GridPane grid) { for(Node cell : grid.getChildren()) { ((Cell) cell).setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, @@ -340,7 +353,7 @@ public class Handler { return false; } - public boolean verifyCages() { + static boolean verifyCages() { for(Cage cage : cages) { if(!verifyCage(cage)) { @@ -350,7 +363,7 @@ public class Handler { return true; } - public boolean verifyCage(Cage cage) { + public static boolean verifyCage(Cage cage) { boolean isCorrect = true; if(cage.isCompleted()) { @@ -373,7 +386,7 @@ public class Handler { return isCorrect; } - public boolean verifySum(Cage cage) { + public static boolean verifySum(Cage cage) { int sum = 0; for(Cell cell : cage.getCells()) { sum = sum + Integer.parseInt(cell.getTextField().getText()); @@ -381,7 +394,7 @@ public class Handler { return (sum == Integer.parseInt(cage.getResult())); } - public boolean verifyProduct(Cage cage) { + public static boolean verifyProduct(Cage cage) { int product = 1; for(Cell cell : cage.getCells()) { product = product * Integer.parseInt(cell.getTextField().getText()); @@ -389,7 +402,7 @@ public class Handler { return ((product == Integer.parseInt(cage.getResult()))); } - public boolean verifySubtraction(Cage cage) { + public static boolean verifySubtraction(Cage cage) { int x = cage.getValues()[0]; System.out.println(); System.out.println(x); @@ -400,7 +413,7 @@ public class Handler { return (x == Integer.parseInt(cage.getResult())); } - public boolean verifyDivision(Cage cage) { + public static boolean verifyDivision(Cage cage) { int x = cage.getValues()[0]; for(int i = 1; i < cage.getValues().length; i ++) { if(x % cage.getValues()[i] == 0) { @@ -469,7 +482,7 @@ public class Handler { timeline.play(); } - public Cell getCell(GridPane grid, int i, int j) { + static Cell getCell(GridPane grid, int i, int j) { for(Node cell : grid.getChildren()) { if(((Cell) cell).getRow() == i && ((Cell) cell).getColumn() == j) { return (Cell) cell; @@ -495,4 +508,3 @@ public class Handler { return true; } } - diff --git a/Solver.java b/Solver.java new file mode 100644 index 0000000000000000000000000000000000000000..20866ff37ca05ac6814d41b29fde31799ca5f914 --- /dev/null +++ b/Solver.java @@ -0,0 +1,98 @@ +import javafx.scene.layout.GridPane; + +import javax.print.attribute.HashDocAttributeSet; + +public class Solver { + + private int n; + private int[][] sol; + private GridPane grid; + + public Solver(int n, GridPane grid) { + this.n = n; + sol = new int[n+1][n+1]; + for(int i = 1; i <= n; i ++) { + for(int j = 1; j <= n; j ++) { + sol[i][j] = 0; + } + } + this.grid = grid; + } + + void first(int row, int col) { + sol[row][col] = 0; + } + + boolean next(int row, int col) { + if(sol[row][col] < n) { + sol[row][col]++; + return true; + } + return false; + } + + boolean isValid(int row, int col) { + for(int i = 1; i < col; i ++) { + if(sol[row][col] == sol[row][i]) { + return false; + } + } + + for(int i = 1; i < row; i ++) { + if(sol[row][col] == sol[i][col]) { + return false; + } + } + + for(int i = 1; i < col; i ++) { + Handler.getCell(grid, row - 1, i - 1).setValue(sol[row][i]); + } + + for(int i = 1; i < row; i ++) { + Handler.getCell(grid, i - 1, col - 1).setValue(sol[i][col]); + } + + for(Cage cage : Handler.cages) { + if(!Handler.verifyCage(cage)) { + return false; + } + } + + return true; + } + + boolean isSolution(int col, int row) { + if(col == n && row == n) { + Handler.getCell(grid, row - 1, col - 1).setValue(sol[row][col]); + return true; + } + return false; + } + + void out() { + for(int i = 1; i <= n; i ++) { + for(int j = 1; j <=n; j ++) { + System.out.print(sol[i][j] + " "); + } + System.out.println(); + } + } + + void backtracking(int row, int col) { + first(row, col); + while(next(row, col)) { + if(isValid(row, col)) { + if(isSolution(col, row) && Handler.verifyCages()) { + out(); + System.out.println(); + } else { + if(col == n) { + backtracking(row + 1, 1); + } else { + backtracking(row, col + 1); + } + } + } + } + } +} diff --git a/Window.java b/Window.java index a7a56563d96e104934ec61ff34f145acd60411da..1139c3d580b6e11f9ea2220a3dc209e8ccf6372c 100644 --- a/Window.java +++ b/Window.java @@ -8,6 +8,7 @@ import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; import javafx.stage.Modality; import javafx.stage.Stage; import javafx.stage.StageStyle; @@ -23,30 +24,34 @@ public class Window { VBox menu = new VBox(10); menu.setPadding(new Insets(10)); menu.setAlignment(Pos.TOP_LEFT); - menu.setStyle("-fx-background: rgb(201, 76, 76);"); + menu.setStyle("-fx-background-color: MediumSeaGreen; -fx-border-color: white; -fx-border-width: 4 4 4 4;"); Label message = new Label("Which one will you choose?"); message.setFont(Font.font(20)); + message.setStyle("-fx-font-size: 20px; -fx-font-weight:bold;"); menu.getChildren().add(message); ToggleGroup toggle = new ToggleGroup(); + RadioButton delete = new RadioButton("Delete"); + delete.setFont(Font.font(20)); + delete.setStyle("-fx-font-size: 20px; -fx-font-weight:bold;"); + delete.setToggleGroup(toggle); + menu.getChildren().add(delete); + for(int i = 0; i < n; i ++) { RadioButton number = new RadioButton(String.valueOf(i + 1)); number.setFont(Font.font(20)); + number.setStyle("-fx-font-size: 20px; -fx-font-weight:bold;"); number.setToggleGroup(toggle); menu.getChildren().add(number); } - RadioButton delete = new RadioButton("Delete"); - delete.setFont(Font.font(20)); - delete.setToggleGroup(toggle); - menu.getChildren().add(delete); - Button choice = new Button("Choose"); choice.setFont(Font.font(20)); + choice.setStyle("-fx-font-size: 20px; -fx-font-weight:bold;"); choice.setCancelButton(true); menu.getChildren().add(choice); @@ -76,24 +81,25 @@ public class Window { inputButtons.initStyle(StageStyle.UTILITY); inputButtons.initModality(Modality.APPLICATION_MODAL); inputButtons.setTitle("Button Control"); - inputButtons.setMinWidth(300); - inputButtons.setMinHeight(370); + //inputButtons.setMinWidth(300); + //inputButtons.setMinHeight(100); inputButtons.setX(400); inputButtons.setY(400); inputButtons.toFront(); inputButtons.show(); + inputButtons.setResizable(false); } - public void win(String message1, String message2) { + public Stage win(String message1, String message2) { VBox menu = new VBox(5); menu.setAlignment(Pos.TOP_CENTER); menu.setStyle("-fx-background: pink;"); Label congrats = new Label(message1); - congrats.setFont(Font.font(35)); + congrats.setFont(Font.font( "Verdana", FontWeight.BOLD, 35)); Label message = new Label(message2); message.setFont(Font.font(25)); @@ -106,7 +112,7 @@ public class Window { winWindow.initStyle(StageStyle.UTILITY); winWindow.initModality(Modality.APPLICATION_MODAL); - winWindow.setTitle("Game Won!"); + winWindow.setTitle("Situation"); winWindow.setMinHeight(150); winWindow.setMinWidth(400); @@ -114,7 +120,7 @@ public class Window { winWindow.setX(800); winWindow.setY(500); - winWindow.show(); + return winWindow; } public Alert getAlertDialog(String str) { @@ -152,6 +158,7 @@ public class Window { }; VBox root = new VBox(5); + root.setStyle("-fx-background-color: SeaShell"); root.setAlignment(Pos.CENTER); root.setPadding(new Insets(20)); @@ -210,15 +217,17 @@ public class Window { Label note = new Label("Double click the space provided for text in cell"); Button submit = new Button("Submit"); + Button solve = new Button("Solve"); note.setStyle("-fx-font-size: 15px; -fx-font-weight:bold;"); - submit.setStyle("-fx-font-size: 20px; -fx-font-weight:bold; -fx-background-color: MediumSeaGreen; " + + submit.setStyle("-fx-font-size: 20px; -fx-font-weight:bold; -fx-background-color: pink; " + "-fx-border-color: black; -fx-border-width: 4 4 4 4;"); handler.changeFont(small, 20); handler.changeFont(medium, 30); handler.changeFont(large, 40); - handler.submitButton(submit); + handler.submitButton(submit, stage); + handler.solveBoard(solve, grid, n); root.getChildren().addAll(buttons, grid, sizes, note, submit);