From 0198ba6e5636ed1fda6beada4cbe4fe0acb979ac Mon Sep 17 00:00:00 2001 From: Tyler Date: Sun, 28 Jul 2024 23:11:22 +0100 Subject: [PATCH] Add JavaDocs --- .../com/shr4pnel/minesweeper/Controller.java | 247 +++++++++++++++++- .../java/com/shr4pnel/minesweeper/Grid.java | 14 +- .../com/shr4pnel/minesweeper/GridWrapper.java | 74 ++++++ .../java/com/shr4pnel/minesweeper/Main.java | 16 ++ 4 files changed, 345 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/shr4pnel/minesweeper/Controller.java b/src/main/java/com/shr4pnel/minesweeper/Controller.java index 578374d..3b405ff 100644 --- a/src/main/java/com/shr4pnel/minesweeper/Controller.java +++ b/src/main/java/com/shr4pnel/minesweeper/Controller.java @@ -23,29 +23,83 @@ import javafx.scene.layout.GridPane; import javafx.stage.Stage; public class Controller { + /** + * The grid within the FXML holding all the tiles. + */ @FXML private GridPane grid; + /** + * Images within the FXML structure. + */ @FXML private ImageView smiley, time_1, time_2, time_3, bomb_2, bomb_3; + /** + * Menu items + */ @FXML private RadioMenuItem color, marks; + /** + * The menu item "about". + */ @FXML private MenuItem about; + /** + * The underlying 2D array representing where bombs are on the grid. + */ private Grid gridHandler; + + /** + * A wrapper to make operations on the underlying grid easier. + */ private GridWrapper wrapper; + + /** + * Handles the grid during a game over state preventing further clicking of the grid. + */ private boolean gameOver = false; + + /** + * A variable specifying if the game has been loaded for the first time. + */ private boolean isFirstLoad = true; + + /** + * The game timer. + */ static Timer timer = new Timer(); + + /** + * The time that has elapsed since the game started. + */ private int time = 0; + + /** + * The time the game was started at. + */ private long startTime; + + /** + * The number of bombs on the grid. + */ private int bombCount = 99; + + /** + * A 2D array holding all tiles that have previously recursively been expanded. + */ private boolean[][] expandedTiles; + + /** + * A boolean that shows if the user has performed their first click on the GridPane. + */ private boolean isFirstClick = true; + /** + * Sets important variables on Main class initialization + */ @FXML private void initialize() { setNotYetImplemented(color); @@ -57,6 +111,11 @@ public class Controller { expandedTiles = new boolean[30][16]; } + /** + * Opens the about window when the radiomenuitem is clicked + * + * @param actionEvent The node that the window opens on, on click + */ private void openAbout(ActionEvent actionEvent) { try { Parent root = FXMLLoader.load(getClass().getResource("about.fxml")); @@ -69,6 +128,11 @@ public class Controller { } } + /** + * Opens a window if an unimplemented menu item is clicked + * + * @param node The node that the window opens on, on click + */ private void setNotYetImplemented(RadioMenuItem node) { node.setOnAction(new EventHandler<>() { @Override @@ -86,6 +150,9 @@ public class Controller { }); } + /** + * Fills the GridPane with blank buttons + */ private void setupGrid() { for (int column = 0; column < 30; ++column) { for (int row = 0; row < 16; ++row) { @@ -95,6 +162,11 @@ public class Controller { } } + /** + * Used during initialization to fill the board + * + * @return A blank button + */ private Button createBlankButton() { Image blank = new Image(String.valueOf(getClass().getResource("img/blank.png")), 16, 16, true, false); ImageView blankImage = new ImageView(blank); @@ -107,18 +179,34 @@ public class Controller { return blankButton; } + /** + * Changes smiley face to concerned look when the mouse is held + * + * @param mouseEvent Stub, required by EventHandler interface + */ private void mouseHeld(MouseEvent mouseEvent) { if (gameOver) return; setImage(smiley, "img/face_ooh.png"); } + /** + * Changes smiley face back to smiling after mouse release + * + * @param mouseEvent Stub, required by EventHandler interface + */ private void mouseReleased(MouseEvent mouseEvent) { if (gameOver) return; setImage(smiley, "img/face_smile.png"); } + /** + * Handles the main GridPane being clicked + * Identifies the intention of the click (e.g. flag, open tile) + * + * @param e The event fired on click + */ private void buttonClicked(MouseEvent e) { if (gameOver) { return; @@ -139,9 +227,33 @@ public class Controller { if (buttonImage.getUrl().contains("flagged.png")) { return; } +// if (buttonImage.getUrl().contains("num") && !buttonImage.getUrl().contains("num_0.png")) { +// chord(clicked, buttonImage.getUrl()); +// return; +// } handlePrimaryClick(clicked, column, row); } +// private void chord(Button clicked, String clickedURL) { +// int column = GridPane.getColumnIndex(clicked); +// int row = GridPane.getRowIndex(clicked); +// int urlLength = clickedURL.length(); +// char expectedAdjacentChar = clickedURL.charAt(urlLength-5); +// int expectedAdjacent = Integer.parseInt(Character.toString(expectedAdjacentChar)); +// int actualAdjacent = wrapper.atColumn(column).atRow(row).adjacentBombCount(); +// if (expectedAdjacent != actualAdjacent) { +// return; +// } +// // chord logic +// } + + /** + * If the first tile clicked was a bomb, move that bomb to the first available column on row 0 before opening it + * + * @param column The column of the clicked tile + * @param row The row of the clicked tile + * @return An array containing the column and row the bomb was moved to + */ private int[] setBombIfFirstTileIsBomb(int column, int row) { for (int c = 0; c < 30; ++c) { for (int r = 0; r < 16; ++r) { @@ -152,15 +264,27 @@ public class Controller { } } } + // this realistically should never happen... sorry! return null; } + /** + * Extracts the URL of a given button + * + * @param button A javafx.scene.control.Button object + * @return A string representation of the image URL + */ private String getButtonURL(Button button) { ImageView graphic = (ImageView) button.getGraphic(); Image image = graphic.getImage(); return image.getUrl(); } + /** + * Iterates through the grid to find all tiles that are either blank or flagged + * + * @return The number of tiles that were blank or flagged + */ private int numberOfUnrevealedTiles() { int unrevealedTiles = 0; int column, row; @@ -177,11 +301,24 @@ public class Controller { return unrevealedTiles; } + /** + * Checks if the number of unrevealed tiles is equal to 99 (the win condition) + * + * @return A boolean representing if the game has been won + * @see #numberOfUnrevealedTiles() + */ private boolean checkWinCondition() { int unrevealedTiles = numberOfUnrevealedTiles(); return unrevealedTiles == 99; } + /** + * Handles a left mouse click on an unopened tile + * + * @param clicked The button that was clicked + * @param column The column that it was clicked at + * @param row The row that it was clicked at + */ private void handlePrimaryClick(Button clicked, int column, int row) { if (wrapper.atColumn(column).atRow(row).isBomb() && !isFirstClick) { gameOver(clicked); @@ -209,6 +346,13 @@ public class Controller { } } + /** + * If a tile is surrounded by tiles with no adjacent bombs, open all of the tiles recursively + * + * @param column The column that was clicked + * @param row The row that was clicked + * @see #expandTile(int, int) + */ private void recursiveExpandTiles(int column, int row) { if (column < 0 || column >= 30 || row < 0 || row >= 16 || expandedTiles[column][row] && !wrapper.atColumn(column).atRow(row).isBomb()) { @@ -227,6 +371,12 @@ public class Controller { } } + /** + * Opens a tile, and adds it to an array to prevent overflow + * + * @param column The column of the button to open + * @param row The row of the button to open + */ private void expandTile(int column, int row) { Node tile = getNodeByRowColumnIndex(row, column); if (tile != null) { @@ -242,6 +392,13 @@ public class Controller { } } + /** + * Iterates over the grid and returns the node when it's at the correct child + * + * @param row The desired row to find + * @param column The desired column to find + * @return The node at the specified position + */ private Node getNodeByRowColumnIndex(int row, int column) { for (Node node : grid.getChildren()) { if (GridPane.getRowIndex(node) == row && GridPane.getColumnIndex(node) == column) { @@ -251,6 +408,9 @@ public class Controller { return null; } + /** + * Resets important state variables on restart + */ @FXML private void reinitialize() { gameOver = false; @@ -264,6 +424,11 @@ public class Controller { isFirstClick = true; } + /** + * Resets the timer to 0 + * + * @see #reinitialize() + */ private void resetTimer() { URL zeroSecondURL = getClass().getResource("img/0_seconds.png"); time_1.setImage(new Image(String.valueOf(zeroSecondURL))); @@ -274,6 +439,11 @@ public class Controller { timer = new Timer(); } + // todo figure out why createBlankButton errors here + + /** + * Refills the grid with new blank buttons + */ private void resetGrid() { URL blank = getClass().getResource("img/blank.png"); for (Node node : grid.getChildren()) { @@ -282,6 +452,11 @@ public class Controller { } } + /** + * When a bomb is clicked, change the smiley, set gameOver to true. + * + * @param tileClicked The node that was clicked, to set the correct image + */ private void gameOver(Node tileClicked) { gameOver = true; timer.cancel(); @@ -290,6 +465,9 @@ public class Controller { showAllBombs(GridPane.getColumnIndex(tileClicked), GridPane.getRowIndex(tileClicked)); } + /** + * Creates a win state, change the smiley. + */ private void win() { gameOver = true; timer.cancel(); @@ -297,6 +475,9 @@ public class Controller { flagAllRemaining(); } + /** + * After a win, set every bomb tile that is unflagged to flagged. + */ private void flagAllRemaining() { int column, row; for (column = 0; column < 30; ++column) { @@ -312,12 +493,17 @@ public class Controller { } } + /** + * Create a flag on a tile, representing a tile that the user suspects has a bomb behind it. + * This prevents the tile being clicked on. + * + * @param tileClicked + */ private void flag(Node tileClicked) { Button tileAsButton = (Button) tileClicked; ImageView tileGraphic = (ImageView) tileAsButton.getGraphic(); Image tileGraphicImage = tileGraphic.getImage(); - if (!tileGraphicImage.getUrl().contains("blank.png") && - !tileGraphicImage.getUrl().contains("flagged.png")) { + if (!tileGraphicImage.getUrl().contains("blank.png") && !tileGraphicImage.getUrl().contains("flagged.png")) { return; } boolean flagged = tileGraphicImage.getUrl().contains("flagged.png"); @@ -334,17 +520,30 @@ public class Controller { } + /** + * On flag or reinitialization, update the bomb counter to represent the amount of currently flagged tiles + */ private void updateBombCounter() { String bombCountString = String.format("%03d", bombCount); setBombCounterImage(bomb_2, bombCountString.charAt(1)); setBombCounterImage(bomb_3, bombCountString.charAt(2)); } + /** + * Used to update the digits of the bomb counter. + * + * @param imageView The ImageView representing the counter digit to be changed. + * @param digit The digit the counter should be changed to. + * @see #updateBombCounter() + */ private void setBombCounterImage(ImageView imageView, char digit) { URL imageURL = getClass().getResource("img/" + digit + "_seconds.png"); imageView.setImage(new Image(String.valueOf(imageURL))); } + /** + * Starts the timer when the user clicks or flags their first tile. + */ private void scheduleTimer() { startTime = System.currentTimeMillis(); TimerTask task = new TimerTask() { @@ -360,6 +559,11 @@ public class Controller { timer.schedule(task, 0, 1000); } + /** + * Calculates the time since the game started, and passes it on. + * + * @see #setTimerImage(ImageView, char) + */ private void updateTimer() { long elapsedTimeMillis = System.currentTimeMillis() - startTime; time = (int) (elapsedTimeMillis / 1000); @@ -369,11 +573,24 @@ public class Controller { setTimerImage(time_3, timeString.charAt(2)); } + /** + * Updates the timer digits individually based on updateTimer() + * + * @param imageView The ImageView to be updated + * @param digit The digit to update the ImageView with + * @see #updateTimer() + */ private void setTimerImage(ImageView imageView, char digit) { URL imageURL = getClass().getResource("img/" + digit + "_seconds.png"); imageView.setImage(new Image(String.valueOf(imageURL))); } + /** + * On game over, show every bomb that was left on the grid, as well as every incorrectly flagged bomb. + * + * @param clickedColumn The column that caused the game over. + * @param clickedRow The row that caused the game over. + */ private void showAllBombs(int clickedColumn, int clickedRow) { for (Node node : grid.getChildren()) { Button b = (Button) node; @@ -390,27 +607,53 @@ public class Controller { } } + /** + * Sets a button's graphic. + * + * @param button The button to change. + * @param imagePath A path to an image to change the graphic to. + */ private void setImage(Button button, String imagePath) { URL imageURL = getClass().getResource(imagePath); button.setGraphic(new ImageView(new Image(String.valueOf(imageURL), 16, 16, true, false))); } + /** + * Replaces an ImageView's graphic with another. + * + * @param imageView The ImageView to change. + * @param imagePath A path to an image to change the graphic to. + */ private void setImage(ImageView imageView, String imagePath) { URL imageURL = getClass().getResource(imagePath); imageView.setImage(new Image(String.valueOf(imageURL))); } + /** + * Replaces the smiley with a pressed down variant when it is clicked. + */ @FXML private void smileyPressed() { setImage(smiley, "img/face_smile_pressed.png"); } + /** + * Releases the smiley from being pressed down, starts reinitialization. + * + * @see #reinitialize() + */ @FXML private void smileyReleased() { setImage(smiley, "img/face_smile.png"); reinitialize(); } + /** + * Sets the graphic of a tile to indicate how many bombs are adjacent to it. + * + * @param tileClicked The tile to update. + * @param adjacentBombs The number of bombs adjacent to the tile. + */ private void setAdjacentCount(Node tileClicked, int adjacentBombs) { Button button = (Button) tileClicked; setImage(button, "img/num_" + adjacentBombs + ".png"); diff --git a/src/main/java/com/shr4pnel/minesweeper/Grid.java b/src/main/java/com/shr4pnel/minesweeper/Grid.java index 7eeae09..f89960b 100644 --- a/src/main/java/com/shr4pnel/minesweeper/Grid.java +++ b/src/main/java/com/shr4pnel/minesweeper/Grid.java @@ -1,18 +1,24 @@ package com.shr4pnel.minesweeper; - import java.util.concurrent.ThreadLocalRandom; public class Grid { final GridWrapper grid = new GridWrapper(); + /** + * Generates the bomb-grid structure on instantiation + * + * @see #generateBombs(int) + */ public Grid() { - // todo fix beginner mode and intermediate! - // sorry :3 - // 99 bombs in expert: generateBombs(99); } + /** + * Generates a boolean 2D array by randomly selecting a column and row, and setting it to true to represent a bomb. + * + * @param bombMax The number of bombs to generate. + */ private void generateBombs(int bombMax) { int i; boolean success; diff --git a/src/main/java/com/shr4pnel/minesweeper/GridWrapper.java b/src/main/java/com/shr4pnel/minesweeper/GridWrapper.java index ecdd87c..b0075cb 100644 --- a/src/main/java/com/shr4pnel/minesweeper/GridWrapper.java +++ b/src/main/java/com/shr4pnel/minesweeper/GridWrapper.java @@ -1,33 +1,72 @@ package com.shr4pnel.minesweeper; public class GridWrapper { + /** + * Number of columns. + */ private static final int COLUMNS = 30; + /** + * Number of rows. + */ private static final int ROWS = 16; + /** + * A low level 2D array representing the position of bombs. + */ final boolean[][] grid = new boolean[COLUMNS][ROWS]; + /** + * Points at a column in grid. + */ private int currentColumn; + /** + * Points at a row in grid. + */ private int currentRow; + /** + * On instantiation, initialize currentColumn and currentRow to 0 + */ public GridWrapper() { this.currentColumn = 0; this.currentRow = 0; } + /** + * Sets currentColumn to a specified column. + * + * @param column The column specified. + * @return Current GridWrapper instance. + */ public GridWrapper atColumn(int column) { this.currentColumn = column; return this; } + /** + * Sets currentColumn to a specified column. + * + * @param row The row specified. + * @return Current GridWrapper instance. + */ public GridWrapper atRow(int row) { this.currentRow = row; return this; } + /** + * Sets a bomb in grid at the position specified by currentColumn and currentRow. + */ public void setBomb() { if (isValid(currentColumn, currentRow)) { grid[currentColumn][currentRow] = true; } } + /** + * Switches a bomb from one position to another. + * + * @param destinationColumn The new column for the bomb. + * @param destinationRow The new row for the bomb. + */ public void switchBomb(int destinationColumn, int destinationRow) { if (isValid(currentColumn, currentRow) && isValid(destinationColumn, destinationRow)) { grid[destinationColumn][destinationRow] = true; @@ -35,10 +74,20 @@ public class GridWrapper { } } + /** + * Checks if there is a bomb at the specified position. + * + * @return The value in grid specified by currentColumn and currentRow. + */ public boolean isBomb() { return isValid(currentColumn, currentRow) && grid[currentColumn][currentRow]; } + /** + * Checks for bombs in every direction from the currentColumn and currentRow. + * + * @return The number of adjacent bombs. + */ public int adjacentBombCount() { int count = 0; @@ -69,15 +118,40 @@ public class GridWrapper { return count; } + // todo use switchBomb + + /** + * Swaps one bomb with another. + * + * @param oldColumn The original column of the bomb. + * @param oldRow The original row of the bomb. + * @param newColumn The destination column of the bomb. + * @param newRow The destination row of the bomb. + */ public void updateGrid(int oldColumn, int oldRow, int newColumn, int newRow) { grid[oldColumn][oldRow] = false; grid[newColumn][newRow] = true; } + /** + * Checks if a bomb is at a specified position. + * + * @param column The column specified. + * @param row The row specified. + * @return A boolean AND representing a valid position and a bomb being present. + * @see #isValid(int, int) + */ private boolean isBombAt(int column, int row) { return isValid(column, row) && grid[column][row]; } + /** + * Checks if a given column and row is in bounds. + * + * @param column The column provided. + * @param row The row provided. + * @return A boolean representing the tile being in bounds. + */ private boolean isValid(int column, int row) { return column >= 0 && column < COLUMNS && row >= 0 && row < ROWS; } diff --git a/src/main/java/com/shr4pnel/minesweeper/Main.java b/src/main/java/com/shr4pnel/minesweeper/Main.java index 2c65e06..cb00e71 100644 --- a/src/main/java/com/shr4pnel/minesweeper/Main.java +++ b/src/main/java/com/shr4pnel/minesweeper/Main.java @@ -1,6 +1,7 @@ package com.shr4pnel.minesweeper; import java.io.IOException; + import javafx.application.Application; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -9,6 +10,12 @@ import javafx.scene.image.Image; import javafx.stage.Stage; public class Main extends Application { + /** + * JavaFX opening method. Creates the stage and bootstraps the application. + * + * @param stage The stage object passed in by JavaFX. + * @throws IOException If the FXML template or app icon are not found in resources. + */ @Override public void start(Stage stage) throws IOException { Image icon = new Image(String.valueOf(getClass().getResource("winmine.png"))); @@ -21,10 +28,19 @@ public class Main extends Application { stage.show(); } + /** + * Application opening method. Calls the JavaFX start method. + * + * @param args Optional commandline parameters, unimplemented. + * @see #start(Stage) + */ public static void main(String[] args) { launch(); } + /** + * Cancels the timer on application stop. + */ @Override @FXML public void stop() {