Tic Tac Toe

Main.java

import java.util.Scanner;
 
public class Main {
	// global variables
    private static String[] board = new String[9];
    private static int turn = 0;
    private static boolean playing = false;
 
    private static Player player1 = new Player(1, "X", false);
    private static Player player2 = new Player(2, "O", false);
 
    public static void main(String[] args) {
        // caleb b
        // Tic Tac Toe
        for(int i = 0; i < board.length; i++) {
            // Set up the board by setting each index to its index number
            board[i] = String.valueOf(i);
        }
        // Print the welcome message & choice messages
        System.out.println("Welcome to Tic Tac Toe");
        System.out.println("Would you like to play against another player or against a computer?");
        System.out.println("1. Player");
        System.out.println("2. Computer");
        // Get the user's choice
        Scanner input = new Scanner(System.in);
        int choice = input.nextInt();
        if (choice == 2) {
            player1.isComputer = true;
        } else {
            player1.isComputer = false;
        }
        // Start the game loop
        playing = true;
        while(playing) {
            clearScreen();
            printBoard();
            if (turn % 2 == 0) {
                // Handle turns for player 1
                if (player1.isComputer) {
                    // Computer logic
                    System.out.println("Computer's turn");
                    int computerMove = player1.computerMove(); // Use the minimax algorithm to pick the best move
                    try {
                        board[computerMove] = player1.playerSymbol;
                    } catch (ArrayIndexOutOfBoundsException e) {
                        board[computerMove - 1] = player1.playerSymbol;
                    }
                    turn++;
                    sleep(1000);
                    if (checkWin(player1.playerSymbol)) {
                        // Check if the computer won
                        clearScreen();
                        printBoard();
                        System.out.println("Computer won!");
                        playing = false;
                        continue;
                    }
                } else {
                    // Regular player 1 logic
                    System.out.println("Player 1's turn");
                    System.out.println("Enter a number to place your symbol");
                    int move = input.nextInt();
                    if (move < 0 || move > 8) {
                        System.out.println("That is not a valid move!");
                        continue;
                    }
                    if (!board[move].equals(String.valueOf(move))) {
                        System.out.println("That space is already taken!");
                        continue;
                    }
                    board[move] = player1.playerSymbol;
                    if (checkWin(player1.playerSymbol)) {
                        clearScreen();
                        printBoard();
                        System.out.println("Player 1 won!");
                        playing = false;
                        continue;
                    }
                    turn++;
                }
            } else {
                // Player 2 logic
                System.out.println("Player 2's turn");
                System.out.println("Enter a number to place your symbol (0-8)");
                int move = input.nextInt();
                if (move < 0 || move > 8) {
                    System.out.println("That is not a valid move!");
                    continue;
                }
                if (!board[move].equals(String.valueOf(move))) {
                    System.out.println("That space is already taken!");
                    continue;
                }
                board[move] = player2.playerSymbol;
                if (checkWin(player2.playerSymbol)) {
                    // Check if player 2 won
                    clearScreen();
                    printBoard();
                    System.out.println("Player 2 won!");
                    playing = false;
                    continue;
                }
                turn++;
            }
            if (Player.getAvailableMoves(board).length == 0) {
                // Check if the board is full, if it is, then there was a draw.
                clearScreen();
                printBoard();
                System.out.println("It's a tie!");
                playing = false;
                break;
            }
        }
        // Ask to play again
        System.out.println("Thanks for playing!");
        System.out.println("Play again? (y/n)");
        Scanner newInput = new Scanner(System.in);
        String playAgain = newInput.nextLine();
        if (playAgain.toLowerCase().equals("y")) {
            // Reset the board and turn counter, then start a new game
            turn = 0;
            board = new String[9];
            main(args);
        } else {
            System.out.println("Goodbye!");
        }
    }
 
    // Quick function to clear the screen on command, just prints 100 blank lines.
    public static void clearScreen() {
        for (int i = 0; i < 100; i++) {
            System.out.println();
        }
    }
 
