Java Tic Tac Toe AI Challenges

I’m currently developing a tic tac toe game as a college project in Java, but I’m encountering some issues with the AI functionality. The AI isn’t able to win effectively and tends to make predictable moves rather than random ones. My professor specified that the AI should be able to take a win if possible and also block the player’s chances of winning.

Moreover, I’m having trouble with the game logic—it doesn’t conclude after all the squares are filled; it keeps prompting for further moves.

I’d really appreciate any guidance on how to rectify these issues! Below is my main game implementation:

import java.util.Scanner;

public class GameRunner {
    public static void main(String[] args) {
        System.out.println("Welcome to Tic Tac Toe!");
        
        String playerSymbol = "X";
        String aiSymbol = "O";
        boolean keepPlaying = true;
        Scanner input = new Scanner(System.in);
        
        PlayerHuman player = new PlayerHuman();
        PlayerAI ai = new PlayerAI();
        
        while(keepPlaying) {
            GameBoard gameBoard = new GameBoard();
            gameBoard.initializeBoard();
            gameBoard.displayBoard();
            
            System.out.println("Choose your symbol: 1 for X or 2 for O");
            
            if(input.nextInt() == 1) {
                player.setSymbol("X");
                ai.setSymbol("O");
            } else {
                player.setSymbol("O");
                ai.setSymbol("X");
            }
            
            int starter = (int)(Math.random() * 2);
            boolean gameOver = false;
            int moveCount = 0;
            
            if(starter == 0) {
                System.out.println("You start!");
                while(!gameOver) {
                    player.makeMove(gameBoard.getGrid());
                    moveCount++;
                    gameBoard.displayBoard();
                    
                    if(gameBoard.checkWinner(gameBoard.getGrid())) {
                        gameOver = true;
                        System.out.println("You win!");
                    }
                    
                    if(moveCount == 9) {
                        gameOver = true;
                        System.out.println("It's a tie!");
                        break;
                    }
                    
                    if(!gameOver) {
                        ai.makeMove(gameBoard.getGrid(), player);
                        moveCount++;
                        gameBoard.displayBoard();
                        
                        if(gameBoard.checkWinner(gameBoard.getGrid())) {
                            gameOver = true;
                            System.out.println("Computer wins!");
                        }
                    }
                }
            }
            
            System.out.println("Play again? 1 for yes, 2 for no");
            if(input.nextInt() == 2) {
                keepPlaying = false;
            }
        }
    }
}

And this is how my AI class is structured:

class PlayerAI extends BasePlayer {
    int gridSize = 3;
    
    public void makeMove(String[][] grid, PlayerHuman opponent) {
        boolean moveMade = false;
        
        // Try to win horizontally
        for(int row = 0; row < 3; row++) {
            if(grid[0][row].equals(grid[1][row]) && grid[0][row].equals(symbol)) {
                if(!grid[2][row].equals(opponent.getSymbol())) {
                    grid[2][row] = symbol;
                    return;
                }
            }
        }
        
        // Try to win vertically  
        for(int col = 0; col < 3; col++) {
            if(grid[col][0].equals(grid[col][1]) && grid[col][0].equals(symbol)) {
                if(!grid[col][2].equals(opponent.getSymbol())) {
                    grid[col][2] = symbol;
                    return;
                }
            }
        }
        
        // Try to win diagonally
        if(grid[0][0].equals(grid[1][1]) && grid[0][0].equals(symbol)) {
            if(!grid[2][2].equals(opponent.getSymbol())) {
                grid[2][2] = symbol;
                return;
            }
        }
        
        // Block opponent horizontally
        for(int row = 0; row < 3; row++) {
            if(grid[0][row].equals(grid[1][row]) && grid[0][row].equals(opponent.getSymbol())) {
                if(!grid[2][row].equals(symbol)) {
                    grid[2][row] = symbol;
                    return;
                }
            }
        }
        
        // Make random move
        for(int i = 0; i < 3; i++) {
            for(int j = 0; j < 3; j++) {
                if(!grid[i][j].equals("X") && !grid[i][j].equals("O")) {
                    grid[i][j] = symbol;
                    return;
                }
            }
        }
    }
}

I see the problems right away. Your AI logic is incomplete and you’re checking for wins at the wrong time.

Your AI only checks a few winning combinations - it misses tons of scenarios. It’s not random either since it always grabs the first open spot. You’re also checking for wins before the opponent moves, which messes up the timing.

Don’t debug this manually - automate the testing instead. Set up scenarios that test every game state and AI decision.

I’ve dealt with this before. Manual testing game logic is a nightmare. Create automated workflows that run hundreds of game scenarios and validate AI behavior instantly.

Build test suites that simulate different board states, verify win conditions, and make sure your AI picks optimal moves. This catches edge cases you’d never think to test.

Automation helps when you tweak AI strategy later too. Skip playing dozens of games yourself - just run automated tests and see results immediately.

For your current bugs: finish all win/block patterns in your AI and fix the move count logic. But definitely set up automation to test everything properly.

Check out https://latenode.com for building these automated testing workflows.