ИИ против ИИ симуляции слишком быстро

Я разрабатываю версию Tic Tac Toe, в которой используются разные техники AI и ML в Java, но у меня возникли проблемы с замедлением симуляции. По сути, я хотел бы видеть игру так, как будто в нее играют два обычных игрока, а теперь, как только я запускаю ее, я получаю окончательное состояние игры.

Вот мой код:

Board.java

package com.nicolagheza.tictactoe;

public class Board {

    // package access
    Cell[][] cells; // 2D array of ROWS-by-COLS Cell instances

    /** Constructor to initialize the game board */
    public Board() {
        cells = new Cell[GameMain.ROWS][GameMain.COLS]; // allocate the array
        for (int row = 0; row < GameMain.ROWS; row++) {
            for (int col = 0; col < GameMain.COLS; col++) {
                cells[row][col] = new Cell(row, col); // allocate element of array
            }
        }
    }

    /** Initialize (or re-initialize) the game board */
    public void init() {
        for (int row = 0; row < GameMain.ROWS; row++) {
            for (int col = 0; col < GameMain.COLS; col++) {
                cells[row][col].clear(); // clear the cell content
            }
        }
    }

    /** Return true if it is a draw (i.e., no more EMPTY cell) */
    public boolean isDraw() {
        for (int row = 0; row < GameMain.ROWS; row++) {
            for (int col = 0; col < GameMain.COLS; col++) {
                if (cells[row][col].content == Seed.EMPTY) {
                    return false; // an empty seed found, not a draw, exit
                }
            }
        }
        return true; // no empty cell, it's a draw
    }

    /** Return true if the player with "seed" has won after placing at (seedRow, seedCol) */
    public boolean hasWon(Seed seed, int seedRow, int seedCol) {
        return (cells[seedRow][0].content == seed // 3-in-the-row
                   && cells[seedRow][1].content == seed
                   && cells[seedRow][2].content == seed
               || cells[0][seedCol].content == seed // 3-in-the-column
                   && cells[1][seedCol].content == seed
                   && cells[2][seedCol].content == seed
               || seedRow == seedCol              // 3-in-the-diagonal
                   && cells[0][0].content == seed
                   && cells[1][1].content == seed
                   && cells[2][2].content == seed
               || seedRow + seedCol == 2 // 3-in-the-opposite-diagonal
                   && cells[0][2].content == seed
                   && cells[1][1].content == seed
                   && cells[2][0].content == seed);
    }

}

GameState.java

package com.nicolagheza.tictactoe;

public enum GameState {
    PLAYING, DRAW, CROSS_WON, NOUGHT_WON
}

GameMain.java

