javajavafxgomoku

How to enable turn taking within game of Gomoku javafx


I am having difficulty in enablng turn taking within my program.

A tile on the board gets clicked and those coordinates are passed to playMakeMove() which makes the move in the board matrix and sets text to represent a move visually.

Although when 2 players are involved (player, RandomAI), after using a loop to alternate turns, the method doesnt work. The RandomAI just makes all its moves in succession with player only making one move in the exact same spot (not waiting for mouse to be clicked).

Im thinking the problem can be sloved via waiting for a tile to be clicked(to run method playerMakeMove) and then letting RandomAI take a turn. Not sure how to implement this.

Below are two classes BoardGUI and Game there is also another class (not included) called Board.

public class BoardGUI extends Application {

    private final int BOARD_SIZE = 15;

    private Tile[][] tileBoard = new Tile[BOARD_SIZE][BOARD_SIZE];

    private Pane root = new Pane();

    private Parent createContent(Game game) {
        root.setPrefSize(755, 755);

        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                Tile tile = new Tile(i, j);
                tile.setTranslateX(j * 50);
                tile.setTranslateY(i * 50);

                tile.setOnMousePressed(e -> {
                    System.out.println("tile clicked");

                    // sets coordinates of tileClicked in game - to be used in making a player move
                    game.getTile().setRow(tile.getTileRow());
                    game.getTile().setColumn(tile.getTileCol());
                });

                root.getChildren().add(tile);

                tileBoard[i][j] = tile;
            }
        }
        return root;
    }

    @Override
    public void start(Stage primaryStage) {
        Game game = new Game();
        Scene scene = new Scene (game.GUI.createContent(game));
        primaryStage.setScene(scene);
        primaryStage.setTitle("Gomoku");
        primaryStage.show();


        for (int turns = 0; turns < (game.getBoardSize()*game.getBoardSize()); turns++) {
            if(game.getGameOver()) break;

            if(!game.isPlayerTurn()) {
                //AI make move
                game.makeMoveAIRandom(game.getGameBoard());
                game.setPlayerTurnTrue();
            }
            else {
                // player make move
                game.playerMakeMove(game);
            }
        }
        System.out.println("game over");
    }

     class Tile extends StackPane {

        Text text = new Text();
        int row, column;

        Tile(int x, int y) {
            this.row = x;
            this.column = y;

            Rectangle border = new Rectangle(50, 50);
            border.setFill(Color.BURLYWOOD);
            border.setStroke(Color.BLACK);
            text.setFont(Font.font(40));
            setAlignment(Pos.CENTER);
            getChildren().addAll(border, text);
        }

        void makeMove(Board board, int player, int row, int col) {
            System.out.println(row + " " + col);

            if (board.isMoveAvailable(row, col)) {
                drawTile(player);
                makeMoveMatrix(board, player, row, col);
            }
            System.out.println("makeMove executed");
        }

        void drawTile(int player) {
            System.out.println("setTextTile executed");
            text.setText("O");
            if (player == 1) text.setFill(Color.BLACK);
            else text.setFill(Color.WHITE);
        }

        void makeMoveMatrix(Board board, int player, int row, int col) {
            board.make_move(player, row, col);
        }

        int getTileRow() { return row; }

        void setRow(int row) { this.row = row; }

        void setColumn(int column) { this.column = column; }

        int getTileCol() { return column; }

    }

    Tile[][] getTileBoard() { return tileBoard; }
}


public class Game extends BoardGUI {

    private final int PLAYER_ONE = 1;
    private final int PLAYER_TWO = 2;
    BoardGUI GUI;
    private Board gameBoard;
    private int boardSize = 15;
    private int winLength = 5;
    private boolean turn = true;
    private boolean isGameOver = false;
    private Tile tileClicked = new Tile(0, 0);

    Game() {
        this.gameBoard = new Board(boardSize, winLength);
        this.GUI = new BoardGUI();
    }

