1

I'm creating the game Snakes And Ladders. I use a game mode that allows you to play against the computer.

When the human player rolls the dice the rollTheDiceAndMove method is called. We wait 1 second (dice is rolling...). Then we call the move function that actually moves the pieces to the specified tile. If a snake or a ladder is hit, I want to wait another 1 second before going to the final tile. Eg. I wait 1 second and I roll 5, I go my_current_pos + dice_roll tiles in front, then I move the piece. If snake or ladder hit: another delay and move again recursively.

Finally, if the "Against Computer" mode is selected, when the human player moves, I want to wait another second before computer automatically rolls the dice. As you see below, I'm using Timer and TimerTask classes but it's very complicated because whatever is inside the Timer scope executes after some period, but code outside the timer is executed without delay and this is causing me many bugs and asynchronization.

What do you suggest using to create this delay?

public void rollTheDiceAndMove() {
    int diceRoll = gameBoard.rollDice();
    // delay for dice roll.

    new Timer().schedule(
        new TimerTask() {
            @Override
            public void run() {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        ////////////////////////////////////////////////////////////////////////////

                        gameGUI.indicateDiceRoll(diceRoll);
                        int newIndex = getPlayerIndexAfterRoll(diceRoll);
                        move(newIndex);
                        System.out.println("change turns");
                        swapTurns();
                        System.out.println(isComputerTurn());
                        gameGUI.updateCurrentTurnLabel();


                        if (newIndex == GameBoard.WIN_POINT) {
                            boolean restartGame = gameBoard.playAgainOrExit();

                            if (restartGame) {
                                Player winner = gameBoard.getCurrentPlayer();
                                gameGUI.updateScore(winner);
                                gameGUI.playAgain();
                            } else {
                                System.exit(0);
                            }
                        }

                        ////////////////////////////////////////////////////////////////////////////
                    }
                });
            }
        }, GameBoard.DICE_ROLL_DELAY
    );

    // try to recursively call this method again for computer turn.
}


public void move(int currentIndex) {
    int[] newCoordinates = gameBoard.getBoardCoordinates(GameBoard.NUMBER_OF_TILES - currentIndex);

    gameBoard.getCurrentPlayer().getPlayerPiece().setPosition(currentIndex);
    gameGUI.movePieceImages(newCoordinates[0], newCoordinates[1]);

    if (gameBoard.getTile(currentIndex).containsLadderOrSnake()) {
        // delay for moving to second tile.

        new Timer().schedule(
            new TimerTask() {
                @Override
                public void run() {
                    Platform.runLater(new Runnable() {
                        @Override
                        public void run() {

                            int updatedIndex = gameBoard.getUpdatedPosition(currentIndex);
                            move(updatedIndex);


                        }
                    });
                }
            }, GameBoard.SECOND_MOVE_DELAY *2
        );

        return;             // we need to return 'cause of recursion. Swap turns will be executed twice, one time for the initial call and the other time on above line.
    }
}
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
A.Tressos
  • 35
  • 7
  • Unless there are multiple things going on at the same time in your main loop could you just use an inline Thread.sleep? – Paul Rubel Aug 07 '18 at 13:00
  • I have tried. Delay at rolling the dice works but when it comes to double move cause snake or ladder found, it's asynchronized – A.Tressos Aug 07 '18 at 13:14
  • I would suggest using [`PauseTransition`](https://docs.oracle.com/javase/8/javafx/api/javafx/animation/PauseTransition.html) or [`Timeline`](https://docs.oracle.com/javase/8/javafx/api/javafx/animation/Timeline.html). – SedJ601 Aug 07 '18 at 13:38
  • 4
    Your `move()` method is not designed correctly. Calling `move` within `move` is terrible idea. You should stay away from recursive calls unless you need to speed up your program. That is not the case here. `move()` should only do one thing and that is move a piece. You currently have it moving a piece, delaying, and trying to move another piece. – SedJ601 Aug 07 '18 at 13:57
  • http://tomasmikula.github.io/blog/2014/06/04/timers-in-javafx-and-reactfx.html – SedJ601 Aug 07 '18 at 14:24

2 Answers2

1

You can use schedule method of ScheduledThreadPoolExecutor class from java.util.concurrent package

schedule(Runnable command, long delay, TimeUnit unit) Creates and executes a one-shot action that becomes enabled after the given delay.

After that you can check on Future object if task is completed or not.

public void rollTheDiceAndMove() {
    int diceRoll = gameBoard.rollDice();
    // delay for dice roll.
    ScheduledFuture<Void> scheduledFuture = Executors.newScheduledThreadPool(1).schedule(new TimerTask() {
        @Override
        public void run() {
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    ////////////////////////////////////////////////////////////////////////////

                    gameGUI.indicateDiceRoll(diceRoll);
                    int newIndex = getPlayerIndexAfterRoll(diceRoll);
                    move(newIndex);
                    System.out.println("change turns");
                    swapTurns();
                    System.out.println(isComputerTurn());
                    gameGUI.updateCurrentTurnLabel();


                    if (newIndex == GameBoard.WIN_POINT) {
                        boolean restartGame = gameBoard.playAgainOrExit();

                        if (restartGame) {
                            Player winner = gameBoard.getCurrentPlayer();
                            gameGUI.updateScore(winner);
                            gameGUI.playAgain();
                        } else {
                            System.exit(0);
                        }
                    }

                    ////////////////////////////////////////////////////////////////////////////
                }
            });
        }
    }, GameBoard.DICE_ROLL_DELAY, TimeUnit.SECONDS);

    // here we wait and don't go to next actions until run() method of TimerTask is finished.
    // we can specify wait timeout value if needed.
    scheduledFuture.get();


    // try to r

