Reputation: 119
I am attempting to create a program to simulate a solve of a Rubik's Cube using Java and JavaFX. The scene has a text box that will show every move the computer makes in proper notation. Next to the text box there is an unfolded cube which should show the current state of the cube.
The problem is that the GUI is only updating to show the list of moves, and the state of the cube at the very end of all processes. I have looked through the documentation, other questions on this site, other questions on other sites, and I have not been able to find anything that would solve this problem. I tried using PauseTransition, and I tried using Run Later a little bit. Both of these seemed like overly complicated ways to achieve a simple result.
public class Main extends Application {
@Override
public void start(Stage Stage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("RubiksFXML.fxml"));
Scene scene = new Scene(root, 675, 450);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
Stage.setScene(scene);
Stage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
@FXML
private Button SolveButton;
@FXML
private ImageView s100, s101, s102, s110, s111, s112, s120, s121, s122;
@FXML
private ImageView s200, s201, s202, s210, s211, s212, s220, s221, s222;
@FXML
private ImageView s300, s301, s302, s310, s311, s312, s320, s321, s322;
@FXML
private ImageView s400, s401, s402, s410, s411, s412, s420, s421, s422;
@FXML
private ImageView s500, s501, s502, s510, s511, s512, s520, s521, s522;
@FXML
private ImageView s600, s601, s602, s610, s611, s612, s620, s621, s622;
@FXML
private TextArea MoveRecord;
private char side1[][] = { { 'y', 'y', 'y' }, { 'y', 'y', 'y' }, { 'y', 'y', 'y' } };
private char side2[][] = { { 'o', 'o', 'o' }, { 'o', 'o', 'o' }, { 'o', 'o', 'o' } };
private char side3[][] = { { 'b', 'b', 'b' }, { 'b', 'b', 'b' }, { 'b', 'b', 'b' } };
private char side4[][] = { { 'r', 'r', 'r' }, { 'r', 'r', 'r' }, { 'r', 'r', 'r' } };
private char side5[][] = { { 'w', 'w', 'w' }, { 'w', 'w', 'w' }, { 'w', 'w', 'w' } };
private char side6[][] = { { 'g', 'g', 'g' }, { 'g', 'g', 'g' }, { 'g', 'g', 'g' } };
private char cen = side3[1][1];
private String MoveList = "";
public static void main(String[] args) {
launch(args);
}
@FXML
protected void SolveClicked(ActionEvent event) {
L();
R();
F();
B();
//Refresh does not happen until this point.
//I want it to happen every time the cube is moved
//Hence the OutputCube() function
}
This code is the code to "turn" the left face clockwise, there are 11 more functions for each "turning" possibility, as well as "Clockwise()" which "turns" the actual face
private void L() {
OutputCube();
MoveRecord.appendText("L");
char temp;
Clockwise(2);
temp = side5[0][0];
side5[0][0] = side3[0][0];
side3[0][0] = side1[0][0];
side1[0][0] = side6[0][0];
side6[0][0] = temp;
temp = side5[2][0];
side5[2][0] = side3[2][0];
side3[2][0] = side1[2][0];
side1[2][0] = side6[2][0];
side6[2][0] = temp;
temp = side5[1][0];
side5[1][0] = side3[1][0];
side3[1][0] = side1[1][0];
side1[1][0] = side6[1][0];
side6[1][0] = temp;
}
The following function is not complete this only updates the first side of the cube if the piece is yellow. This procedure is actually performed 5 more times to check each color, then all six colors (with the for loops), are performed five more times to update each side. The complete code produces the desired result, but does not update the ImageViews until every move has been made despite the calling of this function at the beginning of each move.
@FXML
private void OutputCube() {
int imageview = 0;
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
switch (side1[row][col]) {
case 'y':
switch (imageview) {
case 0:
s100.setImage(new Image("yellow.png"));
break;
case 1:
s101.setImage(new Image("yellow.png"));
break;
case 2:
s102.setImage(new Image("yellow.png"));
break;
case 3:
s110.setImage(new Image("yellow.png"));
break;
case 4:
s111.setImage(new Image("yellow.png"));
break;
case 5:
s112.setImage(new Image("yellow.png"));
break;
case 6:
s120.setImage(new Image("yellow.png"));
break;
case 7:
s121.setImage(new Image("yellow.png"));
break;
case 8:
s122.setImage(new Image("yellow.png"));
break;
}
break;
}
imageview++;
}
}
}
Upvotes: 0
Views: 867
Reputation: 209358
You don't see the effect until the end because you are executing everything on the FX Application Thread. This is the same thread that is responsible for rendering the UI, so if some action (such as your SolveClicked
[sic: please use proper naming conventions] method) is performed on that thread, the UI cannot be rendered until the action is complete.
If you think about this the right way, what you are really trying to do is create an animation. (You don't just want to show the solution; you want to show multiple "animation frames" to the user, each of which represents a step in the solution.) So you should use the animation API. The simplest way might be a Timeline
:
@FXML
protected void solveClicked(ActionEvent event) {
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(500), e -> L()),
new KeyFrame(Duration.millis(1000), e -> R()),
new KeyFrame(Duration.millis(1500), e -> F()),
new KeyFrame(Duration.millis(2000), e -> B()));
timeline.play();
}
If you need a more "automated" way of generating the timeline, you can do (fun) things like:
@FXML
protected void solveClicked(ActionEvent event) {
Runnable[] steps = new Runnable[] {this::L, this::R, this::F, this::B};
createTimeline(steps, 500).play();
}
private Timeline createTimeline(Runnable[] steps, int delay) {
Duration frameTime = Duration.millis(delay);
Duration increment = Duration.millis(delay);
Timeline timeline = new Timeline() ;
for (Runnable step : steps) {
timeline.getKeyFrames().add(new KeyFrame(frameTime, e -> step.run()));
frameTime = frameTime.add(increment);
}
return timeline ;
}
A more sophisticated approach might be to represent each move of a face by an animation (showing the actual rotation), and then combine them all into a SequentialTransition
.
Upvotes: 2