Check if there are at least 4 consecutive elements of the Same Color in the 2D array aligned either Horizontally, Vertically or Diagonally

Viewed 79

I want to check the diagonal lines and horizontal lines of my 2D-enum-array.

The 2D-enum-array looks like this:

private Zelle[][] board = new Zelle[width][height];

My enum looks like this:

public enum Zelle {
        RED("RED   "),
        YELLOW("YELLOW"),
        Empty("EMPTY ");
}

The function isConnect4() can already check the vertical lines.

public void isConnect4(int col) {
    for (int i = 0; i < height; i++) {
        //vertical
        switch (board[col][i]) {
            case RED -> --counter;
            case YELLOW -> ++counter;
            case Empty -> counter = 0;
        }
    }
    if (Math.abs(counter) == 4  ) {
        System.out.println("Es liegt ein Gewinner vor");
        winner = true;
    }
}

I fill my 2d array with the makeMove(int col) method and always check if there is a winning line:

public void makeMove(int col) {
    if (start && !winner && (IntStream.rangeClosed(0, 6).boxed().toList().contains(col))
            && board[col][0].equals(Zelle.Empty)) {
        board[col][IntStream.rangeClosed(0, 6).map(i -> 5 - i).
                filter(i -> board[col][i].equals(Zelle.Empty)).findFirst().getAsInt()] = currentPlayer();
        next();

        System.out.println(printBoard());
    } else if (!(board[col][0].equals(Zelle.Empty))) {
        System.out.println("You cannot move there, please pick another Column");
    } else if (!start){
        System.out.println("Game has not started yet");
    }
    else {
        System.out.println("Game Over! Winner is: " + currentplayer );
    }
    isConnect4(col);
}

For the horizontal, I need to get the current position and look 3 to the left and 3 to the right. For the diagonal, I need to look into 4 different position: left top, left bottom, right top and right bottom. Maybe something like this:

//pseudocode
//this for 4 different directions(left top, left bottom, right top and right bottom.)
for(int i = 0; i <3; i++){
    switch (board[col+i][currentPosition]) {
        case RED -> --counter;
        case YELLOW -> ++counter;
        case Empty -> counter = 0;
    }

What is the best approach for this? It would also be nice to use Streams (if that's possible).

1 Answers

If I understood correctly, the winning condition the winning condition is when there are at least 4 consecutive non-empty cells having the same color.

And these cells might be aligned either horizontally, vertically or diagonally. We can define a nested array representing each of these directions as a static field.

public static final int[][][] DIRECTIONS =
    {{{0, -1}, {0, 1}},    // horizontal -
     {{-1, 0}, {1, 0}},    // vertical |
     {{-1, -1}, {1, 1}},   // diagonal \
     {{1, -1}, {-1, 1}}};  // diagonal /

Each subarray in the DIRECTIONS array represents a shift which needs to be applied to the row and the col while "moving" in a particular direction backward ("left" subarray) and forward ("right" subarray).

When the player makes a move, the state of the board changes and at the end of the move we need to check if there's a winning combination on the board. It makes sense to check only combinations of cell that start from the current cell (the cell changed by the player) because it's the only part of the board that has been changed.

For that, we need to move in every direction at most 3 cells further both to the "left" and to the "right" and check for every encountered cell whether such cell exists (coordinates are valid) and if the color of the next cell matches the color of the cell changed by the player. If the total of consecutive cell on the "right" and on the "left" is greater or equal to 3 (not 4 because the current cell also contributes the winning line) then the winning condition is meat.

To implement this logic, we can create a stream over the DIRECTIONS array and by using anyMatch() operation find out if there's at list one direction that gives a winning line.

To find the count of elements having the same color while examining a particular direction, we can use a combination of takeWhile() and count().

That how it might look like:

public class ColoredCells {
    
    public static final int[][][] DIRECTIONS =
        {{{0, -1}, {0, 1}},    // horizontal -
         {{-1, 0}, {1, 0}},    // vertical |
         {{-1, -1}, {1, 1}},   // diagonal \
         {{1, -1}, {-1, 1}}};  // diagonal /
    
    private int width;
    private int height;
    private Zelle[][] board = new Zelle[width][height];
    
    // other properties, constractor, etc.
    
    public boolean isWinningMove(int row, int col) {
        if (board[row][col] == Zelle.Empty) return false; // or `throw new IllegalStateException();`
        
        Zelle startCellColor = board[row][col];
        
        return Arrays.stream(DIRECTIONS).anyMatch(shifts ->
            countConsecutive(shifts, startCellColor, row, col) >= 3
        );
    }
    
    public int countConsecutive(int[][] shifts, Zelle startCellColor, int row, int col) {
        int leftCellsCount = getCount(shifts[0], startCellColor, row, col);
        int rightCellsCount = getCount(shifts[0], startCellColor, row, col);
        
        return leftCellsCount + rightCellsCount;
    }
    
    public int getCount(int[] shiftArr, Zelle startCellColor, int row, int col) {
        
        return (int) IntStream.rangeClosed(1, 3)
            .takeWhile(i -> {
                int shiftedRow = applyShift(row, shiftArr[0], i);
                int shiftedCol = applyShift(row, shiftArr[1], i);
                return cellExists(shiftedRow, shiftedCol) && hasSameColor(shiftedRow, shiftedCol, startCellColor);
            })
            .count();
    }
    
    public int applyShift(int coordinate, int shift, int multiplier) {
        return coordinate + shift * multiplier;
    }
    
    public boolean cellExists(int shiftedRow, int shiftedCol) {
        
        return shiftedRow >= 0 && shiftedRow < height
            && shiftedCol >= 0 && shiftedCol < width;
    }
    
    public boolean hasSameColor(int shiftedRow, int shiftedCol, Zelle startCellColor) {
        
        return board[shiftedRow][shiftedCol] == startCellColor;
    }
    
    // other methods
}
Related