    // Quick function for safe Thread sleeping.
    public static void sleep(double millis) {
        try {
            Thread.sleep((long) millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    // Function to print the board in a nice format
    public static void printBoard() {
        String[] board2 = board.clone();
        for (int i = 0; i < board.length; i++) {
            if (board2[i].equals(String.valueOf(i))) {
                // If the index is equal to its index number, then it is empty, so set it to a space
                board2[i] = " ";
            }
        }
        System.out.println(" " + board2[0] + " | " + board2[1] + " | " + board2[2] + " ");
        System.out.println("-----------");
        System.out.println(" " + board2[3] + " | " + board2[4] + " | " + board2[5] + " ");
        System.out.println("-----------");
        System.out.println(" " + board2[6] + " | " + board2[7] + " | " + board2[8] + " ");
        System.out.println();
    }
 
    // Function to check if a player has won
    public static boolean checkWin(String symbol) {
        if (board[0].equals(symbol) && board[1].equals(symbol) && board[2].equals(symbol)) {
            return true;
        }
        if (board[3].equals(symbol) && board[4].equals(symbol) && board[5].equals(symbol)) {
            return true;
        }
        if (board[6].equals(symbol) && board[7].equals(symbol) && board[8].equals(symbol)) {
            return true;
        }
        if (board[0].equals(symbol) && board[3].equals(symbol) && board[6].equals(symbol)) {
            return true;
        }
        if (board[1].equals(symbol) && board[4].equals(symbol) && board[7].equals(symbol)) {
            return true;
        }
        if (board[2].equals(symbol) && board[5].equals(symbol) && board[8].equals(symbol)) {
            return true;
        }
        if (board[0].equals(symbol) && board[4].equals(symbol) && board[8].equals(symbol)) {
            return true;
        }
        if (board[2].equals(symbol) && board[4].equals(symbol) && board[6].equals(symbol)) {
            return true;
        }
        return false;
    }
 
    // Getters and setters for global variables so they can be accessed from other classes safely.
 
    public static String[] getBoard() {
        return board;
    }
 
    public static void setBoard(String[] board) {
        Main.board = board;
    }
 
    public static int getTurn() {
        return turn;
    }
 
    public static void setTurn(int turn) {
        Main.turn = turn;
    }
 
    public static Player getPlayer1() {
        return player1;
    }
 
    public static void setPlayer1(Player player1) {
        Main.player1 = player1;
    }
 
    public static Player getPlayer2() {
        return player2;
    }
 
    public static void setPlayer2(Player player2) {
        Main.player2 = player2;
    }
 
    public static boolean isPlaying() {
        return playing;
    }
 
    public static void setPlaying(boolean playing) {
        Main.playing = playing;
    }
}

Player.java

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
 
public class Player {
    public int playerNumber;
    public String playerSymbol;
    public boolean isComputer;
    private List<Integer> moves = new ArrayList<>();
 
    // Constructor, take in the player number, symbol, and whether or not the player is a computer
    public Player(int playerNumber, String playerSymbol, boolean isComputer) {
        this.playerNumber = playerNumber;
        this.playerSymbol = playerSymbol;
        this.isComputer = isComputer;
    }
 
    // Logic for the computer minmaxing algorithm
    public int computerMove() {
        // Get the board and available moves
        String[] board = Main.getBoard();
        String[] availableMoves = getAvailableMoves(board);
        int bestMove = -1;
        int bestScore = -1000;
 
        // The computer always goes first, so pick a random good location for the first move.
        if (Main.getTurn() == 0 || Main.getTurn() == 1) {
            double random = Math.random();
            if (random < 0.33) {
                return 0;
            } else if (random < 0.66) {
                return 4;
            } else {
                return 8;
            }
        }
        // Loop through each available move and get the score for each move
        for (int i = 0; i < availableMoves.length; i++) {
            String[] boardCopy = board.clone();
            boardCopy[Integer.parseInt(availableMoves[i])] = "X";
            int score = minimax(boardCopy, getAvailableMoves(boardCopy), false);
            // If the score is better than the current best score, set the best score to the current score and set the best move to the current move
            if (score > bestScore) {
                bestScore = score;
                bestMove = Integer.parseInt(availableMoves[i]);
            }
        }
 
        // Return the best move
        return bestMove;
    }
 
 
    private int minimax(String[] board, String[] availableMoves, boolean isMaximizing) {
        // While iterating through the available moves, check if the current move is a winning move, a losing move, or a tie.
        if (winning(board, "O")) {
            return -10;
        } else if (winning(board, "X")) {
            return 10;
        } else if (availableMoves.length == 0) {
            return 0;
        }
 
        int bestScore = isMaximizing ? Integer.MIN_VALUE : Integer.MAX_VALUE;
 
        // for each available move, make a copy of the board, place the move, and then call the minimax function again with the new board and available moves
        for (int i = 0; i < availableMoves.length; i++) {
            String[] boardCopy = board.clone();
            boardCopy[Integer.parseInt(availableMoves[i])] = isMaximizing ? "X" : "O";
 
            int score = minimax(boardCopy, getAvailableMoves(boardCopy), !isMaximizing);
 
            if (isMaximizing) {
                bestScore = Math.max(bestScore, score);
            } else {
                bestScore = Math.min(bestScore, score);
            }
 
            // Undo the move for the next iteration
            boardCopy[Integer.parseInt(availableMoves[i])] = availableMoves[i];
        }
 
        return bestScore;
    }
 
 
    static String[] getAvailableMoves(String[] board) {
        // Get the available moves by checking if the index is equal to its index number
        List<String> availableMoves = new ArrayList<>();
        for (int i = 0; i < board.length; i++) {
            if (board[i].equals(String.valueOf(i))) {
                availableMoves.add(String.valueOf(i));
            }
        }
        return availableMoves.toArray(new String[0]);
    }
 
    public static boolean winning(String[] board, String symbol) {
        // Check if a player has won
        if (board[0].equals(symbol) && board[1].equals(symbol) && board[2].equals(symbol)) {
            return true;
        }
        if (board[3].equals(symbol) && board[4].equals(symbol) && board[5].equals(symbol)) {
            return true;
        }
        if (board[6].equals(symbol) && board[7].equals(symbol) && board[8].equals(symbol)) {
            return true;
        }
        if (board[0].equals(symbol) && board[3].equals(symbol) && board[6].equals(symbol)) {
            return true;
        }
        if (board[1].equals(symbol) && board[4].equals(symbol) && board[7].equals(symbol)) {
            return true;
        }
        if (board[2].equals(symbol) && board[5].equals(symbol) && board[8].equals(symbol)) {
            return true;
        }
        if (board[0].equals(symbol) && board[4].equals(symbol) && board[8].equals(symbol)) {
            return true;
        }
        if (board[2].equals(symbol) && board[4].equals(symbol) && board[6].equals(symbol)) {
            return true;
        }
        return false;
    }
}