impliment something from each lesson
This commit is contained in:
7 files changed
+900
-430
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,25 @@
|
|||||||
|
package ca.bcit.comp2522.project;
|
||||||
|
|
||||||
|
public abstract class GameBoard
|
||||||
|
{
|
||||||
|
protected int width;
|
||||||
|
protected int height;
|
||||||
|
|
||||||
|
protected GameBoard(final int width, final int height)
|
||||||
|
{
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getWidth()
|
||||||
|
{
|
||||||
|
return this.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getHeight()
|
||||||
|
{
|
||||||
|
return this.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void reset();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ca.bcit.comp2522.project;
|
||||||
|
|
||||||
|
public class InvalidMoveException extends Exception
|
||||||
|
{
|
||||||
|
public InvalidMoveException(final String message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,45 +1,17 @@
|
|||||||
package ca.bcit.comp2522.project;
|
package ca.bcit.comp2522.project;
|
||||||
|
|
||||||
import javafx.animation.Timeline;
|
import java.io.BufferedReader;
|
||||||
import javafx.application.Application;
|
import java.io.BufferedWriter;
|
||||||
import javafx.geometry.Insets;
|
import java.io.File;
|
||||||
import javafx.geometry.Pos;
|
import java.io.FileReader;
|
||||||
import javafx.scene.Scene;
|
import java.io.FileWriter;
|
||||||
import javafx.scene.control.Alert;
|
import java.io.IOException;
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.layout.GridPane;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.scene.text.Font;
|
|
||||||
import javafx.scene.text.FontWeight;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.Scanner;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class Mines extends Application
|
public class Mines extends GameBoard
|
||||||
{
|
{
|
||||||
private static final int FONT_SIZE = 18;
|
|
||||||
private static final Font FONT = Font.font("Arial", FontWeight.BOLD, FONT_SIZE);
|
|
||||||
|
|
||||||
private static final int WINDOW_WIDTH = 1920;
|
|
||||||
private static final int WINDOW_HEIGHT = 1080;
|
|
||||||
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 EASY_WIDTH = 8;
|
|
||||||
private static final int EASY_HEIGHT = 8;
|
|
||||||
private static final int HARD_WIDTH = 36;
|
|
||||||
private static final int HARD_HEIGHT = 16;
|
|
||||||
private static final int PADDING = 20;
|
|
||||||
private static final int GAME_PADDING = 5;
|
|
||||||
private static final int VERTICAL_MARGIN = 15;
|
|
||||||
|
|
||||||
private static final int EASY_MINES = 10;
|
|
||||||
private static final int HARD_MINES = 99;
|
|
||||||
private static final int MINE = -1;
|
private static final int MINE = -1;
|
||||||
private static final int NO_MINE = 0;
|
private static final int NO_MINE = 0;
|
||||||
|
|
||||||
@@ -49,98 +21,120 @@ public class Mines extends Application
|
|||||||
private static final int FIRST_ROW = 0;
|
private static final int FIRST_ROW = 0;
|
||||||
private static final int FIRST_COL = 0;
|
private static final int FIRST_COL = 0;
|
||||||
|
|
||||||
private static final int NO_FLAG = 0;
|
public static final int NO_FLAG = 0;
|
||||||
private static final int FLAG = 1;
|
public static final int FLAG = 1;
|
||||||
private static final int FLAG_QUESTION = 2;
|
public static final int FLAG_QUESTION = 2;
|
||||||
|
|
||||||
private static final int DEFAULT_BUTTON = -2;
|
private static final int INITIAL_BEST_SCORE = Integer.MAX_VALUE;
|
||||||
private static final Map<Integer, String> BUTTON_THEMES = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
private static final String SCORE_FILE_NAME = "scores.txt";
|
||||||
BUTTON_THEMES.put(-2, "-fx-background-color: #b3b3b3; -fx-text-fill: black;");
|
|
||||||
BUTTON_THEMES.put(-1, "-fx-background-color: #d9d9d9; -fx-text-fill: black;");
|
private static final Random RANDOM_GENERATOR;
|
||||||
BUTTON_THEMES.put(0, "-fx-background-color: #d9d9d9; -fx-text-fill: black;");
|
|
||||||
BUTTON_THEMES.put(1, "-fx-background-color: #c8ffbf; -fx-text-fill: black;");
|
static
|
||||||
BUTTON_THEMES.put(2, "-fx-background-color: #edfc9f; -fx-text-fill: black;");
|
{
|
||||||
BUTTON_THEMES.put(3, "-fx-background-color: #fad08c; -fx-text-fill: black;");
|
RANDOM_GENERATOR = new Random();
|
||||||
BUTTON_THEMES.put(4, "-fx-background-color: #f59338; -fx-text-fill: black;");
|
|
||||||
BUTTON_THEMES.put(5, "-fx-background-color: #ff644d; -fx-text-fill: black;");
|
|
||||||
BUTTON_THEMES.put(6, "-fx-background-color: #ff644d; -fx-text-fill: black;");
|
|
||||||
BUTTON_THEMES.put(7, "-fx-background-color: #ff644d; -fx-text-fill: black;");
|
|
||||||
BUTTON_THEMES.put(8, "-fx-background-color: #ff644d; -fx-text-fill: black;");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final int totalMines;
|
||||||
|
private boolean randomMode;
|
||||||
|
|
||||||
private int[] field;
|
private int[] field;
|
||||||
private boolean[] revealed;
|
private boolean[] revealed;
|
||||||
private int[] flagged;
|
private int[] flagged;
|
||||||
private Button[] buttons;
|
|
||||||
private int width;
|
|
||||||
private int height;
|
|
||||||
private int flags;
|
|
||||||
|
|
||||||
private Label flagLabel;
|
private int bestScoreSeconds;
|
||||||
private Label timerLabel;
|
|
||||||
private int totalMines;
|
|
||||||
|
|
||||||
private int seconds;
|
public Mines(
|
||||||
private Timeline timer;
|
final int width,
|
||||||
private boolean timerRunning = false;
|
final int height,
|
||||||
private boolean randomMode = false;
|
final int mines,
|
||||||
|
final boolean randomMode
|
||||||
private void startTimer() {
|
) {
|
||||||
seconds = 0;
|
super(width, height);
|
||||||
timerLabel.setText("Time: 0");
|
this.totalMines = mines;
|
||||||
|
this.randomMode = randomMode;
|
||||||
timer = new javafx.animation.Timeline(
|
this.bestScoreSeconds = INITIAL_BEST_SCORE;
|
||||||
new javafx.animation.KeyFrame(javafx.util.Duration.seconds(1), e -> {
|
reset();
|
||||||
seconds++;
|
loadBestScore();
|
||||||
timerLabel.setText("Time: " + seconds);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
timer.setCycleCount(javafx.animation.Animation.INDEFINITE);
|
|
||||||
timer.play();
|
|
||||||
timerRunning = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopTimer() {
|
public void startNewGame()
|
||||||
if (timer != null) {
|
{
|
||||||
timer.stop();
|
reset();
|
||||||
}
|
}
|
||||||
timerRunning = false;
|
|
||||||
|
public void startNewGame(final boolean randomMode)
|
||||||
|
{
|
||||||
|
this.randomMode = randomMode;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRandomMode(final boolean randomMode)
|
||||||
|
{
|
||||||
|
this.randomMode = randomMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRandomMode()
|
||||||
|
{
|
||||||
|
return this.randomMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(final Stage stage)
|
public void reset()
|
||||||
{
|
{
|
||||||
final Label title;
|
final int totalCells;
|
||||||
final Button smallBtn;
|
|
||||||
final Button largeBtn;
|
|
||||||
|
|
||||||
title = new Label("Select Game Size");
|
totalCells = this.width * this.height;
|
||||||
title.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;");
|
|
||||||
|
|
||||||
smallBtn = new Button("8 × 8 (Beginner)");
|
this.field = new int[totalCells];
|
||||||
largeBtn = new Button("36 × 16 (Expert)");
|
this.revealed = new boolean[totalCells];
|
||||||
|
this.flagged = new int[totalCells];
|
||||||
|
|
||||||
smallBtn.setPrefWidth(MENU_BUTTON_WIDTH);
|
generateField();
|
||||||
largeBtn.setPrefWidth(MENU_BUTTON_WIDTH);
|
}
|
||||||
smallBtn.setOnAction(e -> startGame(EASY_WIDTH, EASY_HEIGHT, EASY_MINES));
|
|
||||||
largeBtn.setOnAction(e -> startGame(HARD_WIDTH, HARD_HEIGHT, HARD_MINES));
|
|
||||||
|
|
||||||
final VBox root;
|
private void generateField()
|
||||||
final Scene scene;
|
{
|
||||||
|
int placedMines;
|
||||||
|
final int totalCells;
|
||||||
|
|
||||||
root = new VBox(VERTICAL_MARGIN);
|
placedMines = NO_MINE;
|
||||||
scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
|
totalCells = this.width * this.height;
|
||||||
|
|
||||||
root.setPadding(new Insets(PADDING));
|
while (placedMines < totalMines)
|
||||||
root.setAlignment(Pos.CENTER);
|
{
|
||||||
root.getChildren().addAll(title, smallBtn, largeBtn);
|
final int index;
|
||||||
|
|
||||||
stage.setTitle("Random Mines - A Minesweeper Game");
|
index = RANDOM_GENERATOR.nextInt(totalCells);
|
||||||
stage.setResizable(false);
|
|
||||||
stage.setScene(scene);
|
if (this.field[index] != MINE)
|
||||||
stage.show();
|
{
|
||||||
|
this.field[index] = MINE;
|
||||||
|
placedMines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < this.field.length; i++)
|
||||||
|
{
|
||||||
|
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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forEachNeighbor(
|
private void forEachNeighbor(
|
||||||
@@ -150,8 +144,8 @@ public class Mines extends Application
|
|||||||
final int row;
|
final int row;
|
||||||
final int col;
|
final int col;
|
||||||
|
|
||||||
row = index / width;
|
row = index / this.width;
|
||||||
col = index % width;
|
col = index % this.width;
|
||||||
|
|
||||||
for (int dr = MIN_OFFSET; dr <= MAX_OFFSET; dr++)
|
for (int dr = MIN_OFFSET; dr <= MAX_OFFSET; dr++)
|
||||||
{
|
{
|
||||||
@@ -168,70 +162,34 @@ public class Mines extends Application
|
|||||||
nr = row + dr;
|
nr = row + dr;
|
||||||
nc = col + dc;
|
nc = col + dc;
|
||||||
|
|
||||||
if (nr < FIRST_ROW || nr >= height ||
|
if (nr < FIRST_ROW || nr >= this.height ||
|
||||||
nc < FIRST_COL || nc >= width)
|
nc < FIRST_COL || nc >= this.width)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int neighborIndex;
|
final int neighborIndex;
|
||||||
neighborIndex = nr * width + nc;
|
|
||||||
|
neighborIndex = nr * this.width + nc;
|
||||||
action.accept(neighborIndex);
|
action.accept(neighborIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateField(final int mines)
|
|
||||||
{
|
|
||||||
this.revealed = new boolean[width * height];
|
|
||||||
this.field = new int[width * height];
|
|
||||||
this.flagged = new int[width * height];
|
|
||||||
|
|
||||||
final Random rand;
|
|
||||||
int placed;
|
|
||||||
|
|
||||||
rand = new Random();
|
|
||||||
placed = NO_MINE;
|
|
||||||
|
|
||||||
while (placed < mines)
|
|
||||||
{
|
|
||||||
final int index;
|
|
||||||
index = rand.nextInt(width * height);
|
|
||||||
|
|
||||||
if (this.field[index] != MINE)
|
|
||||||
{
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void popFieldVoid(final int index)
|
private void popFieldVoid(final int index)
|
||||||
{
|
{
|
||||||
forEachNeighbor(index, neighborIndex -> {
|
forEachNeighbor(index, neighborIndex -> {
|
||||||
|
|
||||||
if (!this.revealed[neighborIndex])
|
if (!this.revealed[neighborIndex])
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
reveal(neighborIndex);
|
reveal(neighborIndex);
|
||||||
|
}
|
||||||
|
catch (final InvalidMoveException e)
|
||||||
|
{
|
||||||
|
// revealing neighbors should never be invalid
|
||||||
|
}
|
||||||
|
|
||||||
if (this.field[neighborIndex] == NO_MINE)
|
if (this.field[neighborIndex] == NO_MINE)
|
||||||
{
|
{
|
||||||
@@ -241,283 +199,13 @@ public class Mines extends Application
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startGame(
|
public boolean reveal(final int index)
|
||||||
final int width,
|
throws InvalidMoveException
|
||||||
final int height,
|
|
||||||
final int mines
|
|
||||||
) {
|
|
||||||
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);
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
this.flagged[index] = (this.flagged[index] + FLAG) % (FLAG_QUESTION + FLAG);
|
if (this.flagged[index] == FLAG)
|
||||||
|
|
||||||
final String buttonText;
|
|
||||||
|
|
||||||
buttonText = this.flagged[index] == FLAG ? "F" :
|
|
||||||
this.flagged[index] == FLAG_QUESTION ? "?" : "";
|
|
||||||
|
|
||||||
this.buttons[index].setText(buttonText);
|
|
||||||
|
|
||||||
this.flags = this.flagged[index] == 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;
|
throw new InvalidMoveException("Cannot reveal a flagged cell.");
|
||||||
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(
|
|
||||||
final int width,
|
|
||||||
final int height
|
|
||||||
) {
|
|
||||||
final GridPane grid;
|
|
||||||
grid = new GridPane();
|
|
||||||
|
|
||||||
grid.setPadding(new Insets(GAME_PADDING));
|
|
||||||
grid.setHgap(GAME_PADDING);
|
|
||||||
grid.setVgap(GAME_PADDING);
|
|
||||||
|
|
||||||
for (int i = 0; i < width; i++)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < height; j++)
|
|
||||||
{
|
|
||||||
final Button button;
|
|
||||||
final int index;
|
|
||||||
|
|
||||||
index = (j * width) + i;
|
|
||||||
button = new Button();
|
|
||||||
|
|
||||||
button.setFont(FONT);
|
|
||||||
button.setPrefSize(BUTTON_WIDTH, BUTTON_HEIGHT);
|
|
||||||
button.setStyle(BUTTON_THEMES.get(DEFAULT_BUTTON));
|
|
||||||
button.setOnMouseEntered(e -> button.setCursor(javafx.scene.Cursor.HAND));
|
|
||||||
button.setOnMouseExited(e -> button.setCursor(javafx.scene.Cursor.DEFAULT));
|
|
||||||
button.setOnMouseClicked(e -> {
|
|
||||||
if (e.getButton() == MouseButton.PRIMARY)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
flag(index);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// do nothing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
grid.add(button, i, j);
|
|
||||||
this.buttons[index] = button;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String buttonText;
|
|
||||||
final Button button;
|
|
||||||
|
|
||||||
button = this.buttons[index];
|
|
||||||
|
|
||||||
buttonText = "" + (this.field[index] == MINE ? "*" :
|
|
||||||
this.field[index] == NO_MINE ? " " :
|
|
||||||
this.field[index]);
|
|
||||||
|
|
||||||
button.setText(buttonText);
|
|
||||||
button.setStyle(BUTTON_THEMES.get(this.field[index]));
|
|
||||||
button.setMouseTransparent(true);
|
|
||||||
button.setFocusTraversable(false);
|
|
||||||
|
|
||||||
this.revealed[index] = true;
|
this.revealed[index] = true;
|
||||||
|
|
||||||
@@ -529,8 +217,199 @@ public class Mines extends Application
|
|||||||
return this.field[index] == MINE;
|
return this.field[index] == MINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(final String[] args)
|
public int toggleFlag(final int index)
|
||||||
{
|
{
|
||||||
launch(args);
|
final int nextState;
|
||||||
|
|
||||||
|
nextState = (this.flagged[index] + FLAG) % (FLAG_QUESTION + FLAG);
|
||||||
|
this.flagged[index] = nextState;
|
||||||
|
return nextState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFieldValue(final int index)
|
||||||
|
{
|
||||||
|
return this.field[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRevealed(final int index)
|
||||||
|
{
|
||||||
|
return this.revealed[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMine(final int index)
|
||||||
|
{
|
||||||
|
return this.field[index] == MINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFlagged(final int index)
|
||||||
|
{
|
||||||
|
return this.flagged[index] == FLAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isQuestionMarked(final int index)
|
||||||
|
{
|
||||||
|
return this.flagged[index] == FLAG_QUESTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalMines()
|
||||||
|
{
|
||||||
|
return this.totalMines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasWon()
|
||||||
|
{
|
||||||
|
int revealedCount;
|
||||||
|
|
||||||
|
revealedCount = NO_MINE;
|
||||||
|
|
||||||
|
for (int i = 0; i < this.field.length; i++)
|
||||||
|
{
|
||||||
|
if (this.revealed[i])
|
||||||
|
{
|
||||||
|
revealedCount++;
|
||||||
|
}
|
||||||
|
else if (this.field[i] != MINE)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return revealedCount == (this.field.length - this.totalMines);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void randomizeRemaining()
|
||||||
|
{
|
||||||
|
if (!this.randomMode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minesToReplant;
|
||||||
|
|
||||||
|
minesToReplant = NO_MINE;
|
||||||
|
|
||||||
|
for (int i = 0; i < this.field.length; i++)
|
||||||
|
{
|
||||||
|
if (this.field[i] == MINE && this.flagged[i] == FLAG)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.field[i] == MINE)
|
||||||
|
{
|
||||||
|
minesToReplant++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.field[i] = NO_MINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (minesToReplant > NO_MINE)
|
||||||
|
{
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
index = RANDOM_GENERATOR.nextInt(this.width * this.height);
|
||||||
|
|
||||||
|
if (this.field[index] == NO_MINE && !this.revealed[index])
|
||||||
|
{
|
||||||
|
this.field[index] = MINE;
|
||||||
|
minesToReplant--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < this.field.length; i++)
|
||||||
|
{
|
||||||
|
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++)
|
||||||
|
{
|
||||||
|
if (this.revealed[i] && this.field[i] == NO_MINE)
|
||||||
|
{
|
||||||
|
popFieldVoid(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveScore(final int seconds)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
final BufferedWriter writer;
|
||||||
|
|
||||||
|
writer = new BufferedWriter(new FileWriter(SCORE_FILE_NAME));
|
||||||
|
|
||||||
|
writer.write(Integer.toString(seconds));
|
||||||
|
writer.newLine();
|
||||||
|
}
|
||||||
|
catch (final IOException e)
|
||||||
|
{
|
||||||
|
// ignore failure
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds < this.bestScoreSeconds)
|
||||||
|
{
|
||||||
|
this.bestScoreSeconds = seconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadBestScore()
|
||||||
|
{
|
||||||
|
final File scoreFile;
|
||||||
|
|
||||||
|
scoreFile = new File(SCORE_FILE_NAME);
|
||||||
|
|
||||||
|
if (!scoreFile.exists())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int best;
|
||||||
|
best = INITIAL_BEST_SCORE;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
final Scanner scanner;
|
||||||
|
scanner = new Scanner(new BufferedReader(new FileReader(scoreFile)));
|
||||||
|
|
||||||
|
while (scanner.hasNextInt())
|
||||||
|
{
|
||||||
|
final int candidate;
|
||||||
|
|
||||||
|
candidate = scanner.nextInt();
|
||||||
|
|
||||||
|
if (candidate < best)
|
||||||
|
{
|
||||||
|
best = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final IOException e)
|
||||||
|
{
|
||||||
|
// ignore failure
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bestScoreSeconds = best;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBestScoreSeconds()
|
||||||
|
{
|
||||||
|
return this.bestScoreSeconds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,471 @@
|
|||||||
|
package ca.bcit.comp2522.project;
|
||||||
|
|
||||||
|
import javafx.animation.Animation;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
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;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
|
import javafx.scene.text.FontWeight;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MinesUI
|
||||||
|
{
|
||||||
|
private static final int FONT_SIZE = 18;
|
||||||
|
private static final Font FONT = Font.font("Arial", FontWeight.BOLD, FONT_SIZE);
|
||||||
|
|
||||||
|
private static final int WINDOW_WIDTH = 900;
|
||||||
|
private static final int WINDOW_HEIGHT = 700;
|
||||||
|
private static final int BUTTON_WIDTH = 40;
|
||||||
|
private static final int BUTTON_HEIGHT = 40;
|
||||||
|
private static final int MENU_BUTTON_WIDTH = 200;
|
||||||
|
private static final int MENU_BUTTON_HEIGHT = 60;
|
||||||
|
|
||||||
|
private static final int PADDING = 20;
|
||||||
|
private static final int GAME_PADDING = 5;
|
||||||
|
private static final int VERTICAL_MARGIN = 15;
|
||||||
|
private static final int TOPBAR_SPACING = 5;
|
||||||
|
|
||||||
|
private static final int EASY_WIDTH = 8;
|
||||||
|
private static final int EASY_HEIGHT = 8;
|
||||||
|
private static final int EASY_MINES = 10;
|
||||||
|
private static final int HARD_WIDTH = 36;
|
||||||
|
private static final int HARD_HEIGHT = 16;
|
||||||
|
private static final int HARD_MINES = 99;
|
||||||
|
|
||||||
|
private static final int STARTING_FLAGS = 0;
|
||||||
|
private static final int STARTING_SECONDS = 0;
|
||||||
|
private static final int TIMER_TICK_SECONDS = 1;
|
||||||
|
|
||||||
|
private static final int DEFAULT_BUTTON_KEY = -2;
|
||||||
|
private static final int MINE_KEY = -1;
|
||||||
|
private static final int ZERO_KEY = 0;
|
||||||
|
|
||||||
|
private static final String TITLE_TEXT = "Random Mines - A Minesweeper Game";
|
||||||
|
|
||||||
|
private static final Map<Integer, String> BUTTON_THEMES;
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
BUTTON_THEMES = new java.util.HashMap<>();
|
||||||
|
BUTTON_THEMES.put(-2, "-fx-background-color: #b3b3b3; -fx-text-fill: black;");
|
||||||
|
BUTTON_THEMES.put(-1, "-fx-background-color: #d9d9d9; -fx-text-fill: black;");
|
||||||
|
BUTTON_THEMES.put(0, "-fx-background-color: #d9d9d9; -fx-text-fill: black;");
|
||||||
|
BUTTON_THEMES.put(1, "-fx-background-color: #c8ffbf; -fx-text-fill: black;");
|
||||||
|
BUTTON_THEMES.put(2, "-fx-background-color: #edfc9f; -fx-text-fill: black;");
|
||||||
|
BUTTON_THEMES.put(3, "-fx-background-color: #fad08c; -fx-text-fill: black;");
|
||||||
|
BUTTON_THEMES.put(4, "-fx-background-color: #f59338; -fx-text-fill: black;");
|
||||||
|
BUTTON_THEMES.put(5, "-fx-background-color: #ff644d; -fx-text-fill: black;");
|
||||||
|
BUTTON_THEMES.put(6, "-fx-background-color: #ff644d; -fx-text-fill: black;");
|
||||||
|
BUTTON_THEMES.put(7, "-fx-background-color: #ff644d; -fx-text-fill: black;");
|
||||||
|
BUTTON_THEMES.put(8, "-fx-background-color: #ff644d; -fx-text-fill: black;");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Button> buttons;
|
||||||
|
|
||||||
|
private Mines game;
|
||||||
|
|
||||||
|
private Label flagLabel;
|
||||||
|
private Label timerLabel;
|
||||||
|
private Label bestLabel;
|
||||||
|
|
||||||
|
private boolean randomMode;
|
||||||
|
private int flagsPlaced;
|
||||||
|
private int seconds;
|
||||||
|
|
||||||
|
private Timeline timer;
|
||||||
|
private boolean timerRunning;
|
||||||
|
|
||||||
|
public MinesUI()
|
||||||
|
{
|
||||||
|
this.buttons = new ArrayList<>();
|
||||||
|
this.randomMode = false;
|
||||||
|
this.timerRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showMainMenu(final Stage primaryStage)
|
||||||
|
{
|
||||||
|
final Label titleLabel;
|
||||||
|
final Button easyButton;
|
||||||
|
final Button hardButton;
|
||||||
|
final Label modeLabel;
|
||||||
|
final Button toggleModeButton;
|
||||||
|
final VBox root;
|
||||||
|
final Scene scene;
|
||||||
|
|
||||||
|
titleLabel = new Label("Select Game Size");
|
||||||
|
titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;");
|
||||||
|
|
||||||
|
easyButton = new Button("8 x 8 (Beginner)");
|
||||||
|
hardButton = new Button("36 x 16 (Expert)");
|
||||||
|
|
||||||
|
easyButton.setPrefWidth(MENU_BUTTON_WIDTH);
|
||||||
|
easyButton.setPrefHeight(MENU_BUTTON_HEIGHT);
|
||||||
|
hardButton.setPrefWidth(MENU_BUTTON_WIDTH);
|
||||||
|
hardButton.setPrefHeight(MENU_BUTTON_HEIGHT);
|
||||||
|
|
||||||
|
modeLabel = new Label("Random Mode: OFF");
|
||||||
|
toggleModeButton = new Button("Toggle Random Mode");
|
||||||
|
|
||||||
|
toggleModeButton.setOnAction(e -> {
|
||||||
|
this.randomMode = !this.randomMode;
|
||||||
|
modeLabel.setText("Random Mode: " + (this.randomMode ? "ON" : "OFF"));
|
||||||
|
});
|
||||||
|
|
||||||
|
easyButton.setOnAction(e -> startGame(EASY_WIDTH, EASY_HEIGHT, EASY_MINES, primaryStage));
|
||||||
|
hardButton.setOnAction(e -> startGame(HARD_WIDTH, HARD_HEIGHT, HARD_MINES, primaryStage));
|
||||||
|
|
||||||
|
root = new VBox(VERTICAL_MARGIN);
|
||||||
|
root.setPadding(new Insets(PADDING));
|
||||||
|
root.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
root.getChildren().addAll(titleLabel, easyButton, hardButton, modeLabel, toggleModeButton);
|
||||||
|
|
||||||
|
scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||||
|
|
||||||
|
primaryStage.setTitle(TITLE_TEXT);
|
||||||
|
primaryStage.setResizable(false);
|
||||||
|
primaryStage.setScene(scene);
|
||||||
|
primaryStage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startGame(
|
||||||
|
final int width,
|
||||||
|
final int height,
|
||||||
|
final int mines,
|
||||||
|
final Stage ownerStage
|
||||||
|
) {
|
||||||
|
final Stage gameStage;
|
||||||
|
final VBox root;
|
||||||
|
final VBox topBar;
|
||||||
|
final GridPane grid;
|
||||||
|
final Scene scene;
|
||||||
|
|
||||||
|
this.flagsPlaced = STARTING_FLAGS;
|
||||||
|
this.seconds = STARTING_SECONDS;
|
||||||
|
this.timerRunning = false;
|
||||||
|
|
||||||
|
this.game = new Mines(width, height, mines, this.randomMode);
|
||||||
|
|
||||||
|
this.buttons.clear();
|
||||||
|
|
||||||
|
this.flagLabel = new Label("Flags: " + this.flagsPlaced + " / " + this.game.getTotalMines());
|
||||||
|
this.timerLabel = new Label("Time: " + this.seconds);
|
||||||
|
|
||||||
|
final int bestScore;
|
||||||
|
bestScore = this.game.getBestScoreSeconds();
|
||||||
|
|
||||||
|
if (bestScore == Integer.MAX_VALUE)
|
||||||
|
{
|
||||||
|
this.bestLabel = new Label("Best: -");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.bestLabel = new Label("Best: " + bestScore + "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.flagLabel.setFont(FONT);
|
||||||
|
this.timerLabel.setFont(FONT);
|
||||||
|
this.bestLabel.setFont(FONT);
|
||||||
|
|
||||||
|
topBar = new VBox(
|
||||||
|
TOPBAR_SPACING,
|
||||||
|
this.flagLabel,
|
||||||
|
this.timerLabel,
|
||||||
|
this.bestLabel
|
||||||
|
);
|
||||||
|
topBar.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
grid = createGrid(width, height);
|
||||||
|
|
||||||
|
root = new VBox();
|
||||||
|
root.setAlignment(Pos.CENTER);
|
||||||
|
root.setPadding(new Insets(GAME_PADDING));
|
||||||
|
root.getChildren().addAll(topBar, grid);
|
||||||
|
|
||||||
|
scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||||
|
|
||||||
|
gameStage = new Stage();
|
||||||
|
gameStage.initOwner(ownerStage);
|
||||||
|
gameStage.setTitle("Random Mines " + width + "x" + height);
|
||||||
|
gameStage.setScene(scene);
|
||||||
|
gameStage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private GridPane createGrid(final int width, final int height)
|
||||||
|
{
|
||||||
|
final GridPane grid;
|
||||||
|
|
||||||
|
grid = new GridPane();
|
||||||
|
grid.setPadding(new Insets(GAME_PADDING));
|
||||||
|
grid.setHgap(GAME_PADDING);
|
||||||
|
grid.setVgap(GAME_PADDING);
|
||||||
|
grid.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
|
for (int j = 0; j < height; j++)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < width; i++)
|
||||||
|
{
|
||||||
|
final int index;
|
||||||
|
final Button button;
|
||||||
|
|
||||||
|
index = (j * width) + i;
|
||||||
|
button = new Button();
|
||||||
|
|
||||||
|
button.setFont(FONT);
|
||||||
|
button.setPrefSize(BUTTON_WIDTH, BUTTON_HEIGHT);
|
||||||
|
button.setStyle(BUTTON_THEMES.get(DEFAULT_BUTTON_KEY));
|
||||||
|
|
||||||
|
button.setOnMouseEntered(e -> button.setCursor(javafx.scene.Cursor.HAND));
|
||||||
|
button.setOnMouseExited(e -> button.setCursor(javafx.scene.Cursor.DEFAULT));
|
||||||
|
|
||||||
|
button.setOnMouseClicked(e -> {
|
||||||
|
if (e.getButton() == MouseButton.PRIMARY)
|
||||||
|
{
|
||||||
|
handleReveal(index);
|
||||||
|
}
|
||||||
|
else if (e.getButton() == MouseButton.SECONDARY)
|
||||||
|
{
|
||||||
|
handleFlag(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.add(button, i, j);
|
||||||
|
this.buttons.add(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleReveal(final int index)
|
||||||
|
{
|
||||||
|
if (!timerRunning)
|
||||||
|
{
|
||||||
|
startTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
final boolean hitMine;
|
||||||
|
hitMine = this.game.reveal(index);
|
||||||
|
|
||||||
|
if (hitMine)
|
||||||
|
{
|
||||||
|
handleLoss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.game.isRandomMode())
|
||||||
|
{
|
||||||
|
this.game.randomizeRemaining();
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshAllButtons();
|
||||||
|
|
||||||
|
if (this.game.hasWon())
|
||||||
|
{
|
||||||
|
handleWin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (final InvalidMoveException e)
|
||||||
|
{
|
||||||
|
final Alert warn = new Alert(Alert.AlertType.WARNING);
|
||||||
|
warn.setHeaderText("Invalid move");
|
||||||
|
warn.setContentText(e.getMessage());
|
||||||
|
warn.showAndWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFlag(final int index)
|
||||||
|
{
|
||||||
|
final int newState;
|
||||||
|
|
||||||
|
newState = this.game.toggleFlag(index);
|
||||||
|
|
||||||
|
if (newState == Mines.FLAG)
|
||||||
|
{
|
||||||
|
this.flagsPlaced++;
|
||||||
|
}
|
||||||
|
else if (newState == Mines.FLAG_QUESTION)
|
||||||
|
{
|
||||||
|
this.flagsPlaced--;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.flagLabel.setText("Flags: " +
|
||||||
|
this.flagsPlaced +
|
||||||
|
" / " +
|
||||||
|
this.game.getTotalMines());
|
||||||
|
|
||||||
|
updateButtonDisplay(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateButtonDisplay(final int index)
|
||||||
|
{
|
||||||
|
final Button button;
|
||||||
|
|
||||||
|
button = this.buttons.get(index);
|
||||||
|
|
||||||
|
if (this.game.isFlagged(index))
|
||||||
|
{
|
||||||
|
button.setText("F");
|
||||||
|
button.setStyle(BUTTON_THEMES.get(DEFAULT_BUTTON_KEY));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.game.isQuestionMarked(index))
|
||||||
|
{
|
||||||
|
button.setText("?");
|
||||||
|
button.setStyle(BUTTON_THEMES.get(DEFAULT_BUTTON_KEY));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.game.isRevealed(index))
|
||||||
|
{
|
||||||
|
button.setText("");
|
||||||
|
button.setStyle(BUTTON_THEMES.get(DEFAULT_BUTTON_KEY));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
value = this.game.getFieldValue(index);
|
||||||
|
|
||||||
|
if (value == MINE_KEY)
|
||||||
|
{
|
||||||
|
button.setText("*");
|
||||||
|
button.setStyle(BUTTON_THEMES.get(MINE_KEY));
|
||||||
|
}
|
||||||
|
else if (value == ZERO_KEY)
|
||||||
|
{
|
||||||
|
button.setText(" ");
|
||||||
|
button.setStyle(BUTTON_THEMES.get(ZERO_KEY));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
button.setText(Integer.toString(value));
|
||||||
|
button.setStyle(BUTTON_THEMES.get(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshAllButtons()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < this.buttons.size(); i++)
|
||||||
|
{
|
||||||
|
updateButtonDisplay(i);
|
||||||
|
if (this.game.isRevealed(i))
|
||||||
|
{
|
||||||
|
disableButton(this.buttons.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startTimer()
|
||||||
|
{
|
||||||
|
final KeyFrame tick;
|
||||||
|
|
||||||
|
seconds = STARTING_SECONDS;
|
||||||
|
this.timerLabel.setText("Time: " + seconds);
|
||||||
|
|
||||||
|
tick = new KeyFrame(Duration.seconds(TIMER_TICK_SECONDS), e -> {
|
||||||
|
this.seconds++;
|
||||||
|
this.timerLabel.setText("Time: " + this.seconds + "s");
|
||||||
|
});
|
||||||
|
|
||||||
|
this.timer = new Timeline(tick);
|
||||||
|
this.timer.setCycleCount(Animation.INDEFINITE);
|
||||||
|
this.timer.play();
|
||||||
|
|
||||||
|
this.timerRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopTimer()
|
||||||
|
{
|
||||||
|
if (this.timer != null)
|
||||||
|
{
|
||||||
|
this.timer.stop();
|
||||||
|
}
|
||||||
|
this.timerRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleWin()
|
||||||
|
{
|
||||||
|
final Alert winAlert;
|
||||||
|
|
||||||
|
stopTimer();
|
||||||
|
this.game.saveScore(this.seconds);
|
||||||
|
this.bestLabel.setText("Best: " + this.game.getBestScoreSeconds() + "s");
|
||||||
|
|
||||||
|
disableAllButtons();
|
||||||
|
|
||||||
|
winAlert = new Alert(Alert.AlertType.INFORMATION);
|
||||||
|
winAlert.setHeaderText("You Win!");
|
||||||
|
winAlert.setContentText("You successfully cleared all safe squares!");
|
||||||
|
winAlert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLoss()
|
||||||
|
{
|
||||||
|
final Alert lossAlert;
|
||||||
|
|
||||||
|
stopTimer();
|
||||||
|
disableAllButtons();
|
||||||
|
showAllMines();
|
||||||
|
|
||||||
|
lossAlert = new Alert(Alert.AlertType.ERROR);
|
||||||
|
lossAlert.setHeaderText("You lost...");
|
||||||
|
lossAlert.setContentText("You dug up a mine. Better luck next time.");
|
||||||
|
lossAlert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showAllMines()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < this.buttons.size(); i++)
|
||||||
|
{
|
||||||
|
if (this.game.isMine(i))
|
||||||
|
{
|
||||||
|
final Button button;
|
||||||
|
button = this.buttons.get(i);
|
||||||
|
|
||||||
|
button.setText("*");
|
||||||
|
button.setStyle(BUTTON_THEMES.get(MINE_KEY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableAllButtons()
|
||||||
|
{
|
||||||
|
final Iterator<Button> iterator;
|
||||||
|
|
||||||
|
iterator = this.buttons.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext())
|
||||||
|
{
|
||||||
|
final Button button;
|
||||||
|
|
||||||
|
button = iterator.next();
|
||||||
|
disableButton(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableButton(final Button button)
|
||||||
|
{
|
||||||
|
button.setMouseTransparent(true);
|
||||||
|
button.setFocusTraversable(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ca.bcit.comp2522.project;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point for the Minesweeper game.
|
||||||
|
* Lesson 9: JavaFX GUI entry class.
|
||||||
|
*/
|
||||||
|
public class MyGame extends Application
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void start(final Stage primaryStage)
|
||||||
|
{
|
||||||
|
final MinesUI ui;
|
||||||
|
|
||||||
|
ui = new MinesUI();
|
||||||
|
ui.showMainMenu(primaryStage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(final String[] args)
|
||||||
|
{
|
||||||
|
launch(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package ca.bcit.comp2522.project.tests;
|
||||||
|
|
||||||
|
import ca.bcit.comp2522.project.Mines;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lesson 10: unit tests for MinesGame.
|
||||||
|
*/
|
||||||
|
public class MinesTest
|
||||||
|
{
|
||||||
|
private static final int TEST_WIDTH = 5;
|
||||||
|
private static final int TEST_HEIGHT = 5;
|
||||||
|
private static final int TEST_MINES = 3;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newGameHasCorrectMineCount()
|
||||||
|
{
|
||||||
|
final Mines game;
|
||||||
|
int mineCount;
|
||||||
|
|
||||||
|
game = new Mines(TEST_WIDTH, TEST_HEIGHT, TEST_MINES, false);
|
||||||
|
mineCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < TEST_WIDTH * TEST_HEIGHT; i++)
|
||||||
|
{
|
||||||
|
if (game.isMine(i))
|
||||||
|
{
|
||||||
|
mineCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(TEST_MINES, mineCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void revealNonMineDoesNotLose()
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
final Mines game;
|
||||||
|
int safeIndex;
|
||||||
|
|
||||||
|
game = new Mines(TEST_WIDTH, TEST_HEIGHT, TEST_MINES, false);
|
||||||
|
safeIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < TEST_WIDTH * TEST_HEIGHT; i++)
|
||||||
|
{
|
||||||
|
if (!game.isMine(i))
|
||||||
|
{
|
||||||
|
safeIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNotEquals(-1, safeIndex);
|
||||||
|
|
||||||
|
assertFalse(game.reveal(safeIndex));
|
||||||
|
assertTrue(game.isRevealed(safeIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user