The renderHanoi() method is supposed to move the disks, by clearing the disks from the VBoxes and then adding them again in the new order after each move is made, but it seems nothing is shown unless it's the last move which makes everything pretty pointless.
I tried different methods of creating delays like Thread.sleep, Platform.runLater, etc. None of them seem to work. How do I solve this?
import java.util.Arrays;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.application.Platform;
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;
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;
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 solve(int n, Tower src, Tower aux, Tower dest) {
if (n < 1) {
return;
}
solve(n-1, src, dest, aux);
dest.push(src.pop());
System.out.println(n);
solve(n-1, aux, src, dest);
}
public void solve() {
renderHanoi();
timer.start();
solve(n, src, aux, dest);
}
AnimationTimer timer = new AnimationTimer() {
@Override
public void handle(long now) {
renderHanoi(); // Update UI after each frame
}
};
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());
}
}
The problem is that your solve() method returns in a tiny fraction of a second. It happens so fast that there is nothing to animate.
You were on the right track. We want to run the
solvemethod in a different thread, with calls to Thread.sleep, so it doesn’t run too quickly. We cannot and must not call Thread.sleep in the JavaFX application thread (the thread that callsstartand all event handlers), because that will cause all processing of events to be delayed, including painting of windows and processing of mouse and keyboard input.So first, let’s add that Thread.sleep, some calls to Platform.runLater to update the window, and a Thread to do it all safely:
We still have one problem: changes to variables or fields made in one thread, are not guaranteed to be seen in other threads. This section of the Java Language Specification explains it succinctly:
Java has many ways to safely handle multi-threaded access. In this case, it is sufficient to use
synchronizedin the Tower class andfinalin the Hanoi class.So we change the method declarations of Tower to look like this:
This guarantees that changes made to a Tower’s fields in the
solvemethod will be visible to the JavaFX application thread.The Hanoi class itself also references src, aux, and dest in different threads. We could use
synchronizedhere, but it’s simpler to just make those fields final:Java can make a lot of safe assumptions about
finalfields when accessing them from multiple threads, since there is no need to worry about multiple threads failing to see changes to those fields.