Boring weekend, write a minesweeping game in Java

Posted by lihman on Tue, 11 Jan 2022 03:24:49 +0100

It's boring at the weekend. I wrote a minesweeping program in Java. Speaking of it, it should be fun to write it at school. After all, it's fun to implement a small game by myself. To be honest, the core thing in the minesweeping program can only trigger the step of updating data when clicking.

Swing is out of date, but fun won't be out of date. Don't spray if you don't like it

Address of source code: https://github.com/Damaer/Gam...

Let's talk about the design inside:

  • Data structure design
  • Separate views and data as much as possible
  • Use BFS scan when clicking
  • Judge success or failure

Data structure design

In this program, for convenience, the global Data class Data class is used to maintain the Data of the whole game, which is directly set as a static variable, that is, only one game window can run at a time, otherwise there will be Data security problems. (just for convenience)

There are the following data (partial codes):

public class Data {
    // Game status
    public static Status status = Status.LOADING;
    // Minefield size
    public static int size = 16;
    // Number of Mines
    public static int numOfMine = 0;
    // Indicates whether there is thunder. 1: Yes, 0: No
    public static int[][] maps = null;
    // Is it accessed
    public static boolean[][] visited = null;
    // Number of surrounding mines
    public static int[][] nums = null;
    // Is it marked
    public static boolean[][] flags = null;
    // Last accessed block coordinates
    public static Point lastVisitedPoint = null;
    // Difficult mode
    private static DifficultModeEnum mode;
      ...
}

The data to be maintained are as follows:

  • Game status: start, end, success, failure, etc
  • Mode: simple, medium or difficult. This will affect the number of Mines automatically generated
  • Size of minefield: 16 * 16 small square
  • Number of thunder: it is related to mode selection and is a random number
  • Identify whether there is thunder in each block: the most basic data, which needs to be updated synchronously after generation
  • Indicates whether each square has been swept: it is not swept by default
  • The number of mines around each block: the result is calculated synchronously when it is generated. I don't want to calculate it after each click. After all, it is a data that won't be updated once and for all
  • Whether the identification box is marked: when sweeping mines, we use a small flag to mark the box, indicating that this is mine. When all mines are marked, it is successful
  • Square coordinates of the last visit: this can not be recorded, but it is different from other mine displays in order to represent the explosion effect, so it is recorded

Separate view from data

Try to follow a principle that views are separated from data or data changes to facilitate maintenance. We know that Swing is used to draw the graphical interface in Java. This thing is really difficult to draw. The view is more complex, but it can't draw anything.

The separation of view and data is also an excellent feature of almost all frameworks. It is mainly convenient for maintenance. If the view and data are combined to update data and operate the view, it will be messy. (of course, I wrote a rough version, just a simple distinction)

In this mine clearance program, there are basically clicking events, triggering changes in the data, calling the view refresh after the data changes, and maintaining the logic of the view rendering and the logic of data changes separately.

Each small box adds a click event, data Visit (x, y) is the data refresh and repaintBlocks() is the view refresh. The specific code will not be released. If you are interested, you can see the source code on Github:

new MouseListener() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        if (Data.status == Status.GOING) {
                            int c = e.getButton(); // Get the mouse button pressed
                            Block block = (Block) e.getComponent();
                            int x = block.getPoint_x();
                            int y = block.getPoint_y();
                            if (c == MouseEvent.BUTTON1) {
                                Data.visit(x, y);
                            } else if (c == MouseEvent.BUTTON3) {// It is inferred that the right mouse button is pressed
                                if (!Data.visited[x][y]) {
                                    Data.flags[x][y] = !Data.flags[x][y];
                                }
                            }
                        }
                        repaintBlocks();
                    }
}

It is a pity that there is a background url in each box that has not been extracted. This is changing data and should not be placed in the view:

public class Block extends JPanel {
    private int point_x;
    private int point_y;

    private String backgroundPath = ImgPath.DEFAULT;

