-2

I am just trying to construct a gameboard with tiles to work with. I got it to work by painting the tile into rows and columns with BufferedImage, but I am going to need to constantly update the GUI as the game is played.

So I tried to add the tile image to an ImageIcon, then to a JLabel array so that I could set the position. After that I would add the labels to the gui JPanel, but they had these default gaps between them.

Am I approaching this incorrectly? I am very new to Swing and Java

    public static void main (String[] args) throws IOException{
    int NUMROWS = 10;
    int NUMCOLS = 10;
    int NUMMINES = 10;

    int[] mineList = GameBoard.setMines(NUMMINES, NUMROWS, NUMCOLS);
    int[][] cellNums = GameBoard.setCellNum(NUMROWS, NUMCOLS);
    boolean[][] mineField = GameBoard.getMineField(mineList, cellNums);
    int[][] adjacentMineVals = GameBoard.getAdjacentMineVals(cellNums, mineField);
    ImageIcon img = new ImageIcon(ImageIO.read(new File("GraphicFile\\Cell.png")));
    JLabel[][] label = new JLabel[NUMROWS][NUMCOLS];

    for (int i = 0; i < NUMROWS; i++){
        for (int j = 0; j < NUMCOLS; j++){
            label[i][j] = new JLabel();
            label[i][j].setIcon(img);
            label[i][j].setLocation(i*img.getIconHeight(), j*img.getIconWidth());
        }
    }
    JFrame frame = buildFrame();
    int fX = 2*frame.getInsets().left;
    int fY = (frame.getInsets().top + frame.getInsets().bottom);

    JPanel GUI = new JPanel();
    GUI.setSize(NUMCOLS*img.getIconWidth(), NUMROWS*img.getIconHeight());
    for (int i = 0; i < NUMCOLS; i++){
        for (int j = 0; j < NUMROWS; j ++){
            GUI.add(label[i][j]);

        }
    }
    frame.setSize(NUMCOLS*img.getIconWidth() + fX, NUMROWS*img.getIconHeight() + fY);
    frame.add(GUI);

}

public static JFrame buildFrame(){
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    return frame;
}

Here's what it gives me https://i.stack.imgur.com/rX3yM.png

Update, to new version. Previous problem has been solved by using a grid and putting the images into the buttons as icons

Here is what I have. It is not putting the label with the text onto the button that has been pressed. I debugged, and its receiving input for coords and for text, but its just not painting it onto the panel

 public MinesweeperGraphics() throws IOException {
    GUI.setOpaque(false);

    for (int i = 0; i < NUMROWS; i++){
        for (int j = 0; j < NUMCOLS; j++){
            buttons[i][j] = new JButton();
            buttons[i][j].setIcon(tileUp);
            buttons[i][j].setBorder(null);
            buttons[i][j].addActionListener(this);
            buttons[i][j].setPressedIcon(tilePressed);

            GUI.add(buttons[i][j]);
        }
    }

    frame.add(GUI, BorderLayout.CENTER);
    frame.add(reset, BorderLayout.NORTH);
    reset.addActionListener(this);
    frame.setResizable(false);
    frame.pack();
    GUI.setLayout(null);

}