    void makeMoveAIRandom(Board gameBoard) {

        Random random = new Random();
        int index = random.nextInt(gameBoard.availableMoves.size());
        int[] move = gameBoard.availableMoves.get(index);
        int row = move[0];
        int col = move[1];

        GUI.getTileBoard()[row][col].makeMove(gameBoard, PLAYER_TWO, move[0], move[1]);

        setGameOver(getGameBoard().check_win_all(PLAYER_TWO, row, col));

        gameBoard.availableMoves.remove(index);

        setPlayerTurnTrue();
    }

    void playerMakeMove(Game game) {

        int row = game.getTile().getTileRow();
        int col = game.getTile().getTileCol();

        game.GUI.getTileBoard()[row][col].makeMove(game.getGameBoard(), PLAYER_ONE, row, col);

        setGameOver(getGameBoard().check_win_all(PLAYER_TWO, row, col));

        System.out.println("player taken turn");
        setPlayerTurnFalse();
    }


    Board getGameBoard() { return gameBoard; }

    int getBoardSize () { return boardSize; }

    boolean isPlayerTurn() { return turn; }

    private void setPlayerTurnFalse() { turn = false; }

    void setPlayerTurnTrue() { turn = true; }

    boolean getGameOver () { return isGameOver; }

    private void setGameOver (boolean result) { isGameOver = result; }

    Tile getTile () { return tileClicked; }
}


