kuhaku kuhaku - 3 months ago 12
Java Question

Breaking recursion from listeners

I'm trying to break a special case that makes this code recursive.

I have a Javafx game where there are human and computer players each play when it's his turn and there can be many rounds.

A computer is supposed to play automatically and move to the next player immediately and show no direct indication to the UI (but it's possible to review what it did afterwards).

The problem is in the case where there are only computer players, we will come here the moment the

currentBoardPane
was loaded, enter the condition since all players are computers, set the board of the next player, and then without finishing the call, call this same function again:

currentBoardPane.addListener((e) -> {
if(gameManager.checkIfCurrentPlayerIsComputer()){

gameManager.playAutoMovesForCurrentPlayer();
gameManager.setNextPlayer(); // it does current player property = next player

//update board on scene
currentBoardPaneIndex = ((currentBoardPaneIndex + 1) % gameManager.getPlayers().size());
currentBoardPane.setValue(boardPanes.get((currentBoardPaneIndex))); //this is a recursive call
}
});


Instead of this, if I subscribe a listener to the
currentPlayer
property in
GameManager
then I still need to call
setNextPlayer()
from that listener which is again recursive.

I can make a special case if all players are a computer, then run the game from a
while(true){}
instead of listeners and binds but there has to be a better way to break this recursion.

Is there a way to not get into recursion while still having listeners and binds?

Notes:

currentBoardPane
signifies the current game board on the screen and it's an
ObjectProperty
.

Answer

Making the following assumptions about your code:

  1. Everything is currently running on the FX Application Thread
  2. The currentBoardPane.setValue(...) causes the UI to update (so you update the UI each move)

then a "quick and dirty" way to do this is:

currentBoardPane.addListener((e) -> {  
    if(gameManager.checkIfCurrentPlayerIsComputer()){

        gameManager.playAutoMovesForCurrentPlayer();

        //update board on scene
        Platform.runLater(() -> {
            gameManager.setNextPlayer(); // it does current player property = next player
            currentBoardPaneIndex = ((currentBoardPaneIndex + 1) % gameManager.getPlayers().size());
            currentBoardPane.setValue(boardPanes.get((currentBoardPaneIndex))); //this is a recursive call
        });
    }
});

This delegates the updates to a new Runnable, schedules that runnable to execute on the FX Application Thread, and exits the handler immediately. Thus the call to currentBoardPane.setValue(...) is executed later and is no longer recursive.

In fact, if you do just a little more work:

private final Executor aiExecutor = Executors.newSingleThreadExecutor();

// ...

currentBoardPane.addListener((e) -> {  
    if(gameManager.checkIfCurrentPlayerIsComputer()){

        Task<Void> makeMoveTask = new Task<Void>() {
            @Override
            protected Void call() {
                gameManager.playAutoMovesForCurrentPlayer();
                return null ;
            }
        };

        makeMoveTask.setOnSucceeded(e -> {

            //update board on scene
            gameManager.setNextPlayer(); // it does current player property = next player
            currentBoardPaneIndex = ((currentBoardPaneIndex + 1) % gameManager.getPlayers().size());
            currentBoardPane.setValue(boardPanes.get((currentBoardPaneIndex))); //this is a recursive call
        });

        aiExecutor.execute(makeMoveTask);
    }
});

then this is exactly the code you would use if computing the move took enough time that it would not be acceptable to block the UI while it was happening. (And if computing the move takes very little time, this will still work just fine.) This assumes that playAutoMovesForCurrentPlayer() doesn't update the UI.