This is my first stab at a game AI, and I've been working on multiple game mode difficulties, so I decided to start with a simple game. :) I'm most concerned about efficiency of the AI algorithms, and any possible way of reducing the duplicate code. I know I can't change the int values by reference, so would it be more efficient to use Integers, or would that cause other problems? Also, what is the most readable/informative way to document array returns in javadocs that show how the output array is ordered? (I also left out the more intuitive Javadocs to save space).ConsoleGame.javaimport java.util.Random;import java.util.Scanner;public class ConsoleGame {    private final static Board board = new Board();    private final static Scanner scanner = new Scanner(System.in);    private final static Random generator = new Random();    private final static int MAX_TURNS = 9;    public static void main(String[] args) {        String playAgain;        do {            AI computer = null;            System.out.print(Play against the computer [Y/n]? );            String input = scanner.nextLine();            boolean playComputer = false;            if (!input.equalsIgnoreCase(n) && !input.equalsIgnoreCase(no)) {                playComputer = true;                do {                    System.out.print(Pick game mode [easy | medium | hard]: );                    input = scanner.nextLine();                    if (input.equalsIgnoreCase(easy)) {                        computer = new EasyAI();                    } else if (input.equalsIgnoreCase(medium)) {                        computer =  new MediumAI();                    } else if (input.equalsIgnoreCase(hard)) {                        computer = new HardAI();                    } else {                        System.out.print(Invalid mode. );                    }                } while (!input.equalsIgnoreCase(easy) && !input.equalsIgnoreCase(medium)                            && !input.equalsIgnoreCase(hard));            }            int computerTurn = playComputer ? generator.nextInt(2) : -1;            int[] move;            CellState winner = null;            for (int turns = 0; turns < MAX_TURNS && winner == null; turns++) {                if (turns % 2 == computerTurn) { //Computer goes second                    move = computer.getMove(board, turns);                } else {                    drawBoard();                    move = promptMove(turns);                }                if (move == null) {                    System.out.println(Thanks for playing!);                } else {                    board.makeTurn(move[0], move[1]);                    System.out.println();                    if (turns >= 4) {                        winner = board.verifyWinner();                    }                }            }            if (winner != null) {                drawBoard();                System.out.println(winner +  won!!);            } else {                System.out.println(It was a tie.);            }            System.out.print(Play again [y/N]? );            playAgain = scanner.nextLine();            if (playAgain.equalsIgnoreCase(y) || playAgain.equalsIgnoreCase(yes)) {                board.clearBoard();            }        } while (playAgain.equalsIgnoreCase(y) || playAgain.equalsIgnoreCase(yes));    }    private static void drawBoard() {        String b = \n;        b += 1   + cellStr(0, 0) +  |  + cellStr(1, 0) +  |  + cellStr(2, 0) + \n          +    ---+---+---\n          +  2   + cellStr(0, 1) +  |  + cellStr(1, 1) +  |  + cellStr(2, 1) + \n          +    ---+---+---\n          +  3   + cellStr(0, 2) +  |  + cellStr(1, 2) +  |  + cellStr(2, 2) + \n          +     1   2   3\n;        System.out.println(b);    }    private static String cellStr(int column, int row) {        return board.getCell(column, row).toString();    }    /**     * Prompts the user for their move.     *     * @param turn Turn number     * @return Selected cell, or null if user wants to quit     */    private static int[] promptMove(int turn) {        if (turn % 2 == 0) {            System.out.println(It's X's turn!);        } else {            System.out.println(It's O's turn!);        }        int[] cell = { 0, 0 };        boolean validTurn = false;        do {            System.out.print(Enter your turn (<column> <row>): );            String[] input = scanner.nextLine().split( );            if (input.length < 2) {                if (input[0].equalsIgnoreCase(quit)) {                    System.exit(0);                } else {                    System.out.println(Please enter both a column and a row!);                }            }            else {                try {                    cell[0] = Integer.parseInt(input[0]) - 1;                    cell[1] = Integer.parseInt(input[1]) - 1;                    if (cell[0] < 0 || cell[0] > 2 || cell[1] < 0 || cell[1] > 2) {                        System.out.println(That cell doesn't exist!);                    } else if (board.getCell(cell[0], cell[1]) != CellState.BLANK) {                        System.out.println(That cell has already been played!);                    } else {                        validTurn = true;                    }                } catch (NumberFormatException e) {                    System.out.println(Please enter a valid column and row.);                }            }        } while (!validTurn);        return cell;    }}Board.javaimport java.util.Arrays;public class Board {    private CellState[][] squares;  //[column][row]    private boolean xTurn;    public Board(boolean xFirst) {        xTurn = xFirst;        squares = new CellState[3][3];        clearBoard();    }    public Board() {        this(true);    }    public boolean makeTurn(int column, int row) {        if (column < 0 || column > 2 || row < 0 || row > 2) {            return false;        }        if (xTurn) {            squares[column][row] = CellState.X;        } else {            squares[column][row] = CellState.O;        }        xTurn = !xTurn;        return true;    }    final public void clearBoard() {        for (CellState[] row : squares) {            Arrays.fill(row, CellState.BLANK);        }    }    public CellState getCell(int column, int row) {        return squares[column][row];    }    public CellState getCell(int[] cell) {        if (cell.length != 2) {            throw new IllegalArgumentException(Cells must have columns and rows.);        }        return getCell(cell[0], cell[1]);    }    public CellState verifyWinner() {        CellState winner = null;        //Test columns        for (int r = 0; winner == null && r < 3; r++) {            if (lineMatches(squares[0][r], squares[1][r], squares[2][r])) {                winner = squares[0][r];            }        }        //Test rows        for (int c = 0; winner == null && c < 3; c++) {            if (lineMatches(squares[c][0], squares[c][1], squares[c][2])) {                winner = squares[c][0];            }        }        //Test diagonals        if (winner == null && lineMatches(squares[0][0], squares[1][1], squares[2][2]))        {            winner = squares[0][0];        }        if (winner == null && lineMatches(squares[0][2], squares[1][1], squares[2][0]))        {            winner = squares[0][2];        }        return winner;    }    /**     * Converts an int to a cell. All cell numbers start at 0 and go top to bottom, left to right.     * @param cellNumber Cell number to be converted     * @return { column, row }     */    public static int[] intToCell(int cellNumber) {        int cell[] = { cellNumber / 3, cellNumber % 3 };        return cell;    }    private static boolean lineMatches(CellState cell1, CellState cell2, CellState cell3) {        return (cell1.matches(cell2) && cell2.matches(cell3));    }}AI.javaThis is where I'm most concerned... Although I'm also concerned that I have some duplicate code between some of the AI classes that I can't promote into this class.import java.util.ArrayList;public abstract class AI {    public abstract int[] getMove(Board board, int turns);    /**     * Gets a list of all empty cells remaining on the given board. All cell locations use the     * Board's definition of cell numbers, which start at 0 and go top to bottom, left to right.     *     * @param board Board being used     * @return List of empty cells' numbers     */    protected static ArrayList<Integer> getEmptyCells(Board board) {        ArrayList<Integer> emptyCells = new ArrayList<>(9);        for (int i = 0; i < 9; i++) {            if (board.getCell(i / 3, i % 3) == CellState.BLANK) {                emptyCells.add(i);            }        }        return emptyCells;    }    /**     * Predicts if either the computer or player can win in the next move. If X or O can't win,     * its value will be null.     *     * @param board Board being used     * @return { winning X cell number, winning O cell number }     */    protected static int[] predictWin(Board board) {        int[] winner;        int[] cells = new int[3];        int xWin = -1, oWin = -1;        //Test columns        for (int c = 0; (xWin == -1 || oWin == -1) && c < 3; c++ ) {            for (int r = 0; r < 3; r++) {                cells[r] = c * 3 + r;            }            winner = winsNextTurn(board, cells[0], cells[1], cells[2]);            if (winner != null) {                if (winner[0] == CellState.X.ordinal()) {                    xWin = winner[1];                } else if (winner[0] == CellState.O.ordinal()) {                    oWin = winner[1];                }            }        }        //Test rows        for (int r = 0; (xWin == -1 || oWin == -1) && r < 3; r++) {            for (int c = 0; c < 3; c++ ) {                cells[c] = c * 3 + r;            }            winner = winsNextTurn(board, cells[0], cells[1], cells[2]);            if (winner != null) {                if (winner[0] == CellState.X.ordinal()) {                    xWin = winner[1];                } else if (winner[0] == CellState.O.ordinal()) {                    oWin = winner[1];                }            }        }        //Test diagonals        if (xWin == -1 || oWin == -1) {            winner = winsNextTurn(board, 0, 4, 8);            if (winner != null) {                if (winner[0] == CellState.X.ordinal()) {                    xWin = winner[1];                }                else if (winner[0] == CellState.O.ordinal()) {                    oWin = winner[1];                }            }        }        if (xWin == -1 || oWin == -1) {            winner = winsNextTurn(board, 2, 4, 6);            if (winner != null) {                if (winner[0] == CellState.X.ordinal()) {                    xWin = winner[1];                }                else if (winner[0] == CellState.O.ordinal()) {                    oWin = winner[1];                }            }        }        int[] winningMoves = { xWin, oWin };        return winningMoves;    }    /**     * Returns the cell number of the winning cell and which state would win it as an ordinal.     * If there is no winner, null is returned.     *     * @param board Tic-Tac-Toe Board being played on     * @param cell1 Int location of first cell     * @param cell2 Int location of second cell     * @param cell3 Int location of third cell     * @return { CellState winner's ordinal, cell number }     */    private static int[] winsNextTurn(Board board, int cell1, int cell2, int cell3) {        final int PLAYER = 0, CELL = 1;        int[] winner = new int[2];        CellState cs1 = board.getCell(Board.intToCell(cell1));        CellState cs2 = board.getCell(Board.intToCell(cell2));        CellState cs3 = board.getCell(Board.intToCell(cell3));        if (cs1.matches(cs2) && cs3 == CellState.BLANK) {            winner[PLAYER] = cs1.ordinal();            winner[CELL]   = cell3;            return winner;        }        if (cs2.matches(cs3) && cs1 == CellState.BLANK) {            winner[PLAYER] = cs2.ordinal();            winner[CELL]   = cell1;            return winner;        }        if (cs1.matches(cs3) && cs2 == CellState.BLANK) {            winner[PLAYER] = cs1.ordinal();            winner[CELL]   = cell2;            return winner;        }        return null;    }}EasyAI.javaimport java.util.ArrayList;import java.util.Random;public class EasyAI extends AI {    Random generator;    public EasyAI() {        generator = new Random();    }    /**     * Chooses the next move by selecting an empty remaining cell at random.     *     * @param board Board being used     * @param turns Turn number isn't used in this implementation     * @return { column, row } or null if no move available     */    @Override    public int[] getMove(Board board, int turns) {        ArrayList<Integer> emptyCells = getEmptyCells(board);        int move = emptyCells.get(generator.nextInt(emptyCells.size()));        return Board.intToCell(move);    }}MediumAI.javaimport java.util.ArrayList;import java.util.Random;public class MediumAI extends AI {    private static Random generator = new Random();    /**     * Chooses the next move by trying to win first, then check to block the player's win. If there     * are no upcoming winning moves, it will choose a remaining empty cell at random.     *     * @param board Board being used     * @param turns Turn number isn't used in this implementation     * @return { column, row } or null if no move available     */    @Override    public int[] getMove(Board board, int turns) {        if (turns >= 3) {            final int X_WIN = 0, O_WIN = 1;            int[] winner = predictWin(board);            if (winner[O_WIN] != -1) {                return Board.intToCell(winner[O_WIN]);            } else if (winner[X_WIN] != -1) {                return Board.intToCell(winner[X_WIN]);            }        }        ArrayList<Integer> emptyCells = getEmptyCells(board);        int move = emptyCells.get(generator.nextInt(emptyCells.size()));        return Board.intToCell(move);    }}HardAI.javapublic class HardAI extends AI {    /**     * Chooses the next move as intelligently as possible. If the center square is open, it will     * choose it immediately. Thus, the center square will be filled on either turn 0 or 1. Next,     * try to win, or else block the player from winning. If there are no immediate wins, try to     * take a corner, or a side if a corner isn't available. This mode should always win or tie.     * At this time, the AI always plays as O.     *     * @param board Board being used     * @param turns Turn number isn't used in this implementation     * @return { column, row } or null if no move available     */    @Override    public int[] getMove(Board board, int turns) {        if (board.getCell(1, 1) == CellState.BLANK) {            return Board.intToCell(4);        }        if (turns >= 3) { //No one can win before 4th move            final int X_WIN = 0, O_WIN = 1;            int[] winner = predictWin(board);            if (winner[O_WIN] != -1) {                return Board.intToCell(winner[O_WIN]);            } else if (winner[X_WIN] != -1) {                return Board.intToCell(winner[X_WIN]);            }        }        final int[] corners = { 0, 6, 2, 8 };        int[] currentCell;        for (int i = 0; i < 4; i++) {            currentCell = Board.intToCell(corners[i]);            if (board.getCell(currentCell) == CellState.BLANK) {                return currentCell;            }        }        for (int i = 1; i < 8; i += 2) {            currentCell = Board.intToCell(i);            if (board.getCell(currentCell) == CellState.BLANK) {                return currentCell;            }        }        return null;    }}CellState.javaenum CellState {    BLANK, X, O;    @Override    public String toString() {        if (this.name().equals(BLANK)) {            return  ;        } else {            return this.name();        }    }    /**     * Compares two cell states to see if they're equal but not blank.     *     * @param state2 Compared cell state     * @return True if matching and not blank     */    public boolean matches(CellState state2) {        return this == state2 && state2 != BLANK;    }}
Tic Tac Toe with 3 AI modes or Two-Player
java;performance;algorithm;tic tac toe;ai
first stab at a game AIreducing the duplicate codereadable/informative way to document array returnsWARNING: this answer may be a little over-engineered.In Game Development, we often talk about the concept of a Controller. The Controller is an Interface/Abstract that contains all of the method signatures that will be used to control the actions of a player or AI player; this allows the Controller to be effectively hot-swapped between player and AI, or between a smart and not as smart AI.Another key idea is the separation of game logic and game I/O (the Player Controller is a necessary exception). In your ConsoleGame class, you have both logic to set up the game and run it. I would extract your main class (what is run) to a seperate class, probably Main.java because I suck at names, and rename ConsoleGame to TicTacToeGame, which would only handle the actual game logic.More generally talking about Java design, it's a bad idea to pollute the global package. You should have a package declaration, e.g. package brainfrz.tictactoe, at the top of each java file. The .java will then be located in a folder hiearchy ProjectBase/brainfrz/tictactoe/*.java.One final design point is that you've implemented the entire ConsoleGame statically. Obviously the main() has to be static, but it is best design to create a TicTacToeGame instance that contains all state info, for what if you want to run two at the same time? Or nine, if we extend this to a game of super-tic-tac-toe?Now, let's get to some code, shall we?I'm focusing on the public facing API, and there may be more private helper methods that I've glossed over as /* Implementation */package brainfrz.tictactoe;class TicTacToeGame {    private final TicTacToeBoard board = new TicTacToeBoard();    private final TicTacToeController p1, p2;    public TicTacToeGame(TicTacToeController p1, TicTacToeController p2) {        this.p1 = p1;        this.p2 = p2;    }    /**     * Runs a loop taking moves from p1 and p2 until the game is finished.     *      * In a bigger application, you'd want to split this singular call     * into many piece-meal calls, often that you can call every frame.     * Since we're just doing a console application, however, stalling     * for input is OK.     */    public void gameLoop() {        bool oddTurn = true;        while (!board.gameEnded()) {            if (oddTurn) {                p1.makeMove(board);            } else {                p2.makeMove(board);            }        }    }    /**     * Determine who won this game of TicTacToe.     *      * @return the TicTacToeController of the winning player.     *             null if the game hasn't been won or is a tie.     */    public TicTacToeController getWinner() {        if (board.winningState() == p1.getToken()) {            return p1;        } else if (board.winningState() == p2.getToken()) {            return p2;        } else {            return null;        }    }}That's pretty clean, isn't it? That's because we've delegated responsibility to the best actor for each job. So let's go down the hiearchy, and take a look at TicTacToeController.package brainfrz.tictactoe;interface TicTacToeController {    /**     * Make a move.     *     * Plays in-place.     * @param board the board to move on.     */    public void makeMove(TicTacToeBoard board);    public TicTacToeBoard.CellState getToken();}Yes, that's it. All that this interface is is a specification for how the specific Controllers interact with the Board. I would structure the Board itself something like the following:package brainfrz.tictactoe;import java.util.Arrays;class TicTacToeBoard {    static enum CellState {        EMPTY, X, Y        /* toString and matches */    }    static class TicTacToePosition {        public final int x,y;        public TicTacToePosition(int x, int y) {            this.x = x;            this.y = y;        }    }    private final CellState[][] cells = CellState[3][3];    public Board() {        for (CellState[] row : cells) {            Arrays.fill(row, CellState.EMPTY);        }    }    public void placePiece(TicTacToePosition pos, CellState piece) {        cells[pos.y][pos.x] = piece;    }    /**     * Determine which CellState won.     *      * @return the winning cell state.     *         null if there is no win.     */    public CellState winningState() {        /* Implementation from verifyWinner() */    }    /**     * @return if the game has ended     */    public boolean gameEnded() {        /* Implementation from verifyWinner() */    }}Now on to the actual Controllers. I'm putting them all in one code block to save a small amount of vertical space, but of course the different classes need to be in different files.You mentioned wanting to clarify the return int[]. I would recommend to instead use a BoardPosition, as I have defined in the above TicTacToeBoard class. In C# this would be a struct, but since Java offers no such construct, we have to settle for a regular class.package brainfrz.tictactoe;class PlayerController implements TicTacToeController {    private final TicTacToeBoard.CellState token;    public PlayerController(TicTacToeBoard.CellState token) {        this.token = token;    }    public TicTacToeBoard.CellState getToken() {        return token;    }    public void makeMove(TicTacToeBoard board) {        /* Implementation */    }}abstract AIController implements TicTacToeController {    /* AI.java resources */    private final TicTacToeBoard.CellState token;    public AIController(TicTacToeBoard.CellState token) {        this.token = token;    }}class EasyAI extends AIController {    public void makeMove() {        /* Implementation */    }}class MediumAI extends AIController {    public void makeMove() {        /* Implementation */    }}class HardAI extends AIController {    public void makeMove() {        /* Implementation */    }}I don't see anything bad in your implementations of the AI classes, really, other than the int[] return type which is ambiguous. Having the interface makeMove and then using an internal representation would clear up the question of vague specification of return types. I would use the Position type to pass around positions on the board, though.There is some duplication in the AI classes, but as I read it as necessary duplication. Unless I'm missing something, it's more the structure that's repeated than the actual logic, which points more to them just being regularly set up sibling classes than needing refactoring because of just that. Rather than check turns >= 3, I'd just check to see if there is a winning condition always, as it won't hurt to check.The last thing to cover is the Main class, as I've taken that logic out of the TicTacToeGame class. So, as a skelleton of Main.java:package brainfrz.tictactoe;class Main {    public static void main(String[] args) {        boolean again;        do {            TicTacToeController p1 = new PlayerController(TicTacToeBoard.CellState.X);            TicTacToeController p2 = getOpponent(TicTacToeBoard.CellState.O);            TicTacToeGame game = new TicTacToeGame(p1, p2);            game.gameLoop();            String winner = Tie;            TicTacToeController winningController = board.getWinner();            if (winningController == p1) {                winner = P1;            } else if (winningController == p2) {                winner = P2;            }            System.out.println(Congrats  + winner);            // This would be more sophisticated based on what opponent you chose, and to have a better output.            again = confirmRepeat();        } while (again);    }    private static TicTacToeController getOpponent(TicTacToe.CellState token) {        switch(userInput()):            return /* Appropriate Controller */(token);    }    private static boolean confirmRepeat() {        /* Implementation */    }}It's always good to have your driver be in its own class separate from the logic of your game/backend/whatever, because then the game/backend/whatever isn't tied to this one way to run it. By taking the run logic into its own class, we allow you or others to import logic from this class and extend it. And extensability is the goal of good programming.Disclaimer: this code is untested, and written on a machine without the JDK installed. I do quite like your code, and these are just suggestions on how I'd organize it as a representation of some of the abstractions that a larger system benefits from.Also, Docs are important. I've left out much of the JavaDocs from my examples here in intrest of space and because I hope my explanation would make them redundant, but because your code doesn't come with this accompanying document, you NEED JavaDocs for any public-facing API and SHOULD have JavaDocs for any private functions. Simple single-line functions can be an exception, but if they are public they should have a JavaDoc even if it's obvious.