0

I'm creating a java implementation of Java for fun and I'm trying to fill in all zeros when you click a pool of them. (Play Minesweeper to see what I'm talking about)

Here is my recursive call:

private void revealZeros(int x, int y) {

    if (board[y][x].revealed)
        return;
    board[y][x].revealed = true;
    if (y > 0) {
        if (x > 0)
            if (!board[y - 1][x - 1].revealed && board[y - 1][x - 1].b == 0)
                revealZeros(y - 1, x - 1);
        if (x < 15) {
            if (!board[y - 1][x + 1].revealed && board[y - 1][x + 1].b == 0)
                revealZeros(y - 1, x + 1);
        }
        if (!board[y - 1][x].revealed && board[y - 1][x].b == 0)
            revealZeros(y - 1, x);
    }
    if (x > 0)
        if (!board[y][x - 1].revealed && board[y][x - 1].b == 0)
            revealZeros(y, x - 1);
    if (x < 15)
        if (!board[y][x + 1].revealed && board[y][x + 1].b == 0)
            revealZeros(y, x + 1);
    if (y < 15) {
        if (x > 0)
            if (!board[y + 1][x - 1].revealed && board[y + 1][x - 1].b == 0)
                revealZeros(y + 1, x - 1);
        if (x < 15)
            if (!board[y + 1][x + 1].revealed && board[y + 1][x + 1].b == 0)
                revealZeros(y + 1, x + 1);
        if (!board[y + 1][x].revealed && board[y + 1][x].b == 0)
            revealZeros(y + 1, x);
    }

}

The call is not working properly. It reveals blocks other than 0 and does not reveal all 0 blocks.

Space.b = the number of bombs around it
Space.revealed = is the space revealed?

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
Will Sherwood
  • 1,484
  • 3
  • 14
  • 27
  • Wouldn't an iterative solution be easier to implement? – Tyler Oct 01 '13 at 02:19
  • Recursion is supposed to shrink your code -- make it smaller and simpler at the cost of more memory use. Your huge code doesn't make sense. – Hovercraft Full Of Eels Oct 01 '13 at 02:19
  • I wouldn't know how to do it. I thought this might be able to be solved recursively – Will Sherwood Oct 01 '13 at 02:19
  • I recommend having all of your tiles in a linked graph like structure, and keeping a simple count of how many tiles that are linked any given tile are explosive. Then simply traverse the graph exposing all of the tiles with no links to explosive tiles. – Rob G Oct 01 '13 at 02:21

4 Answers4

2

Wow!! I was able to think of a solution. Very simple. Here was my end code:

private void revealZeros(int x, int y) {
        if (x < 0 || x > 15 || y < 0 || y > 15) return; // check for bounds

           if ( board[y][x].b == 0 && !board[y][x].revealed) {
               board[y][x].revealed = true;
               revealZeros( x+1, y );
               revealZeros( x-1, y );
               revealZeros( x, y-1 );
               revealZeros( x, y+1 );
           } else {
               return;
           }
        }
Will Sherwood
  • 1,484
  • 3
  • 14
  • 27
0

Sorry for being blunt, but your code is plain crazy. Recursion is supposed to simplify your code, make it smaller and easier at the expense of more memory use. For example my recursive method in my MineSweeper implementation is:

  private void zeroValuePress(int row, int col) {
     int rMin = Math.max(row - 1, 0);
     int cMin = Math.max(col - 1, 0);
     int rMax = Math.min(row + 1, cellModelGrid.length - 1);
     int cMax = Math.min(col + 1, cellModelGrid[row].length - 1);
     for (int row2 = rMin; row2 <= rMax; row2++) {
        for (int col2 = cMin; col2 <= cMax; col2++) {
           cellModelGrid[row2][col2].pressedAction();
        }
     }
  }

It doesn't call itself directly but rather calls the pressedAction method of all cells around it, which changes their state to pressed. PropertyChangeListeners then kick in to get back to this code. The beauty of MVC.

The whole PropertyChangeListener:

private class CellModelPropertyChangeListener implements
        PropertyChangeListener {

  public void propertyChange(PropertyChangeEvent evt) {
     MineCellModel model = (MineCellModel) evt.getSource();
     int row = model.getRow();
     int col = model.getCol();

     if (evt.getPropertyName().equals(MineCellModel.BUTTON_PRESSED)) {
        if (cellModelGrid[row][col].isMineBlown()) {
           mineBlown();
        } else {
           buttonsRemaining--;
           if (buttonsRemaining <= 0) {
              JOptionPane.showMessageDialog(null, "You've Won!!!", "Congratulations", JOptionPane.PLAIN_MESSAGE);
           }
           if (cellModelGrid[row][col].getValue() == 0) {
              zeroValuePress(row, col);
           }
        }
     }
  }

  private void mineBlown() {
     for (int r = 0; r < cellModelGrid.length; r++) {
        for (int c = 0; c < cellModelGrid[r].length; c++) {
           MineCellModel model = cellModelGrid[r][c];
           if (model.isMined()) {
              model.setMineBlown(true);
           }
        }
     }

  }

  private void zeroValuePress(int row, int col) {
     int rMin = Math.max(row - 1, 0);
     int cMin = Math.max(col - 1, 0);
     int rMax = Math.min(row + 1, cellModelGrid.length - 1);
     int cMax = Math.min(col + 1, cellModelGrid[row].length - 1);
     for (int row2 = rMin; row2 <= rMax; row2++) {
        for (int col2 = cMin; col2 <= cMax; col2++) {
           cellModelGrid[row2][col2].pressedAction();
        }
     }
  }
}
Community
  • 1
  • 1
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
0

In a partner work for our university we used the following recursive method. Having 2 2D Arrays, one holding the solution and the other the visible field for the user output(console).

It also opens up any number fields next to zero fields(as in ms minesweeper). It gets messy around 1000x1000 fields(stackoverflow) - but that would be quite a long play anyway...

private void openSurroundingFields(int i, int e) {
  for (int j=i-1;j<i+2;j++)              
     for (int h=e-1;h<e+2;h++)
         if ( (j>-1) && (j<field.length) && (h>-1) && (h<field[0].length) && (field[j][h] != ZERO_SYMBOL)){
            if (mineField[j][h] == 0){ 
              field[j][h] = ZERO_SYMBOL;
              openSurroundingFields(j, h);
            } 
            else if (mineField[j][h] != -1)
               field[j][h] = Integer.toString(mineField[j][h]).charAt(0);     
         }
}
Nico Müller
  • 1,784
  • 1
  • 17
  • 37
0

Will Sherwood's answer may offer a solution with some minor changes but explaining these changes verbally will be confusing. Thus, I implemented both the UI and the algorithm below:

public class Minesweeper extends JFrame implements MouseListener {

    // I assume that the grid has to be square (LENGTH*LENGTH)
    private static final int LENGTH = 10, MINE_NUM = 10;
    private static Button[][] buttons = new Button[LENGTH][LENGTH];
    // Stores whether a box holds a mine or not
    private static boolean[][] places;
    // Stores indicator numbers that show how many mines around the box
    private static int[][] indicators;

    public static void main(String[] args) {
        Minesweeper ms = new Minesweeper(LENGTH, LENGTH);
        ms.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        ms.pack();
        ms.setVisible(true);

        places = getLocationOfMines(LENGTH, MINE_NUM);
        setMineIndicators(places); 
    }

    public Minesweeper(int rows, int cols) {
        Container pane = getContentPane();
        pane.setLayout(new GridLayout(rows, cols));
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                buttons[i][j] = new Button();
                buttons[i][j].addMouseListener(this);
                buttons[i][j].setPreferredSize(new Dimension(30, 30));
                buttons[i][j].setBackground(Color.LIGHT_GRAY);
                pane.add(buttons[i][j]);
            }
        }
    }

    private static boolean[][] getLocationOfMines(int length, int mineNum) {

        boolean[][] places = new boolean [length][length];
        ArrayList<Integer> listX = new ArrayList<Integer>();
        ArrayList<Integer> listY = new ArrayList<Integer>();
        for (int i = 0; i < length; i++) {
            listX.add(new Integer(i));
            listY.add(new Integer(i));
        }

        // Get randomized X, Y indices for hidden mines
        Collections.shuffle(listX);
        Collections.shuffle(listY);
        for (int i = 0; i < mineNum; i++) {
            places[listX.get(i)][listY.get(i)] = true;
        }

        return places;
    }

    private static int[] getButtonIndices(Button button) {
        int[] indices = new int[2];
        for (int i = 0; i < buttons.length; i++) {
            for (int j = 0; j < buttons[i].length; j++) {
                if (buttons[i][j] == button)
                {
                    indices[0] = i;
                    indices[1] = j;
                    return indices;
                }
            }
        }
        return null;
    }

    // Calculates how many mines around the box
    private static void setMineIndicators(boolean[][] places) {
        indicators = new int[places.length][places.length];
        int mineCount = 0;
        for(int i = 0; i < indicators.length; i++) {
            for (int j = 0; j < indicators[i].length; j++) {
                // 00 10 20 
                // 01 11 21
                // 02 12 22
                if (i-1 > -1 && j-1 > -1 && places[i-1][j-1]) {
                    mineCount++;
                }
                if (j-1 > -1 && places[i][j-1]) {
                    mineCount++;
                }
                if (i+1 < indicators.length && j-1 > -1 && places[i+1][j-1]) {
                    mineCount++;
                }
                if (i-1 > -1 && places[i-1][j]) {
                    mineCount++;
                }
                if (i+1 < indicators.length && places[i+1][j]) {
                    mineCount++;
                }
                if (i-1 > -1 && j+1 < indicators.length && places[i-1][j+1]) {
                    mineCount++;
                }
                if (j+1 < indicators.length && places[i][j+1]) {
                    mineCount++;
                }
                if (i+1 < indicators.length && j+1 < indicators.length && places[i+1][j+1]) {
                    mineCount++;
                }

                indicators[i][j] = mineCount;
                mineCount = 0;
            }
        }
    }

    private static void activateMineChain(int x, int y) {
        if (x < 0 || x > LENGTH - 1 || y < 0 || y > LENGTH - 1 || places[x][y] || buttons[x][y].getBackground() == Color.GREEN || indicators[x][y] == -1) {
            return;
        }

        if (indicators[x][y] != 0) {

            buttons[x][y].setLabel("" + indicators[x][y]);
            buttons[x][y].setBackground(Color.ORANGE);

            // If an indicator is visited, do not visit it again
            indicators[x][y] = -1;
            return;
        }

        else if (indicators[x][y] == 0) {
            buttons[x][y].setBackground(Color.YELLOW);
            indicators[x][y] = -1;

            activateMineChain(x, y-1);
            activateMineChain(x-1, y); 
            activateMineChain(x+1, y);
            activateMineChain(x, y+1);

            // Diagonals
            activateMineChain(x-1, y-1);
            activateMineChain(x+1, y-1); 
            activateMineChain(x-1, y+1);
            activateMineChain(x+1, y+1);

            return;
        }
        else {
            return;
        }

    }

    // Check the player is going to win or not (after each green "there is a mine" mark)
    private boolean checkWin() {
        for (int i = 0; i < LENGTH; i++) {
           for (int j = 0; j < LENGTH; j++) {
               if(places[i][j] && !(buttons[i][j].getBackground() == Color.GREEN)) {
                   return false;
               }
           } 
        }
        System.out.println("YOU WIN!");
        for (int i = 0; i < LENGTH; i++) {
           for (int j = 0; j < LENGTH; j++) {
               buttons[i][j].removeMouseListener(this);
           } 
        }
        return true;
    }

    @Override
    public void mouseClicked(MouseEvent me) {

    }

    @Override
    public void mousePressed(MouseEvent me) {
        if (me.getSource() instanceof Button) {
            int[] indices = getButtonIndices((Button) me.getSource());
            if (places[indices[0]][indices[1]] && buttons[indices[0]][indices[1]].getBackground() != Color.GREEN)
            {
                buttons[indices[0]][indices[1]].setBackground(Color.RED);
                System.out.println("YOU LOST!");
                for (int i = 0; i < LENGTH; i++) {
                    for (int j = 0; j < LENGTH; j++) {
                        buttons[i][j].removeMouseListener(this);
                    } 
                }
            }

            else {
                activateMineChain(indices[0], indices[1]);
            }
        }
    }

    // To handle "there is a mine" situation
    @Override
    public void mouseReleased(MouseEvent me) {
        if(SwingUtilities.isRightMouseButton(me)){
            int[] indices = getButtonIndices((Button) me.getSource());

            if (buttons[indices[0]][indices[1]].getBackground() != Color.GREEN) {
                buttons[indices[0]][indices[1]].setBackground(Color.GREEN);
                checkWin();
            }
            else {
                buttons[indices[0]][indices[1]].setBackground(Color.LIGHT_GRAY);
            }
        }
    }

    @Override
    public void mouseEntered(MouseEvent me) {
    }

    @Override
    public void mouseExited(MouseEvent me) {
    }
}

You can play the game just like Microsoft's Minesweeper. Right click for marking the mine slots (The clicked button will be green) and left click for opening the slots. If the slot has a mine, it will be red. Else, it will be yellow (if there is no indicator) or orange (if the slot has an indicator number that shows how many mines around that slot). Some parts might be hardcoded and it is possible to make the code more efficient with ease but I just show how we can use this algorithm with UI on the fly. Sorry about that.

Dorukhan Arslan
  • 2,676
  • 2
  • 24
  • 42