Handler.java 29.53 KiB
import javafx.animation.*;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.*;
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;
/**
* this class handles the actions of the user when he interacts with the elements from the mathdoku stage
* it manages the action of the buttons
* it holds the method to draw the board of the game
* it supervises the user interaction with the game, calculating results and compares the numbers introduced
* of the player tot he rules of the game
* when the game is solved correctly, the stage closes, and the application terminates
*/
public class Handler {
private Window window = new Window(); //manager of the windows that must be opened in different circumstances
private int n = Window.n; //size of board nxn
static int[][] cellValues; //matrix of values of the cells
private UndoRedo undoStack = new UndoRedo(); //stack to memorise the actions of the player
private UndoRedo redoStack = new UndoRedo(); //stack to redo the actions the player had undone
public static ArrayList<Cage> cages; //memorised the cages of the board
/**
* this is the constructor of the class which will set the cages array as a static variable
* creates the matix of values of size nxn
* @param cages -> are set to the static variable cages
*/
public Handler(ArrayList<Cage> cages) {
Handler.cages = cages;
cellValues = new int[n][n];
}
/**
* this method will draw the grid and the cages will be emphasized
* @param grid -> the grid to sustain the cells
*/
public void drawGrid(GridPane grid) {
//loop through the cages
for(Cage cage : cages) {
//loop through the cells within a cage
for(Cell cell : cage.getCells()) {
//draw the cell depending on the neighbours it has in the cage
if(cell.hasLeftNeighbour(cage) && cell.hasUpNeighbour(cage)) {
cell.setStyle("-fx-border-color: black; -fx-border-width: 0 4 4 0;");
} else if(cell.hasLeftNeighbour(cage)) {
cell.setStyle("-fx-border-color: black; -fx-border-width: 4 4 4 0;");
} else if(cell.hasUpNeighbour(cage)) {
cell.setStyle("-fx-border-color: black; -fx-border-width: 0 4 4 4;");
} else {
cell.setStyle("-fx-border-color: black; -fx-border-width: 4 4 4 4;");
}
doubleClick(cell); //add the property to introduce values via mouse
memoriseValues(cell); //memorise he values as the player introduces them in the cell
grid.add(cell, cell.getColumn(), cell.getRow(), 1, 1); // add the cell to the grid
}
}
}
/**
* method that will display a window which will permit the player to introduce or delete values via mouse
* @param cell -> the cell to add the property to
*/
public void doubleClick(Cell cell) {
//mouse click event
cell.getTextField().setOnMouseClicked(mouseEvent -> {
if(mouseEvent.getButton().equals(MouseButton.PRIMARY)) {
//count the number of mouse clicks, if they're 2 => show the button window
if(mouseEvent.getClickCount() == 2) {
window.showButtons(cell); //show button window to introduce or delete a value from the cell
}
}
});
}
/**
* this method adds a listener to the text within a cell
* interactively changes the values of the matrix depending on the values from the cells that are changed
* @param cell -> cell to add the listener to
*/
public void memoriseValues(Cell cell) {
cell.getTextField().textProperty().addListener((observableValue, oldValue, newValue) -> {
//check if the player entered a numeric value
if (isNumeric(newValue)) {
//check if the player introduced a number between 1 and n
if (Integer.parseInt(newValue) > n || Integer.parseInt(newValue) == 0) {
cell.getTextField().setText(oldValue); //remain to the old value if not
} else {
//the value is correct, set it to the cell and the matrix
cell.setValue(Integer.parseInt(newValue));
cellValues[cell.getRow()][cell.getColumn()] = Integer.parseInt(newValue);
}
} else if (!newValue.matches("\\d*")) {
//the player introduced another character than a number => set the text to null and the matrix element to 0
cell.getTextField().setText(newValue.replaceAll("[^\\d]", ""));
cellValues[cell.getRow()][cell.getColumn()] = 0;
}
});
}
/**
* method that manages the resizing of the board
* @param root -> root of mathdoku stage
* @param grid -> the board to resize
*/
public void resize(VBox root, GridPane grid) {
//add a listener to see when the width of the root is resized
root.widthProperty().addListener((obs, oldVal, newVal) -> {
grid.setMaxWidth((Double) newVal - 100); //set the new width of the grid
//loop through the cages of the board
for(Cage cage : cages) {
//loop through the cells of a cage
for(Cell cell : cage.getCells()) {
//resize each cell and the text field from inside it accordingly
cell.setPrefWidth((Double) newVal - 100);
cell.getTextField().setPrefSize(cell.getWidth(), cell.getHeight());
}
}
});
//add a listener to see when the height of the root is resized
root.heightProperty().addListener((obs, oldVal, newVal) -> {
grid.setMaxHeight((Double) newVal - 100); //set the new height of the grid
//loop through the cages of the board
for(Cage cage : cages) {
//loop through the cells of the cage
for(Cell cell : cage.getCells()) {
//resize each cell and the text field from inside it accordingly
cell.setPrefHeight((Double) newVal- 100);
cell.getTextField().setPrefSize(cell.getWidth(), cell.getHeight());
}
}
});
}
/**
* method that manages the help action from the mathdoku stage
* @param help -> the button to add the action to
* @param grid -> the grid changes properties depending on the help option activated (enabled or not)
*/
public void helpButton(Button help, GridPane grid) {
help.setOnAction(actionEvent -> {
//the button will change the text depending on the help option is enabled or not
if(help.getText().equals("Enable Help")) {
help.setText("Disable Help"); //the help button is enabled => set the text to "Disable Help"
highlight(grid);//highlight the grid accordingly (incorrect cages with red, incorrect rows or
// columns with yellow
//interactively highlight the board depending on the values introduced to the cells
for(Node cell : grid.getChildren()) {
enableHighlight((Cell) cell, grid);
}
} else {
help.setText("Enable Help"); //the help button is disabled => set the text to "Enable Help"
setTransparent(grid); //make the whole grid transparent
//disable the highlight option for each cell
for(Node cell : grid.getChildren()) {
disableHighlight((Cell) cell, grid);
}
}
});
}
/**
* this method will enable the highlight of the board accordingly
* @param cell -> the cell to add the listener, it will be highlighted interactively
* @param grid -> the board to highlight
*/
public void enableHighlight(Cell cell, GridPane grid) {
cell.getTextField().textProperty().addListener((observableValue, s, t1) -> {
setTransparent(grid); //set all the board transparent before highlighting
highlight(grid); //highlight the grid
});
}
/**
* this method disables the highlight option
* @param cell -> the cell to add the listener
* @param grid -> the board to set to be transparent
*/
public void disableHighlight(Cell cell, GridPane grid) {
cell.getTextField().textProperty().addListener((observableValue, s, t1) -> setTransparent(grid));
}
/**
* this method highlights the grid accordingly
* @param grid to be highlighted
*/
public void highlight(GridPane grid) {
//loop through the the cells in the grid
for(int i = 0; i < n; i ++) {
for(int j = 0; j < n; j ++) {
//check for cells that contain a value
if(cellValues[i][j] != 0) {
//highlight the row if the row doesn't consist of unique numbers
if(!verifyRow(cellValues[i][j], i)) {
highlightRow(i, grid);
}
//highlight the column if the column doesn't consist of unique numbers
if(!verifyColumn(cellValues[i][j], j)) {
highlightColumn(j, grid);
}
}
}
}
//highlight the cages
for(Cage cage : cages) {
highlightCage(cage);
}
}
/**
* this method highlights a specific row from the board if it doesn't contain unique numbers
* @param row to be highlighted
* @param grid which contains the row to be highlighted
*/
public void highlightRow(int row, GridPane grid) {
//loop through the cells of that row and highlight them with yellow
for(int i = 0; i < n; i ++) {
getCell(grid, row, i).setBackground(new Background(new BackgroundFill(Color.YELLOW,
CornerRadii.EMPTY, Insets.EMPTY)));
}
}
/**
* this method highlights a specific column from the board if it doesn't contain unique numbers
* @param column to be highlighted
* @param grid which contains the column to be highlighted
*/
public void highlightColumn(int column, GridPane grid) {
//loop through the cells of the column and highlight them with yellow
for(int i = 0; i < n; i ++) {
getCell(grid, i, column).setBackground(new Background(new BackgroundFill(Color.YELLOW,
CornerRadii.EMPTY, Insets.EMPTY)));
}
}
/**
* this method highlights a cage if it the values within the cells of the cage don't satisfy the result
* @param cage to be highlighted
*/
public void highlightCage(Cage cage) {
//verify if the values satisfy the result of the cage
if(!verifyCage(cage)) {
//loop through the cells of the cage to highlight them with red
for(Cell cell : cage.getCells()) {
cell.setBackground(new Background(new BackgroundFill(Color.RED,
CornerRadii.EMPTY, Insets.EMPTY)));
}
}
}
/**
* this method makes the board transparent
* helps with managing the help button
* @param grid to be set transparent
*/
public void setTransparent(GridPane grid) {
//loop through the cells from the grid
for(Node cell : grid.getChildren()) {
((Cell) cell).setBackground(new Background(new BackgroundFill(Color.TRANSPARENT,
CornerRadii.EMPTY, Insets.EMPTY))); //make each cell transparent
}
}
/**
* method that manages the submit button from the mathdoku stage
* @param submit -> the button to add the action to
* @param stage -> necessary for closing the mathdoku stage if the game is correctly completed
*/
public void submitButton(Button submit, Stage stage) {
submit.setOnAction(actionEvent -> {
boolean isCompleted = true; //will verify if the board is completed
//loop through the cages and break if there exist a cage that is not completed
for(Cage cage : cages) {
if(!cage.isCompleted()) {
window.win("Complete de board!", "Don't stop now...").show(); //show window with appropriate message
isCompleted = false; //is not completed => set it to false
break;
}
}
if(isCompleted) {
//check if the board is correctly completed
if(verify() && verifyCages()) {
win(); //animation => each cell randomly changes colors
window.win("Congratulations!", "You are the master!").showAndWait(); //show appropriate message
stage.close(); //close the mathdoku stage when the window with the message is closed
} else {
//the board is not correctly completed
window.win("Try again...", "...it's not correct.").show(); //show message in the win window
}
}
});
}
/**
* this is the animation that will be displayed when the player wins the game
* each cell from the board randomly will be colored differently with a time of changing of 100 milliseconds
* it will look like a disco ball :)
*/
public void win() {
//create the timeline for managing the animation
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(100), actionEvent -> {
//loop through the cages and the cells to change the color randomly
for(Cage cage : cages) {
for(Cell cell : cage.getCells()) {
cell.setBackground(new Background(new BackgroundFill(Color.color(Math.random(), Math.random(),
Math.random()), CornerRadii.EMPTY, Insets.EMPTY)));
}
}
}));
timeline.setCycleCount(Transition.INDEFINITE); //make the changing of color go infinitely
timeline.play(); //start the time line
}
/**
* method that manages the clear button from the mathdoku stage
* @param clear -> the button to add the action to
* @param grid -> necessary to clear the board of the game
* @param undo -> the undo button must be disabled
* @param redo -> the redo button must be disabled
*/
public void clearBoard(Button clear, GridPane grid, Button undo, Button redo) {
//display a confirmation of action window so the player will confirm to clear the board
clear.setOnAction(actionEvent -> window.alertDialog("Are you sure you want to clear " +
"the board?").showAndWait().ifPresent(response -> {
//if the response is "YES" => clear board
if(response == ButtonType.YES) {
clear(grid);
undo.setDisable(true);
redo.setDisable(true);
}
}));
}
/**
* this method clears the board -> sets the values from the cells to null
* @param grid -> the grid to clear
*/
public void clear(GridPane grid) {
//loop through the cells of the grid
for(int i =0; i < n; i ++) {
for(int j = 0; j < n; j ++) {
cellValues[i][j] = 0; //set the value in the matrix at that position to 0
getCell(grid, i, j).getTextField().clear(); //clear the text of the cell
getCell(grid, i, j).setBackground(new Background(new BackgroundFill(Color.TRANSPARENT,
CornerRadii.EMPTY, Insets.EMPTY))); //make the cell transparent again
}
}
//the undo and redo stacks point now to null
undoStack.root = null;
redoStack.root = null;
}
/**
* this method manages the undo button from the mathdoku stage
* @param undo -> button to add the action to
* @param redo -> needed to enable the redo button
* @param grid -> to get a specific cell from the grid
*/
public void undoAction(Button undo, Button redo, GridPane grid) {
undo.setOnAction(actionEvent -> {
//disable the undo button when the undo stack is empty => can't do any other undo action
if(undoStack.isEmpty()) {
undo.setDisable(true);
} else {
redo.setDisable(false); //enable the redo action
Element e = undoStack.pop(); //pop an element from the undo stack
redoStack.push(e);//push that element to the redo stack
getCell(grid, e.i, e.j).getTextField().clear(); //clear the content from that specific cell
cellValues[e.i][e.j] = 0; //set the value of the matrix at that position
}
});
}
/**
* this method manages the redo button from the mathdoku stage
* @param redo -> button to add the action to
* @param grid -> to get the specific cell from the grid
*/
public void redoAction(Button redo, GridPane grid) {
redo.setOnAction(actionEvent -> {
//disable the redo button when the redo stack is empty => can't do any redo action
if(redoStack.isEmpty()) {
redo.setDisable(true);
} else {
Element e = redoStack.pop(); //pop the element from the redo stack
getCell(grid, e.i, e.j).getTextField().setText(String.valueOf(e.value)); //set the content of that
// cell to the element popped from the redo stack
undoStack.push(e); //push the element back to the undo stack
}
});
}
/**
* method that will be responsible with adding the player's actions to the undo stack
* @param grid -> to loop through the cells from the grid
* @param undo -> to enable the undo stack
*/
public void addActions(GridPane grid, Button undo) {
//loop through the cells of the board
for(Node cell : grid.getChildren()) {
//add a listener to the text of the cells to add the numbers introduced to the undo stack
((Cell) cell).getTextField().textProperty().addListener((observableValue, s, t1) -> {
//only add the numeric values
if(isNumeric(((Cell) cell).getTextField().getText())) {
Element e = new Element(((Cell) cell).getRow(), ((Cell) cell).getColumn(),
Integer.parseInt(((Cell) cell).getTextField().getText())); //create the element that must
// be added to the undo stack
undoStack.push(e); //push that element to the undo stack
undo.setDisable(false); //enable the undo button
}
});
}
}
/**
* method that manages the font size change
* @param button -> press to change size of the text within a cell and the label of a cage
* @param number -> the number to use to change the size of the text
*/
public void changeFont(Button button, int number) {
button.setOnAction(actionEvent -> {
//loop through the cages of the board
for(Cage cage : cages) {
//loop through the cells of the board
for(Cell cell : cage.getCells()) {
//set the new properties of the text
cell.getTextField().setStyle("-fx-text-box-border: transparent; " +
"-fx-background-color: transparent;");
cell.getTextField().setFont(Font.font("Verdana", FontWeight.BOLD, number));
}
//change the size of the label of the cage
cage.getCells().get(0).getLabel().setFont(Font.font(number - 10));
}
});
}
/**
* this method helps with the functionality of the submit button
* determines if the board satisfied the property of having unique individual numbers on the row and column
* @return boolean depending if the row and column both satisfy the property
*/
public boolean verify() {
//loop through the values of the matrix of the values of the cells
for(int i = 0; i < n; i ++) {
for(int j = 0; j < n; j ++) {
//verify for cells that are completed
if(cellValues[i][j] !=0 ) {
//if the number is not unique on the row or the column, return false
if(!verifyRow(cellValues[i][j], i) || !verifyColumn(cellValues[i][j], j)) {
return false;
}
}
}
}
return true; //return true when the property is satisfied
}
/**
* this method verifies if the row contains only one unique value of a number
* @param x -> the number to verify if it appears only once in the row
* @param row -> the row to verify
* @return true if the number x only appears once in the row
*/
public boolean verifyRow(int x, int row) {
int count = 0; //set the number of appearances to 0
//loop in the matrix of values on the row specified
for(int i = 0; i < n; i ++) {
if(cellValues[row][i] == x) {
count++; //count whenever a number in the row is equal to the number to be verified
}
}
return count == 1; //if it only appears once, return true, else return false
}
/**
* this method verifies if the column contains only one unique value of a number
* @param x -> the number to verify if it appears only once in the column
* @param column -> the column to verify
* @return true if the number x only appears once in the column
*/
public boolean verifyColumn(int x, int column) {
int count = 0; //set the number of appearances to 0
//loop in the matrix of values on the column specified
for(int i = 0; i < n; i ++) {
if(cellValues[i][column] == x) {
count++; //count whenever a number in the column is equal to the number to be verified
}
}
return count == 1; //if it only appears once, return true, else return false
}
/**
* this method helps with the functionality of the submit button
* it verifies if all the cages contain cells with values that satisfy the result
* @return true if all the cages satisfy the condition, else false
*/
static boolean verifyCages() {
for(Cage cage : cages) {
if(!verifyCage(cage)) {
return false;
}
}
return true;
}
/**
* this method verifies is the values from the cells within a cage satisfy the operator of the cage and the result
* @param cage -> the cage to be verified
* @return true if the result is correct
*/
public static boolean verifyCage(Cage cage) {
boolean isCorrect = true;
//if the cage is not complete, the method will return true
if(cage.isCompleted()) {
//if the cage doesn't have a symbol => the result from the cell must be the same as the symbol,
// else return false
if(!cage.hasSymbol()) {
if(Integer.parseInt(cage.getCells().get(0).getTextField().getText()) != Integer.parseInt(cage.getResult()) &&
Integer.parseInt(cage.getCells().get(0).getTextField().getText()) != 0) {
isCorrect = false;
}
} else if(cage.getSymbol() == '+' && !verifySum(cage)) {
isCorrect = false;
} else if((cage.getSymbol() == 'x' || cage.getSymbol() == '*') && !verifyProduct(cage)) {
isCorrect = false;
} else if(cage.getSymbol() == '-' && !verifySubtraction(cage)) {
isCorrect = false;
} else if((cage.getSymbol() == '/' || cage.getSymbol() == '÷') && !verifyDivision(cage)) {
isCorrect = false;
}
}
return isCorrect;
}
/**
* this method verifies a cage when the symbol for that cage is '+'
* @param cage -> cage to be verified
* @return true if the sum of the cells in the cage have the correct result of the cage
*/
public static boolean verifySum(Cage cage) {
int sum = 0; //initialise the sum with 0
//loop through the cells of the cage
for(Cell cell : cage.getCells()) {
sum = sum + Integer.parseInt(cell.getTextField().getText()); //sum the value sof the cells from the cage
}
return (sum == Integer.parseInt(cage.getResult())); //return true if the results are the same
}
/**
* this method verifies a cage when the symbol for the cage is 'x' or '*'
* @param cage -> cage to be verified
* @return true if the product of the values of the cells from the cage has the correct result
*/
public static boolean verifyProduct(Cage cage) {
int product = 1; //initialise the product with 1
//loop through the cells of the cage
for(Cell cell : cage.getCells()) {
product = product * Integer.parseInt(cell.getTextField().getText()); //calculate the product of the values
}
return ((product == Integer.parseInt(cage.getResult()))); //return true if the results are the same
}
/**
* this method verifies a cage when the symbol for that cage is '-'
* sort the values of the cells from the cage in descending order and subtract in order
* @param cage -> cage to be verified
* @return true if the subtraction of the values of the cells from the cage has the correct result
*/
public static boolean verifySubtraction(Cage cage) {
int x = cage.getValues()[0]; //get the first value of the descending sorted array of the
// values of the cells from the cage
//loop through the values of the descending sorted array of the values of the cells from the cage
for(int i = 1; i < cage.getValues().length; i ++) {
x = x - cage.getValues()[i]; //subtract each value from the result
}
return (x == Integer.parseInt(cage.getResult())); //return true if the results correspond
}
/**
* this method verifies a cage when the symbol for the cage is '/' or '÷'
* sort the values of the cells of the cage in descending order and divide in order
* @param cage -> cage to be verified
* @return true if the division of the values of the cells from the cage has the correct result
*/
public static boolean verifyDivision(Cage cage) {
int x = cage.getValues()[0]; //get the first value of the descending sorted array of the
// values of the cells form the cage
//loop through the values of the descending sorted array of the values of the cells from the cage
for(int i = 1; i < cage.getValues().length; i ++) {
//if the module operator doesn't result in 0, then return false
if(x % cage.getValues()[i] == 0) {
x = x / cage.getValues()[i]; //else, divide the greatest number (x) to the next value in the array
} else {
return false;
}
}
return (x == Integer.parseInt(cage.getResult())); //return true if the results correspond
}
/**
* this method will return the cell from a grid pane, placed at a specific position (i, j)
* @param grid -> the grid which contains the cell
* @param i -> number of the row position
* @param j -> number of the column position
* @return the cell at the corresponding position in the grid
*/
static Cell getCell(GridPane grid, int i, int j) {
//loop through the cells of the grid
for(Node cell : grid.getChildren()) {
//return the cell at the position (i,j)
if(((Cell) cell).getRow() == i && ((Cell) cell).getColumn() == j) {
return (Cell) cell;
}
}
return null; //return null when the cell doesn't exist in the grid
}
/**
* this method verifies if the number within a cage is numeric
* @param x -> the value to be verified
* @return true if there exists no exception when trying to convert the value into an integer, false otherwise
*/
public static boolean isNumeric(String x) {
try {
Integer.parseInt(x);
} catch (NumberFormatException e) {
return false;
}
return true;
}
public void solveBoard(GridPane grid) {
Solver solver = new Solver(n, grid);
solver.backtracking(1, 1);
clear(grid);
}
public void hint(Button hint, GridPane grid) {
hint.setOnAction(actionEvent -> {
boolean found = false;
for(int i = 0; i < n && !found; i ++) {
for(int j = 0; j < n; j ++) {
if(cellValues[i][j] != Solver.solved[i][j]) {
getCell(grid, i, j).setValue(Solver.solved[i][j]);
cellValues[i][j] = Solver.solved[i][j];
found = true;
break;
}
}
}
});
}
}