Java 中断侦听器的递归
我试图打破一个使代码递归的特殊情况 我有一个Javafx游戏,当轮到他时,每个人都有人和电脑玩家玩,可以玩很多回合 计算机应该自动播放,并立即移动到下一个播放器,并且不向用户界面显示任何直接指示(但可以查看它之后做了什么) 问题是,如果只有计算机播放器,我们将在加载Java 中断侦听器的递归,java,recursion,javafx,java-8,javafx-8,Java,Recursion,Javafx,Java 8,Javafx 8,我试图打破一个使代码递归的特殊情况 我有一个Javafx游戏,当轮到他时,每个人都有人和电脑玩家玩,可以玩很多回合 计算机应该自动播放,并立即移动到下一个播放器,并且不向用户界面显示任何直接指示(但可以查看它之后做了什么) 问题是,如果只有计算机播放器,我们将在加载currentBoardPane时来到这里,输入条件,因为所有播放器都是计算机,设置下一个播放器的板,然后在不完成调用的情况下,再次调用相同的函数: currentBoardPane.addListener((e) ->
currentBoardPane
时来到这里,输入条件,因为所有播放器都是计算机,设置下一个播放器的板,然后在不完成调用的情况下,再次调用相同的函数:
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
}
});
相反,如果我订阅了GameManager
中的currentPlayer
属性的侦听器,那么我仍然需要从该侦听器调用setNextPlayer()
,这也是递归的
我可以做一个特例,如果所有玩家都是一台计算机,然后从while(true){}
而不是监听器和绑定运行游戏,但是必须有更好的方法来打破这种递归
有没有一种方法可以在仍然有侦听器和绑定的情况下不进入递归
注:
currentBoardPane
表示屏幕上的当前游戏板,它是一个对象属性
对您的代码做出以下假设:
currentBoardPane.setValue(…)
会导致UI更新(因此每次移动都会更新UI)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
});
}
});
这会将更新委托给一个新的Runnable
,安排Runnable在FX应用程序线程上执行,并立即退出处理程序。因此,对currentBoardPane.setValue(…)
的调用将在稍后执行,不再是递归的
事实上,如果你多做一点工作:
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);
}
});
private final Executor aiExecutor=Executors.newSingleThreadExecutor();
// ...
currentBoardPane.addListener((e)->{
如果(gameManager.checkIfCurrentPlayerIsComputer()){
Task makeMoveTask=新任务(){
@凌驾
受保护的无效调用(){
gameManager.playAutoMovesForCurrentPlayer();
返回null;
}
};
makeMoveTask.setOnSucceeded(e->{
//现场更新板
gameManager.setNextPlayer();//它没有当前玩家属性=下一个玩家
currentBoardPaneIndex=((currentBoardPaneIndex+1)%gameManager.getPlayers().size());
currentBoardPane.setValue(boardPanes.get((currentBoardPaneIndex));//这是一个递归调用
});
aiExecutor.execute(makeMoveTask);
}
});
如果计算移动花费了足够的时间,以至于在移动过程中阻止UI是不可接受的,那么这正是您将使用的代码。(如果计算移动只需要很少的时间,这仍然可以正常工作。)这假设
playAutoMovesForCurrentPlayer()
不会更新用户界面。如果playAutoMovesForCurrentPlayer()
方法花费了很长时间(例如,你在下棋,或者其他一些移动计算量很大的游戏),然后,将对任务中的PlayAutoMovesforrentPlayer()
的调用包装起来,在后台线程上执行,并在onSucceeded
处理程序中执行剩余的代码(setNextPlayer()
,更新索引和窗格)。这将破坏递归,因为处理程序不会直接调用更改。如果你知道移动接近瞬间,这可能不是一个理想的解决方案,但它会解决问题。这是一个简单的游戏,“AI”只是随机选择,所以它是瞬间的。是的,但它仍然解决了问题。如果我理解正确,每次移动都会更新UI,对吗?因此,如果您使用您建议的解决方案(while(true){doMoves();}
),您必须将其放在后台线程中,并使用Platform.runLater(…)
更新UI,否则您将看不到UI更改(直到循环完成,这似乎永远不会发生)。因此,无论哪种方式,您都需要使用后台线程。while循环将调用使用事件或侦听器更新UI的方法,但它仍然会阻止javafx主线程,直到它完成为止?那么我可能更愿意使用你的第一个建议。如果它在FX应用程序线程上运行,那么是的,它将阻止该线程,直到它完成。(如果不是,您已经创建了一个后台线程,因此无论采用哪种方式,您都可以使用线程。)好的,playAutoMovesForCurrentPlayer()
在控制器中有更新UI的事件或侦听器……所以您可以只使用第一个选项。也许,将计算移动的逻辑(无论多么琐碎)与获取移动表示并用其更新UI的逻辑分离出来会更干净。再次感谢您的帮助。