简体   繁体   中英

Detect adjacent elements in matrix (SameGame)

SameGame example

Let's take for example a SameGame board.

Two blocks are adjacent if they have one side in common. A group is a set of at least two blocks, all of the same type and each adjacent to at least one other member of the group. When the mouse hovers over a block that is part of a group, the whole group should be visually highlighted.

So, take for example a matrix such as this one:

    public void initGrid() {
    int i,j;
    for (i=0; i<row; i++) {
        for (j=0; j<col; j++) {
        int randomBlock=this.random.nextInt(3);
            switch(randomBlock) {
                case 0:
                    charTab[i][j]='R';
                    break;
                case 1:
                    charTab[i][j]='V';
                    break;
                case 2:
                    charTab[i][j]='B';
                    break;
            }
            this.blockTab[i][j] = new Block(j, i, charTab[i][j]);
        }
    }
}

How can I find a set when the mouse hovers?

I thought about recursion but to be honest I'm quite lost as to how. BFS seemed like something I could do, but it seems too complex for such a "simple" thing.

Thanks for the help and sorry !

Consider a simple case of the array:

|---|---|---|
| 1 | 2 | 3 |
|---|---|---|
| 4 | 5 | 6 |
|---|---|---|
| 7 | 8 | 9 |
|---|---|---|

The 2D array can be thought of as one continuous array.

[1, 2, 3][4, 5, 6][7, 8, 9]

Essentially same as

[1, 2, 3, 4, 5, 6, 7, 8, 9]

Notice how if we consider the element 5 and index variable as i , the element to the left of it would be at index

i - 1

the element to the left of it would be at index

i + 1

the element to the north of it would be at index

i - 3

the element to the south of it would be at index

i + 3

If any of these results are < 0 or > arrayLength - 1 , then there exists no such adjacent spot.

The image you provided displays four (4) different colored circles within the board, Red, Blue, Green, and Yellow. If we were to represent each of these colors with a specific integer value, let's say:

  • Red = 1
  • Blue = 2
  • Green = 3
  • Yellow = 4

then your matrix consists of randomly placed integer values inclusively from 1 to 4. It might look something like this example :

在此处输入图像描述

To see which values are adjacent to each other based on what value the mouse pointer is currently hovering over then you will need to acquire the index value for the grid block that particular value resides in.

So, a 2D array is first created with randomly generated values from 1 to 4 (which represent colors). Based from this matrix a grid of components is created in the game board Window but instead of numerical values in each component, a representative color is in its place. Here is an example:

在此处输入图像描述

Of course the two images shown here above don't have a matching color layouts and this is because the demo runnable application provided below displays a randomly generated matrix for the task upon each load but I think you get the idea here. Both do contain the same matrix size (10 rows x 16 columns). You could create whatever matrix size you want.

Looking at the above runnable demo, the component used to make up the colored grid within the JFrame window is a JPanel utilizing a GridLayout layout manager set to the desired grid size (10 x 16) and the grid itself is an Array of JLabels with the name property of each JLabel element set to the corresponding matrix color for that specific cell. This way, no matter what, you will always be able to get the actual color randomly designated for each JLabel cell.

In order to determine which cell (JLabel) the mouse pointer has entered, you will need a class global mouse handler which each JLabel in the JLabel Array will be assigned to. In the code below, this is provided as a inner class named GridMouseHandler which extends MouseAdapter and overrides the mouseEntered and mouseExited events.

From the mouseEntered event is where the real work-horse for this application is called, the getMatrixConnectingCells() method. It is this method that retrieves all adjacent neighbors to the cell the mouse pointer has entered and any valid neighbors to those neighbors until no more are neighbors are available. I highly recommend you read the attached JavaDoc for this method very carefully since it can be used in many different ways by changing various optional values.

Here is the runnable application:

public class SomeGame {

    private final java.util.List<String> cellNeigborsList = new java.util.ArrayList<>();
    
    private java.awt.Color[] cellColors = {java.awt.Color.RED, java.awt.Color.BLUE,
        java.awt.Color.GREEN, java.awt.Color.YELLOW};
    private java.awt.Color[] highlightedCellColors = {java.awt.Color.decode("#FFCCCC"), java.awt.Color.decode("#CCCCFF"),
        java.awt.Color.decode("#D6F5D6"), java.awt.Color.decode("#FFFFCC")};
    