@Override
public void actionPerformed(ActionEvent e) {
    if (e.getSource().equals(reset)){
        for (int i = 0; i < NUMROWS; i++){
            for (int j = 0; j < NUMCOLS; j++){
                buttons[i][j].setIcon(tileUp);
            }
        }
    }else {
        for (int i = 0; i < buttons.length; i++) {
            for (int j = 0; j < buttons.length; j++) {
                if (e.getSource().equals(buttons[i][j])){
                    if (mineField[i][j] == false){
                        buttons[i][j].setIcon(tileEmpty);
                        numberText.setOpaque(false);
                        numberText.setSize(buttons[i][j].getWidth(), buttons[i][j].getHeight());
                        numberText.setText("a");
                        numberText.setLocation(buttons[i][j].getLocation());
                        GUI.add(numberText);

                    } else if (mineField[i][j] == true){
                        buttons[i][j].setIcon(tileExplodedMine);
                    }
                    //isn't working here
                    buttons[i][j].setEnabled(false);
                }
            }
        }
    }
}
Austen
  • 15
  • 4
  • 2
    The best way to build a Mine Sweeper game would be to put buttons with an action listener into a grid layout. The buttons will respond to both keyboard and mouse input & can display icons. The grid layout can organise them on-screen. – Andrew Thompson Jan 20 '17 at 20:41
  • 1
    General advice: 1) For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). 2) One way to get image(s) for an example is to hot link to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). – Andrew Thompson Jan 20 '17 at 20:42
  • @AndrewThompson What kind of buttons are possible? Can they show different .png images instead of their default? – Austen Jan 20 '17 at 20:59
  • *"What kind of buttons are possible? Can they show different .png images instead of their default?"* [This GUI](http://stackoverflow.com/a/10862262/418556) is made from four buttons (and 5 labels). – Andrew Thompson Jan 20 '17 at 21:11
  • @AndrewThompson That code is a little over my head. I get that you put the image over the 4 buttons so that it appears that they correspond to the compass button locations. So would I create a grid of JButtons and then place a grid of ImageIcons over it? That is still where my problem lies. I can't figure out how to get rid of those gaps between the JLabels. – Austen Jan 20 '17 at 21:33
  • I'd start be separating your model and views into seperate class. Have a look at [Model-View-Controller](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) for more details – MadProgrammer Jan 20 '17 at 21:34
  • @MadProgrammer Right, I have the model that creates the mines and board and checks adjacent mines in my other class. I am trying to create a view, but I have virtually no experience with Swing. I wanted to create the view before I get into actionlisteners. Maybe I should just make a grid of buttons. – Austen Jan 20 '17 at 21:39
  • @Austen I can think of at least 3 basic ways to do it, using `JButton`s and a `GridLayout` or `GridBagLayout` would the simplest – MadProgrammer Jan 20 '17 at 21:58
  • *"..I get that you put the image over the 4 buttons so that it appears that they correspond to the compass button locations"* No! The image (and it actually uses two different images, one for the 'normal' button state, the other with red border for the focused state) as the image icons for the buttons themselves. – Andrew Thompson Jan 20 '17 at 22:20
  • @AndrewThompson Shoot, well I have questions about that then. So you take a subimage from the compass button image, use it as the icon for the normal button. Then take another subimage and make a red outline to use as the icon for the pressed button correct? What is the purpose of the "count" variable? – Austen Jan 20 '17 at 22:32
  • *"What is the purpose of the "count" variable?"* Starting from the upper left, then going left to right from top to bottom, every 2nd position is a button (4 of them), while every other position is a label (5 of them). The `count` attribute makes it easy to tell whether there should be a button or a label using the test seen in `count%2==1` - which is `true` for odd numbers & `false` for even numbers. – Andrew Thompson Jan 20 '17 at 22:37
  • @AndrewThompson Interesting, so it kind of takes apart the image into 9 subimages and reassigns them to either their respective button or label. In my code then, if I use a gridlayout on the JPanel and then add the labels with the images, will it get rid of the gaps? Obviously I will be using a grid of buttons now, but just to see if this was my issue. – Austen Jan 20 '17 at 22:41
  • Yep. You seem to have gained an understanding of it. :) The important part of linking to that example was to show that the button can become effectively 'invisible' and the user only sees the icon (the sub-image) that is set for it. – Andrew Thompson Jan 20 '17 at 22:44
  • Well that worked really well! I am on my way to linking my generated board to the graphics. Question though. I have a loop that changes a pressed button to a new icon, aka an empty cell. How can I remove the actionlistener or effectively make the button unpressable after that? I tried burront.removeActionListener(this) within the actionPerformed void, but it isn't working – Austen Jan 20 '17 at 23:50
  • 1
    Tip, you can tag someone so they get notified of your reply by adding an `@` followed by their name, like @AndrewThompson, you can call the method `setEnabled(false);` to disable your `JButton`, I'm sorry I'm on the phone and can't post the link to the docs right now, but you can look for it in Google – Frakcool Jan 21 '17 at 00:33
  • @Frakcool oh awesome thanks! So, that seems to take the button out of the panel, since then it loses it's icon that shows that it has been pressed. I was hoping to be able to make it unpressable, but keep the icon. Also, I am trying to overlay a text showing a digit on top of the pressed button, but I can't get the JLabel to show up on the panel, even after I changed the layout back to null. – Austen Jan 21 '17 at 00:44
  • 1
    For better help sooner please post a valid [mcve], *even after I changed the layout back to null* that's the worse you could do, please post your code – Frakcool Jan 21 '17 at 00:57
  • @Frakcool I posted as an answer – Austen Jan 21 '17 at 01:36
  • @Austen NO! Don't post as an answer unless it answers your question, instead [edit] your question and add it there – Frakcool Jan 21 '17 at 01:45
  • @Frakcool there. any thoughts? – Austen Jan 21 '17 at 02:00
  • @Frakcool so I fixed it by changing the pressed icon to null after it had been pressed. But now I am trying to figure out a good way to add a text digit to the tiles that have been pressed and that have a certain number of mines around them. But i can't seem to add a jlabel to the panel after all the button have been added – Austen Jan 21 '17 at 02:37
  • Once a button is pressed, I'd add an icon that shows the number. A single method can produce an icon with text (the number) drawn on it. – Andrew Thompson Jan 21 '17 at 06:42

1 Answers1

1

The best way to build a Mine Sweeper game would be to put buttons with an action listener into a grid layout. The buttons will respond to both keyboard and mouse input & can display icons. The grid layout can organise them on-screen.

