MineSweeper CLI 2020 - Pt.4
This code adds the random placement of mines avoiding the area around the first selection.
Also a selection algorithm that automatically reveals all tiles surrounding any selections with zero adjacent mines.
The MineSweeper Pt.4 video lesson documents the writing of this code and explains its structure.
MineSweeper.java
package ceccs;
public class MineSweeper {
public static void main(String[] args){
Grid gameGrid = new Grid(10,10);
//First Turn and game setup
gameGrid.printGrid();
int firstCol = gameGrid.askCol();
int firstRow = gameGrid.askRow();
gameGrid.createRandomMinefield(10,firstCol,firstRow);
gameGrid.calcAdjNums();
gameGrid.selectTile(firstCol,firstRow);
gameGrid.printGrid();
//game loop
boolean keepPlaying = true;
while(keepPlaying) {
gameGrid.playerTurn();
gameGrid.printGrid();
keepPlaying = gameGrid.checkLoss();
if (gameGrid.checkWin()) keepPlaying = false;
}
}
}
Grid.java
package ceccs;
import java.util.Random;
import java.util.Scanner;
public class Grid {
int width, height;
Tile[][] tiles;
Grid(int w, int h){
width = w;
height = h;
tiles = new Tile[width][height];
for(int j = 0 ; j < height; j++){
for(int i = 0; i < width; i++){
tiles[i][j] = new Tile(i, j, false);
}
}
}
Grid() {
width = 8;
height = 7;
tiles = new Tile[width][height];
for(int j = 0 ; j < height; j++){
for(int i = 0; i < width; i++){
tiles[i][j] = new Tile(i, j, false);
}
}
}
void printGrid(){
System.out.print("\n ");
for(int i = 0; i < width; i++){
if (i < 10) System.out.print(" " + i + " ");
else System.out.print(i + " ");
}
System.out.print("\n ");
for(int i = 0; i < width; i++){
System.out.print("___");
}
System.out.print("\n");
for(int j = 0; j < height; j++){
if (j<10) System.out.print(j + " |");
else System.out.print(j + " |");
for (int i =0; i < width; i++){
if (tiles[i][j].isCovered) {
if (tiles[i][j].isFlagged) System.out.print(" f ");
else System.out.print(" = ");
} else if (tiles[i][j].isBomb) {
System.out.print(" * ");
} else {
if(tiles[i][j].numAdjBombs != 0) System.out.print(" " + tiles[i][j].numAdjBombs + " ");
else System.out.print(" . ");
}
}
System.out.print("\n");
}
}
void revealMinefield(){
for(int j = 0; j < height; j++){
for(int i = 0; i < width; i++){
tiles[i][j].isCovered = false;
}
}
}
void createTestMinefield(){
if(width != 8 && height != 7) {
System.out.println("Error: Grid must be 8x7 for test field!");
return;
} else{
tiles[0][0].isBomb = true;
tiles[0][1].isBomb = true;
tiles[0][2].isBomb = true;
tiles[1][0].isBomb = true;
tiles[1][2].isBomb = true;
tiles[1][5].isBomb = true;
tiles[2][0].isBomb = true;
tiles[2][1].isBomb = true;
tiles[2][2].isBomb = true;
tiles[4][0].isBomb = true;
tiles[4][1].isBomb = true;
tiles[5][0].isBomb = true;
tiles[5][4].isBomb = true;
tiles[5][5].isBomb = true;
tiles[5][6].isBomb = true;
tiles[6][0].isBomb = true;
tiles[6][1].isBomb = true;
tiles[6][2].isBomb = true;
tiles[6][4].isBomb = true;
tiles[6][6].isBomb = true;
tiles[7][4].isBomb = true;
tiles[7][5].isBomb = true;
}
}
void createRandomMinefield(int numMines, int firstCol, int firstRow) {
int numTiles = width * height - 9;
if(numMines > numTiles) {
System.out.println("Error: attempted to place too many mines!");
return;
}
Random random = new Random();
int minesPlaced = 0;
while(minesPlaced < numMines) {
int randX = random.nextInt(width);
int randY = random.nextInt(height);
if(
!((randX >= firstCol - 1 && randX <= firstCol + 1)
&&
(randY >= firstRow - 1 && randY <= firstRow + 1))
){
if(!tiles[randX][randY].isBomb) {
tiles[randX][randY].isBomb = true;
minesPlaced++;
}
}
}
}
void calcAdjNums(){
for( int j = 0; j < height; j++){
for( int i = 0; i < width; i++){
// find out which neighbors are bombs!
int count = 0;
// top [i][j-1]
if(j-1 >= 0){
if (tiles[i][j-1].isBomb) count++;
}
//top-right [i+1][j-1]
if(i+1 < width && j-1 >= 0){
if(tiles[i+1][j-1].isBomb) count++;
}
//right[i+1][j]
if(i+1 < width){
if(tiles[i+1][j].isBomb) count++;
}
//bottom-right[i+1][j+1]
if(i+1 < width && j+1 < height){
if(tiles[i+1][j+1].isBomb) count++;
}
//bottom[i][j+1]
if(j+1 < height){
if(tiles[i][j+1].isBomb) count++;
}
//bottom-left[i-1][j+1]
if(i-1 >= 0 && j+1 < height){
if(tiles[i-1][j+1].isBomb) count++;
}
//left[i-1][j]
if(i-1 >= 0){
if(tiles[i-1][j].isBomb) count++;
}
//top-left [i-1][j-1]
if(i-1 >= 0 && j-1 >= 0){
if(tiles[i-1][j-1].isBomb) count++;
}
tiles[i][j].numAdjBombs = count;
}
}
}
void playerTurn(){
int c = askCol();
int r = askRow();
char a = askAction();
if (a == 's') selectTile(c,r);
else tiles[c][r].toggleFlag();
}
void selectTile(int col, int row){
if(tiles[col][row].isCovered) tiles[col][row].isCovered = false;
else return;
if(tiles[col][row].numAdjBombs == 0 && !tiles[col][row].isBomb){
// if 0 bombs around tile try to select all nearby tiles
//top [col] [row-1]
if(row-1 >= 0) {
selectTile(col, row-1);
}
//top right [col+1] [row-1]
if(col+1 < width && row-1 >= 0){
selectTile(col+1, row-1);
}
//right [col+1] [row]
if(col+1 < width){
selectTile(col+1, row);
}
//bottom right [col+1] [row+1]
if(col+1 < width && row + 1 < height){
selectTile(col+1, row+1);
}
//bottom [col] [row+1]
if(row+1 < height){
selectTile(col, row+1);
}
//bottom left [col-1] [row+1]
if(col-1 >= 0 && row+1 < height){
selectTile(col-1, row+1);
}
//left [col-1] [row]
if(col-1 >= 0){
selectTile(col-1, row);
}
//top left [col-1] [row-1]
if(col-1 >= 0 && row-1 >= 0){
selectTile(col-1, row-1);
}
}
}
int askCol(){
Scanner scanner = new Scanner(System.in);
int c = -1;
while (c < 0 || c >= width) {
System.out.println("\nSelect a Column: [0 ... " + (width - 1) + "]");
c = scanner.nextInt();
}
return c;
}
int askRow(){
Scanner scanner = new Scanner(System.in);
int r = -1;
while( r < 0 || r >= height) {
System.out.println("Select a Row: [0 ... " + (height - 1) + "]");
r = scanner.nextInt();
}
return r;
}
char askAction(){
Scanner scanner = new Scanner(System.in);
char a = 'x';
while( a != 'f' && a != 's') {
System.out.println("What Action? [s]-Select [f]-Flag");
a = scanner.nextLine().charAt(0);
}
return a;
}
// returns false if player has blowed up.
boolean checkLoss(){
for(int j = 0; j < height; j++){
for (int i = 0; i < width; i++) {
if (tiles[i][j].isBomb && !tiles[i][j].isCovered) {
System.out.println();
System.out.println("**********");
System.out.println("*BOOOOOM!*");
System.out.println("**********");
System.out.println("You Lose.");
return false;
}
}
}
return true;
}
boolean checkWin(){
boolean win = true;
for (int j = 0; j < height; j++){
for (int i = 0; i < width; i++){
if(tiles[i][j].isCovered && !tiles[i][j].isBomb){
win = false;
}
}
}
if (win) System.out.println("\nYou Win!");
return win;
}
}
Tile.java
package ceccs;
public class Tile {
boolean isCovered, isBomb, isFlagged;
int numAdjBombs, x, y;
Tile(){
isCovered = true;
isBomb = false;
isFlagged = false;
numAdjBombs = 0;
x = 0;
y = 0;
}
Tile(int xCoord, int yCoord, boolean bomb){
x = xCoord;
y = yCoord;
isBomb = bomb;
isCovered = true;
numAdjBombs = 0;
isFlagged = false;
}
void toggleFlag(){
if (isFlagged) isFlagged = false;
else isFlagged = true;
}
}