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;
}
}