The other problem is that Platform.runLater calls are asynchronious too and you are not able to get any result. In Java FX Task can be used to achieve the same:

Platform.runLater and Task in JavaFX

Compete example could be like:

public void rollTheDiceAndMove() {
    int diceRoll = gameBoard.rollDice();
    // delay for dice roll.
    ScheduledFuture<Void> scheduledFuture = Executors.newScheduledThreadPool(1).schedule(
            new Task<Void>() {
                        @Override
                        public Void call() {
                            ////////////////////////////////////////////////////////////////////////////
                            gameGUI.indicateDiceRoll(diceRoll);
                            int newIndex = getPlayerIndexAfterRoll(diceRoll);
                            ScheduledFuture<Void> moveScheduledFuture = move(newIndex);
                            if(moveScheduledFuture != null) {
                                moveScheduledFuture.get();
                            }

                            System.out.println("change turns");
                            swapTurns();
                            System.out.println(isComputerTurn());
                            gameGUI.updateCurrentTurnLabel();


                            if (newIndex == GameBoard.WIN_POINT) {
                                boolean restartGame = gameBoard.playAgainOrExit();

                                if (restartGame) {
                                    Player winner = gameBoard.getCurrentPlayer();
                                    gameGUI.updateScore(winner);
                                    gameGUI.playAgain();
                                } else {
                                    System.exit(0);
                                }
                            }

                            ////////////////////////////////////////////////////////////////////////////
                            return null;
                        }
            }, GameBoard.DICE_ROLL_DELAY, TimeUnit.SECONDS);

    scheduledFuture.get();
    // try to recursively call this method again for computer turn.
}