Solution

  • The following is a complete runable code that implements change of turns.
    It also suggests some improvements as well as design changes. Please note the comments.
    End of game logic was not implemented.
    For convenience the entire code can be copy pasted into one file (BoardGUI.java) and run:

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import javafx.application.Application;
    import javafx.beans.property.BooleanProperty;
    import javafx.beans.property.SimpleBooleanProperty;
    import javafx.geometry.Pos;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.scene.layout.Pane;
    import javafx.scene.layout.StackPane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.text.Font;
    import javafx.scene.text.Text;
    import javafx.stage.Stage;
    
    public class BoardGUI extends Application {
    
        private final static int BOARD_SIZE = 15;
    
        private final Tile[][] tileBoard = new Tile[BOARD_SIZE][BOARD_SIZE];
    
        private final Pane root = new Pane();
    
        private Parent createContent(Game game) {
            root.setPrefSize(755, 755);
    
            for (int i = 0; i < BOARD_SIZE; i++) {
                for (int j = 0; j < BOARD_SIZE; j++) {
                    Tile tile = new Tile(i, j);
                    tile.setTranslateX(j * 50);
                    tile.setTranslateY(i * 50);
    
                    final int row = i, col = j;
                    tile.setOnMousePressed(e -> {
                        //the click causes a move
                        //game.playerMakeMove(game, row, col); //no need to  pass a reference of Game to Game
                        game.playerMakeMove(row, col); //no need to  pass a reference of Game to Game
                    });
    
                    root.getChildren().add(tile);
                    tileBoard[i][j] = tile;
                }
            }
            return root;
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            //there is no need to get a new instance of BoardGUI from Game.
            //Game game = new Game();
            //Scene scene = new Scene (game.gui.createContent(game));
    
            //instead:
            Game game = new Game(this);
            Scene scene = new Scene (createContent(game));
            primaryStage.setScene(scene);
            primaryStage.setTitle("Gomoku");
            primaryStage.show();
    
            //the player should make a move  (click) and it should trigger the next move
        }
    
        //method moved from Tile
        void makeMove(Board board, int player, int row, int col) {
    
            if (board.isMoveAvailable(row, col)) {
                tileBoard[row][col].drawTile(player);
                makeMoveMatrix(board, player, row, col);
            }
        }
    
        //method moved from Tile
        void makeMoveMatrix(Board board, int player, int row, int col) {
            board.make_move(player, row, col);
        }
    
    
        Tile[][] getTileBoard() { return tileBoard; }
    
        class Tile extends StackPane {
    
            Text text = new Text();
            int row, column;
    
            Tile(int row, int col) {
                this.row = row; column = col;
    
                Rectangle border = new Rectangle(50, 50);
                border.setFill(Color.BURLYWOOD);
                border.setStroke(Color.BLACK);
                text.setFont(Font.font(40));
                setAlignment(Pos.CENTER);
                getChildren().addAll(border, text);
            }
    
            void drawTile(int player) {
                text.setText("O");
                if (player == Game.PLAYER_ONE) {
                    text.setFill(Color.BLACK);
                } else {
                    text.setFill(Color.WHITE);
                }
            }
    
            int getTileRow() { return row; }
    
            void setRow(int row) { this.row = row; }
    
            void setColumn(int column) { this.column = column; }
    
            int getTileCol() { return column; }
        }
    
    
        public static void main(String[] args) {
            launch(null);
        }
    }
    
    class Game  {// there is no need for Game to extend BoardGUI
    
        public static final int FREE = 0, PLAYER_ONE = 1, PLAYER_TWO = 2;
        private final BoardGUI gui;
        private final Board gameBoard;
        private final int boardSize = 15, winLength = 5;
        private final Random random = new Random(); //no need to create new Randon with every move
        //replace this attribute with property so you could listen to it
        //private boolean turn = true;
        private final BooleanProperty playerTurn = new SimpleBooleanProperty(true);
    
        private boolean isGameOver = false;
    
        Game(BoardGUI gui) { //get a reference to BoardGUI rather than constructing it
            gameBoard = new Board(boardSize, winLength);
            this.gui = gui;
            playerTurn.addListener( (obs,oldValue,newValue) ->{//listen to turn changes
                if(!newValue) {
                    makeMoveAIRandom();
                }
            });
        }
    
        void makeMoveAIRandom() {
    
            if( playerTurn.get() ) return; //run only if not player turn
    
            List<int[]> availableMoves = gameBoard.getAvailableMoves();
            int index = random.nextInt(availableMoves.size());
            int[] move = availableMoves.get(index);
    
            gui.makeMove(gameBoard, PLAYER_TWO, move[0], move[1]);
            setPlayerTurnTrue();
            System.out.println("AI taken turn " + move[0] +"-"+ move[1]);
        }
    
        //void playerMakeMove(Game game, int row, int col) { //no need to path reference to this
        void playerMakeMove(int row, int col) {
            if(! playerTurn.get() ) return; //run only if player turn
    
            gui.makeMove(gameBoard, PLAYER_ONE, row, col);
            setPlayerTurnFalse();
            System.out.println("player taken turn " + row +"-"+col );
        }
    
        Board getGameBoard() { return gameBoard; }
    
        int getBoardSize () { return boardSize; }
    
        boolean isPlayerTurn() { return playerTurn.get(); }
    
        private void setPlayerTurnFalse() { playerTurn.set(false); }
    
        void setPlayerTurnTrue() { playerTurn.set(true); }
    
        boolean getGameOver () { return isGameOver; }
    
        private void setGameOver (boolean result) { isGameOver = result; }
    }
    
    class Board {
    
        private final int[][] board_matrix;
        private final int board_size, win_length;
    
        Board(int board_size, int win_length) {
            board_matrix = new int[board_size][board_size];
            this.board_size = board_size;
            this.win_length = win_length;
    
            for (int i = 0; i < board_size; i++) {
                for (int j = 0; j < board_size; j++) {
                    board_matrix[i][j] = Game.FREE;
                }
            }
        }
    
        boolean isMoveAvailable(int row, int col) {
    
            return board_matrix[row][col] != Game.PLAYER_ONE && board_matrix[row][col] != Game.PLAYER_TWO ;
        }
    
        void make_move(int player, int x_pos, int y_pos) {
            if (player == Game.PLAYER_ONE) {
                board_matrix[x_pos][y_pos] = Game.PLAYER_ONE;
            } else {
                board_matrix[x_pos][y_pos] = Game.PLAYER_TWO;
            }
        }
    
        List<int[]> getAvailableMoves(){
    
            List<int[]> availableMoves = new ArrayList<>();
            for (int row = 0; row < board_size; row++) {
                for (int col = 0; col < board_size; col++) {
                    if (board_matrix[row][col] == Game.FREE){
                        availableMoves.add(new int[]{ row, col});
                    }
                }
            }
    
            return availableMoves;
        }
    }