You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX汉诺塔游戏问题:仅完成全部求解后才显示界面

汉诺塔UI实时刷新问题解决方法

问题

renderHanoi()方法本该在每次圆盘移动后清除VBox并重新添加圆盘以实现动态效果,但目前仅在最后一步移动完成后才更新界面,移动过程完全无法显示。尝试过Thread.sleep、Platform.runLater等方式添加延迟均无效。

核心原因

原递归solve方法在JavaFX主线程中同步执行,递归会一次性跑完所有圆盘移动操作,期间主线程被完全阻塞,根本没有机会处理UI更新请求。哪怕AnimationTimer在运行,所有UI更新也会被积压到递归结束后才一次性处理,因此只能看到最终状态。

解决方案

先把所有移动步骤预先收集到队列中,再通过JavaFX的Timeline组件逐步执行每一步操作,每执行一步就触发一次UI刷新,确保主线程不被阻塞。

修改后的完整代码

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Queue;
import java.util.Random;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class App extends Application {

    @Override
    public void start(Stage stage) {
        HBox platform = new HBox();
        VBox[] towerBoxes = new VBox[] { new VBox(), new VBox(), new VBox()};
        platform.getChildren().addAll(Arrays.asList(towerBoxes));
        Hanoi testing = new Hanoi(10);
        testing.towerBoxes = towerBoxes;
        var scene = new Scene(platform, 640, 480);
        stage.setScene(scene);
        stage.show();
        testing.solve();
    }

    public static void main(String[] args) {
        launch();
    }
}

class Tower {
    private int sp = 0;
    private Rectangle[] disks;
    
    Tower(int n) {
        disks = new Rectangle[n];
    }
    
    public void push(Rectangle entry) {
        if (sp < disks.length)
            disks[sp++] = entry;
        else
            System.err.println(this + ".push(" + entry + ") failed, stack is full");
    }
    
    public Rectangle pop() {
        if (sp > 0)
            return disks[--sp];
        else {
            System.err.println(this + ".pop() failed, stack is empty");
            return null;
        }
    }
    
    public boolean hasEntry() {
        return sp > 0;
    }
    
    @Override
    public Tower clone() {
        Tower copy = new Tower(disks.length);
        copy.sp = this.sp;
        copy.disks = this.disks.clone();
        return copy;
    }
}

class Hanoi {
    Tower src;
    Tower aux;
    Tower dest;
    int n;
    public VBox[] towerBoxes;
    // 存储移动操作的队列:每个元素为[源塔, 目标塔]
    private Queue<Tower[]> moveQueue = new ArrayDeque<>();

    public Hanoi(int n) {
        src = new Tower(n);
        aux = new Tower(n);
        dest = new Tower(n);
        this.n = n;
        
        for (int i = 0; i < n; i++) {
            Rectangle disk = new Rectangle(30 + 20 * i, 10);
            Color diskColor = generateRandomColor();
            disk.setFill(diskColor);
            disk.setStroke(diskColor);
            src.push(disk);
        }
    }
    
    private static Color generateRandomColor() {
        Random random = new Random();
        double red = random.nextDouble();
        double green = random.nextDouble();
        double blue = random.nextDouble();
        return new Color(red, green, blue, 1.0);
    }
    
    // 修改递归逻辑:不直接移动圆盘,而是收集所有移动步骤到队列
    private void collectMoves(int n, Tower src, Tower aux, Tower dest) {
        if (n < 1) {
            return;
        }
        collectMoves(n-1, src, dest, aux);
        moveQueue.add(new Tower[]{src, dest});
        collectMoves(n-1, aux, src, dest);
    }
    
    public void solve() {
        // 先收集全部移动步骤
        collectMoves(n, src, aux, dest);
        // 初始化显示初始状态
        renderHanoi();
        // 创建Timeline逐步执行移动动画
        Timeline timeline = new Timeline();
        KeyFrame keyFrame = new KeyFrame(Duration.millis(500), event -> {
            if (!moveQueue.isEmpty()) {
                Tower[] move = moveQueue.poll();
                Tower srcTower = move[0];
                Tower destTower = move[1];
                // 执行实际圆盘移动
                destTower.push(srcTower.pop());
                // 刷新UI显示最新状态
                renderHanoi();
            } else {
                // 所有移动完成后停止Timeline
                timeline.stop();
            }
        });
        // 设置循环次数等于移动步骤总数
        timeline.setCycleCount(moveQueue.size());
        timeline.play();
    }
    
    private void renderHanoi() {
        for (VBox towerBox : towerBoxes)
            towerBox.getChildren().clear();
        Tower[] towersCopy = new Tower[]{src.clone(), aux.clone(), dest.clone()};
        for (int i = 0; i < 3; i++) {
            while (towersCopy[i].hasEntry()) {
                towerBoxes[i].getChildren().add(towersCopy[i].pop());
            }
        }
    }
}

代码说明

  1. 收集移动步骤:将原递归移动逻辑改为collectMoves,仅把每一步的源塔和目标塔存入队列,不直接执行移动操作。
  2. Timeline控制动画节奏:用Timeline每隔500ms执行一步移动,既保证动画流畅,又不会阻塞主线程。
  3. 实时UI刷新:每完成一步移动就调用renderHanoi()更新界面,确保用户能看到每一步的移动过程。

内容的提问来源于stack exchange,提问作者hansoko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 11:43:24