diff --git a/src/code/ca/bcit/comp2522/project/AscendingPlacement.java b/src/code/ca/bcit/comp2522/project/AscendingPlacement.java new file mode 100644 index 0000000..8f7f69b --- /dev/null +++ b/src/code/ca/bcit/comp2522/project/AscendingPlacement.java @@ -0,0 +1,77 @@ +package ca.bcit.comp2522.project; + +public class AscendingPlacement + extends PlacementRule +{ + private static final int FIRST = 0; + private static final int NEXT = 1; + + @Override + public boolean isValidPlacement(int[] positions, int index, int value) + { + for (int i = 0; i < positions.length; i++) + { + if (positions[i] == FIRST) + { + continue; + } + + boolean invalidBefore; + boolean invalidAfter; + + invalidBefore = i < index && positions[i] > value; + invalidAfter = i > index && positions[i] < value; + + if (invalidBefore || invalidAfter) + { + return false; + } + } + + return true; + } + + @Override + public boolean canPlaceNext(int[] positions, int nextValue) + { + for (int i = 0; i < positions.length; i++) + { + if (positions[i] != FIRST) + { + continue; + } + + int left; + int right; + + left = Integer.MIN_VALUE; + right = Integer.MAX_VALUE; + + // scan left + for (int l = i - NEXT; l >= FIRST; l--) + { + if (positions[l] != FIRST) + { + left = positions[l]; + break; + } + } + + // scan right + for (int r = i + NEXT; r < positions.length; r++) + { + if (positions[r] != FIRST) + { + right = positions[r]; + break; + } + } + + if (left < nextValue && nextValue < right) + { + return true; + } + } + return false; + } +} diff --git a/src/code/ca/bcit/comp2522/project/Mines.java b/src/code/ca/bcit/comp2522/project/Mines.java index 1c057ac..bd4739b 100644 --- a/src/code/ca/bcit/comp2522/project/Mines.java +++ b/src/code/ca/bcit/comp2522/project/Mines.java @@ -1,9 +1,11 @@ package ca.bcit.comp2522.project; +import javafx.animation.Timeline; import javafx.application.Application; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; +import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.input.MouseButton; @@ -28,7 +30,6 @@ public class Mines extends Application private static final int BUTTON_WIDTH = 60; private static final int BUTTON_HEIGHT = 60; private static final int MENU_BUTTON_WIDTH = 180; - private static final int MENU_BUTTON_HEIGHT = 80; private static final int EASY_WIDTH = 8; private static final int EASY_HEIGHT = 8; private static final int HARD_WIDTH = 36; @@ -77,6 +78,37 @@ public class Mines extends Application private int height; private int flags; + private Label flagLabel; + private Label timerLabel; + private int totalMines; + + private int seconds; + private Timeline timer; + private boolean timerRunning = false; + private boolean randomMode = false; + + private void startTimer() { + seconds = 0; + timerLabel.setText("Time: 0"); + + timer = new javafx.animation.Timeline( + new javafx.animation.KeyFrame(javafx.util.Duration.seconds(1), e -> { + seconds++; + timerLabel.setText("Time: " + seconds); + }) + ); + timer.setCycleCount(javafx.animation.Animation.INDEFINITE); + timer.play(); + timerRunning = true; + } + + private void stopTimer() { + if (timer != null) { + timer.stop(); + } + timerRunning = false; + } + @Override public void start(final Stage stage) { @@ -214,30 +246,55 @@ public class Mines extends Application final int height, final int mines ) { - this.flags = NO_FLAG; - this.buttons = new Button[width * height]; - this.width = width; - this.height = height; + this.flags = NO_FLAG; + this.buttons = new Button[width * height]; + this.width = width; + this.height = height; + this.totalMines = mines; generateField(mines); final Stage gameStage; final VBox box; + final VBox topBar; final GridPane grid; + final Label modeLabel; + final Button toggleModeBtn; - gameStage = new Stage(); - box = new VBox(); - grid = createGrid(width, height); + gameStage = new Stage(); + box = new VBox(); + grid = createGrid(width, height); + modeLabel = new Label("Random Mode: OFF"); + toggleModeBtn = new Button("Toggle Random Mode"); + + this.flagLabel = new Label("Flags: 0 / " + totalMines); + this.timerLabel = new Label("Time: 0"); + this.flagLabel.setFont(FONT); + this.timerLabel.setFont(FONT); + + toggleModeBtn.setOnAction(e -> { + randomMode = !randomMode; + modeLabel.setText("Random Mode: " + (randomMode ? "ON" : "OFF")); + }); + + + topBar = new VBox(5, flagLabel, timerLabel); + + topBar.setAlignment(Pos.CENTER); + box.getChildren().add(0, topBar); box.getChildren().add(grid); box.setAlignment(Pos.CENTER); + grid.setAlignment(Pos.CENTER); final Scene scene; scene = new Scene(box, WINDOW_WIDTH, WINDOW_HEIGHT); + root.getChildren().addAll(title, smallBtn, largeBtn, modeLabel, toggleModeBtn); gameStage.setScene(scene); gameStage.setTitle("Random Mines " + width + "x" + height); gameStage.show(); + startTimer(); } private void flag(final int index) @@ -253,7 +310,49 @@ public class Mines extends Application this.flags = this.flagged[index] == FLAG ? this.flags + FLAG : - this.flags - FLAG; + this.flagged[index] == FLAG_QUESTION ? + this.flags - FLAG : + this.flags; + + this.flagLabel.setText("Flags: " + flags + " / " + totalMines); + } + + private void checkWin() + { + int numRevealed; + numRevealed = NO_MINE; + + for (int i = 0; i < this.field.length; i++) + { + if (this.revealed[i]) + { + numRevealed++; + } + else if (this.field[i] != MINE) + { + // if a non revealed cell is not a mine, we know its not a win + return; + } + else + { + // do nothing + continue; + } + } + + if (numRevealed == (this.field.length - totalMines)) + { + // WIN CONDITION + stopTimer(); + + final Alert win; + win = new Alert(Alert.AlertType.INFORMATION); + win.setHeaderText("You Win!"); + win.setContentText("You successfully cleared all safe squares!"); + win.showAndWait(); + + ((Stage) buttons[0].getScene().getWindow()).close(); + } } private GridPane createGrid( @@ -285,7 +384,26 @@ public class Mines extends Application button.setOnMouseClicked(e -> { if (e.getButton() == MouseButton.PRIMARY) { - reveal(index); + final boolean lossed; + lossed = reveal(index); + + if (lossed) + { + final Alert loss; + loss = new Alert(Alert.AlertType.ERROR); + loss.setHeaderText("You lost..."); + loss.setContentText("Unfortunately, you dug up a mine and lost your legs. I hear there is a deal on wheelchairs though."); + loss.showAndWait(); + + final Stage gameStage; + gameStage = (Stage) buttons[index].getScene().getWindow(); + gameStage.close(); + return; + } + + checkWin(); + + randomizeRemaining(); } else if (e.getButton() == MouseButton.SECONDARY) { @@ -306,11 +424,85 @@ public class Mines extends Application return grid; } - private void reveal(final int index) + private void randomizeRemaining() + { + int regenMine; + + regenMine = NO_MINE; + + for (int i = 0; i < this.field.length; i++) + { + // Ignore correctly flagged mines + if (this.field[i] == MINE && this.flagged[i] == FLAG) + { + continue; + } + + if (this.field[i] == MINE) + { + regenMine++; + } + + this.field[i] = NO_MINE; + } + + final Random rand; + int placed; + + rand = new Random(); + placed = NO_MINE; + + while (placed < regenMine) + { + final int index; + index = rand.nextInt(width * height); + + if (this.field[index] == NO_MINE && !this.revealed[index]) + { + this.field[index] = MINE; + placed++; + } + } + + for (int i = 0; i < this.field.length; i++) + { + if (this.field[i] == MINE) + { + continue; + } + + final int[] count = { 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++) + { + if (this.revealed[i]) + { + this.buttons[i].setText(this.field[i] == NO_MINE ? "" : "" + this.field[i]); + this.buttons[i].setStyle(BUTTON_THEMES.get(this.field[i])); + } + + if (this.revealed[i] && this.field[i] == NO_MINE) + { + popFieldVoid(i); + } + } + } + + private boolean reveal(final int index) { if (this.revealed[index]) { - return; + return false; } final String buttonText; @@ -333,6 +525,8 @@ public class Mines extends Application { popFieldVoid(index); } + + return this.field[index] == MINE; } public static void main(final String[] args) diff --git a/src/code/ca/bcit/comp2522/project/NumberGame.java b/src/code/ca/bcit/comp2522/project/NumberGame.java index 70d66fb..a9e4f5f 100644 --- a/src/code/ca/bcit/comp2522/project/NumberGame.java +++ b/src/code/ca/bcit/comp2522/project/NumberGame.java @@ -12,9 +12,8 @@ import javafx.scene.control.Label; import javafx.scene.layout.VBox; import javafx.scene.control.Button; -import java.util.Random; - -public class NumberGame extends Application +public class NumberGame + extends Application { private static final int STARTING_NUMBERS_PLACED = 0; private static final int INDEX_OFFSET = 1; @@ -35,13 +34,15 @@ public class NumberGame extends Application private Label numberLabel; private int currentNumber; private int[] positions; + private RandomNumberGenerator generator; @Override public void start(final Stage stage) { + this.generator = new RandomNumberGenerator(MIN_RAND_NUM, MAX_RAND_NUM); this.numbersPlaced = STARTING_NUMBERS_PLACED; - this.currentNumber = generateRandomNumber(); - 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.setFont(FONT); @@ -49,14 +50,14 @@ public class NumberGame extends Application this.numberLabel.setAlignment(Pos.CENTER); final VBox root; - root = new VBox(); - root.getChildren().add(this.numberLabel); - final GridPane grid; + final Scene scene; + + root = new VBox(); grid = createGrid(); + root.getChildren().add(this.numberLabel); root.getChildren().add(grid); - final Scene scene; scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT); stage.setScene(scene); @@ -95,13 +96,6 @@ public class NumberGame extends Application return grid; } - private int generateRandomNumber() - { - final Random rand; - rand = new Random(); - return rand.nextInt(MAX_RAND_NUM - MIN_RAND_NUM) + MIN_RAND_NUM; - } - private void triggerFailed() { this.numberLabel.setText("Next number: " + this.currentNumber + " - Impossible to place next number"); @@ -181,7 +175,7 @@ public class NumberGame extends Application return; } - this.currentNumber = generateRandomNumber(); + this.currentNumber = this.generator.generate(); final boolean canPlaceNext; canPlaceNext = canBePlaced(); diff --git a/src/code/ca/bcit/comp2522/project/NumberGenerator.java b/src/code/ca/bcit/comp2522/project/NumberGenerator.java new file mode 100644 index 0000000..4f1a903 --- /dev/null +++ b/src/code/ca/bcit/comp2522/project/NumberGenerator.java @@ -0,0 +1,6 @@ +package ca.bcit.comp2522.project; + +public interface NumberGenerator +{ + int generate(); +} diff --git a/src/code/ca/bcit/comp2522/project/PlacementRule.java b/src/code/ca/bcit/comp2522/project/PlacementRule.java new file mode 100644 index 0000000..272a086 --- /dev/null +++ b/src/code/ca/bcit/comp2522/project/PlacementRule.java @@ -0,0 +1,15 @@ +package ca.bcit.comp2522.project; + +public abstract class PlacementRule +{ + public abstract boolean isValidPlacement( + final int[] positions, + final int index, + final int value + ); + + public abstract boolean canPlaceNext( + final int[] positions, + final int nextValue + ); +} diff --git a/src/code/ca/bcit/comp2522/project/RandomNumberGenerator.java b/src/code/ca/bcit/comp2522/project/RandomNumberGenerator.java new file mode 100644 index 0000000..7750288 --- /dev/null +++ b/src/code/ca/bcit/comp2522/project/RandomNumberGenerator.java @@ -0,0 +1,25 @@ +package ca.bcit.comp2522.project; + +import java.util.Random; + +public class RandomNumberGenerator + implements NumberGenerator +{ + private final Random random; + private final int min; + private final int max; + + public RandomNumberGenerator(int min, int max) + { + random = new Random(); + + this.min = min; + this.max = max; + } + + @Override + public int generate() + { + return random.nextInt(max - min) + min; + } +}