    public Block(int x, int y) {
        this.point_x = x;
        this.point_y = y;
        setBorder(BorderFactory.createEtchedBorder());
    }
}

To reset the square background, you need to center, redraw, and override the void paintComponent(Graphics g) method:

    @Override
    protected void paintComponent(Graphics g) {
        refreshBackground();
        URL url = getClass().getClassLoader().getResource(backgroundPath);
        ImageIcon icon = new ImageIcon(url);
        if (backgroundPath.equals(ImgPath.DEFAULT) || backgroundPath.equals(ImgPath.FLAG)
                || backgroundPath.equals(String.format(ImgPath.NUM, 0))) {
            g.drawImage(icon.getImage(), 0, 0, getWidth(), getHeight(), this);
        } else {
            int x = (int) (getWidth() * 0.1);
            int y = (int) (getHeight() * 0.15);
            g.drawImage(icon.getImage(), x, y, getWidth() - 2 * x, getHeight() - 2 * y, this);
        }
    }

BFS scan

BFS, also known as breadth first search, is the core knowledge point in mine sweeping. That is, when clicking, if the current box is empty, it will trigger the scanning of the surrounding boxes. At the same time, if the surrounding boxes are empty, they will continue to recurse. I used breadth first search, that is, first put them in the queue, take them out, and then judge whether they are empty, Then add the blocks that meet the surrounding conditions and deal with them one by one.

Breadth first search is not expanded here. Its essence is to give priority to the data directly related to it, that is, the points around the box. This is why a queue is needed. We need a queue to save the traversal order.

    public static void visit(int x, int y) {
        lastVisitedPoint.x = x;
        lastVisitedPoint.y = y;
        if (maps[x][y] == 1) {
            status = Status.FAILED;
            // At the end of the game, expose all the thunder
        } else {
            // It's not ray
            Queue<Point> points = new LinkedList<>();
            points.add(new Point(x, y));
            while (!points.isEmpty()) {
                Point point = points.poll();
                visited[point.x][point.y] = true;
                if (nums[point.x][point.y] == 0) {
                    addToVisited(points, point.x, point.y);
                }
            }
        }
    }

    public static void addToVisited(Queue<Point> points, int i, int j) {
        int x = i - 1;
        while (x <= i + 1) {
            if (x >= 0 && x < size) {
                int y = j - 1;
                while (y <= j + 1) {
                    if (y >= 0 && y < size) {
                        if (!(x == i && j == y)) {
                            // Never visited and not ray
                            if (!visited[x][y] && maps[x][y] == 0) {
                                points.add(new Point(x, y));
                            }
                        }
                    }
                    y++;
                }
            }
            x++;
        }
    }

It is worth noting that if there is no thunder around the surrounding point, it will continue to expand, but as long as there is thunder around, it will stop expanding and only display numbers.

Judge success or failure

When a mine is dug, it fails and all thunderstorms will be exposed. In order to show that the points we have dug currently have explosive effects, we record the points of the previous operation. After refreshing the view, a pop-up prompt will pop up:

To judge success, you need to traverse all mines once to judge whether they are marked. This is the rule I simply think. If you forget whether minesweeping is like this, or you can dig out all other non minefields, success is also possible.

summary

Minesweeping is a simple game. You can try it when you're bored, but Java Swing is really difficult to use. You want to find a framework for data-driven view modification, but it doesn't seem to be available. Let's simply implement it. In fact, I spend most of my time looking for icons and testing the UI. There is not much core code.

Here we recommend the icon website: https://www.iconfont.cn/ , even for mine sweeping without any technical content, it's interesting to write about it.

[about the author]:
Qin Huai, the official account of Qin Huai grocery store, author's personal website: http://aphysia.cn , the road of technology is not for a while, with high mountains and long rivers. Even if it is slow, it will not stop.

Sword finger Offer all questions PDF

Open source programming notes

Topics: Java data structure Game Development