    private int numberOfColors = cellColors.length;
    private int matrixRows = 10;
    private int matrixColumns = 16;
    private String desiredSeekDirections = "01011010";   // Default - Top, Bottom, Left, and Right of cells.
    private String[] connectingCellsList = null;
    private int maxNeighborsToGet = -1;  // Default
    private int minNeighborsToGet = 2;   // Default

    public int[][] matrix = new int[matrixRows][matrixColumns];
    private javax.swing.JLabel[][] gridBlock;

    
    public static void main(String[] args) {
        // Started this way to avoid the need for statics
        new SomeGame().startApp(args);
    }

    private void startApp(String[] args) {
        initializeGame();
    }

    private void initializeGame() {
        java.util.Random rand = new java.util.Random();
        GridMouseHandler gridMouseHandler = new GridMouseHandler();

        // Create Window
        javax.swing.JFrame gameWindow = new javax.swing.JFrame("Some Game Window");
        gameWindow.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
        gameWindow.setLayout(new java.awt.GridLayout(1, 1));
        gameWindow.setAlwaysOnTop(true);
        gameWindow.setPreferredSize(new java.awt.Dimension(600, 500));

        // Create Game Board:
        javax.swing.JPanel gameBoard = new javax.swing.JPanel();
        gameBoard.setLayout(new java.awt.GridLayout(matrixRows, matrixColumns));
        gridBlock = new javax.swing.JLabel[matrixRows][matrixColumns];
        javax.swing.border.Border border = javax.swing.BorderFactory
                .createLineBorder(java.awt.Color.GRAY, 1);
        for (int i = 0; i < gridBlock.length; i++) {
            for (int j = 0; j < gridBlock[i].length; j++) {
                gridBlock[i][j] = new javax.swing.JLabel();
                gridBlock[i][j].setOpaque(true);
                int rndColorIndex = rand.nextInt((numberOfColors - 0));
                matrix[i][j] = rndColorIndex;
                java.awt.Color color = cellColors[rndColorIndex];
                gridBlock[i][j].setBackground(color);
                gridBlock[i][j].setBorder(border);
                gridBlock[i][j].setName(String.valueOf(rndColorIndex));
                gridBlock[i][j].addMouseListener(gridMouseHandler);
                gameBoard.add(gridBlock[i][j]);
            }
        }
        gameWindow.add(gameBoard);
        gameWindow.pack();
        gameWindow.setVisible(true);
        gameWindow.setLocationRelativeTo(null);
    }
     
    
    /**
     * In this JavaDoc for the getMatrixConnectingCells() method, a <b>Cell</b> is 
     * considered to be any 2D Array (matrix) element location determined by its 
     * Row and Column index values. This particular method deals with only integer 
     * (int) cell (element) values which can consist of any integer numerical number 
     * which inclusively falls between Java's Integer.MIN_VALUE and Integer.MAX_VALUE.<br>
     * <p>
     * <b><u>NOTE:</u></b> For this method to function, there must be a class member 
     * {@code List<String>} variable named <b>cellNeigborsList</b> declared that is 
     * fully accessible to this method wherever it may reside. For example:<pre>
     *
     * {@code
     *      public MyClass {
     * 
     *          List<String> cellNeigborsList = new ArrayList<>();
     * 
     *          public static void main(String[] args) {
     *              // ... main() method code here (if any) ... //
     *          }
     * 
     *          public String[] getMatrixConnectingCells(int[][] matrix, 
     *                               int matrixCell_X, int matrixCell_Y, 
     *                               Object... options) {
     *              // ... Method code here ... //
     *          }
     *         
     *          // ... The rest of your class code here ... //
     *      }}
     * </pre></p>
     * 
     * This method recursively walks through a matrix (2D) Array starting from a
     * supplied matrix cell's row and column index values, retrieves its data
     * value, then checks to see if a neighboring cell either horizontally,
     * vertically, or diagonally (depends upon the seek directions chosen)
     * contains the very same value. All cells detected as a neighbor are
     * checked for more neighbors until no more valid neighbors can be
     * found.<br>
     * <p>
     * All neighbor cells detected have their respective row/column index values
     * (including the start cell row/column index values) which are semicolon
     * (;) delimited, placed into a String[] Array and returned for further
     * processing by your particular application.
     *
     *
     * @param matrix (Two Dimensional Array of type int) The 2D array to check for 
     * neighbors in.<br>
     * 
     * @param matrixCell_X (int) The starting cell Row index value.<br>
     * 
     * @param matrixCell_Y (int) The starting cell Column index value.<br>
     * 
     * @param options      (Optional - Three Parameters):<pre>
     *
     *      seekDirections - (Binary String) Default is: "01011010".
     *                       Each digit (0 or 1) within the binary string represents
     *                       a seek direction (where this method is to look for a
     *                       neighbor). There are 8 different directions where this 
     *                       method can seek for a neighboring Cell. These <b>Seek 
     *                       Directions</b> are the following (in order):
     *            
     *                                 1)     Top/Left
     *                                 2)     Top
     *                                 3)     Top/Right
     *                                 4)     Left
     *                                 5)     Right
     *                                 6)     Bottom/Left
     *                                 7)     Bottom
     *                                 8)     Bottom/Right.
     * 
     *                       A 0 indicates 'don't use that direction'.
     *                       A 1 indicates 'use that direction'.
     *     
     *                       Knowing this, you can then see that the default binary 
     *                       string ("01011010") will seek in the following directions:
     * 
     *                                 Top, Left, Right, Bottom
     *                       
     *                       and, no seeking is done in any of the diagonal directions.
     * 
     *                       If you want to seek in <b>all</b> directions then you can
     *                       supply: "11111111"
     * 
     *   minNeighborsToGet - (Integer) Default is 2 which informs this method to 
     *                       retrieve <b>at least</b> 2 neighboring Cell Row/Column 
     *                       index values (one of which includes the supplied Cell). 
     *                       If <b><i>any value less than</i></b> 2 is provided then only the 
     *                       Start Cell Row/Column index values which is supplied 
     *                       to the method will be returned. If any other integer 
     *                       value greater than 1 is supplied then there must be a 
     *                       <b>minimum</b> of that supplied number of Cell Row/Column 
     *                       index value neighbor sets encountered in order to be 
     *                       returned.
     * 
     *   maxNeighborsToGet - (Integer) Default is -1 which informs this method to 
     *                       retrieve <b>all</b> neighboring Cell Row/Column index values. 
     *                       If 0 is provided then only the Start Cell Row/Column 
     *                       index values which is supplied to the method will be
     *                       returned. If any other integer value greater than 0 is 
     *                       supplied then only a <b>maximum</b> of that supplied 
     *                       number of Cell Row/Column index value neighbor sets 
     *                       will ever be retured even if it was detected that more 
     *                       neighbors exist.</pre>
     *
     * @return (String[] Array) A String Array containing semicolon delimited sets 
     * of Row;Column index values for the detected neighboring cells. The supplied 
     * <b>starting cell<b> is also part of the returned collection of cells. It will be 
     * the first cell index value set contained within the returned String Array.
     */
    public String[] getMatrixConnectingCells(int[][] matrix, int matrixCell_X, int matrixCell_Y, Object... options) {
        /* In a matrix there can be the possibility of 8 neighbors containing 
           the same value. We need to check if a specific neighbor is actually
           within bounds before checking its value.    */
        String seekDirections = "01011010";
        int[][] availableDirections = {
                    {-1, -1},   // Top/Left
                    {0, -1},    // Top
                    {1, -1},    // Top/Right
                    {-1, 0},    // Left
                    {1, 0},     // Right
                    {-1, 1},    // Bottom/Left
                    {0, 1},     // Bottom
                    {1, 1}      // Bottom/Right  
                };
        int minNeighbors = 2;  // Default - 2 neighbors (one is the supplied start cell)
        int maxNeighbors = -1;  // Default - All Neighbors.
        if (options.length > 0) {
            if (options.length >= 1 && options[0] != null) {
                String seekString = options[0].toString();
                if (seekString.length() != 8 || !seekString.matches("[01]+")) {
                    throw new IllegalArgumentException("getMatrixConnectingCells() Method "
                            + "Error! Invalid argument (" + seekString + ") passed "
                            + "to the directionsToSeek optional parameter! An 8 bit binary "
                            + "string is expected!" + System.lineSeparator());
                }
                seekDirections = seekString;
            }
            if (options.length >= 2 && options[1] != null) {
                minNeighbors = (int)options[1];
            }
            if (options.length >= 3 && options[2] != null) {
                maxNeighbors = (int)options[2];
            }
        }

        int[][] directions = new int[seekDirections.replace("0", "").length()][2];
        int indexCounter = 0;
        for (int i = 0; i < seekDirections.length(); i++) {
            String str = seekDirections.substring(i, i + 1);
            if (str.equals("1")) {
                directions[indexCounter] = availableDirections[i];
                indexCounter++;
            }
        }

        // The start cell
        String startCell = String.valueOf((matrixCell_X)) + ";" + String.valueOf((matrixCell_Y));
        if (!this.cellNeigborsList.contains(startCell)) {
            this.cellNeigborsList.add(startCell);
        }

        for (int[] d : directions) {
            int dx = d[0];  // Rows
            int dy = d[1];  // Columns
            if ((matrixCell_X + dx) >= 0 && (matrixCell_X + dx) < (matrix.length)
                    && (matrixCell_Y + dy) >= 0 && (matrixCell_Y + dy) < (matrix[0].length)
                    && matrix[matrixCell_X + dx][matrixCell_Y + dy] == matrix[matrixCell_X][matrixCell_Y]) {
                // A connected cell with the same elemental value.
                String neighbor = String.valueOf((matrixCell_X + dx)) + ";" + String.valueOf((matrixCell_Y + dy));
                if (!this.cellNeigborsList.contains(neighbor)) {
                    this.cellNeigborsList.add(neighbor);
                    String[] tmp = getMatrixConnectingCells(matrix, matrixCell_X + dx, matrixCell_Y + dy, 
                                                            seekDirections, minNeighbors, maxNeighbors);
                    if (tmp != null) {
                        for (String str : tmp) {
                            if (!this.cellNeigborsList.contains(str)) {
                                this.cellNeigborsList.add(str);
                            }
                        }
                    }
                }
            }
        }
        
        if (maxNeighbors <= -1) {
            if (this.cellNeigborsList.size() >= minNeighbors) {
                return this.cellNeigborsList.toArray(new String[this.cellNeigborsList.size()]);
            }
        }
        else if (maxNeighbors == 0) {
            return new String[] {matrixCell_X + ";" + matrixCell_Y};
        }
        else if (maxNeighbors > 0) {
            if (maxNeighbors > this.cellNeigborsList.size()) {
                if (this.cellNeigborsList.size() >= minNeighbors) {
                    return this.cellNeigborsList.toArray(new String[this.cellNeigborsList.size()]);
                }
            }
            else {
                String[] res = new String[maxNeighbors];
                for (int idx = 0; idx < maxNeighbors; idx++) {
                    res[idx] = this.cellNeigborsList.get(idx);
                }
                if (res.length >= minNeighbors) {
                    return res;
                }
            }
        }
        return null;    
    }
    
