Hey All I'm having some issues with my minesweeper code:
public class MineSweeper {
/**
* This is the main method for Mine Sweeper game!
* This method contains the within game and play again loops and calls
* the various supporting methods.
*
* @param args (unused)
*/
public static void main(String[] args) {
//Variable Declaration
String promptWidth = "What width of map would you like (3 - 20): ";
String promptHeight = "What height of map would you like (3 - 20): ";
String promptRow = "row: ";
String promptColumn = "column: ";
int mapWidth = 0;
int mapHeight = 0;
int userRow = 0;
int userColumn = 0;
int numberOfMines = 0;
int adjacentMines = 0;
String endGame = " ";
boolean continueGame = false;
char adjacentMinesChar = 0;
boolean NoSafeLocations = false;
Scanner scnr = new Scanner(System.in); //initialize scanner
Random randGen = new Random(Config.SEED); //initialize random generator
System.out.println("Welcome to Mine Sweeper!"); //Welcome message
do {
boolean userWin = false;
boolean userLoss = false;
//user prompts for map sizing
mapWidth = promptUser(scnr, promptWidth, Config.MIN_SIZE, Config.MAX_SIZE);
mapHeight = promptUser(scnr, promptHeight, Config.MIN_SIZE, Config.MAX_SIZE);
System.out.println();
//creation of the board
char[][] map = new char[mapHeight][mapWidth]; //initializing the map array with the user input width and height
eraseMap(map); //map creation
boolean[][] mines = new boolean[mapHeight][mapWidth];
numberOfMines = placeMines(mines, randGen); //initializes the mines and brings back number of them
while (userWin == false && userLoss == false) { //game loop begins and continues until player either wins or loses
System.out.println("Mines: " + numberOfMines); //prints out number of mines
printMap(map);
//User play begins
//user selects a field to sweep
userRow = promptUser(scnr, promptRow, 1, (mapHeight)) - 1;
userColumn = promptUser(scnr, promptColumn, 1, (mapWidth)) - 1;
if (mines[userRow][userColumn] == true) { //if user selects mine he loses end of the game
map[userRow][userColumn] = Config.SWEPT_MINE;
showMines(map, mines);
printMap(map);
System.out.println("Sorry, you lost.");
userLoss = true;
} else { //if user selects a mine free field the play continues
adjacentMines = sweepLocation(map, mines, userRow, userColumn);
if (adjacentMines == -2) { //if user selects a previously selected field just reprinting the map
printMap(map);
} else if (adjacentMines == -1) { //if user selects a field with a mine all mines shown
showMines(map, mines);
} else {
if (adjacentMines == 0) { //if a user selects a field with no nearby mines
sweepAllNeighbors(map, mines, userRow, userColumn);
} else { //if a user selects a field with some nearby mines
printMap(map);
}
//Checks if all locations without mines have been swept if so user wins and the loop is broken
NoSafeLocations = allSafeLocationsSwept(map, mines);
if (NoSafeLocations == false) {
System.out.println();
}
if (NoSafeLocations == true) {
showMines(map, mines);
printMap(map);
System.out.println("You Win!");
userWin = true;
}
}
}
}
//Ending the game
System.out.print("Would you like to play again (y/n)? ");
endGame = scnr.next().trim().toLowerCase();
if (endGame.charAt(0) == 'y') { //if user inputs a word starting with y - the game continues
continueGame = true;
} else { //if user enters a character other than y the game ends
continueGame = false;
}
} while (continueGame == true);
System.out.println("Thank you for playing Mine Sweeper!"); //goodbye statement
}
/**
* This method prompts the user for a number, verifies that it is between min
* and max, inclusive, before returning the number.
*
* If the number entered is not between min and max then the user is shown
* an error message and given another opportunity to enter a number.
* If min is 1 and max is 5 the error message is:
* Expected a number from 1 to 5.
*
* If the user enters characters, words or anything other than a valid int then
* the user is shown the same message. The entering of characters other
* than a valid int is detected using Scanner's methods (hasNextInt) and
* does not use exception handling.
*
* Do not use constants in this method, only use the min and max passed
* in parameters for all comparisons and messages.
* Do not create an instance of Scanner in this method, pass the reference
* to the Scanner in main, to this method.
* The entire prompt should be passed in and printed out.
*
* @param in The reference to the instance of Scanner created in main.
* @param prompt The text prompt that is shown once to the user.
* @param min The minimum value that the user must enter.
* @param max The maximum value that the user must enter.
* @return The integer that the user entered that is between min and max,
* inclusive.
*/
public static int promptUser(Scanner in , String prompt, int min, int max) {
//initialize variables
Integer userInput = 0;
boolean userInteger = false;
System.out.print(prompt); //prompts the user for input
userInteger = in .hasNextInt();
while (userInteger == false) { //checks if user input is an integer if not prints out an error
System.out.println("Expected a number from " + min + " to " + max + "."); in .nextLine();
userInteger = in .hasNextInt();
}
while (userInteger == true) { //if user in put is an integer checks that it fits between allowed min and max
userInput = in .nextInt();
while (userInput > max || userInput < min) {
System.out.println("Expected a number from " + min + " to " + max + "."); in .nextLine();
userInteger = in .hasNextInt();
while (userInteger == false) { //if user didn't fit between min and max checks again for integer input
System.out.println("Expected a number from " + min + " to " + max + "."); in .nextLine();
userInteger = in .hasNextInt();
}
userInput = in .nextInt(); in .nextLine();
}
userInteger = false;
}
return userInput; //returns user input
}
/**
* This initializes the map char array passed in such that all
* elements have the Config.UNSWEPT character.
* Within this method only use the actual size of the array. Don't
* assume the size of the array.
* This method does not print out anything. T his method does not
* return anything.
*
* @param map An allocated array. After this method call all elements
* in the array have the same character, Config.UNSWEPT.
*/
public static void eraseMap(char[][] map) {
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
map[i][j] = Config.UNSWEPT;
}
}
return; //sets up a clean map array
}
/**
* This prints out a version of the map without the row and column numbers.
* A map with width 4 and height 6 would look like the following:
* . . . .
* . . . .
* . . . .
* . . . .
* . . . .
* . . . .
* For each location in the map a space is printed followed by the
* character in the map location.
* @param map The map to print out.
*/
public static void simplePrintMap(char[][] map) {
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
System.out.print(" " + map[i][j]);
}
System.out.println();
}
return; //Prints out a map without labels
}
/**
* This prints out the map. This shows numbers of the columns
* and rows on the top and left side, respectively.
* map[0][0] is row 1, column 1 when shown to the user.
* The first column, last column and every multiple of 5 are shown.
*
* To print out a 2 digit number with a leading space if the number
* is less than 10, you may use:
* System.out.printf("%2d", 1);
*
* @param map The map to print out.
*/
public static void printMap(char[][] map) {
for (int j = 0; j < map[0].length; j++) { //prints out the labels for columns)
if (j == 0) {
System.out.print(" 1");
} else if (j == map.length - 1) {
System.out.printf("%2d", (j + 1));
} else if ((j + 1) % 5 == 0) {
System.out.printf("%2d", (j + 1));
} else {
System.out.print("--");
}
}
System.out.println();
for (int i = 0; i < map.length; i++) { //prints out labels for rows and the map
if (i == 0) {
System.out.print(" 1");
} else if (i == map.length - 1) {
System.out.printf("%2d", (i + 1));
} else if ((i + 1) % 5 == 0) {
System.out.printf("%2d", (i + 1));
} else {
System.out.print(" |");
}
for (int j = 0; j < map[0].length; j++) {
if (map[i][j] == Config.NO_NEARBY_MINE) {
System.out.print(" T " + (map[i][j]));
} else {
System.out.print(" " + (map[i][j]));
}
}
System.out.println();
}
return; //prints out a map with row and column labels
}
/**
* This method initializes the boolean mines array passed in. A true value for
* an element in the mines array means that location has a mine, false means
* the location does not have a mine. The MINE_PROBABILITY is used to determine
* whether a particular location has a mine. The randGen parameter contains the
* reference to the instance of Random created in the main method.
*
* Access the elements in the mines array with row then column (e.g., mines[row][col]).
*
* Access the elements in the array solely using the actual size of the mines
* array passed in, do not use constants.
*
* A MINE_PROBABILITY of 0.3 indicates that a particular location has a
* 30% chance of having a mine. For each location the result of
* randGen.nextFloat() < Config.MINE_PROBABILITY
* determines whether that location has a mine.
*
* This method does not print out anything.
*
* @param mines The array of boolean that tracks the locations of the mines.
* @param randGen The reference to the instance of the Random number generator
* created in the main method.
* @return The number of mines in the mines array.
*/
public static int placeMines(boolean[][] mines, Random randGen) {
int mineCount = 0;
for (int i = 0; i < mines.length; i++) {
for (int j = 0; j < mines[0].length; j++) {
if (randGen.nextFloat() < Config.MINE_PROBABILITY) {
mines[i][j] = true;
++mineCount;
}
}
}
return mineCount; //returns the total count of mines on the map
}
/**
* This method returns the number of mines in the 8 neighboring locations.
* For locations along an edge of the array, neighboring locations outside of
* the mines array do not contain mines. This method does not print out anything.
*
* If the row or col arguments are outside the mines array, then return -1.
* This method (or any part of this program) should not use exception handling.
*
* @param mines The array showing where the mines are located.
* @param row The row, 0-based, of a location.
* @param col The col, 0-based, of a location.
* @return The number of mines in the 8 surrounding locations or -1 if row or col
* are invalid.
*/
public static int numNearbyMines(boolean[][] mines, int row, int col) {
int mineCount = 0;
if (row == 0) { //checks all the fields for a field in the first row
if (col == 0) {
if (mines[row + 1][col] == true) {
mineCount++;
}
if (mines[row + 1][col + 1] == true) {
mineCount++;
}
if (mines[row][col + 1] == true) {
mineCount++;
}
} else if (col == mines[0].length - 1) {
if (mines[row][col - 1] == true) {
mineCount++;
}
if (mines[row + 1][col] == true) {
mineCount++;
}
if (mines[row + 1][col - 1]) {
mineCount++;
}
} else {
if (mines[row][col - 1] == true) {
mineCount++;
}
if (mines[row + 1][col] == true) {
mineCount++;
}
if (mines[row + 1][col + 1] == true) {
mineCount++;
}
if (mines[row + 1][col - 1]) {
mineCount++;
}
if (mines[row][col + 1] == true) {
mineCount++;
}
}
} else if (row == mines.length - 1) { //checks all the fields for a field in the last row
if (col == 0) {
if (mines[row - 1][col] == true) {
mineCount++;
}
if (mines[row - 1][col + 1] == true) {
mineCount++;
}
if (mines[row][col + 1] == true) {
mineCount++;
}
} else if (col == mines[0].length - 1) {
if (mines[row][col - 1] == true) {
mineCount++;
}
if (mines[row - 1][col - 1] == true) {
mineCount++;
}
if (mines[row - 1][col] == true) {
mineCount++;
}
} else {
if (mines[row][col - 1] == true) {
mineCount++;
}
if (mines[row - 1][col - 1] == true) {
mineCount++;
}
if (mines[row - 1][col] == true) {
mineCount++;
}
if (mines[row - 1][col + 1] == true) {
mineCount++;
}
if (mines[row][col + 1] == true) {
mineCount++;
}
}
} else { //check all the fields for a field not in the first or the last row
if (col == 0) {
if (mines[row - 1][col] == true) {
mineCount++;
}
if (mines[row - 1][col + 1] == true) {
mineCount++;
}
if (mines[row][col + 1] == true) {
mineCount++;
}
if (mines[row + 1][col + 1] == true) {
mineCount++;
}
if (mines[row + 1][col] == true) {
mineCount++;
}
} else if (col == mines[0].length - 1) {
if (mines[row - 1][col] == true) {
mineCount++;
}
if (mines[row - 1][col - 1] == true) {
mineCount++;
}
if (mines[row][col - 1] == true) {
mineCount++;
}
if (mines[row + 1][col - 1] == true) {
mineCount++;
}
if (mines[row + 1][col] == true) {
mineCount++;
}
} else {
if (mines[row - 1][col] == true) {
++mineCount;
}
if (mines[row - 1][col - 1] == true) {
mineCount++;
}
if (mines[row - 1][col + 1]) {
mineCount++;
}
if (mines[row][col - 1] == true) {
mineCount++;
}
if (mines[row + 1][col] == true) {
mineCount++;
}
if (mines[row + 1][col + 1] == true) {
mineCount++;
}
if (mines[row + 1][col - 1]) {
mineCount++;
}
if (mines[row][col + 1] == true) {
mineCount++;
}
}
}
return mineCount; //returns the number of mines in the spots next to user picked spot
}
/**
* This updates the map with each unswept mine shown with the Config.HIDDEN_MINE
* character. Swept mines will already be mapped and so should not be changed.
* This method does not print out anything.
*
* @param map An array containing the map. On return the map shows unswept mines.
* @param mines An array indicating which locations have mines. No changes
* are made to the mines array.
*/
public static void showMines(char[][] map, boolean[][] mines) {
for (int i = 0; i < mines.length; i++) {
for (int j = 0; j < mines[0].length; j++) {
if (map[i][j] == Config.SWEPT_MINE) {
map[i][j] = Config.SWEPT_MINE;
} else if (mines[i][j] == true) {
map[i][j] = Config.HIDDEN_MINE;
}
}
}
return; //updates the map array to show uncovered mines
}
/**
* Returns whether all the safe (non-mine) locations have been swept. In
* other words, whether all unswept locations have mines.
* This method does not print out anything.
*
* @param map The map showing touched locations that is unchanged by this method.
* @param mines The mines array that is unchanged by this method.
* @return whether all non-mine locations are swept.
*/
public static boolean allSafeLocationsSwept(char[][] map, boolean[][] mines) {
boolean allSafeSwept = true;
for (int i = 0; i < mines.length; i++) {
for (int j = 0; j < mines[0].length; j++) {
if (mines[i][j] == false && map[i][j] == Config.UNSWEPT) {
allSafeSwept = false;
}
}
}
return allSafeSwept; //returns true if all safe locations have been swept
}
/**
* This method sweeps the specified row and col.
* - If the row and col specify a location outside the map array then
* return -3 without changing the map.***
* - If the location has already been swept then return -2 without changing
* the map.
* - If there is a mine in the location then the map for the corresponding
* location is updated with Config.SWEPT_MINE and return -1.
* - If there is not a mine then the number of nearby mines is determined
* by calling the numNearbyMines method.
* - If there are 1 to 8 nearby mines then the map is updated with
* the characters '1'..'8' indicating the number of nearby mines.
* - If the location has 0 nearby mines then the map is updated with
* the Config.NO_NEARBY_MINE character.
* - Return the number of nearbyMines.
*
* @param map The map showing swept locations.
* @param mines The array showing where the mines are located.
* @param row The row, 0-based, of a location.
* @param col The col, 0-based, of a location.
* @return the number of nearby mines, -1 if the location is a mine, -2 if
* the location has already been swept, -3 if the location is off the map.
*/
public static int sweepLocation(char[][] map, boolean[][] mines, int row, int col) {
int nearbyMines = 0;
char nearbyMinesChar = 'x'; //returns -3 if the user spot is out of range of the array
if (row < 0 || row >= map.length || col < 0 || col >= map[0].length) {
return -3;
} else if (map[row][col] != Config.UNSWEPT) { //returns -2 if user spot has been previously swept
return -2;
} else if (mines[row][col] == true) { //returns -1 if there is a mine in the user spot
map[row][col] = Config.SWEPT_MINE;
return -1;
} else if (mines[row][col] == false) { //checks for number of mines if the user spot has not been previously swept
nearbyMines = numNearbyMines(mines, row, col);
if (nearbyMines >= 1 || nearbyMines <= 8) { //if there is at least one mine nearby returns number of mines
nearbyMinesChar = Character.forDigit(nearbyMines, nearbyMines + 1);
map[row][col] = nearbyMinesChar;
} else if (nearbyMines == 0) { //if there are no nearby mines sets map to NO_NEARBY_MINE
map[row][col] = Config.NO_NEARBY_MINE;
}
return nearbyMines;
}
return 0;
}
/**
* This method iterates through all 8 neighboring locations and calls sweepLocation
* for each. It does not call sweepLocation for its own location, just the neighboring
* locations.
* @param map The map showing touched locations.
* @param mines The array showing where the mines are located.
* @param row The row, 0-based, of a location.
* @param col The col, 0-based, of a location.
*/
public static void sweepAllNeighbors(char[][] map, boolean[][] mines, int row, int col) {
if (row == 0) { //Calls sweep location for all the locations in the first row
if (col == 0) {
sweepLocation(map, mines, row + 1, col);
sweepLocation(map, mines, row + 1, col + 1);
sweepLocation(map, mines, row, col + 1);
} else if (col == mines[0].length - 1) {
sweepLocation(map, mines, row, col - 1);
sweepLocation(map, mines, row + 1, col);
sweepLocation(map, mines, row + 1, col - 1);
} else {
sweepLocation(map, mines, row, col - 1);
sweepLocation(map, mines, row + 1, col);
sweepLocation(map, mines, row + 1, col + 1);
sweepLocation(map, mines, row + 1, col - 1);
sweepLocation(map, mines, row, col + 1);
}
} else if (row == mines.length - 1) { //Calls sweep location for all the locations in the last row
if (col == 0) {
sweepLocation(map, mines, row - 1, col);
sweepLocation(map, mines, row - 1, col + 1);
sweepLocation(map, mines, row, col + 1);
} else if (col == mines[0].length - 1) {
sweepLocation(map, mines, row, col - 1);
sweepLocation(map, mines, row - 1, col - 1);
sweepLocation(map, mines, row - 1, col);
} else {
sweepLocation(map, mines, row, col - 1);
sweepLocation(map, mines, row - 1, col - 1);
sweepLocation(map, mines, row - 1, col);
sweepLocation(map, mines, row - 1, col + 1);
sweepLocation(map, mines, row, col + 1);
}
} else { //Calls sweep location for all the locations in not in the first or last row
if (col == 0) {
sweepLocation(map, mines, row - 1, col);
sweepLocation(map, mines, row - 1, col + 1);
sweepLocation(map, mines, row, col + 1);
sweepLocation(map, mines, row + 1, col + 1);
sweepLocation(map, mines, row + 1, col);
} else if (col == mines[0].length - 1) {
sweepLocation(map, mines, row - 1, col);
sweepLocation(map, mines, row - 1, col - 1);
sweepLocation(map, mines, row, col - 1);
sweepLocation(map, mines, row + 1, col - 1);
sweepLocation(map, mines, row + 1, col);
} else {
sweepLocation(map, mines, row - 1, col);
sweepLocation(map, mines, row - 1, col - 1);
sweepLocation(map, mines, row - 1, col + 1);
sweepLocation(map, mines, row, col - 1);
sweepLocation(map, mines, row + 1, col);
sweepLocation(map, mines, row + 1, col + 1);
sweepLocation(map, mines, row + 1, col - 1);
sweepLocation(map, mines, row, col + 1);
}
}
return;
}
}
This code should behave as follows:
However when I run it, it shifts my lines.
I've been trying to change the spacing for a few hours now and I'm hitting a wall. I thought maybe adding an if statement will help. Keep in min that what's inserted is a constant (which is just a space) - I know there are some deficiencies in the code and it could be improved but I'm focusing on trying to get it working correctly - any help would be deeply appreciated.
Take a look at this line in sweepLocation
:
if (nearbyMines >= 1 || nearbyMines <= 8) { //if there is at least one mine nearby returns number of mines
When is the condition in this if
statement true?
The answer is that it is always true! Any number is either at least 1 or at most 8 (or both). Check it if you don't believe me!
You probably wanted to check that nearbyMines
was at least 1 and at most 8. You would do that by replacing ||
with &&
, i.e.:
if (nearbyMines >= 1 && nearbyMines <= 8) { //if there is at least one mine nearby returns number of mines
The reason the lines in the grid were being shifted is due to this line:
nearbyMinesChar = Character.forDigit(nearbyMines, nearbyMines + 1);
The second argument to Character.forDigit
is the 'radix', or the number base, of the digit. If the radix is out of range (and the range happens to be 2 to 36), then this method returns the null character instead. If nearbyMines
was zero, you ended up passing 1 for the radix, which is outside of the range and hence you got back a null character. The lines ended up being shifted because printing the null character does nothing.
I really don't see why you're passing nearbyMines + 1
for the radix here; it would be simpler to just use 10
, as all the digits are in base 10.
After making these changes to your code, it seemed to work more as I would expect it to, in that the problem with shifting lines had gone. (I couldn't reproduce your grid exactly, but that's because I don't know what value you've set Config.SEED
to. For me anyway, setting the seed to zero gave me a grid with no mines, which was enough to reproduce your problem.)