Files
minesweeper-comp2522/src/code/ca/bcit/comp2522/project/Mines.java
T
2025-11-23 15:43:49 -08:00

343 lines
10 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ca.bcit.comp2522.project;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
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.function.Consumer;
public class Mines extends Application
{
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 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;
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 NO_MINE = 0;
private static final int MIN_OFFSET = -1;
private static final int MAX_OFFSET = 1;
private static final int SELF_OFFSET = 0;
private static final int FIRST_ROW = 0;
private static final int FIRST_COL = 0;
private static final int NO_FLAG = 0;
private static final int FLAG = 1;
private static final int FLAG_QUESTION = 2;
private static final int DEFAULT_BUTTON = -2;
private static final Map<Integer, String> BUTTON_THEMES = new HashMap<>();
static {
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 int[] field;
private boolean[] revealed;
private int[] flagged;
private Button[] buttons;
private int width;
private int height;
private int flags;
@Override
public void start(final Stage stage)
{
final Label title;
final Button smallBtn;
final Button largeBtn;
title = new Label("Select Game Size");
title.setStyle("-fx-font-size: 20px; -fx-font-weight: bold;");
smallBtn = new Button("8 × 8 (Beginner)");
largeBtn = new Button("36 × 16 (Expert)");
smallBtn.setPrefWidth(MENU_BUTTON_WIDTH);
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;
final Scene scene;
root = new VBox(VERTICAL_MARGIN);
scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
root.setPadding(new Insets(PADDING));
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(title, smallBtn, largeBtn);
stage.setTitle("Random Mines - A Minesweeper Game");
stage.setResizable(false);
stage.setScene(scene);
stage.show();
}
private void forEachNeighbor(
final int index,
final Consumer<Integer> action
) {
final int row;
final int col;
row = index / width;
col = index % width;
for (int dr = MIN_OFFSET; dr <= MAX_OFFSET; dr++)
{
for (int dc = MIN_OFFSET; dc <= MAX_OFFSET; dc++)
{
if (dr == SELF_OFFSET && dc == SELF_OFFSET)
{
continue;
}
final int nr;
final int nc;
nr = row + dr;
nc = col + dc;
if (nr < FIRST_ROW || nr >= height ||
nc < FIRST_COL || nc >= width)
{
continue;
}
final int neighborIndex;
neighborIndex = nr * width + nc;
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)
{
forEachNeighbor(index, neighborIndex -> {
if (!this.revealed[neighborIndex])
{
reveal(neighborIndex);
if (this.field[neighborIndex] == NO_MINE)
{
popFieldVoid(neighborIndex);
}
}
});
}
private void startGame(
final int width,
final int height,
final int mines
) {
this.flags = NO_FLAG;
this.buttons = new Button[width * height];
this.width = width;
this.height = height;
generateField(mines);
final Stage gameStage;
final VBox box;
final GridPane grid;
gameStage = new Stage();
box = new VBox();
grid = createGrid(width, height);
box.getChildren().add(grid);
box.setAlignment(Pos.CENTER);
final Scene scene;
scene = new Scene(box, WINDOW_WIDTH, WINDOW_HEIGHT);
gameStage.setScene(scene);
gameStage.setTitle("Random Mines " + width + "x" + height);
gameStage.show();
}
private void flag(final int index)
{
this.flagged[index] = (this.flagged[index] + FLAG) % (FLAG_QUESTION + 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.flags - FLAG;
}
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)
{
reveal(index);
}
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 reveal(final int index)
{
if (this.revealed[index])
{
return;
}
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;
if (this.field[index] == NO_MINE)
{
popFieldVoid(index);
}
}
public static void main(final String[] args)
{
launch(args);
}
}