    // Inner Class - A Mouse Handler for ALL cells displayed within the 
    // gameBoard JPanel that fills the gameWindow JFrame. 
    private class GridMouseHandler extends java.awt.event.MouseAdapter {

        @Override
        public void mouseEntered(java.awt.event.MouseEvent evt) {
            javax.swing.JLabel source = (javax.swing.JLabel) evt.getSource();
            for (int i = 0; i < gridBlock.length; i++) {
                for (int j = 0; j < gridBlock[i].length; j++) {
                    if (gridBlock[i][j] == source) {
                        // Mouse pointer has entered this cell...
                        cellNeigborsList.clear();
                        // Get neigboring cells to the one the pointer has entered.
                        connectingCellsList = getMatrixConnectingCells(matrix, i, j, 
                                               desiredSeekDirections, minNeighborsToGet, maxNeighborsToGet);
                        // System.out.println(java.util.Arrays.toString(connectingCellsList).replaceAll("[\\[\\]]", ""));
                        
                        // Highlight all the found neighboring cells.
                        if (connectingCellsList != null && connectingCellsList.length > 1) {
                            for (String cells : connectingCellsList) {
                                int x = Integer.valueOf(cells.split(";")[0]);
                                int y = Integer.valueOf(cells.split(";")[1]);
                                gridBlock[x][y].setBackground(highlightedCellColors[matrix[i][j]]);
                            }
                        }
                    }
                }
            }
        }

        @Override
        public void mouseExited(java.awt.event.MouseEvent evt) {
            // Change all the highlighted cells back to their originl color
            // when mouse pointer exits all neighboring cells.
            if (connectingCellsList != null) {
                for (String cells : connectingCellsList) {
                    int x = Integer.valueOf(cells.split(";")[0]);
                    int y = Integer.valueOf(cells.split(";")[1]);
                    gridBlock[x][y].setBackground(cellColors[matrix[x][y]]);
                }
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM