After click myButton runs 2 Thread. First change the button image, second - loop. I want my program at first сhange image and after run loop. But it doesnt work, they are finished in one moment.
myButton.setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
ImageView imageView = new ImageView(image);
imageView.setFitHeight(my_button_height - 16);
imageView.setFitWidth(my_button_width - 16);
myButton.setStyle("-fx-background-color: #ffffff00; ");
myButton.setGraphic(imageView);
}
});
thread.run();
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 15; i++){
System.out.println(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
thread1.run();
}
});
}
I tried to add .join. It didnt help
Use a PauseTransition
, not threads
You don't need to use threads to implement your requirement.
the game, where to find identical cards with image. Card is button with image. When player opened two cards, my program check them and if they are identical everything is good, in another way closed. But I ran into a problem when player open second card, he cannot see it
The issue is that there is no pause after a player action under certain conditions, so the player cannot see the cards they have flipped over, before they are flipped back.
This can be fixed using a PauseTransition
. Usage is explained in this answer:
Example
Here is some example code for a memory game. The example:
The relevant code is the snippet:
public MemoryGame(String tileText) {
ObservableList<Tile> tiles = createTiles(tileText);
FilteredList<Tile> visibleTiles = tiles.filtered(Tile::isShowing);
board = createBoard(tiles);
PauseTransition pauseForCompletion = new PauseTransition(
FLIP_PAUSE_DURATION
);
pauseForCompletion.setOnFinished(e ->
completeMove(tiles, visibleTiles, board)
);
visibleTiles.addListener((ListChangeListener<Tile>) c -> {
if (visibleTiles.size() == 2) {
board.setDisable(true);
pauseForCompletion.playFromStart();
}
});
}
This is just a demo app, it isn't thoroughly tested and may have some bugs in it. The demo is probably more complex then needed, it uses a filtered list to register flipped cards, but could be simplified with just a couple of object properties to represent flipped cards instead.
For a slightly more complex app (or even this one refactored), I'd suggest using a more MVC style approach where the game state is modeled and tested independent of the UI, but for this proof-of-concept demo, the code below is OK for that purpose.
Hopefully, it illustrates one way to use the PauseTransition
concept to solve an issue similar to the one you have.
MemoryGame.java
import javafx.animation.PauseTransition;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.collections.*;
import javafx.collections.transformation.FilteredList;
import javafx.geometry.Insets;
import javafx.scene.control.Labeled;
import javafx.scene.layout.Pane;
import javafx.scene.layout.TilePane;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class MemoryGame {
private static final Duration FLIP_PAUSE_DURATION = Duration.seconds(
1
);
private final TilePane board;
private final ReadOnlyBooleanWrapper solved = new ReadOnlyBooleanWrapper(
false
);
public MemoryGame(String tileText) {
ObservableList<Tile> tiles = createTiles(tileText);
FilteredList<Tile> visibleTiles = tiles.filtered(Tile::isShowing);
board = createBoard(tiles);
PauseTransition pauseForCompletion = new PauseTransition(
FLIP_PAUSE_DURATION
);
pauseForCompletion.setOnFinished(e ->
completeMove(tiles, visibleTiles, board)
);
visibleTiles.addListener((ListChangeListener<Tile>) c ->
respondToMove(visibleTiles, board, pauseForCompletion)
);
}
private static void respondToMove(
FilteredList<Tile> visibleTiles,
TilePane board,
PauseTransition pauseForCompletion
) {
if (visibleTiles.size() == 2) {
board.setDisable(true);
pauseForCompletion.playFromStart();
}
}
private void completeMove(
ObservableList<Tile> tiles,
FilteredList<Tile> visibleTiles,
TilePane board
) {
long nShowing = visibleTiles.stream()
.map(Labeled::getText)
.distinct()
.count();
if (nShowing == 1) { // all visible tiles match
// replace all matching tiles with a blank pane.
for (Tile tile : visibleTiles) {
int tileIdx = board.getChildren().indexOf(tile);
board.getChildren().set(tileIdx, new Pane());
}
for (int i = visibleTiles.size() - 1; i >= 0; i--) {
tiles.remove(visibleTiles.get(i));
}
if (tiles.isEmpty()) {
solved.set(true);
}
} else {
// tiles don't match, so hide them until they are turned again.
for (int i = visibleTiles.size() - 1; i >= 0; i--) {
visibleTiles.get(i).setShowing(false);
}
}
board.setDisable(false);
}
private static TilePane createBoard(ObservableList<Tile> tiles) {
TilePane board = new TilePane(
10, 10
);
board.getStyleClass().add("board");
board.getChildren().addAll(tiles);
board.setPrefColumns((int) Math.sqrt(tiles.size()));
board.setPadding(new Insets(10));
return board;
}
private static ObservableList<Tile> createTiles(String tileText) {
List<String> gameText =
(tileText + tileText)
.chars()
.mapToObj(Character::toString)
.collect(Collectors.toCollection(ArrayList::new));
Collections.shuffle(gameText);
ObservableList<Tile> tiles = FXCollections.observableArrayList(
tile -> new Observable[] { tile.showingProperty() }
);
gameText.stream()
.map(Tile::new)
.forEachOrdered(tiles::add);
return tiles;
}
public TilePane getBoard() {
return board;
}
public boolean isSolved() {
return solved.get();
}
public ReadOnlyBooleanProperty solvedProperty() {
return solved.getReadOnlyProperty();
}
}
Tile.java
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.Button;
class Tile extends Button {
private static final String BLANK = " ";
private final BooleanProperty showing = new SimpleBooleanProperty(
false
);
public Tile(String tileText) {
getStyleClass().add("tile");
textProperty().bind(
Bindings
.when(showing)
.then(tileText)
.otherwise(BLANK)
);
setOnAction(e -> setShowing(true));
}
public boolean isShowing() {
return showing.get();
}
public BooleanProperty showingProperty() {
return showing;
}
public void setShowing(boolean showing) {
this.showing.set(showing);
}
}
MemoryGameApp.java
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MemoryGameApp extends Application {
private static final String TILE_TEXT = "Qn39Pzr#";
private static final String CSS =
"""
data:text/css,
.board .button:disabled {
-fx-opacity: 1;
}
.tile {
-fx-font-family: monospace; -fx-font-size: 40px;
}
""";
private MemoryGame memoryGame;
private Scene scene;
@Override
public void start(Stage stage) {
memoryGame = new MemoryGame(TILE_TEXT);
scene = new Scene(memoryGame.getBoard());
scene.getStylesheets().add(CSS);
stage.setScene(scene);
stage.show();
memoryGame.solvedProperty().addListener(new EndGameListener());
}
public static void main(String[] args) {
launch(args);
}
class EndGameListener implements ChangeListener<Boolean> {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (memoryGame.isSolved()) {
memoryGame.solvedProperty().removeListener(this);
memoryGame = new MemoryGame(TILE_TEXT);
memoryGame.solvedProperty().addListener(this);
scene.setRoot(memoryGame.getBoard());
}
}
}
}