Creating Smart Computer Opponent for Java Tic Tac Toe Game

Need Help with Computer Player Logic

I built a tic tac toe game in Java with a graphical interface that works great when two humans play. Now I want to add a computer player mode but I’m having trouble with the AI logic.

The Problem

The computer makes weird moves and doesn’t block the player from winning like it should. My plan was to make the computer pick random spots early in the game, then switch to defensive play to stop the human player from getting three in a row. But the computer picks completely wrong positions.

My Setup

I use a 2D button array and check getText() to see who owns each square. Human player is always “X” and computer is “O”. I made an AI class with methods to scan the board and choose moves.

Button Click Handler

public class ClickHandler implements ActionListener {
    private ComputerPlayer cpu = new ComputerPlayer();
    private int row, col;
    
    public ClickHandler(int row, int col) {
        this.row = row;
        this.col = col;
    }
    
    @Override
    public void actionPerformed(ActionEvent e) {
        JButton btn = (JButton) e.getSource();
        if (btn.getText().equals("")) {
            if (playerTurn) {
                btn.setText("X");
                playerTurn = false;
                if (hasWinner(row, col)) {
                    JOptionPane.showMessageDialog(null, "You won!");
                    resetGame();
                }
            } else if (vsComputer) {
                cpu.analyzeBoard();
                cpu.makeMove();
            } else {
                btn.setText("O");
                playerTurn = true;
                if (hasWinner(row, col)) {
                    JOptionPane.showMessageDialog(null, "Player 2 wins!");
                    resetGame();
                }
            }
        }
    }
}

Computer Player Class

public class ComputerPlayer {
    public void makeMove() {
        if (!playerTurn) {
            if (getBlockingMoves().isEmpty()) {
                playerTurn = true;
                int r = getRandomPosition();
                int c = getRandomPosition();
                gameButtons[r][c].setText(getSymbol());
                hasWinner(r, c);
            } else {
                if (gameButtons[getBlockingMoves().get(0)][getBlockingMoves().get(1)].getText().equals("")) {
                    gameButtons[getBlockingMoves().get(0)][getBlockingMoves().get(1)].setText(getSymbol());
                } else {
                    gameButtons[getBlockingMoves().get(0)][getBlockingMoves().get(1)].setText(getSymbol());
                }
                getBlockingMoves().clear();
                playerTurn = true;
            }
        }
    }
    
    public int getRandomPosition() {
        double rand = Math.random();
        return rand > 0.66 ? 2 : rand > 0.33 ? 1 : 0;
    }
    
    public ArrayList<Integer> getBlockingMoves() {
        ArrayList<Integer> moves = new ArrayList<>();
        for (int r = 0; r < 3; r++) {
            for (int c = 0; c < 3; c++) {
                if (gameButtons[r][c].getText().equals("X")) {
                    if (r < 2 && c < 2) {
                        if (hasWinner(r+1, c+1)) {
                            moves.add(r+1);
                            moves.add(c+1);
                        } else if (hasWinner(r+1, c)) {
                            moves.add(r+1);
                            moves.add(c);
                        } else if (hasWinner(r, c+1)) {
                            moves.add(r);
                            moves.add(c+1);
                        }
                    } else if (r > 0 && c > 0) {
                        if (hasWinner(r-1, c-1)) {
                            moves.add(r-1);
                            moves.add(c-1);
                        } else if (hasWinner(r-1, c)) {
                            moves.add(r-1);
                            moves.add(c);
                        } else if (hasWinner(r, c-1)) {
                            moves.add(r);
                            moves.add(c-1);
                        }
                    }
                }
            }
        }
        return moves;
    }
    
    public String getSymbol() {
        return "O";
    }
}

Win Detection Method

public boolean hasWinner(int r, int c) {
    if (gameButtons[0][c].getText().equals(gameButtons[1][c].getText()) && 
        gameButtons[2][c].getText().equals(gameButtons[1][c].getText())) {
        return true;
    } else if (gameButtons[r][0].getText().equals(gameButtons[r][1].getText()) && 
               gameButtons[r][2].getText().equals(gameButtons[r][0].getText())) {
        return true;
    } else if (r == c) {
        return gameButtons[0][0].getText().equals(gameButtons[1][1].getText()) && 
               gameButtons[0][0].getText().equals(gameButtons[2][2].getText());
    } else if ((r == 0 && c == 2) || (r == 2 && c == 0) || r == c) {
        return gameButtons[2][0].getText().equals(gameButtons[1][1].getText()) && 
               gameButtons[2][0].getText().equals(gameButtons[0][2].getText());
    }
    return false;
}

Any ideas what I’m doing wrong with the blocking logic? The computer should pick spots that prevent the human from winning but it doesn’t work right.

Your getBlockingMoves method isn’t working right - it’s not actually finding blocking positions. You’re calling hasWinner on spots next to X’s, but that doesn’t tell you if putting an O there would block anything. What you need to do is scan each row, column, and diagonal for two X’s with an empty spot, not just look at neighbors of existing X marks. Your random move code has another bug - you’re not checking if that random spot is already taken, so the computer will overwrite moves. I’d rewrite the blocking logic to find patterns like X-X-empty or X-empty-X, then return that empty spot. Looking at neighbors of X positions won’t catch most blocks.

Your makeMove() method calls getRandomPosition() without checking if the spot’s empty first - that’s why the computer keeps placing moves on occupied squares. The blocking logic is on the right track but you’re only checking immediate neighbors. You need to scan entire lines for two-in-a-row patterns instead. I ran into this exact issue building my tic tac toe AI last year. Here’s what fixed it: create separate methods for each winning line (3 rows, 3 columns, 2 diagonals) and count X’s and O’s in each. When a line has exactly 2 X’s and 1 empty space, that empty space is your blocking move. One more thing - check for winning moves before blocking. If the computer can win immediately, it should take that over blocking you.