Following is an MCVE of buttons in a GridLayout. Different to as described above is that it:

  • Does not add action listeners
  • Uses a MineFieldModel
  • Uses text instead of icons.

enter image description here

import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;

public class MineSweeper {

    private JComponent ui = null;
    private MineFieldModel mineFieldModel;
    Color[] colors = {
        Color.BLUE,
        Color.CYAN.darker(),
        Color.GREEN.darker(),
        Color.YELLOW.darker(),
        Color.ORANGE.darker(),
        Color.PINK.darker(),
        Color.MAGENTA,
        Color.RED
    };
    public final static String BOMB = new String(Character.toChars(128163));
    JButton[][] buttons;
    int size = 16;

    MineSweeper() {
        initUI();
    }

    public final void initUI() {
        if (ui != null) {
            return;
        }

        ui = new JPanel(new BorderLayout(4, 4));
        ui.setBorder(new EmptyBorder(4, 4, 4, 4));

        mineFieldModel = new MineFieldModel(16, 40);

        JPanel mineFieldContainer = new JPanel(new GridLayout(
                size, size));
        ui.add(mineFieldContainer, BorderLayout.CENTER);
        int in = 5;
        Insets insets = new Insets(in, in, in, in);
        Font f = getCompatibleFonts().firstElement().deriveFont(16f);
        buttons = new JButton[size][size];
        for (int ii = 0; ii < size; ii++) {
            for (int jj = 0; jj < size; jj++) {
                JButton b = new JButton();
                b.setMargin(insets);
                b.setFont(f);
                b.setText("?");
                if (mineFieldModel.isExposed(ii, jj)) {
                    if (mineFieldModel.isBomb(ii, jj)) {
                        b.setForeground(Color.red);
                        b.setForeground(Color.BLACK);
                        b.setText(BOMB);
                    } else if (mineFieldModel.countSurroundingMines(ii, jj) > 0) {
                        int count = mineFieldModel.countSurroundingMines(ii, jj);
                        if (count > 0) {
                            b.setForeground(colors[count - 1]);
                            b.setText("" + count);
                        }
                    } else {
                        b.setText("");
                    }
                }
                mineFieldContainer.add(b);
            }
        }
    }

    private static Vector<Font> getCompatibleFonts() {
        Font[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
        Vector<Font> fontVector = new Vector<>();

        for (Font font : fonts) {
            if (font.canDisplayUpTo("12345678" + BOMB) < 0) {
                fontVector.add(font);
            }
        }

        return fontVector;
    }

    public JComponent getUI() {
        return ui;
    }

    public static void main(String[] args) {
        Runnable r = () -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception useDefault) {
            }
            MineSweeper o = new MineSweeper();

            JFrame f = new JFrame(o.getClass().getSimpleName());
            f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            f.setLocationByPlatform(true);

            f.setContentPane(o.getUI());
            f.pack();
            f.setMinimumSize(f.getSize());

            f.setVisible(true);
        };
        SwingUtilities.invokeLater(r);

    }
}

class MineFieldModel {

    public int size;
    /**
     * Records bomb locations.
     */
    boolean[][] mineField;
    /**
     * Records whether this location has been exposed.
     */
    boolean[][] fieldPlaceExposed;
    int numberMines;
    Random r = new Random();

    MineFieldModel(int size, int numberMines) {
        this.size = size;
        this.numberMines = numberMines;

        mineField = new boolean[size][size];
        fieldPlaceExposed = new boolean[size][size];
        ArrayList<Point> locations = new ArrayList<>();
        for (int ii = 0; ii < this.size; ii++) {
            for (int jj = 0; jj < size; jj++) {
                mineField[ii][jj] = false;
                // must change this to false for the actual game.
                fieldPlaceExposed[ii][jj] = true;
                Point p = new Point(ii, jj);
                locations.add(p);
            }
        }
        Collections.shuffle(locations, r);
        for (int ii = 0; ii < numberMines; ii++) {
            Point p = locations.get(ii);
            mineField[p.x][p.y] = true;
        }
    }

    public boolean isBomb(int x, int y) {
        return mineField[x][y];
    }

    public boolean isExposed(int x, int y) {
        return fieldPlaceExposed[x][y];
    }

    public int getSize() {
        return size;
    }

    public int countSurroundingMines(int x, int y) {
        int lowX = x - 1;
        lowX = lowX < 0 ? 0 : lowX;
        int highX = x + 2;
        highX = highX > size ? size : highX;

        int lowY = y - 1;
        lowY = lowY < 0 ? 0 : lowY;
        int highY = y + 2;
        highY = highY > size ? size : highY;

        int count = 0;
        for (int ii = lowX; ii < highX; ii++) {
            for (int jj = lowY; jj < highY; jj++) {
                if (ii != x || jj != y) {
                    if (mineField[ii][jj]) {
                        count++;
                    }
                }
            }
        }

        return count;
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433