Home > Enterprise >  Sorting through 2D array for Tic Tac Toe winner
Sorting through 2D array for Tic Tac Toe winner

Time:12-11

I have made this very obnoxious way of finding the winner in a tic tac toe game and I was just wondering if there is a simpler way of doing this. This is the method I currently have, and as you can see it is very redundant and repetitive. Any tips to narrow this down would be amazing. I'm thinking maybe a nested for-loop might work but not entirely sure how to set that up within this.

public static boolean isGameOver(char[][] gameBoard) {

    //Testing for Horizontal Win
    if(gameBoard[0][0] == 'X' && gameBoard[0][2] == 'X' && gameBoard [0][4] == 'X') {
        System.out.println("Player Wins!\n");
        playerScore  ;
        return true;
    }
    if(gameBoard[0][0] == 'O' && gameBoard[0][2] == 'O' && gameBoard [0][4] == 'O') {
        System.out.println("CPU Wins!\n");
        cpuScore  ;
        return true;
    }
    if(gameBoard[2][0] == 'X' && gameBoard[2][2] == 'X' && gameBoard [2][4] == 'X') {
        System.out.println("Player Wins!\n");
        playerScore  ;
        return true;
    }
    if(gameBoard[2][0] == 'O' && gameBoard[2][2] == 'O' && gameBoard [2][4] == 'O') {
        System.out.println("CPU Wins!\n");
        cpuScore  ;
        return true;
    }
    if(gameBoard[4][0] == 'X' && gameBoard[4][2] == 'X' && gameBoard [4][4] == 'X') {
        System.out.println("Player Wins!\n");
        playerScore  ;
        return true;
    }
    if(gameBoard[4][0] == 'O' && gameBoard[4][2] == 'O' && gameBoard [4][4] == 'O') {
        System.out.println("CPU Wins!\n");
        cpuScore  ;
        return true;
    }

    //Testing for Vertical Win
    if(gameBoard[0][0] == 'X' && gameBoard[2][0] == 'X' && gameBoard [4][0] == 'X') {
        System.out.println("Player Wins!\n");
        playerScore  ;
        return true;
    }
    if(gameBoard[0][0] == 'O' && gameBoard[2][0] == 'O' && gameBoard [4][0] == 'O') {
        System.out.println("CPU Wins!\n");
        cpuScore  ;
        return true;
    }
    if(gameBoard[0][2] == 'X' && gameBoard[2][2] == 'X' && gameBoard [4][2] == 'X') {
        System.out.println("Player Wins!\n");
        playerScore  ;
        return true;
    }
    if(gameBoard[0][2] == 'O' && gameBoard[2][2] == 'O' && gameBoard [4][2] == 'O') {
        System.out.println("CPU Wins!\n");
        cpuScore  ;
        return true;
    }
    if(gameBoard[0][4] == 'X' && gameBoard[2][4] == 'X' && gameBoard [4][4] == 'X') {
        System.out.println("Player Wins!\n");
        playerScore  ;
        return true;
    }
    if(gameBoard[0][4] == 'O' && gameBoard[2][4] == 'O' && gameBoard [4][4] == 'O') {
        System.out.println("CPU Wins!\n");
        cpuScore  ;
        return true;
    }

    //Testing for Diagonal Win
    if(gameBoard[0][0] == 'X' && gameBoard[2][2] == 'X' && gameBoard [4][4] == 'X') {
        System.out.println("Player Wins!\n");
        playerScore  ;
        return true;
    }
    if(gameBoard[0][0] == 'O' && gameBoard[2][2] == 'O' && gameBoard [4][4] == 'O') {
        System.out.println("CPU Wins!\n");
        cpuScore  ;
        return true;
    }
    if(gameBoard[4][0] == 'X' && gameBoard[2][2] == 'X' && gameBoard [0][4] == 'X') {
        System.out.println("Player Wins!\n");
        playerScore  ;
        return true;
    }
    if(gameBoard[4][0] == 'O' && gameBoard[2][2] == 'O' && gameBoard [0][4] == 'O') {
        System.out.println("CPU Wins!\n");
        cpuScore  ;
        return true;
    }

    //Testing for Tie
    if(gameBoard[0][0] != ' ' && gameBoard[0][2] != ' ' && gameBoard[0][4] != ' ' &&
            gameBoard[2][0] != ' ' && gameBoard[2][2] != ' ' && gameBoard[2][4] != ' ' &&
            gameBoard[4][0] != ' ' && gameBoard[4][2] != ' ' && gameBoard[4][4] != ' ') {
        System.out.println("It's a tie!!!\n");
        numOfTies  ;
        return true;
    }
    return false;
}

CodePudding user response:

Ok, I got a little carried away. But perhaps you could use some ideas presented here. My main goal was to make it so the entire board need not be checked after each move. This is the type of issue that is best considered during the design phase of a program.

  • I created a TriGroup class (which is essentially a mutable string to hold successive moves.
  • Then a map is used to hold all the groupings which have a common coordinate.
  • when a move is made, those groupings have the current player appended.
  • and check is done to see if that player won.
  • this program will run by itself using random moves resulting in a victory or a tie.

Some border cases may have been overlooked.

public class TicTacToeCheck {
    int moveCount = 0;
    static int MAX_MOVES = 27;
    class TriGroup {
        public String group = "";
        
        @Override
        public String toString() {
            return group;
        }
    }
    
    TriGroup row1 = new TriGroup();
    TriGroup row2 = new TriGroup();
    TriGroup row3 = new TriGroup();
    TriGroup col1 = new TriGroup();
    TriGroup col2 = new TriGroup();
    TriGroup col3 = new TriGroup();
    TriGroup diag1 = new TriGroup();
    TriGroup diag2 = new TriGroup();
    
    Map<String, List<TriGroup>> commonGroupings = new HashMap<>();
    {
        commonGroupings.put("00", List.of(row1, col1, diag1));
        commonGroupings.put("02", List.of(row1, col2));
        commonGroupings.put("04", List.of(row1, col3));
        
        commonGroupings.put("20", List.of(row2, col1));
        commonGroupings.put("22", List.of(row2, col2, diag1, diag2));
        commonGroupings.put("24", List.of(row2, col3));
        
        commonGroupings.put("40", List.of(row3, col1, diag1));
        commonGroupings.put("42", List.of(row3, col2));
        commonGroupings.put("44", List.of(row3, col3));
    }
    
    public static void main(String[] args) {
        new TicTacToeCheck().start();
    }
    
    public void start() {
        
        char player = 'X';
        Random r = new Random();
        outer: while (moveCount < MAX_MOVES) {
            commonGroupings.entrySet().forEach(System.out::println);
            
            System.out.println();
            int row = r.nextInt(3) * 2;
            int col = r.nextInt(3) * 2;
            System.out.println("Move: "   row   ", "   col);
            player = player == 'X' ? 'O' : 'X';
            char val;
            switch (val = recordMove(row, col, player)) {
                case 'X' -> {
                    System.out.println("X wins!");
                    break outer;
                }
                case 'O' -> {
                    System.out.println("O wins!");
                    break outer;
                }
                case 0 -> {
                    System.out.println("Tie!");
                    break outer;
                }
                
                default -> {
                }
                
            }
        }
        commonGroupings.entrySet().forEach(System.out::println);
    }
    
    public char recordMove(int row, int col, char c) {
        moveCount  ;
        
        for (TriGroup tri : commonGroupings.get(row   ""   col)) {
            if (tri.group.length() > 2) {
                // just ignore the row/col and try the next
                continue;
            }
            
            // update group
            tri.group  = c;
            if (tri.group.equals(c   ""   c   ""   c)) {
                return c;
            }
            
        }
        if (moveCount == MAX_MOVES) {
            return 0;
        }
        return '#';
    }
}

CodePudding user response:

This version uses 2 auxiliary, private methods to make the code less repetitive, without changing the behavior of calling isGameOver. The main observation is that there is very little difference between checking for a win by O or checking for one by X - only a single character. So that becomes checkWins. The next observation is that there is a lot of repetition involved in checking 3 positions of the board that are next to each other. If you give me the char to expect, where to start, and where to look next (dcol and drow), that becomes allEqual.

Your code skips over uneven positions of the board; my code does not. I feel that it is an error to mix presentation ("how you show things to users") with model ("how you represent things internally"); so my code is not currently a drop-in replacement for yours, but can quickly be fixed to be such a replacement by tweaking values in checkWins (diagonal down would be 0, 0, 2, 2, and so on).

Note that, efficiency-wise, your code is probably faster. But I find this version to be much shorter, more readable, and therefore easier to debug & maintain.

private static boolean allEqual(char expected, char[][] b, 
                                int row, int col, int drow, int dcol) {
    for (int i=0; i<b[0].length; i  ) {
        if (b[row][col] != expected) return false;
        row  = drow;
        col  = dcol;
    }
    return true;
}

private static boolean checkWins(char playerChar, char[][]b) {
    boolean win = allEqual(playerChar, b, 0, 0, 0, 1)  // 1st row 
               || allEqual(playerChar, b, 1, 0, 0, 1) 
               || allEqual(playerChar, b, 2, 0, 0, 1)  // 3rd row
               || allEqual(playerChar, b, 0, 0, 1, 0)  // 1st col
               || allEqual(playerChar, b, 0, 1, 1, 0) 
               || allEqual(playerChar, b, 0, 2, 1, 0)  // 3rd col
               || allEqual(playerChar, b, 0, 0, 1, 1)  // diagonal down
               || allEqual(playerChar, b, 2, 0, 1,-1); // diagonal up
    return win;
}

public static boolean isGameOver(char[][] gameBoard) {
    if (checkWins('X', gameBoard)) {
        System.out.println("Player Wins!\n");
        playerScore   ;
        return true;
    } else if (checkWins('O', gameBoard)) {
        System.out.println("CPU Wins!\n");
        cpuScore   ;
        return true;
    } else {
        return false;
    }
}

CodePudding user response:

Take a look at this: https://www.geeksforgeeks.org/tic-tac-toe-game-in-java/

Add the strings of gameBoard[x][y] and check them in a switch statement. If the compound strings are equals to XXX or OOO, you can return the winner.

For your code something like this:

for (int a = 0; a < 8; a  ) {
            String line = null;
  
            switch (a) {
            case 0:
                line = gameBoard[0][0]   gameBoard[0][1]   gameBoard[0][2];
                break;
            case 1:
                line = gameBoard[1][0]   gameBoard[1][1]   gameBoard[1][2];
                break;
            case 2:
                line = gameBoard[2][0]   gameBoard[2][1]   gameBoard[2][2];
                break;
            case 3:
                line = gameBoard[0][0]   gameBoard[1][0]   gameBoard[2][0];
                break;
            case 4:
                line = gameBoard[0][1]   gameBoard[1][1]   gameBoard[2][1];
                break;
            case 5:
                line = gameBoard[0][2]   gameBoard[1][2]   gameBoard[2][2];
                break;
            case 6:
                line = gameBoard[0][0]   gameBoard[1][1]   gameBoard[2][2];
                break;
            case 7:
                line = gameBoard[0][2]   gameBoard[1][1]   gameBoard[2][0];
                break;
            }
            //For X winner
            if (line.equals("XXX")) {
                return "X";
            }
              
            // For O winner
            else if (line.equals("OOO")) {
                return "O";
            }
        }
  • Related