
Minesweeper
FH Campus Wien - January 2025
This project involved leading a team of four university colleagues at the University of Applied Sciences FH Campus Wien in the development of a Minesweeper game using JavaFX. The primary objective was to create a functional and modular game while fostering a collaborative environment and a good team workflow where all team members could learn and contribute effectively, despite varying levels of technical experience. This note blog contains only the core logic of the game. A link to the GitHub repository can be found here.
Role
Lead developer
Responsibilities
Technical leadership
Project planing
Core development
Team Workflow
Code quality assurance
Team

Rami Azab

Orsolya Nemere

Sara Hikal

Zine Ayaz
1. Approach
As the sole team member with prior software development experience, I took responsibility for defining the project’s technical foundation. This involved initializing the project, configuring the development environment, and establishing coding standards to ensure quality and consistency. Leading this project was a rewarding experience, allowing me to plan, build a strong foundation, and facilitate collaborative meetings with my colleagues.
1.1 Planning
I began by creating a strong foundation for the project, which included:
Setting up Communication Tools: We utilized OneDrive for file sharing and documentation, leveraging its real-time synchronization and backup features.
Creating a GitHub Repository: I initialized a JavaFX project with a README file, license, and essential structure, enabling seamless collaborative coding.
A pre-kickoff meeting ensured that all team members successfully integrated the shared OneDrive folder, set up the IntelliJ project, and became familiar with GitHub. Following this, we divided the project into manageable tasks, fostering active participation and idea-sharing among the team.
Inspired by lectures on Teamarbeit ( Teamwork), we created a Story Map to deconstruct the game into key areas such as UI, fields, interactions, and animations. Tasks were then distributed based on each team member’s interests and preferences.

