content: package com.almasb.mp;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  import javafx.animation.FadeTransition;
  import javafx.application.Application;
  import javafx.geometry.Pos;
  import javafx.scene.Parent;
  import javafx.scene.Scene;
  import javafx.scene.input.MouseEvent;
  import javafx.scene.layout.Pane;
  import javafx.scene.layout.StackPane;
  import javafx.scene.paint.Color;
  import javafx.scene.shape.Rectangle;
  import javafx.scene.text.Font;
  import javafx.scene.text.Text;
  import javafx.stage.Stage;
  import javafx.util.Duration;
  
  public class MemoryPuzzleApp extends Application {
  
      private static final int NUM_OF_PAIRS = 72;
      private static final int NUM_PER_ROW = 12;
  
      private Tile selected = null;
      private int clickCount = 2;
  
      private Parent createContent() {
          Pane root = new Pane();
          root.setPrefSize(600, 600);
  
          char c = 'A';
          List<Tile> tiles = new ArrayList<>();
          for (int i = 0; i < NUM_OF_PAIRS; i++) {
              tiles.add(new Tile(String.valueOf(c)));
              tiles.add(new Tile(String.valueOf(c)));
              c++;
          }
  
          Collections.shuffle(tiles);
  
          for (int i = 0; i < tiles.size(); i++) {
              Tile tile = tiles.get(i);
              tile.setTranslateX(50 * (i % NUM_PER_ROW));
              tile.setTranslateY(50 * (i / NUM_PER_ROW));
              root.getChildren().add(tile);
          }
  
          return root;
      }
  
      @Override
      public void start(Stage primaryStage) throws Exception {
          primaryStage.setScene(new Scene(createContent()));
          primaryStage.show();
      }
  
      private class Tile extends StackPane {
          private Text text = new Text();
  
          public Tile(String value) {
              Rectangle border = new Rectangle(50, 50);
              border.setFill(null);
              border.setStroke(Color.BLACK);
  
              text.setText(value);
              text.setFont(Font.font(30));
  
              setAlignment(Pos.CENTER);
              getChildren().addAll(border, text);
  
              setOnMouseClicked(this::handleMouseClick);
              close();
          }
  
          public void handleMouseClick(MouseEvent event) {
              if (isOpen() || clickCount == 0)
                  return;
  
              clickCount--;
  
              if (selected == null) {
                  selected = this;
                  open(() -> {});
              }
              else {
                  open(() -> {
                      if (!hasSameValue(selected)) {
                          selected.close();
                          this.close();
                      }
  
                      selected = null;
                      clickCount = 2;
                  });
              }
          }
  
          public boolean isOpen() {
              return text.getOpacity() == 1;
          }
  
          public void open(Runnable action) {
              FadeTransition ft = new FadeTransition(Duration.seconds(0.5), text);
              ft.setToValue(1);
              ft.setOnFinished(e -> action.run());
              ft.play();
          }
  
          public void close() {
              FadeTransition ft = new FadeTransition(Duration.seconds(0.5), text);
              ft.setToValue(0);
              ft.play();
          }
  
          public boolean hasSameValue(Tile other) {
              return text.getText().equals(other.text.getText());
          }
      }
  
      public static void main(String[] args) {
          launch(args);
      }
  }
  