I have been working on a text based minesweeper game in Java and I am looking for some ways to improve it. I worked through this project through JetBrains academy. I will attach the instructions of the final part of the project
Objectives In this final stage, your program should contain the following additional functionality:
Print the current state of the minefield starting with all unexplored cells at the beginning, ask the player for their next move with the message Set/unset mine marks or claim a cell as free:, treat the player's move according to the rules, and print the new minefield state. Ask for the player's next move until the player wins or steps on a mine. The player's input contains a pair of cell coordinates and a command: mine to mark or unmark a cell, free to explore a cell.
If the player explores a mine, print the field in its current state, with mines shown as X symbols. After that, output the message You stepped on a mine and failed!.
Generate mines like in the original game: the first cell explored with the free command cannot be a mine; it should always be empty. You can achieve this in many ways – it's up to you.
Use the following symbols to represent each cell's state:
.
as unexplored cells
/
as explored free cells without mines around itNumbers from 1 to 8 as explored free cells with 1 to 8 mines around them, respectively
X
as mines
*
as unexplored marked cells
Here is part of an example they gave
The greater-than symbol followed by a space (> ) represents the user input. Note that it's > not part of the input.
Example 1: the user loses after exploring a cell that contains a mine
|123456789| -|---------| 1|.........| 2|.........| 3|.........| 4|.........| 5|.........| 6|.........| 7|.........| 8|.........| 9|.........| -|---------| Set/unset mines marks or claim a cell as free: > 3 2 free |123456789| -|---------| 1|.1///1...| 2|.1//12...| 3|11//1....| 4|////1....| 5|11111....| 6|.........| 7|.........| 8|.........| 9|.........| -|---------| Set/unset mines marks or claim a cell as free: > 1 1 free |123456789| -|---------| 1|11///1...| 2|.1//12...| 3|11//1....| 4|////1....| 5|11111....| 6|.........| 7|.........| 8|.........| 9|.........| -|---------| Set/unset mines marks or claim a cell as free: > 1 2 mine |123456789| -|---------| 1|11///1...| 2|*1//12...| 3|11//1....| 4|////1....| 5|11111....| 6|.........| 7|.........| 8|.........| 9|.........| -|---------|
I will attach the code below. Any advice would be greatly appreciated but here are some of my overarching questions I have about my code
I wasn't sure how much code I should write in my main method. Does the game logic belong in the class or in the main method? One major debate I had with myself was whether to put the scanner in the main method or in the class itself (as you can see, I went with the latter). What would you have done?
I had to duplicate my code whenever I was doing operations with neigboring cells. For example, I use
for (int i = Math.max(0, row - 1); i <= Math.min(8, row + 1) ; i++)
andfor (int j = Math.max(0, initialCol - 1); j <= Math.min(8, initialCol + 1); j++)
whenever I need to iterate through a cells neighbors. Is there any way I could improve how I go about doing this?Jetbrains academy introduced enums before completing this project but I didn't know how I would use them. Would they have made my code any better?
You may notice the lack of comments in my code. I don't use them too often (I know it's a bad habit to get into). Is the code self explanatory or does it need more comments?
And here is my code:
package minesweeper; import java.util.Objects; import java.util.Random; import java.util.Scanner; public class Main { public static void main(String[] args) { // write your code here Minefield game = new Minefield(); while (!game.hasLost || !game.hasWon) { game.nextTurn(); } } } class Minefield { int[][] board = new int[9][9]; boolean[][] mineLocations = new boolean[9][9]; boolean[][] marked = new boolean[9][9]; boolean[][] explored = new boolean[9][9]; boolean hasWon; boolean hasLost; boolean boardSet; static Scanner scanner = new Scanner(System.in); private int mines; public Minefield(){ System.out.print("How many mines do you want on the field? "); this.mines = scanner.nextInt(); this.showMinefield(); } public void nextTurn() { System.out.print("Set/unset mines marks or claim a cell as free: "); int col = scanner.nextInt() - 1; int row = scanner.nextInt() - 1; String command = scanner.next(); if (this.isExplored(row, col)) { System.out.println("This cell is already explored!"); } else { executeTurn(row, col, command); } } private void executeTurn(int row, int col, String command) { switch (command) { case "free": if (!boardSet) { boardSet = true; this.newBoard(row, col); explore(row, col); } if (this.hasMineAt(row, col)) { this.revealMinefield(); System.out.println("You stepped on a mine and failed!"); this.hasLost = true; } else { explore(row, col); } break; case "mine": this.marked[row][col] = !this.marked[row][col]; } this.checkWin(); this.showMinefield(); } private void newBoard(int initialRow, int initialCol){ //ensures the first move doesn't have any mines for (int i = Math.max(0, initialRow - 1); i <= Math.min(8, initialRow + 1); i++) { for (int j = Math.max(0, initialCol - 1); j <= Math.min(8, initialCol + 1); j++) { this.board[i][j] = -1; } } Random random = new Random(); for (int i = 0; i < this.mines; i++) { int mineRow , mineCol; do { mineRow = random.nextInt(9); mineCol = random.nextInt(9); } while (this.board[mineRow][mineCol] == -1); this.board[mineRow][mineCol] = -1; this.mineLocations[mineRow][mineCol] = true; } for (int i = Math.max(0, initialRow - 1); i <= Math.min(8, initialRow + 1); i++) { for (int j = Math.max(0, initialCol - 1); j <= Math.min(8, initialCol + 1); j++) { this.board[i][j] = 0; } } for (int i = 0; i < this.board.length; i++) { for (int j = 0; j < this.board[0].length; j++) { if (this.hasMineAt(i,j)) { for (int k = Math.max(0, i - 1); k <= Math.min(8, i + 1) ; k++) { for (int l = Math.max(0, j - 1); l <= Math.min(8, j + 1); l++) { if (!(k == i && l == j)) { this.board[k][l]++; } } } } } } if (board[initialRow][initialCol] != 0) { System.out.println("Error with mine spawning"); } } private void showMinefield() { System.out.println(" |123456789|"); System.out.println("-|---------|"); for (int i = 0; i < this.board.length; i++) { System.out.print((i + 1) + "|"); for (int j = 0; j < this.board[0].length; j++) { if (this.isMarked(i, j)) { System.out.print("*"); } else if (this.board[i][j] == 0 && this.isExplored(i, j)) { System.out.print("/"); } else if (this.isExplored(i, j)){ System.out.print(this.board[i][j]); } else { System.out.print("."); } } System.out.print("|"); System.out.println(); } System.out.println("-|---------|"); } private void revealMinefield() { System.out.println(" |123456789|"); System.out.println("-|---------|"); for (int i = 0; i < this.board.length; i++) { System.out.print((i + 1) + "|"); for (int j = 0; j < this.board[0].length; j++) { if (this.hasMineAt(i, j)) { System.out.print("X"); } else if (this.board[i][j] == 0 && this.isExplored(i, j)) { System.out.print("/"); } else if (this.isExplored(i, j)){ System.out.print(this.board[i][j]); } else { System.out.print("."); } } System.out.print("|"); System.out.println(); } System.out.println("-|---------|"); } private boolean hasMineAt(int row, int col) { return this.mineLocations[row][col]; } private boolean isMarked(int row, int col) { return this.marked[row][col]; } private boolean isExplored(int row, int col) { return this.explored[row][col]; } private void explore(int row, int col) { this.explored[row][col] = true; this.marked[row][col] = false; if (this.board[row][col] == 0) { for (int i = Math.max(0, row - 1); i <= Math.min(8, row + 1) ; i++) { for (int j = Math.max(0, col - 1); j <= Math.min(8, col + 1) ; j++) { if (!this.isExplored(i, j)) { this.explore(i, j); } } } } } private void checkWin(){ if (this.marked == this.mineLocations) { this.hasWon = true; return; } for (int i = 0; i < this.board.length; i++) { for (int j = 0; j < this.board[0].length; j++) { if (this.hasMineAt(i, j) && !this.isMarked(i,j)) { return; } } } this.hasWon = true; } }