Source 1: Story Map created by our colleague Zine.
1.2 Tasks
I started the project by developing the core logic of Minesweeper in a single Java file, which served as a blueprint for the team. We then refactored this code into modular classes for better scalability and maintainability:
Refactoring into Classes: Breaking the core code into smaller classes for modularity.
UI and Design: Enhancing the game’s user interface for a more engaging experience.
Programming Logic: Implementing features such as user input handling, board generation, and cell logic.
To enhance collaboration, the group was divided into two smaller teams. Team 1 developed a class and shared their work with Team 2, who built complementary functionality. Regular updates and reviews ensured alignment with project goals.
Examples of tasks include: Task Cell, Task GenerateBoard, Task Art 1.
2. Development
Our Minesweeper game builds upon the classic concept with an exciting twist: four difficulty modes (Easy, Medium, Hard, and 50/50). The core logic was inspired by the Minesweeper wiki, with additional features developed to make the game more engaging.
2.1 Board
The board is implemented using a GridPane from JavaFX, a container ideal for displaying the grid of rows and columns. For example, the Easy mode generates an 8x8 grid with 10 bombs.
Each cell is represented by a Button, providing the flexibility to modify states dynamically. A 2D Array serves as a mask for the board, where each cell's state is represented numerically or as a boolean value.
Bombs are randomly placed within the array, ensuring no duplicates. This logic ensures the correct number of bombs are distributed across the board.
Source 2: Visualization of the board and mask array.
2.2 Field
Since our programming classes focused on object-oriented programming, we decided to implement everything within a single class, which we named Cell. Each cell represents a field on the GridPane and extends the JavaFX Button class, inheriting its properties.
Note: In most cases, this approach is not recommended, as combining UI and logic in a single class can lead to issues. It is better to separate the two, making it easier to serialize primitive variables and ensuring a more secure and maintainable backend for the game. We stuck with it because it's a single player game and we wanted to align with our programming courses.
public class Cell extends Button{
private boolean isBomb;
private boolean isFlag;
private boolean isRevealed;
private int neighborBombs;
public Cell(boolean isBomb, boolean isFlag, boolean isRevealed)
{
this.isFlag = isFlag;
this.isRevealed = isRevealed;
this.isBomb = isBomb;
this.getStyleClass().add("cell-button");
if(isBomb)
this.getStyleClass().add("bomb");
}
// Getters
public boolean isBomb() {return this.isBomb;}
public boolean isFlagged() {return this.isFlag;}
public boolean isRevealed() {return this.isRevealed;}
public int getNeighborBombs() {return this.neighborBombs;}
// Setter
public void setNeighborBombs(int neighborBombs) {this.neighborBombs = neighborBombs;}
// Toggle Flag
public void toggleFlag(){
this.isFlag = !this.isFlag;
if(this.isFlag)
{
ImageView icon = new ImageView(Objects.requireNonNull(getClass().getResource("/art/flag.png")).toExternalForm());
this.setGraphic(icon);
System.out.println("Cell toggleFlag() - Flag placed.");
}
else
{
this.setGraphic(null);
System.out.println("Cell toggleFlag() - Flag removed.");
}
}
}
Code Snippet 1: Cell class
Our class extends the Button
class, inheriting all its properties. Instead of maintaining a two-dimensional array for the game logic, each cell has its own properties. Specifically, there are three key conditions: isBomb, isFlag, and isRevealed. Additionally, a fourth property, neighborBombs, is used to calculate and display the number of nearby bombs around a selected cell.
The constructor initializes all conditions to false
. The isBomb property is later updated during board generation, where random cells are selected, and their isBomb value is set to true
. Depending on whether the cell is a bomb or not, an appropriate style class is applied.
The toggleFlag method adds interactivity to the cell by allowing the player to flag or unflag it. This method toggles the isFlag state and updates the cell's appearance accordingly. When flagged, a flag icon is displayed using an ImageView
loaded from the application's resources. Unflagging removes the graphic, restoring the cell's default appearance.
public void setStyle(int number)
{
this.getStyleClass().add("revealed");
switch(number) {
case 0:
break;
case 1:
this.getStyleClass().add("revealed-1");
break;
case 2:
this.getStyleClass().add("revealed-2");
break;
case 3:
this.getStyleClass().add("revealed-3");
break;
case 4:
this.getStyleClass().add("revealed-4");
break;
case 5,6:
this.getStyleClass().add("revealed-5-6");
break;
case 7,8:
this.getStyleClass().add("revealed-7-8");
break;
default:
throw new IllegalArgumentException("Class Cell - Invalid number style. ");
}
}
Code Snippet 2: Cell styling
The setStyle
method is responsible for updating the visual style of a cell based on a given numeric value. This method assigns CSS classes to the cell to reflect its revealed state and any additional styling based on the number of neighboring bombs.
Initially, the method applies the "revealed" CSS class to the cell to indicate it has been uncovered. Depending on the number
parameter, additional style classes are added to represent specific cases. For example, "revealed-1", "revealed-2", and "revealed-3" are applied for numbers 1, 2, and 3, respectively, indicating distinct visual cues for each number. These classes can be found in the style.css file.
2.3 Generating Board
The GenerateBoard class is responsible for creating and initializing the game board for a Minesweeper-style game. The main method, generateBoard
, takes three parameters: rows, cols, and bombCount. It first calculates the total number of cells and ensures the number of bombs does not exceed this limit, throwing an exception if it does. A 2D array of Cell objects is then created and initialized, with each cell starting as an empty cell (no bomb, not flagged, and not revealed).
public class GenerateBoard {
public static Cell [][] generateBoard(int rows, int cols, int bombCount) {
// Total cell count
int totalCells = rows * cols;
// Error Exception - Too many bombs than cells
if (bombCount > totalCells) {
throw new IllegalArgumentException("Too many bombs! There are only " + totalCells + " Cells.");
}
// Create a board
Cell[][] board = new Cell [rows][cols];
// Initialize the board as empty cells
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
board [y][x] = new Cell(false, false,false);
}
}
// Randomly place bombs and calculate neighbor numbers
placeBombs(rows, cols, board, bombCount);
calculateNeighbors(board, rows, cols);
return board;
}
private static void placeBombs(int rows, int cols, Cell[][] board, int bombCount) {
// Starting Argument
Random rand = new Random();
int placedBombs = 0;
// Go through the amount of bombs needed to be placed and randomly place them
while (placedBombs < bombCount) {
int x = rand.nextInt(cols);
int y = rand.nextInt(rows);
// If the chosen cell is not a bomb, place a bomb else generate again
if (!board[y][x].isBomb()) {
board[y][x] = new Cell (true, false, false);
placedBombs++;
}
}
}
// Go through the whole board and calculate the neighbors numbers
private static void calculateNeighbors(Cell[][] board, int rows, int cols) {
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
if (!board[y][x].isBomb()) {
int neighborBombs = countNeighborBombs(board, x, y, rows, cols);
board[y][x].setNeighborBombs(neighborBombs); // Store the count in a field
}
}
}
}
// Calculate each individual cells number
private static int countNeighborBombs(Cell[][] board, int x, int y, int rows, int cols) {
int count = 0;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
int neighborX = x + i;
int neighborY = y + j;
if (neighborX >= 0 && neighborX < cols && neighborY >= 0 && neighborY < rows) {
if (board [neighborY][neighborX].isBomb()) {
count++;
}
}
}
}
return count;
}
}
Code Snippet 3: Generating Board Class
Bombs are placed randomly on the board using the private method placeBombs
. A Random
object generates random coordinates until the specified number of bombs are placed. If a randomly chosen cell already contains a bomb, the method skips it and generates new coordinates, ensuring no duplicate placements.
After placing bombs, the calculateNeighbors
method iterates through every cell on the board to determine the number of bombs in its neighboring cells. For cells that are not bombs, it uses the helper method countNeighborBombs
to count the bombs in adjacent cells. The neighbor count is stored in the cell’s neighborBombs property using the cell class setter. This ensures that the game logic for displaying numbers on non-bomb cells is correctly initialized.
The countNeighborBombs
method checks all eight possible neighbors of a cell using nested loops. It ensures the coordinates are within bounds before checking if the neighboring cell contains a bomb. This modular design keeps the logic for counting bombs separate, making it easier to debug or modify.
Source 3: Multiple boards being generated (black squares are bombs)
2.4 Board Class
The Board class is responsible for managing the game board, including its dimensions, difficulty settings, and interactions. It uses a GridPane
from JavaFX to visually represent the grid of cells. The constructor initializes the GridPane
and applies a CSS class for styling.
public class Board {
private int rows, cols, bombs;
private String difficulty;
private Cell[][] gameBoard;
private final GridPane gridPane;
public Board() {
this.gridPane = new GridPane();
this.gridPane.getStyleClass().add("grid-pane"); // Add CSS class for the GridPane
}
public void setDifficulty(String difficulty) {
setDifficulty(difficulty, 0, 0);
}
public void setDifficulty(String difficulty, int customRows, int customCols) {
this.difficulty = difficulty;
switch (difficulty) {
case "Easy":
this.rows = 8;
this.cols = 8;
this.bombs = 10;
break;
case "Medium":
this.rows = 16;
this.cols = 16;
this.bombs = 40;
break;
case "Difficult":
this.rows = 16;
this.cols = 32;
this.bombs = 99;
break;
case "50/50":
this.rows = 1;
this.cols = 2;
this.bombs = 1;
break;
case "Custom":
if (customCols < 1 || customCols > 32 || customRows < 1 || customRows > 32) {
throw new IllegalArgumentException("Custom rows and columns must be between 1 and 32");
}
this.rows = customRows;
this.cols = customCols;
this.bombs = (customCols / 2) * (customRows / 2);
break;
default:
throw new IllegalArgumentException("Invalid difficulty level");
}
}
public void generateGrid() {
this.gridPane.getChildren().clear();
// Generate the board using GenerateBoard
gameBoard = GenerateBoard.generateBoard(rows, cols, bombs);
// Hide numbers initially by not setting text in the cells
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
Cell cell = gameBoard[row][col];
this.gridPane.add(cell, col, row);
// Add click event handling
cell.setOnMouseClicked(event -> {
boolean isRightClick = event.getButton() == MouseButton.SECONDARY;
handleCellClick(GridPane.getRowIndex(cell), GridPane.getColumnIndex(cell), isRightClick);
});
}
}
}
public GridPane getGridPane() {
return this.gridPane;
}
/**
* Click event handler for every cell
* Determines what gets executed with left and right click
* The actual reveal() code lies int the cell code itself
*/
public void handleCellClick(int row, int col, boolean isRightClick) {
Cell cell = gameBoard[row][col];
// Right click
if (isRightClick) {
if(cell.isFlagged()) {
cell.toggleFlag();
}
else if(!cell.isFlagged() && !cell.isRevealed())
{
cell.toggleFlag();
}
}
// Left Click
else {
if (cell.isFlagged()) {
System.out.println("Cannot reveal a flagged cell.");
return;
}
if (cell.isBomb()) {
cell.reveal();
System.out.println("Game Over!");
// Optionally, reveal all bombs and end the game
}
else
{
revealCell(row, col); // Reveal this cell and potentially adjacent cells
}
}
}
/**
* Reveals the cell at (row, col). If the cell is empty (neighbor bombs == 0),
* recursively reveals adjacent cells.
*/
private void revealCell(int row, int col) {
Cell cell = gameBoard[row][col];
// Base case: don't reveal already revealed or flagged cells
if (cell.isRevealed() || cell.isFlagged()) {
return;
}
cell.reveal(); // Reveal the current cell
if (cell.getNeighborBombs() == 0) {
// Recursively reveal adjacent cells
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
int neighborRow = row + i;
int neighborCol = col + j;
// Skip the current cell itself and out-of-bounds indices
if ((i != 0 || j != 0) && isInBounds(neighborRow, neighborCol)) {
revealCell(neighborRow, neighborCol);
}
}
}
}
}
/**
* Helper method to check if a row and column are within the game board bounds.
*/
private boolean isInBounds(int row, int col) {
return row >= 0 && row < rows && col >= 0 && col < cols;
}
}
Code Snippet 4: Board Class
The setDifficulty
method defines the board's size and bomb count based on the selected difficulty level. It supports predefined difficulties such as Easy, Medium, Difficult, and 50/50, as well as a Custom mode. Custom settings allow players to specify the number of rows and columns, with constraints ensuring values remain between 1 and 32. If invalid input is provided, an exception is thrown.
The generateGrid
method creates the game board by calling GenerateBoard.generateBoard
. It clears any existing cells in the GridPane
, then populates it with Cell objects. Each cell is added to the grid, and mouse click events are registered to handle interactions such as left and right clicks.
When a cell is clicked, the handleCellClick
method determines the action based on the type of click. A right click toggles the cell’s flag state, marking it as suspected to contain a bomb. A left click reveals the cell. If the cell contains a bomb, the game ends, optionally revealing all bombs. Otherwise, the cell is revealed, and if it has no neighboring bombs, adjacent cells are recursively revealed using the revealCell
method.
The revealCell
method ensures that only unrevealed and unflagged cells are processed. If a cell has zero neighboring bombs, the method recursively reveals all adjacent cells. This recursion is bounded by the isInBounds
helper method, which verifies that the given row and column indices are within the board’s dimensions.
3. Lessons Learned
This project was a challenging yet incredibly educational experience, especially in my role as Lead Developer and Team Leader. Since I was the only team member with prior experience in software development, it was my responsibility to define the project's technical foundation. Setting up the development environment and implementing the core game logic were key tasks. By introducing coding standards and best practices, I ensured that the code quality remained consistently high and that the team followed a unified development style.
Leading a team with varying levels of experience was a new and valuable challenge for me. I aimed to create a collaborative environment where all team members could actively contribute to the project's development. Regular meetings allowed us to discuss progress, analyze issues, and find solutions together. Through a clear task distribution, I was able to assign roles that matched each team member’s individual skills and learning progress.
This project made me realize the importance of developing not only technical expertise but also leadership skills. The ability to motivate a team, tackle challenges collectively, and maintain a productive workflow is crucial. I learned that effective communication and structured planning are key to a successful project. At the same time, I became more patient and learned to appreciate the learning potential of each team member. Through this experience, I now feel more confident in leading future technical projects.
A heartfelt thank you to every team member who contributed to this project and inspired me along the way.