6
\$\begingroup\$

I am nearly complete with my Minesweeper game but the rough idea is done.

My approach is with three classes. One is the tile class which represents a tile on the gameboard. The other is the actual gameboard which is made out of a 2-d array from the tiles. There is no function when you win but wanted to get ideas/ tips from coders. You can play until you lose.

I have my concerns with the current way I have coded things for example, I am unsure if using faceUp is the best way I can represent an unopened tile but for now that is all I could think of. Furthermore, I implemented nearly every possible edge case I could think of for the board.

The main area I would like more help in is checking whether an given coordinate is within bounds.

 public class Tile {

    private int numBombsNearBy;
    private String faceUp;
    private boolean isBomb;

    public Tile(){
        numBombsNearBy = 0;
        faceUp = "X";
        isBomb = false;
    }

GameBoard class:

public class GameBoard {

    /*
    TO DO:
        Create method to show the rest ofthe bombs when the user loses.
     */
    private Tile[][] gameBoard;
    private int xdim = 8;
    private int ydim = 8;
    private List<Pair> listOfBombs = new ArrayList<>();

    public GameBoard(){
        gameBoard = new Tile[xdim][ydim];
        for(int row = 0; row < xdim; row++){
            for(int col = 0; col < ydim; col++){
                gameBoard[row][col] = new Tile();
            }
        }
    }

    public Tile[][] getGameBoard(){
        return gameBoard;
    }
    public void gameOver(Tile[][] gameBoard){
        for(int bomb = 0; bomb < listOfBombs.size();bomb++){
            Pair temp = listOfBombs.get(bomb);
            gameBoard[temp.getX()][temp.getY()].setFaceUp("*");
        }
        displaygameBoard();
    }

    public void displaygameBoard(){
        for(int row = 0; row < xdim; row++){
            for(int col = 0; col < ydim; col++){
                System.out.print(gameBoard[row][col].getFaceUp() + "|");
            }
            System.out.println();
        }
    }

    public int generateRandomNumber(){

        Random rn = new Random();
        int range = 7 - 0+ 1;
        int randomNum =  rn.nextInt(range) + 0;
        return randomNum;
    }

    public void fillBoardBombs(){
        int row, col = 0;
        for(int i = 0; i < 9; i++){
            row = generateRandomNumber();
            col = generateRandomNumber();
            listOfBombs.add(new Pair(row, col));
            gameBoard[row][col].setBomb(true);
            updateNearByBombs(row, col);
        }
    }

    public boolean checkIfBomb(int x, int y){
        if(gameBoard[x][y].isBomb() == true){
            return true;
        }
        return false;
    }

    public void showTileBomb(int x, int y){
        gameBoard[x][y].setFaceUp(String.valueOf(gameBoard[x][y].getNumBombsNearBy()));
    }




    public void updateNearByBombs(int inputrow, int inputcol){

        //For (0,0)
        if(inputrow == 0 && inputcol == 0){
            for(int row = inputrow; row <= inputrow + 1; row++){
                for(int col = inputcol; col <= inputcol + 1; col++){
                    if(row == inputrow && col == inputcol){
                        gameBoard[row][col].setNumBombsNearBy(0);
                    }else{
                        gameBoard[row][col].setNumBombsNearBy(gameBoard[row][col].getNumBombsNearBy() + 1);

                    }
                }
            }
        }

        //For (0, (1 - 6))
        else if(inputrow == 0 &&(inputcol >= 1 && inputcol <= 6)){
            for(int row = inputrow; row <= inputrow + 1; row++){
                for(int col = inputcol - 1; col <= inputcol + 1; col++){
                    if(row == inputrow && col == inputcol){
                        gameBoard[row][col].setNumBombsNearBy(0);
                    }else{
                        gameBoard[row][col].setNumBombsNearBy(gameBoard[row][col].getNumBombsNearBy() + 1);

                    }
                }
            }
        }

        //For (0,7)
        else if(inputrow == 0 && inputcol == 7){
            for(int row = inputrow; row <= inputrow + 1; row++){
                for(int col = inputcol - 1; col <= inputcol;col++){
                    if(row == inputrow && col == inputcol){
                        gameBoard[row][col].setNumBombsNearBy(0);
                    }else{
                        gameBoard[row][col].setNumBombsNearBy(gameBoard[row][col].getNumBombsNearBy() + 1);

                    }
                }
            }
        }


        //For (1 - 6), 0)
        else if((inputrow >= 1 && inputrow <= 6) && inputcol == 0){
            for(int  row = inputrow - 1; row <= inputrow + 1; row++){
                for(int col = inputcol; col <= inputcol + 1; col++){
                    if(row == inputrow && col == inputcol){
                        gameBoard[row][col].setNumBombsNearBy(0);
                    }else{
                        gameBoard[row][col].setNumBombsNearBy(gameBoard[row][col].getNumBombsNearBy() + 1);

                    }
                }
            }
        }

        //For (7,0)
        else if(inputrow == 7 && inputcol == 0){
            for(int  row = inputrow - 1; row<= inputrow;row++){
                for(int col = inputcol; col <= inputcol + 1;col++){
                    if(row == inputrow && col == inputcol){
                        gameBoard[row][col].setNumBombsNearBy(0);
                    }else{
                        gameBoard[row][col].setNumBombsNearBy(gameBoard[row][col].getNumBombsNearBy() + 1);

                    }
                }
            }
        }

        //For (7, (1 - 6))
        else if(inputrow == 7 && (inputcol >= 1 && inputcol <= 6)){
            for(int row = inputrow - 1; row <= inputrow; row++){
                for(int col = inputcol - 1; col <= inputcol + 1; col++){
                    if(row == inputrow && col == inputcol){
                        gameBoard[row][col].setNumBombsNearBy(0);
                    }else{
                        gameBoard[row][col].setNumBombsNearBy(gameBoard[row][col].getNumBombsNearBy() + 1);

                    }
                }
            }
        }

        //For (7,7)
        else if(inputrow == 7 && inputcol == 7){
            for(int row = inputrow - 1; row <= inputrow; row++){
                for(int col = inputcol - 1; col <= inputcol; col++){
                    if(row == inputrow && col == inputcol){
                        gameBoard[row][col].setNumBombsNearBy(0);
                    }else{
                        gameBoard[row][col].setNumBombsNearBy(gameBoard[row][col].getNumBombsNearBy() + 1);

                    }
                }
            }
        }


        //For (1 - 6), 7)
        else if((inputrow >= 1 && inputrow <= 6) && inputcol == 7){
            for(int row = inputrow - 1; row<= inputrow + 1; row++){
                for(int col = inputcol - 1; col <= inputcol;col++){
                    if(row == inputrow && col == inputcol){
                        gameBoard[row][col].setNumBombsNearBy(0);
                    }else{
                        gameBoard[row][col].setNumBombsNearBy(gameBoard[row][col].getNumBombsNearBy() + 1);

                    }
                }
            }
        }


        //for the rest
           else for(int row = inputrow - 1; row <= inputrow + 1; row++){
                for(int col = inputcol - 1; col<= inputcol + 1;col++){
                    if(row == inputrow && col == inputcol) {
                        gameBoard[row][col].setNumBombsNearBy(0);
                    }else{
                        gameBoard[row][col].setNumBombsNearBy(gameBoard[row][col].getNumBombsNearBy() + 1);

                    }
                }
        }

    }

}

Main:

public class JMineSweeper {

    final static String regex = "^[0-7]\\s+[0-7]$";


    public static String askForUserInput() {
        Scanner input = new Scanner(System.in);
        String xy = null;
        System.out.print("Enter x and y coordinates seperated by a space: ");
        return xy = input.nextLine();
    }

    public static boolean isCorrectInput(String input){
        if(input.matches(regex)){
            return true;
        }
        return false;
    }

    public static List<Integer> convertUserInput(String input){
        String [] splited = input.split("\\s+");
        List<Integer> coordinates =  new ArrayList<>();
        for(int i = 0; i < splited.length; i++){
            coordinates.add(Integer.parseInt(splited[i]));
        }
        return coordinates;
    }



    public static void main(String[] args) {
        Boolean gameOver = false;
        GameBoard gameBoard = new GameBoard();
        gameBoard.fillBoardBombs();


        while(!gameOver){
            try{
                gameBoard.displaygameBoard();
                String userInput = askForUserInput();
                if(isCorrectInput(userInput)){
                    List<Integer> coordinates = convertUserInput(userInput);
                    System.out.println(coordinates.toString());
                    if(gameBoard.checkIfBomb(coordinates.get(0), coordinates.get(1))){
                        System.out.println("You lose, tile is a bomb");
                        gameBoard.gameOver(gameBoard.getGameBoard());
                        gameOver = true;
                    }else{
                        gameBoard.showTileBomb(coordinates.get(0), coordinates.get(1));
                    }
                }
            }catch (Exception e){
                System.out.println("Incorrect input");
            }
        }
    }
}'
\$\endgroup\$
3
\$\begingroup\$

Thanks for sharing the code!

I have my concerns with the current way I have coded things for example, I am unsure if using faceUp is the best way I can represent an unopened tile [...].

No, it isn't.

You already modeled the fields as objects of a class Tile. You should go one step further:

There are three types of "Tiles" with different behavior:

  1. bombs which explode when clicked,
  2. neighbors of bombs which show how many bombs are around them.
  3. safe fields without neighboring bombs

In OOP we create new classes when we have differing behavior. So you need to create 3 more classes extending the Tile class (which should rather be an interface though...)

Having this you can move some of the logic to the different subclasses of Tile. E.g.: a safe field could uncover its neighbors automatically:

class SafeTile{
   private final Collection<Tile> neighbors = new  HashSet<>();
   @Override
   public void addNeighbor(Tile neighbor){ neighbors.add(neighbor);}
   @Override
   public void coverUp(){
     for(Tile neighbor : neighbors)
        neighbors.coverUp();
   }
}

I implemented nearly every possible edge case I could think of for the board.

There is a natural way to deal with that: when the tiles know their neighbors you don't need a sophisticated logic to deal with edges and corners. These tiles just have less neighbors...

All you need is a smart way to fill the game board initially. YOu could do like this:

class GameBoard{
  private final Tile[][] fields;
  GameBoard(int width, int height, int bombCount){
    fields = new Tile[width][height];
    // deploy bombs
    List<Pair> fieldPositions = new ArrayList<>();
    for(int i =0;i<width;i++)
     for(int j=0; j<height;j++)
       fieldPositions.add(new Pair(i,j));
    List<Pair> bombPositions = new ArrayList<>();
    for(int i =0;i< bombCount;i++)
      bombPositions.add(fieldPositions.remove(new Random().nextInt(fieldPositions.size()));
    for(Pair bombPos : bombPositions){
       fields[bombPos.x][bombPos.y]= new TileBomb();
         // set bomb neigbors, corners and edges not yet handled...
         for(int i =-1; i< 2; i++)
           for(int j =-1; j< 2; j++)
             if(null==fields[bombPos.x+i][bombPos.y+j])
               fields[bombPos.x+i][bombPos.y+j]= new NeighborTile();
    }
    // set safe fields    
    for(int i =0;i<width;i++)
     for(int j=0; j<height;j++)
       if(null==fields[i][j])
         fields[i][j]= new SafeTile();
    // set neigbours   
    for(int i =0;i<width-1;i++)
      for(int j=0; j<height-1;j++){                 
          fields[i][j].addNeigbor(fields[i][j+1]);                 
          fields[i][j].addNeigbor(fields[i+1][j+1]);                 
          fields[i][j].addNeigbor(fields[i+1][j]);  
          fields[i][j+1].addNeigbor(fields[i][j]);                 
          fields[i+1][j+1].addNeigbor(fields[i][j]);                 
          fields[i+1][j].addNeigbor(fields[i][j]);       
       }
  }
}

The advantage is that you do not need handle field positions during runtime anymore. Just select a field and tell it that it has been uncovered.


Why in the if statement for setting neighbor bombs is there if(null == field...).

Initially there are no objects in the field array, any field refers to null.

After placing a new bomb (b) we want to place all neighbor fields with new objects:

 - - -      n n n
 - b -  ->  n b n
 - - -      n n n

The two inner loops iterate the sequence -1, 0, 1. This means we get all 9 possible combinations. But at x+0 - y+0 is the bomb object we don't want to overwrite. We could have checked for k and l not being both 0 which would have worked for the current bomb position. But what if we have placed another bomb (B) in a field nearby in some previous iteration?

 N N N -
 N B N -
 N N b -
 - - - -

By checking the possible neighbor field for being empty (referencing to null) we do not need a sophisticated logic to avoid replacing the actual or any previously placed bomb with a neighbor object by accident. We also don't replace any already placed neighbor field (except with a bomb) but that is less important.

On the other hand a new bomb can (and should) replace a previously placed neighbor. Therefore we do not check when placing a bomb.


why does a safe tile need to know tis neighbors

A SafeTile is a field without a bomb in any of its neighbor fields. By the rules of "mine sweeper" all connected safe and neighbor fields are uncovered when a safe field is hit.

By having the SafeTile knowing its neighbors (which are either of class SafeTile too or of class NeighborTile, but never bombs) the current SafeTile can call the coverUp() method on its neigbors. Any neighbor which is a SafeTile itself will also propagate the call to its neighbors recursively, causing a chain reaction. keep in mind that my quick shot causes an infinite loop...

and what would be the meaning of cover up?

The method coverUp() is meant to be called by the User Interface (UI) to trigger the state change from "unknown" to "visited". Obviously the subclasses of Tile will behave differently when this method is called on them.

\$\endgroup\$
  • \$\begingroup\$ Thank you for your feedback Timothy! I am going over the code to see how I can implement it but before I do, I have a question. Why in the if statement for setting neighbor bombs is there if(null == field...). I have not seen this before and wanted to get an understanding of it. \$\endgroup\$ – TheLearner Jun 27 '17 at 19:56
  • \$\begingroup\$ @TheLearner "Why in the if statement for setting neighbor bombs is there if(null == field...). " - Answer updated. \$\endgroup\$ – Timothy Truckle Jun 27 '17 at 20:44
  • \$\begingroup\$ Awesome explanation! One more question if I may. Within the set safe fields code block, why do we make all other tiles Neighbor tiles? \$\endgroup\$ – TheLearner Jun 27 '17 at 20:49
  • \$\begingroup\$ @TheLearner "Within the set safe fields code block, why do we make all other tiles Neighbor tiles?" - because I did not correct it after copy/paste (but now...) ;o) \$\endgroup\$ – Timothy Truckle Jun 27 '17 at 20:51
  • \$\begingroup\$ Actually I have one more question why does a safe tile need to know tis neighbors and what would be the meaning of cover up?, Thank you for your feedback this is helping me! I am going to make these improvements within my code thank you Timothy! \$\endgroup\$ – TheLearner Jun 27 '17 at 20:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.