add comments

This commit is contained in:
SowinskiBraeden committed 2025-11-28 00:11:21 -08:00
1 parent 6c2255ea1c
commit 13000cce58
18 files changed
+618 -120

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
lesson 1:
- static initializer used in Mines.java
- static initializer used in MinesUI.java
- method overloading in Mines.java > startNewGame()
lesson 2:
- inheritance in Mines.java (extends GameBoard)
- custom exception in InvalidMoveException.java
- throwing exception in Mines.java > reveal()
- catching exception in MinesUI.java > handleReveal()
lesson 3:
- method overriding in Mines.java > reset()
- substitution via GameBoard parent reference in constructor chain
lesson 4:
- abstract class in GameBoard.java
- abstract method reset() in GameBoard.java
- implementation of abstract method in Mines.java > reset()
lesson 5:
- collections in MinesUI.java > List<Button> buttons
- iterators in MinesUI.java > disableAllButtons()
lesson 6:
- lambda expressions in Mines.java > forEachNeighbor()
- lambda expressions in MinesUI.java > button.setOnMouseClicked()
lesson 8:
- Scanner used in Mines.java > loadBestScore()
- BufferedReader used in Mines.java > loadBestScore()
- BufferedWriter used in Mines.java > saveScore()
- File used in Mines.java > loadBestScore()
lesson 9:
- JavaFX GUI in MinesUI.java
- JavaFX application entry class in MyGame.java > start()
lesson 10:
- unit tests to be shown in accompanying JUnit test files
@@ -1,11 +1,26 @@
package ca.bcit.comp2522.project; package ca.bcit.comp2522.project;
/**
* AscendingPlacement contains the logical and
* validation of placing an integer in an array
* of integers following an ascending order.
*/
public class AscendingPlacement public class AscendingPlacement
extends PlacementRule extends PlacementRule
{ {
private static final int FIRST = 0; private static final int FIRST = 0;
private static final int NEXT = 1; private static final int NEXT = 1;
/**
* isValidPlacement ensures that a value placed at a given index
* is in the correct position, no number greater than the value
* comes before and no number smaller than the value comes after
* within the positions array.
* @param positions represents a 2d int array in 1d array
* @param index to check if value can be placed here
* @param value to be placed
* @return if the value is in a valid placement
*/
@Override @Override
public boolean isValidPlacement(int[] positions, int index, int value) public boolean isValidPlacement(int[] positions, int index, int value)
{ {
@@ -31,6 +46,14 @@ public class AscendingPlacement
return true; return true;
} }
/**
* canPlaceNext detects if a given value
* has a valid position to be placed.
* @param positions represents a 2d int array in 1d array
* @param nextValue to place
* @return if the value can be placed in the positions array
* without violating the ascending rule
*/
@Override @Override
public boolean canPlaceNext(int[] positions, int nextValue) public boolean canPlaceNext(int[] positions, int nextValue)
{ {
+27 -1
View File
@@ -3,7 +3,9 @@ package ca.bcit.comp2522.project;
import java.util.Random; import java.util.Random;
/** /**
* Country class * Country class stores a country's
* name, capital city name, and NUMBER_OF_FACTS
* about the country.
* *
* @author Braeden Sowinski * @author Braeden Sowinski
* @version 1.0.0 * @version 1.0.0
@@ -16,6 +18,11 @@ public class Country
private final String capitalCityName; private final String capitalCityName;
private final String[] facts; private final String[] facts;
/**
* validateString ensures an input String is
* not null and not blank
* @param str to validate
*/
private static void validateString(final String str) private static void validateString(final String str)
{ {
if (str == null || str.isBlank()) if (str == null || str.isBlank())
@@ -24,6 +31,13 @@ public class Country
} }
} }
/**
* Country constructor takes in the
* name, capital, and facts of a country
* @param name of country
* @param capitalCityName of country
* @param facts about the country
*/
public Country( public Country(
final String name, final String name,
final String capitalCityName, final String capitalCityName,
@@ -37,16 +51,28 @@ public class Country
this.facts = facts; this.facts = facts;
} }
/**
* getName of the country
* @return name of the country
*/
public String getName() public String getName()
{ {
return this.name; return this.name;
} }
/**
* getCapital of country
* @return capital of country
*/
public String getCapital() public String getCapital()
{ {
return this.capitalCityName; return this.capitalCityName;
} }
/**
* getRandomFact of country
* @return random fact from the list of facts of the country
*/
public String getRandomFact() public String getRandomFact()
{ {
final Random rand; final Random rand;
@@ -1,25 +1,47 @@
package ca.bcit.comp2522.project; package ca.bcit.comp2522.project;
/**
* GameBoard is a simple abstract class
* to represent some two-dimensional board,
* it stores the width and height of board
* and must have a reset method.
*/
public abstract class GameBoard public abstract class GameBoard
{ {
protected int width; protected int width;
protected int height; protected int height;
/**
* GameBoard constructor creates the board
* @param width of board
* @param height of board
*/
protected GameBoard(final int width, final int height) protected GameBoard(final int width, final int height)
{ {
this.width = width; this.width = width;
this.height = height; this.height = height;
} }
/**
* getWidth of game board
* @return width of game board
*/
public final int getWidth() public final int getWidth()
{ {
return this.width; return this.width;
} }
/**
* getHeight of game board
* @return height of game board
*/
public final int getHeight() public final int getHeight()
{ {
return this.height; return this.height;
} }
/**
* reset game board
*/
public abstract void reset(); public abstract void reset();
} }
@@ -0,0 +1,16 @@
package ca.bcit.comp2522.project;
/**
* Generator interface has a generate method
*
* @author Braeden Sowinski
* @version 1.0.0
*/
public interface Generator
{
/**
* generate integer value
* @return integer value
*/
int generate();
}
@@ -1,7 +1,18 @@
package ca.bcit.comp2522.project; package ca.bcit.comp2522.project;
/**
* InvalidMoveException is a custom error
* to convey that an attempted move is invalid.
*
* @author Braeden Sowinski
* @version 1.0.0
*/
public class InvalidMoveException extends Exception public class InvalidMoveException extends Exception
{ {
/**
* InvalidMoveException constructor
* @param message of InvalidMoveException
*/
public InvalidMoveException(final String message) public InvalidMoveException(final String message)
{ {
super(message); super(message);
+7 -1
View File
@@ -3,13 +3,19 @@ package ca.bcit.comp2522.project;
import java.util.Scanner; import java.util.Scanner;
/** /**
* Main program entry to access a games menu * Main program entry to access a games menu,
* users can select from 3 games. Word Game,
* Number Game, or My Game (Minesweeper)
* *
* @author Braeden Sowinski * @author Braeden Sowinski
* @version 1.0.0 * @version 1.0.0
*/ */
public class Main public class Main
{ {
/**
* main program entry
* @param args from the command line
*/
public static void main(final String[] args) public static void main(final String[] args)
{ {
final Scanner scanner; final Scanner scanner;
+140 -39
View File
@@ -10,6 +10,16 @@ import java.util.Random;
import java.util.Scanner; import java.util.Scanner;
import java.util.function.Consumer; import java.util.function.Consumer;
/**
* Mines class holds all relevant game logic
* for minesweeper, generating field, tracking
* game board (field), tracks cells revealed,
* cells flagged and flagged status, randomizing
* board if needed and provide getters for cells.
*
* @author Braeden Sowinski
* @version 1.0.0
*/
public class Mines extends GameBoard public class Mines extends GameBoard
{ {
private static final int MINE = -1; private static final int MINE = -1;
@@ -27,7 +37,7 @@ public class Mines extends GameBoard
private static final int INITIAL_BEST_SCORE = Integer.MAX_VALUE; private static final int INITIAL_BEST_SCORE = Integer.MAX_VALUE;
private static final String SCORE_FILE_NAME = "scores.txt"; private static final String SCORE_FILE_NAME = "minesweeper-score.txt";
private static final Random RANDOM_GENERATOR; private static final Random RANDOM_GENERATOR;
@@ -36,8 +46,8 @@ public class Mines extends GameBoard
RANDOM_GENERATOR = new Random(); RANDOM_GENERATOR = new Random();
} }
private final int totalMines; private final int totalMines;
private boolean randomMode; private final boolean randomMode;
private int[] field; private int[] field;
private boolean[] revealed; private boolean[] revealed;
@@ -45,6 +55,14 @@ public class Mines extends GameBoard
private int bestScoreSeconds; private int bestScoreSeconds;
/**
* Mines constructor generates minefield board
* with a given width, height and number of mines.
* @param width of minefield
* @param height of minefield
* @param mines to place in minefield
* @param randomMode to enable randomizing the field
*/
public Mines( public Mines(
final int width, final int width,
final int height, final int height,
@@ -59,27 +77,21 @@ public class Mines extends GameBoard
loadBestScore(); loadBestScore();
} }
public void startNewGame() /**
{ * isRandomMode checks if the mode is random or not
reset(); * @return random mode value
} */
public void startNewGame(final boolean randomMode)
{
this.randomMode = randomMode;
reset();
}
public void setRandomMode(final boolean randomMode)
{
this.randomMode = randomMode;
}
public boolean isRandomMode() public boolean isRandomMode()
{ {
return this.randomMode; return this.randomMode;
} }
/**
* reset game board, refreshes field,
* revealed, and flagged arrays to
* default values of total number of cells
* then generates field values
*/
@Override @Override
public void reset() public void reset()
{ {
@@ -94,6 +106,12 @@ public class Mines extends GameBoard
generateField(); generateField();
} }
/**
* generateField places number of mines randomly
* within the minefield, then for each cell count
* number of neighboring mines and set values in
* the field
*/
private void generateField() private void generateField()
{ {
int placedMines; int placedMines;
@@ -115,6 +133,19 @@ public class Mines extends GameBoard
} }
} }
countNeighboringMines();
}
/**
* countNeighboringMines iterates over the field
* and for each cell of the field, checks all 8
* neighbors, top, top right, right, bottom right,
* and so on to count the total number of mines
* surround that cell, then updates the mine count
* for that cell in the field.
*/
private void countNeighboringMines()
{
for (int i = 0; i < this.field.length; i++) for (int i = 0; i < this.field.length; i++)
{ {
if (this.field[i] == MINE) if (this.field[i] == MINE)
@@ -137,6 +168,16 @@ public class Mines extends GameBoard
} }
} }
/**
* forEachNeighbor is a helper method that takes in
* an index of the field and a Consumer to perform
* an action for each neighbor of the given index.
*
* Performs bounds checking to ensure there is no
* out of bounds errors.
* @param index to get each neighbor of
* @param action to perform on each neighbor of the given cell index
*/
private void forEachNeighbor( private void forEachNeighbor(
final int index, final int index,
final Consumer<Integer> action final Consumer<Integer> action
@@ -176,6 +217,14 @@ public class Mines extends GameBoard
} }
} }
/**
* popFieldVoid reveals all neighboring cells of a
* 0 value cell till it reaches a cell that has a
* value greater than 0. i.e. a cell that has a
* neighboring mine. Recursively calls itself to
* accomplish this and "pop" a "void" within the field.
* @param index to pop
*/
private void popFieldVoid(final int index) private void popFieldVoid(final int index)
{ {
forEachNeighbor(index, neighborIndex -> { forEachNeighbor(index, neighborIndex -> {
@@ -199,6 +248,13 @@ public class Mines extends GameBoard
}); });
} }
/**
* reveal a given cell and returns if it was
* a mine or not or if invalid reveal
* @param index of the cell to reveal
* @return if revealed cell was a mine
* @throws InvalidMoveException when attempting to reveal a flagged cell
*/
public boolean reveal(final int index) public boolean reveal(final int index)
throws InvalidMoveException throws InvalidMoveException
{ {
@@ -217,6 +273,12 @@ public class Mines extends GameBoard
return this.field[index] == MINE; return this.field[index] == MINE;
} }
/**
* toggleFlag of a given cell to either flag,
* question, or no flag.
* @param index of cell to flag
* @return the updated state of the flag
*/
public int toggleFlag(final int index) public int toggleFlag(final int index)
{ {
final int nextState; final int nextState;
@@ -226,36 +288,71 @@ public class Mines extends GameBoard
return nextState; return nextState;
} }
/**
* getFieldValue returns the value of a given cell
* used to show values in the button of the UI
* @param index of cell to get value
* @return value of given cell
*/
public int getFieldValue(final int index) public int getFieldValue(final int index)
{ {
return this.field[index]; return this.field[index];
} }
/**
* isRevealed returns if a given cell has been revealed
* @param index of cell to check if revealed
* @return if given cell is revealed
*/
public boolean isRevealed(final int index) public boolean isRevealed(final int index)
{ {
return this.revealed[index]; return this.revealed[index];
} }
/**
* isMine checks if a given cell is a mine
* @param index of cell to check if mine
* @return if given cell is mine
*/
public boolean isMine(final int index) public boolean isMine(final int index)
{ {
return this.field[index] == MINE; return this.field[index] == MINE;
} }
/**
* isFlagged checks if a given cell has been flagged
* @param index of cell to check if flagged
* @return if given cell is flagged
*/
public boolean isFlagged(final int index) public boolean isFlagged(final int index)
{ {
return this.flagged[index] == FLAG; return this.flagged[index] == FLAG;
} }
/**
* isQuestionMarked checks if a given cell has been questioned
* @param index of cell to check if questioned
* @return if given cell is questioned
*/
public boolean isQuestionMarked(final int index) public boolean isQuestionMarked(final int index)
{ {
return this.flagged[index] == FLAG_QUESTION; return this.flagged[index] == FLAG_QUESTION;
} }
/**
* getTotalMines returns the total mines in the field
* @return total mines in the field
*/
public int getTotalMines() public int getTotalMines()
{ {
return this.totalMines; return this.totalMines;
} }
/**
* hasWon checks if all cells that are not mines have
* been revealed, which is considered a win
* @return if the game is over and has won
*/
public boolean hasWon() public boolean hasWon()
{ {
int revealedCount; int revealedCount;
@@ -277,6 +374,14 @@ public class Mines extends GameBoard
return revealedCount == (this.field.length - this.totalMines); return revealedCount == (this.field.length - this.totalMines);
} }
/**
* randomizeRemaining removes any mines from the field
* that have not been flagged, then for each mine removed
* it is randomly placed back into the field in an unrevealed
* cell. Effectively "randomizing" undiscovered cells within
* the field. Field values are then recalculated to reflect new
* mine positions, and newly discovered voids are popped.
*/
public void randomizeRemaining() public void randomizeRemaining()
{ {
if (!this.randomMode) if (!this.randomMode)
@@ -316,26 +421,7 @@ public class Mines extends GameBoard
} }
} }
for (int i = 0; i < this.field.length; i++) countNeighboringMines();
{
if (this.field[i] == MINE)
{
continue;
}
final int[] count;
count = new int[] { NO_MINE };
forEachNeighbor(i, neighborIndex -> {
if (this.field[neighborIndex] == MINE)
{
count[SELF_OFFSET]++;
}
});
this.field[i] = count[SELF_OFFSET];
}
for (int i = 0; i < this.field.length; i++) for (int i = 0; i < this.field.length; i++)
{ {
@@ -347,6 +433,11 @@ public class Mines extends GameBoard
} }
/**
* saveScore to file, then store seconds if
* less than last score
* @param seconds of game time
*/
public void saveScore(final int seconds) public void saveScore(final int seconds)
{ {
try try
@@ -369,6 +460,11 @@ public class Mines extends GameBoard
} }
} }
/**
* loadBestScore from score file, gets
* the best score (the least number of
* seconds) from the score file.
*/
private void loadBestScore() private void loadBestScore()
{ {
final File scoreFile; final File scoreFile;
@@ -408,6 +504,11 @@ public class Mines extends GameBoard
this.bestScoreSeconds = best; this.bestScoreSeconds = best;
} }
/**
* getBestScoreSeconds returns the best
* score in seconds
* @return best score in seconds
*/
public int getBestScoreSeconds() public int getBestScoreSeconds()
{ {
return this.bestScoreSeconds; return this.bestScoreSeconds;
+93 -1
View File
@@ -22,6 +22,15 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/**
* MinesUI handles all Minesweeper UI elements,
* main menu, grid generation of the minefield.
* Updating button displays, disabling buttons,
* showing flags, warnings, etc.
*
* @author Braeden Sowinski
* @version 1.0.0
*/
public class MinesUI public class MinesUI
{ {
private static final int FONT_SIZE = 18; private static final int FONT_SIZE = 18;
@@ -89,6 +98,11 @@ public class MinesUI
private Timeline timer; private Timeline timer;
private boolean timerRunning; private boolean timerRunning;
/**
* MinesUI constructor creates an ArrayList
* to store buttons and track which mode
* the game is in, normal or random
*/
public MinesUI() public MinesUI()
{ {
this.buttons = new ArrayList<>(); this.buttons = new ArrayList<>();
@@ -96,6 +110,11 @@ public class MinesUI
this.timerRunning = false; this.timerRunning = false;
} }
/**
* showMainMenu displays main menu to choose
* game mode, and field size.
* @param primaryStage to display main menu to
*/
public void showMainMenu(final Stage primaryStage) public void showMainMenu(final Stage primaryStage)
{ {
final Label titleLabel; final Label titleLabel;
@@ -142,6 +161,17 @@ public class MinesUI
primaryStage.show(); primaryStage.show();
} }
/**
* startGame creates new Mine game, to
* generate field, place mines, handle
* randomization if enabled. Creates
* grid of buttons for minefield, displays
* new window of game instance of given size.
* @param width of field to generate
* @param height of field to generates
* @param mines to generate in field
* @param ownerStage to display minefield grid to
*/
private void startGame( private void startGame(
final int width, final int width,
final int height, final int height,
@@ -205,6 +235,14 @@ public class MinesUI
gameStage.show(); gameStage.show();
} }
/**
* createGrid generates the button grid
* that represents the minefield of the
* given dimensions
* @param width of minefield grid
* @param height of minefield grid
* @return GridPane containing buttons of minefield
*/
private GridPane createGrid(final int width, final int height) private GridPane createGrid(final int width, final int height)
{ {
final GridPane grid; final GridPane grid;
@@ -251,6 +289,13 @@ public class MinesUI
return grid; return grid;
} }
/**
* handleReveal reveals the given cell in the
* minefield ensures the timer is running if first
* reveal, call randomize on board if random mode
* enabled, and refresh all button UI elements.
* @param index to reveal
*/
private void handleReveal(final int index) private void handleReveal(final int index)
{ {
if (!timerRunning) if (!timerRunning)
@@ -292,6 +337,11 @@ public class MinesUI
} }
} }
/**
* handleFlag updates the given cell to be
* flagged, questions, or back to no flag.
* @param index to flag
*/
private void handleFlag(final int index) private void handleFlag(final int index)
{ {
final int newState; final int newState;
@@ -315,6 +365,12 @@ public class MinesUI
updateButtonDisplay(index); updateButtonDisplay(index);
} }
/**
* updateButtonDisplay updates a given button
* by index depending on how the cell is configured.
* Flagged, questioned, or revealed.
* @param index of cell to update button
*/
private void updateButtonDisplay(final int index) private void updateButtonDisplay(final int index)
{ {
final Button button; final Button button;
@@ -363,6 +419,12 @@ public class MinesUI
} }
} }
/**
* refreshAllButtons iterates over all buttons
* and calls updateButtonDisplay for that button,
* as well as disables button if revealed, preventing
* further clicking on the button.
*/
private void refreshAllButtons() private void refreshAllButtons()
{ {
for (int i = 0; i < this.buttons.size(); i++) for (int i = 0; i < this.buttons.size(); i++)
@@ -375,6 +437,10 @@ public class MinesUI
} }
} }
/**
* startTimer when initial cell is revealed to
* track the game time.
*/
private void startTimer() private void startTimer()
{ {
final KeyFrame tick; final KeyFrame tick;
@@ -394,6 +460,9 @@ public class MinesUI
this.timerRunning = true; this.timerRunning = true;
} }
/**
* stopTime once game is over, either lost or won.
*/
private void stopTimer() private void stopTimer()
{ {
if (this.timer != null) if (this.timer != null)
@@ -403,6 +472,11 @@ public class MinesUI
this.timerRunning = false; this.timerRunning = false;
} }
/**
* handleWin displays a win message window, with the time
* taken to win the game, show all mines, and disable all
* buttons.
*/
private void handleWin() private void handleWin()
{ {
final Alert winAlert; final Alert winAlert;
@@ -411,6 +485,7 @@ public class MinesUI
this.game.saveScore(this.seconds); this.game.saveScore(this.seconds);
this.bestLabel.setText("Best: " + this.game.getBestScoreSeconds() + "s"); this.bestLabel.setText("Best: " + this.game.getBestScoreSeconds() + "s");
showAllMines();
disableAllButtons(); disableAllButtons();
winAlert = new Alert(Alert.AlertType.INFORMATION); winAlert = new Alert(Alert.AlertType.INFORMATION);
@@ -419,6 +494,10 @@ public class MinesUI
winAlert.showAndWait(); winAlert.showAndWait();
} }
/**
* handleLoss displays a loss message, reveals all
* mines and stops timer. Forces user to restart game.
*/
private void handleLoss() private void handleLoss()
{ {
final Alert lossAlert; final Alert lossAlert;
@@ -429,10 +508,14 @@ public class MinesUI
lossAlert = new Alert(Alert.AlertType.ERROR); lossAlert = new Alert(Alert.AlertType.ERROR);
lossAlert.setHeaderText("You lost..."); lossAlert.setHeaderText("You lost...");
lossAlert.setContentText("You dug up a mine. Better luck next time."); lossAlert.setContentText("You dug up a mine and lost your legs.");
lossAlert.showAndWait(); lossAlert.showAndWait();
} }
/**
* showAllMines shows all mine locations for when
* a user wins or loses.
*/
private void showAllMines() private void showAllMines()
{ {
for (int i = 0; i < this.buttons.size(); i++) for (int i = 0; i < this.buttons.size(); i++)
@@ -448,6 +531,10 @@ public class MinesUI
} }
} }
/**
* disableAllButtons iterates over all buttons
* and calls the disableButton method on it.
*/
private void disableAllButtons() private void disableAllButtons()
{ {
final Iterator<Button> iterator; final Iterator<Button> iterator;
@@ -463,6 +550,11 @@ public class MinesUI
} }
} }
/**
* disableButton prevents user from
* interacting with it by clicking.
* @param button to disable
*/
private void disableButton(final Button button) private void disableButton(final Button button)
{ {
button.setMouseTransparent(true); button.setMouseTransparent(true);
+14 -2
View File
@@ -4,11 +4,19 @@ import javafx.application.Application;
import javafx.stage.Stage; import javafx.stage.Stage;
/** /**
* Main entry point for the Minesweeper game. * MyGame manager for Minesweeper application
* Lesson 9: JavaFX GUI entry class. * in JavaFX.
*
* @author Braeden Sowinski
* @version 1.0.0
*/ */
public class MyGame extends Application public class MyGame extends Application
{ {
/**
* start JavaFX application by creating
* the MinesUI handler to show main menu.
* @param primaryStage to show minesweeper menu UI
*/
@Override @Override
public void start(final Stage primaryStage) public void start(final Stage primaryStage)
{ {
@@ -18,6 +26,10 @@ public class MyGame extends Application
ui.showMainMenu(primaryStage); ui.showMainMenu(primaryStage);
} }
/**
* main program entry for quick testing
* @param args from command line
*/
public static void main(final String[] args) public static void main(final String[] args)
{ {
launch(args); launch(args);
@@ -12,6 +12,17 @@ import javafx.scene.control.Label;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.control.Button; import javafx.scene.control.Button;
/**
* NumberGame is a GUI game with JavaFX
* where a user has to place randomly generated
* numbers into a 5x4 grid of buttons. Numbers
* must be played in order to win.
*
* Extends JavaFX Application
*
* @author Braeden Sowinski
* @version 1.0.0
*/
public class NumberGame public class NumberGame
extends Application extends Application
{ {
@@ -35,14 +46,20 @@ public class NumberGame
private int currentNumber; private int currentNumber;
private int[] positions; private int[] positions;
private RandomNumberGenerator generator; private RandomNumberGenerator generator;
private AscendingPlacement placementValidator;
/**
* start NumberGame GUI
* @param stage to show
*/
@Override @Override
public void start(final Stage stage) public void start(final Stage stage)
{ {
this.generator = new RandomNumberGenerator(MIN_RAND_NUM, MAX_RAND_NUM); this.placementValidator = new AscendingPlacement();
this.numbersPlaced = STARTING_NUMBERS_PLACED; this.generator = new RandomNumberGenerator(MIN_RAND_NUM, MAX_RAND_NUM);
this.currentNumber = this.generator.generate(); this.numbersPlaced = STARTING_NUMBERS_PLACED;
this.positions = new int[GRID_WIDTH * GRID_HEIGHT]; this.currentNumber = this.generator.generate();
this.positions = new int[GRID_WIDTH * GRID_HEIGHT];
this.numberLabel = new Label("Next number: " + this.currentNumber + " - Select a slot."); this.numberLabel = new Label("Next number: " + this.currentNumber + " - Select a slot.");
this.numberLabel.setFont(FONT); this.numberLabel.setFont(FONT);
@@ -66,6 +83,11 @@ public class NumberGame
stage.show(); stage.show();
} }
/*
* createGrid is used to generate the
* button grid for users to play in.
* @return GridPane with Buttons
*/
private GridPane createGrid() private GridPane createGrid()
{ {
final GridPane grid; final GridPane grid;
@@ -96,50 +118,22 @@ public class NumberGame
return grid; return grid;
} }
private void triggerFailed() /*
* triggerFailed handles stopping the game
* when lost, i.e. impossible to place next
* number
*/
private void triggerFailed(final String message)
{ {
this.numberLabel.setText("Next number: " + this.currentNumber + " - Impossible to place next number"); this.numberLabel.setText("Next number: " + this.currentNumber + " - " + message);
}
private boolean canBePlaced()
{
for (int i = 0; i < positions.length; i++) {
if (this.positions[i] != STARTING_NUMBERS_PLACED)
{
continue;
}
int left;
left = Integer.MIN_VALUE;
for (int j = i - INDEX_OFFSET; j >= STARTING_NUMBERS_PLACED; j--)
{
if (this.positions[j] != STARTING_NUMBERS_PLACED)
{
left = this.positions[j];
break;
}
}
int right;
right = Integer.MAX_VALUE;
for (int j = i + INDEX_OFFSET; j < this.positions.length; j++)
{
if (this.positions[j] != STARTING_NUMBERS_PLACED) {
right = this.positions[j];
break;
}
}
if (left < currentNumber && currentNumber < right) {
return true;
}
}
return false;
} }
/*
* handlePress of button to place number
* in that cell
* @param button pressed
* @param index of button to place value
*/
private void handlePress(final Button button, final int index) private void handlePress(final Button button, final int index)
{ {
button.setText("" + this.currentNumber); button.setText("" + this.currentNumber);
@@ -149,24 +143,17 @@ public class NumberGame
this.positions[index] = this.currentNumber; this.positions[index] = this.currentNumber;
// Detect bad placement // Detect bad placement
for (int i = 0; i < GRID_WIDTH * GRID_HEIGHT; i++) final boolean isValidPlacement;
isValidPlacement = this.placementValidator.isValidPlacement(
this.positions,
index,
this.currentNumber
);
if (!isValidPlacement)
{ {
final boolean largerBelow; triggerFailed("Placed number incorrectly.");
final boolean smallerAbove; return;
largerBelow = i < index &&
this.positions[i] != STARTING_NUMBERS_PLACED &&
this.positions[i] > this.positions[index];
smallerAbove = i > index &&
this.positions[i] != STARTING_NUMBERS_PLACED &&
this.positions[i] < this.positions[index];
if (largerBelow || smallerAbove)
{
triggerFailed();
return;
}
} }
if (this.numbersPlaced >= TOTAL_NUMBERS) if (this.numbersPlaced >= TOTAL_NUMBERS)
@@ -178,17 +165,21 @@ public class NumberGame
this.currentNumber = this.generator.generate(); this.currentNumber = this.generator.generate();
final boolean canPlaceNext; final boolean canPlaceNext;
canPlaceNext = canBePlaced(); canPlaceNext = this.placementValidator.canPlaceNext(this.positions, this.currentNumber);
if (!canPlaceNext) if (!canPlaceNext)
{ {
triggerFailed(); triggerFailed("Impossible to place next number.");
return; return;
} }
this.numberLabel.setText("Next number: " + this.currentNumber + " - Select a slot."); this.numberLabel.setText("Next number: " + this.currentNumber + " - Select a slot.");
} }
/**
* main program entry for quick testing
* @param args from command line
*/
public static void main(final String[] args) public static void main(final String[] args)
{ {
launch(args); launch(args);
@@ -1,6 +0,0 @@
package ca.bcit.comp2522.project;
public interface NumberGenerator
{
int generate();
}
@@ -1,13 +1,34 @@
package ca.bcit.comp2522.project; package ca.bcit.comp2522.project;
/**
* PlacementRule defines abstract methods
* that must be implemented to validate positions
* in a number game.
*
* @author Braeden Sowinski
* @version 1.0.0
*/
public abstract class PlacementRule public abstract class PlacementRule
{ {
/**
* isValidPlacement abstract method
* @param positions represents a 2d int array in 1d array
* @param index to check if value can be placed here
* @param value to be placed
* @return if value can be placed at index
*/
public abstract boolean isValidPlacement( public abstract boolean isValidPlacement(
final int[] positions, final int[] positions,
final int index, final int index,
final int value final int value
); );
/**
* canPlaceNext abstract method
* @param positions represents a 2d int array in 1d array
* @param nextValue to place
* @return if next value to place can be placed at all
*/
public abstract boolean canPlaceNext( public abstract boolean canPlaceNext(
final int[] positions, final int[] positions,
final int nextValue final int nextValue
@@ -2,24 +2,44 @@ package ca.bcit.comp2522.project;
import java.util.Random; import java.util.Random;
/**
* RandomNumberGenerator is a helper class
* that generates random numbers within a
* defined range
*
* @author Braeden Sowinski
* @version 1.0.0
*/
public class RandomNumberGenerator public class RandomNumberGenerator
implements NumberGenerator implements Generator
{ {
private final Random random; private final Random random;
private final int min; private final int min;
private final int max; private final int max;
public RandomNumberGenerator(int min, int max) /**
* RandomNumberGenerator constructor
* @param min number that can be randomly generated inclusive
* @param max number that can be randomly generated inclusive
*/
public RandomNumberGenerator(
final int min,
final int max)
{ {
random = new Random(); this.random = new Random();
this.min = min; this.min = min;
this.max = max; this.max = max;
} }
/**
* generate a random number between
* inclusive min and inclusive max value
* @return random number between min and max inclusive
*/
@Override @Override
public int generate() public int generate()
{ {
return random.nextInt(max - min) + min; return this.random.nextInt(this.max - this.min) + this.min;
} }
} }
+58 -1
View File
@@ -12,7 +12,10 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
/** /**
* Score manager for the word game. * Score manager for WordGame, writes and reads
* scores from file, get and validate high-scores,
* calculate score averages and provides useful
* toString methods to summarize scores.
* *
* @author Braeden Sowinski * @author Braeden Sowinski
* @version 1.0.0 * @version 1.0.0
@@ -31,6 +34,13 @@ public class Score
private final int numIncorrectTwoAttempts; private final int numIncorrectTwoAttempts;
private final int score; private final int score;
/**
* appendScoreToFile takes in a Score object
* and filepath, appends Score values to file
* accordingly.
* @param score to append to filepath
* @param scoreFilePath filepath to append Score to
*/
public static void appendScoreToFile( public static void appendScoreToFile(
final Score score, final Score score,
final String scoreFilePath final String scoreFilePath
@@ -52,6 +62,12 @@ public class Score
} }
} }
/**
* readScoresFromFile returns a history of all Scores
* read from the given filepath.
* @param scoreFilePath to read scores from
* @return a list of all Scores read from given filepath
*/
public static List<Score> readScoresFromFile(final String scoreFilePath) public static List<Score> readScoresFromFile(final String scoreFilePath)
{ {
final List<Score> scores; final List<Score> scores;
@@ -110,6 +126,11 @@ public class Score
return scores; return scores;
} }
/**
* getHighScore from a given List of Scores
* @param scores list to find high-score from
* @return high Score from list of scores
*/
public static Score getHighScore(final List<Score> scores) public static Score getHighScore(final List<Score> scores)
{ {
return scores.stream() return scores.stream()
@@ -117,6 +138,14 @@ public class Score
.orElse(null); .orElse(null);
} }
/**
* isHighScore takes a List of Scores and
* new Score to check if the new Score is
* a high-score in the given scores list.
* @param score to check if is high score
* @param scores list to compare Score to
* @return if score is a new high-score in list
*/
public static boolean isHighScore( public static boolean isHighScore(
final Score score, final Score score,
final List<Score> scores final List<Score> scores
@@ -129,6 +158,17 @@ public class Score
highScore.getScore() < score.getScore()); highScore.getScore() < score.getScore());
} }
/**
* Score constructor saves score dateTime,
* number of games played for this score,
* and relevant scores based on number of
* guesses.
* @param dateTime the score was recorded
* @param numGamesPlayed is the number of WordGames played
* @param numCorrectFirstAttempt is the number of times a guess was correct first try
* @param numCorrectSecondAttempt is the number of times a guess was correct on second try
* @param numIncorrectTwoAttempts is the number of times both guesses were incorrect
*/
public Score( public Score(
final LocalDateTime dateTime, final LocalDateTime dateTime,
final int numGamesPlayed, final int numGamesPlayed,
@@ -151,11 +191,20 @@ public class Score
this.numCorrectSecondAttempt * SECOND_GUESS_POINTS; this.numCorrectSecondAttempt * SECOND_GUESS_POINTS;
} }
/**
* getDateTimePlayed of Score
* @return dateTimePlayed as a String
*/
public String getDateTimePlayed() public String getDateTimePlayed()
{ {
return this.dateTimePlayed; return this.dateTimePlayed;
} }
/**
* toString neatly presents this Score
* in a String format
* @return formatted String of Score details
*/
@Override @Override
public String toString() public String toString()
{ {
@@ -185,11 +234,19 @@ public class Score
return log.toString(); return log.toString();
} }
/**
* getScore returns total points of this Score
* @return total points score
*/
public int getScore() public int getScore()
{ {
return this.score; return this.score;
} }
/**
* calculateAverage points per round
* @return average points scored per round
*/
public float calculateAverage() public float calculateAverage()
{ {
return (float) this.score / (float) this.numGamesPlayed; return (float) this.score / (float) this.numGamesPlayed;
@@ -6,6 +6,16 @@ import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.Scanner; import java.util.Scanner;
/**
* WordGame asks NUMBER_OF_QUESTIONS where you are
* either asked about a country by capital,
* asked about a capital by country,
* or asked about a country by a random fact.
*
* Score is kept and saved
* @author Braeden Sowinski
* @version 1.0.0
*/
public class WordGame public class WordGame
{ {
private static final String SCORE_PATH = "./data/score.txt"; private static final String SCORE_PATH = "./data/score.txt";
@@ -26,12 +36,24 @@ public class WordGame
private final World world; private final World world;
/**
* WordGame constructor creates a World class to
* access Countries and their details.
* @throws IOException if World failed to read countries
*/
public WordGame() public WordGame()
throws IOException throws IOException
{ {
this.world = new World(); this.world = new World();
} }
/*
* checkAnswer reads user input and compares it to
* a given correct answer and updates scores accordingly.
* @param correctAnswer to check user input to
* @param scanner to read user input from
* @param scores to update accordingly
*/
private static void checkAnswer( private static void checkAnswer(
final String correctAnswer, final String correctAnswer,
final Scanner scanner, final Scanner scanner,
@@ -66,6 +88,16 @@ public class WordGame
System.out.printf("The correct answer was %s\n", correctAnswer); System.out.printf("The correct answer was %s\n", correctAnswer);
} }
/*
* startTrivia starts the game and asks NUMBER_OF_QUESTIONS,
* randomly chooses a Country and randomly chooses which
* kind of question to ask:
* country by capital,
* capital by country, or
* country by fact.
* @param scanner to pass to checkAnswer method
* @param scores to pass to checkAnswer method
*/
private void startTrivia( private void startTrivia(
final Scanner scanner, final Scanner scanner,
final int[] scores final int[] scores
@@ -109,6 +141,14 @@ public class WordGame
} }
} }
/**
* runTrivia initializes score, scanner, and keeps
* track if the user wants to keep playing after
* a round is completed.
*
* Once a user is done playing, score from all rounds is
* saved and high-scores are calculated.
*/
public void runTrivia() public void runTrivia()
{ {
final Scanner scanner; final Scanner scanner;
@@ -201,6 +241,10 @@ public class WordGame
} }
} }
/**
* main method for quickly testing the WordGame
* @param args from command line
*/
public static void main(final String[] args) public static void main(final String[] args)
{ {
final WordGame test; final WordGame test;
@@ -15,6 +15,10 @@ import java.util.Random;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* World class reads countries and their facts
* from a file and stores the Country in a Map.
* Also provides useful functions to get a random
* Country from the Map.
* @author Braeden Sowinski * @author Braeden Sowinski
* @version 1.0.0 * @version 1.0.0
*/ */
@@ -27,6 +31,13 @@ public class World
private final Map<String, Country> countries; private final Map<String, Country> countries;
/*
* readCuntryData from a given line
* @param line to parse
* @param reader to read line
* @return Country with name and facts
* @throws IOException
*/
private static Country readCountryData( private static Country readCountryData(
final String line, final String line,
final BufferedReader reader final BufferedReader reader
@@ -50,6 +61,12 @@ public class World
return new Country(countryName, countryCapital, facts); return new Country(countryName, countryCapital, facts);
} }
/**
* World constructor creates a hashmap
* where the key is a Country name, and
* the value is the Country object.
* @throws IOException
*/
public World() public World()
throws IOException throws IOException
{ {
@@ -90,6 +107,11 @@ public class World
} }
} }
/**
* getRandomCountry returns a random
* Country from this countries Map.
* @return random Country
*/
public Country getRandomCountry() public Country getRandomCountry()
{ {
final Random rand; final Random rand;
@@ -1,4 +1,4 @@
package ca.bcit.comp2522.project.tests; package ca.bcit.comp2522.project;
import ca.bcit.comp2522.project.Mines; import ca.bcit.comp2522.project.Mines;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;