public ScheduledFuture<Void> move(int currentIndex) {
    int[] newCoordinates = gameBoard.getBoardCoordinates(GameBoard.NUMBER_OF_TILES - currentIndex);

    gameBoard.getCurrentPlayer().getPlayerPiece().setPosition(currentIndex);
    gameGUI.movePieceImages(newCoordinates[0], newCoordinates[1]);

    if (gameBoard.getTile(currentIndex).containsLadderOrSnake()) {
        // delay for moving to second tile.

      return   Executors.newScheduledThreadPool(1).schedule(
                new Task<Void>() {
                    @Override
                    public Void call() {
                                int updatedIndex = gameBoard.getUpdatedPosition(currentIndex);
                                move(updatedIndex);

                    }
                }, GameBoard.SECOND_MOVE_DELAY *2, TimeUnit.SECONDS);

        // we need to return 'cause of recursion. Swap turns will be executed twice, one time for the initial call and the other time on above line.
    }
    return null;
}
Andrei Prakhov
  • 206
  • 1
  • 5
  • 1. Could you give an example with my code? 2. Does it solve the problem that the outter code is executed without delay? Is it going to stay inside the schedule block while we wait ? – A.Tressos Aug 07 '18 at 11:17
  • 1
    1. yes, I have added it to an answer 2. yes you can wait on future object in the outter code until task is finished - you can also return Future object from the method to the outer code – Andrei Prakhov Aug 07 '18 at 12:04
  • What about the "SECOND_MOVE_DELAY"? I want to move the piece and if snake or ladder hit move again. If I add use this way it will ignore the first move and just move to the final tile. – A.Tressos Aug 07 '18 at 12:26
  • move() method can also return a Future object instead of void and then we can wait on it when we call move() inside TimerTask. Every time you need to do an asynchronious action (with delay or not) you can use Future object to check if task is completed – Andrei Prakhov Aug 07 '18 at 13:07
  • Could you provide some code for this? I 've never heard of it before. – A.Tressos Aug 07 '18 at 13:17
  • It seems taht you can avoid nested Runnables if you use Task class provided in Java FX. Check the updated example – Andrei Prakhov Aug 07 '18 at 14:52
  • I see your point, but maybe it's becoming a little complicated. I guess there is another, easier way? Thanks a lot for the responses tho. – A.Tressos Aug 07 '18 at 15:21
  • Yes, as Sedrick has suggested it is better to redesign code and avoid recursion. Then it will be easier to manage the workflow – Andrei Prakhov Aug 07 '18 at 15:31
  • Yeah, I'm gonna give refactoring a try – A.Tressos Aug 07 '18 at 16:18
1

Create a copy of your project and try using PauseTransition.

public void rollTheDiceAndMove() 
{
    int diceRoll = gameBoard.rollDice();
    System.out.println("Player: rolling the dice");

    PauseTransition pause = new PauseTransition(Duration.seconds(1));
    pause.setOnFinished(event ->{
        System.out.println("1 second after rolling the dice");
        gameGUI.indicateDiceRoll(diceRoll);
        int newIndex = getPlayerIndexAfterRoll(diceRoll);
        playerMove(newIndex);
        if(checkWin(Player))
        {
            System.out.println("Player won!");                
        }
        else
        {
            System.out.println("change turns");
            swapTurns();
            System.out.println(isComputerTurn());
            gameGUI.updateCurrentTurnLabel();
            computerRollDiceAndMove();
        }






    });
    pause.play();
}


public void computerRollDiceAndMove()
{
    int diceRoll = gameBoard.rollDice();
    System.out.println("Computer: rolling the dice");

    PauseTransition pause = new PauseTransition(Duration.seconds(1));
    pause.setOnFinished(event ->{
        System.out.println("1 second after rolling the dice");
        gameGUI.indicateDiceRoll(diceRoll);
        int newIndex = getComputerIndexAfterRoll(diceRoll);
        computerMove(newIndex);
        if(checkWin(computer))
        {
            System.out.println("Computer won!");
        }
        else{
            System.out.println(isComputerTurn());
            gameGUI.updateCurrentTurnLabel();
        }
    });
    pause.play();   
}
SedJ601
  • 12,173
  • 3
  • 41
  • 59
  • 1. You are using computerMove recursively 2. There is no " computerMove(newIndex) " defined 3. As I see it's completely the same as the normal move, what am I missing ? – A.Tressos Aug 07 '18 at 15:23
  • You are correct. That should not be there. This is more like sudo code. – SedJ601 Aug 07 '18 at 15:38
  • I copied that code. I can fix it when I get back to work. – SedJ601 Aug 07 '18 at 15:39
  • Fixed. This code is not going to meld with your code. It's simply a guideline to follow. – SedJ601 Aug 07 '18 at 15:42
  • 1
    Thanks a lot. So the general idea is to avoid recursion and create two separate methods for the two kinds of players. – A.Tressos Aug 07 '18 at 16:20