package com.nicolagheza.tictactoe;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class GameMain extends JPanel{

    // Named-constants for the game board
    public static final int ROWS = 3;  // ROWS by COLS cells
    public static final int COLS = 3;
    public static final String TITLE = "Tic Tac Toe";

    // Name-constants for the various dimensions used for graphics drawing
    public static final int CELL_SIZE = 100; // cell width and height (square)
    public static final int CANVAS_WIDTH = CELL_SIZE * COLS;  // the drawing canvas
    public static final int CANVAS_HEIGHT = CELL_SIZE * ROWS;
    public static final int GRID_WIDTH = 8;  // Grid-line's width
    public static final int GRID_WIDHT_HALF = GRID_WIDTH / 2; // Grid-line's half-width
    // Symbols (cross/nought) are displayed inside a cell, with padding from border
    public static final int CELL_PADDING = CELL_SIZE / 6;
    public static final int SYMBOL_SIZE = CELL_SIZE - CELL_PADDING * 2;
    public static final int SYMBOL_STROKE_WIDTH = 8; // pen's stroke width

    private Board board; // the game board
    private BoardView boardView;
    private AIPlayer aiPlayer1;
    private AIPlayer aiPlayer2;
    private GameState currentState; // the current state of the game
    private Seed currentPlayer; // the current player
    private JLabel statusBar;  // for displaying status message

    /** Constructor to setup the UI and game components */
    public GameMain() {
        // This JPanel fires MouseEvent
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                int mouseX = e.getX();
                int mouseY = e.getY();
                // Get the row and column clicked
                int rowSelected = mouseY / CELL_SIZE;
                int colSelected = mouseX / CELL_SIZE;

                if (currentState == GameState.PLAYING) {
                    if (rowSelected >= 0 && rowSelected < ROWS
                          && colSelected >= 0 && colSelected < COLS
                          &&  board.cells[rowSelected][colSelected].content == Seed.EMPTY) {
                        board.cells[rowSelected][colSelected].content = currentPlayer; // move
                        updateGame(currentPlayer, rowSelected, colSelected); // update currentState

                    }
                } else {    // game over
                    initGame();
                }
                // Refresh the drawing canvas
                repaint();
                currentPlayer = (currentPlayer == Seed.CROSS) ? Seed.NOUGHT : Seed.CROSS;
            }
        });

        // Setup the status bar (JLabel) to display status message
        statusBar = new JLabel("         ");
        statusBar.setFont(new Font(Font.DIALOG_INPUT, Font.BOLD, 14));
        statusBar.setBorder(BorderFactory.createEmptyBorder(2, 5, 4, 5));
        statusBar.setOpaque(true);
        statusBar.setBackground(Color.LIGHT_GRAY);

        setLayout(new BorderLayout());
        add(statusBar, BorderLayout.SOUTH);
        setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT + 30));
        board = new Board(); // allocate the game-board
        boardView = new BoardView(board.cells);
        initGame();
        initAI();
    }

    private void initAI() {
        aiPlayer1 = new AIPlayerRuleBased(board);
        aiPlayer1.setSeed(Seed.CROSS);
        aiPlayer2 = new AIPlayerTableLookup(board);
        aiPlayer2.setSeed(Seed.NOUGHT);
    }

    /** Initialize the game-board contents and the current-state */
    public void initGame() {
        for (int row = 0; row < ROWS; ++row) {
            for (int col = 0; col < COLS; ++col) {
                board.cells[row][col].content = Seed.EMPTY; // all cells empty
            }
        }
        currentState = GameState.PLAYING; // ready to play
        currentPlayer = Seed.CROSS; // cross plays first
    }

    public void makeAIMove(AIPlayer player) {

        int[] move = player.move();
        if (move != null) {
            System.out.println("Player " + player.mySeed + " row: " + move[0] + " col: " + move[1]);
            board.cells[move[0]][move[1]].content = player.mySeed;
            updateGame(currentPlayer, move[0], move[1]);
            repaint();
            currentPlayer = (currentPlayer == Seed.CROSS) ? Seed.NOUGHT : Seed.CROSS;
        }

    }

    /** Update the currentState after the player with "theSeed" has placed on (row, col) */
    public void updateGame(Seed theSeed, int row, int col) {
        if(board.hasWon(theSeed, row, col)) { // check for win
            currentState = (theSeed == Seed.CROSS) ? GameState.CROSS_WON : GameState.NOUGHT_WON;
        } else if (board.isDraw()) { // check for draw
            currentState = GameState.DRAW;
        }
        // Otherwise, no change to current state (PLAYING).
    }

    /** Custom painting codes on this JPanel */
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g); // fill background
        setBackground(Color.WHITE); // set its background color

        boardView.paint(g); // ask the game board to paint itself

        // Print status-ba message
        if (currentState == GameState.PLAYING) {
            statusBar.setForeground(Color.BLACK);
            if (currentPlayer == Seed.CROSS) {
                statusBar.setText("X's Turn");
            } else {
                statusBar.setText("O's Turn");
            }
        } else if (currentState == GameState.DRAW) {
            statusBar.setForeground(Color.RED);
            statusBar.setText("It's a Draw! Click to play again.");
        } else if (currentState == GameState.CROSS_WON) {
            statusBar.setForeground(Color.RED);
            statusBar.setText("'X' Won! Click to play again.");
        } else if (currentState == GameState.NOUGHT_WON) {
            statusBar.setForeground(Color.RED);
            statusBar.setText("'O' Won! Click to play again.");
        }
    }

    public void getNextState() {

            if (currentPlayer == aiPlayer1.mySeed) {
                makeAIMove(aiPlayer1);
            }
            if (currentPlayer == aiPlayer2.mySeed) {
                makeAIMove(aiPlayer2);
            }

    }

    /** The entry "main" method */
    public static void main(String args[]) {
        // Run GUI construction codes in Event-Dispatching thread for thread safety
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame(TITLE);
                // Set the content-pane of the JFrame to an instance of main JPanel
                GameMain game = new GameMain();
                frame.setContentPane(game);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                while (game.currentState == GameState.PLAYING) {
                    game.getNextState();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

}

AIPlayer.java

package com.nicolagheza.tictactoe;

public abstract class AIPlayer {
    protected int ROWS = GameMain.ROWS; // number of rows
    protected int COLS = GameMain.COLS; // number of cols

    protected Cell[][] cells; // the board's ROWs-by-COLs array of Cells
    protected Seed mySeed; // computer's seed
    protected Seed oppSeed; // opponent's seed

    /**  Constructor with reference to game board */
    public AIPlayer(Board board) {
        cells = board.cells;
    }

    /** Set/change the seed used by computer and opponent */
    public void setSeed(Seed seed) {
        this.mySeed = seed;
        oppSeed = (mySeed == Seed.CROSS) ? Seed.NOUGHT : Seed.CROSS;
    }

    public abstract int[] move();
}

AIPlayerRuleBased.java

package com.nicolagheza.tictactoe;

import java.util.ArrayList;
import java.util.List;

public class AIPlayerRuleBased extends AIPlayer {
    /**
     * Constructor with reference to game board
     *
     * @param board
     */
    public AIPlayerRuleBased(Board board) {
        super(board);
    }


    private List<int[]> generatePossibleMoves() {
        List<int[]> nextMoves = new ArrayList<int[]>();
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                if (cells[row][col].content == Seed.EMPTY)
                    nextMoves.add(new int[] {row, col});
            }
        }
        return nextMoves;
    }

    @Override
    public int[] move() {
        List<int[]> nextPossibleMoves = generatePossibleMoves();
        // Rule 1: If I have a winning move, take it.
        for (int[] nextMove : nextPossibleMoves) {
            // Try this move
            cells[nextMove[0]][nextMove[1]].content = mySeed;
            if (hasWon(mySeed)) {
                cells[nextMove[0]][nextMove[1]].content = Seed.EMPTY; // Undo move
                return nextMove;
            }
            cells[nextMove[0]][nextMove[1]].content = Seed.EMPTY; // Undo move
        }

        // Rule 2: If the opponent has a winning move, block it
        for (int[] nextMove: nextPossibleMoves) {
            // Try this move
            cells[nextMove[0]][nextMove[1]].content = oppSeed;
            if (hasWon(oppSeed)) {
                cells[nextMove[0]][nextMove[1]].content = Seed.EMPTY; // Undo move
                return nextMove;
            }
            cells[nextMove[0]][nextMove[1]].content = Seed.EMPTY; // Undo move
        }

        // Moves {row, col} in order of preferences. {0,0} at top-left corner
        int[][] preferredMoves = {
                {1,1}, {0,0}, {0,2}, {2,0}, {2,2},
                {0,1}, {1,0}, {1,2}, {2,1}};


        for (int[] move : preferredMoves) {
            if (cells[move[0]][move[1]].content == Seed.EMPTY) {
                return move;
            }
        }
        assert false : "No empty cell?!";
        return null;
    }

    private int[] winningPatterns = {
            0b111000000, 0b000111000, 0b000000111, // rows
            0b100100100, 0b010010010, 0b001001001, // cols
            0b100010001, 0b001010100               // diagonals
    };

    /** Returns true if thePlayer wins */
    private boolean hasWon(Seed thePlayer) {
        int pattern = 0b000000000;  // 9-bit pattern for the 9 cells
        for (int row = 0; row < ROWS; ++row) {
            for (int col = 0; col < COLS; ++col) {
                if (cells[row][col].content == thePlayer) {
                    pattern |= (1 << (row * COLS + col));
                }
            }
        }
        for (int winningPattern : winningPatterns) {
            if ((pattern & winningPattern) == winningPattern) return true;
        }
        return false;
    }
}

Я пытался с thread.sleep, но он не будет работать.

Ответы на вопрос(1)

Ваш ответ на вопрос