/* * Copyright (C) 2013-2015 2048FX * Jose Pereda, Bruno Borges & Jens Deters * All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.jpereda.game2048; import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ListIterator; import java.util.Map; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Group; import javafx.scene.Node; import static javafx.scene.Node.BASELINE_OFFSET_SAME_AS_HEIGHT; import javafx.scene.control.Button; import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; import javafx.scene.input.KeyCode; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; import javafx.scene.text.TextFlow; import javafx.util.Duration; import com.gluonhq.charm.down.common.PlatformFactory; /** * * @author jpereda */ public class Board extends Group { public static final int CELL_SIZE = 128; private static final int BORDER_WIDTH = (14 + 2) / 2; private static final int TOP_HEIGHT = 92; private static final int GAP_HEIGHT = 50; private static final int TOOLBAR_HEIGHT = 80; private final IntegerProperty gameScoreProperty = new SimpleIntegerProperty(0); private final IntegerProperty gameBestProperty = new SimpleIntegerProperty(0); private final IntegerProperty gameMovePoints = new SimpleIntegerProperty(0); private final BooleanProperty gameWonProperty = new SimpleBooleanProperty(false); private final BooleanProperty gameOverProperty = new SimpleBooleanProperty(false); private final BooleanProperty gameAboutProperty = new SimpleBooleanProperty(false); private final BooleanProperty gamePauseProperty = new SimpleBooleanProperty(false); private final BooleanProperty gameTryAgainProperty = new SimpleBooleanProperty(false); private final BooleanProperty gameSaveProperty = new SimpleBooleanProperty(false); private final BooleanProperty gameRestoreProperty = new SimpleBooleanProperty(false); private final BooleanProperty gameQuitProperty = new SimpleBooleanProperty(false); private final BooleanProperty layerOnProperty = new SimpleBooleanProperty(false); private final BooleanProperty resetGame = new SimpleBooleanProperty(false); private final BooleanProperty clearGame = new SimpleBooleanProperty(false); private final BooleanProperty restoreGame = new SimpleBooleanProperty(false); private final BooleanProperty saveGame = new SimpleBooleanProperty(false); private LocalTime time; private Timeline timer; private final StringProperty clock = new SimpleStringProperty("00:00:00"); private final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.systemDefault()); // User Interface controls private final VBox vGame = new VBox(0); private final Group gridGroup = new Group(); private final HBox hTop = new HBox(0); private final VBox vScore = new VBox(-5); private final Label lblScore = new Label("0"); private final Label lblBest = new Label("0"); private final Label lblPoints = new Label(); private final HBox overlay = new HBox(); private final VBox txtOverlay = new VBox(10); private final Label lOvrText= new Label(); private final Label lOvrSubText= new Label(); private final HBox buttonsOverlay = new HBox(); private final Button bTry = new Button("Try again"); private final Button bContinue = new Button("Keep going"); private final Button bContinueNo = new Button("No, keep going"); private final Button bSave = new Button("Save"); private final Button bRestore = new Button("Restore"); private final Button bQuit = new Button("Quit"); private final HBox hToolbar = new HBox(); private final Label lblTime=new Label(); private Timeline timerPause; private final int gridWidth; private final GridOperator gridOperator; private final SessionManager sessionManager; public Board(GridOperator grid){ this.gridOperator=grid; gridWidth = CELL_SIZE * grid.getGridSize() + BORDER_WIDTH * 2; sessionManager = new SessionManager(gridOperator); createScore(); createGrid(); initGameProperties(); } private void createScore() { Label lblTitle = new Label("2048"); lblTitle.getStyleClass().addAll("game-label","game-title"); Label lblSubtitle = new Label("FX"); lblSubtitle.getStyleClass().addAll("game-label","game-subtitle"); HBox hFill = new HBox(); HBox.setHgrow(hFill, Priority.ALWAYS); hFill.setAlignment(Pos.CENTER); VBox vScores = new VBox(); HBox hScores=new HBox(5); vScore.setAlignment(Pos.CENTER); vScore.getStyleClass().add("game-vbox"); Label lblTit = new Label("SCORE"); lblTit.getStyleClass().addAll("game-label","game-titScore"); lblScore.getStyleClass().addAll("game-label","game-score"); lblScore.textProperty().bind(gameScoreProperty.asString()); vScore.getChildren().addAll(lblTit, lblScore); VBox vRecord = new VBox(-5); vRecord.setAlignment(Pos.CENTER); vRecord.getStyleClass().add("game-vbox"); Label lblTitBest = new Label("BEST"); lblTitBest.getStyleClass().addAll("game-label","game-titScore"); lblBest.getStyleClass().addAll("game-label","game-score"); lblBest.textProperty().bind(gameBestProperty.asString()); vRecord.getChildren().addAll(lblTitBest, lblBest); hScores.getChildren().addAll(vScore,vRecord); VBox vFill = new VBox(); VBox.setVgrow(vFill, Priority.ALWAYS); vScores.getChildren().addAll(hScores,vFill); hTop.getChildren().addAll(lblTitle, lblSubtitle, hFill,vScores); hTop.setMinSize(gridWidth, TOP_HEIGHT); hTop.setPrefSize(gridWidth, TOP_HEIGHT); hTop.setMaxSize(gridWidth, TOP_HEIGHT); vGame.getChildren().add(hTop); HBox hTime=new HBox(); hTime.setMinSize(gridWidth, GAP_HEIGHT); hTime.setAlignment(Pos.BOTTOM_RIGHT); lblTime.getStyleClass().addAll("game-label","game-time"); lblTime.textProperty().bind(clock); timer=new Timeline(new KeyFrame(Duration.ZERO, e->{ clock.set(LocalTime.now().minusNanos(time.toNanoOfDay()).format(fmt)); }),new KeyFrame(Duration.seconds(1))); timer.setCycleCount(Animation.INDEFINITE); hTime.getChildren().add(lblTime); vGame.getChildren().add(hTime); getChildren().add(vGame); lblPoints.getStyleClass().addAll("game-label","game-points"); lblPoints.setAlignment(Pos.CENTER); lblPoints.setMinWidth(100); getChildren().add(lblPoints); } private Rectangle createCell(int i, int j){ final double arcSize = CELL_SIZE / 6d; Rectangle cell = new Rectangle(i * CELL_SIZE, j * CELL_SIZE, CELL_SIZE, CELL_SIZE); // provide default style in case css are not loaded cell.setFill(Color.WHITE); cell.setStroke(Color.GREY); cell.setArcHeight(arcSize); cell.setArcWidth(arcSize); cell.getStyleClass().add("game-grid-cell"); return cell; } private void createGrid() { for(int i=0; i<gridOperator.getGridSize(); i++){ for(int j=0; j<gridOperator.getGridSize(); j++){ gridGroup.getChildren().add(createCell(i, j)); } } gridGroup.getStyleClass().add("game-grid"); gridGroup.setManaged(false); gridGroup.setLayoutX(BORDER_WIDTH); gridGroup.setLayoutY(BORDER_WIDTH); HBox hBottom = new HBox(); hBottom.getStyleClass().add("game-backGrid"); hBottom.setMinSize(gridWidth, gridWidth); hBottom.setPrefSize(gridWidth, gridWidth); hBottom.setMaxSize(gridWidth, gridWidth); // Clip hBottom to keep the dropshadow effects within the hBottom Rectangle rect = new Rectangle(gridWidth, gridWidth); hBottom.setClip(rect); hBottom.getChildren().add(gridGroup); vGame.getChildren().add(hBottom); // toolbar HBox hPadding= new HBox(); hPadding.setMinSize(gridWidth, TOOLBAR_HEIGHT); hPadding.setPrefSize(gridWidth, TOOLBAR_HEIGHT); hPadding.setMaxSize(gridWidth, TOOLBAR_HEIGHT); hToolbar.setAlignment(Pos.CENTER); hToolbar.getStyleClass().add("game-backGrid"); hToolbar.setMinSize(gridWidth, TOOLBAR_HEIGHT); hToolbar.setPrefSize(gridWidth, TOOLBAR_HEIGHT); hToolbar.setMaxSize(gridWidth, TOOLBAR_HEIGHT); vGame.getChildren().add(hPadding); vGame.getChildren().add(hToolbar); } public void setToolBar(HBox toolbar){ toolbar.disableProperty().bind(layerOnProperty); int div=7; if(PlatformFactory.getPlatform().getName().equals(PlatformFactory.DESKTOP)){ div=10; } toolbar.spacingProperty().bind(Bindings.divide(vGame.widthProperty(), div)); hToolbar.getChildren().add(toolbar); } public void tryAgain(){ if(!gameTryAgainProperty.get()){ gameTryAgainProperty.set(true); } } private void btnTryAgain(){ timerPause.stop(); layerOnProperty.set(false); doResetGame(); } private void keepGoing(){ timerPause.stop(); layerOnProperty.set(false); gamePauseProperty.set(false); gameTryAgainProperty.set(false); gameSaveProperty.set(false); gameRestoreProperty.set(false); gameAboutProperty.set(false); gameQuitProperty.set(false); timer.play(); } private void quit() { timerPause.stop(); PlatformService.getInstance().exit(); } private final Overlay wonListener= new Overlay("You win!","",bContinue, bTry, "game-overlay-won", "game-lblWon",true); private class Overlay implements ChangeListener<Boolean> { private final Button btn1, btn2; private final String message, warning; private final String style1, style2; private final boolean pause; public Overlay(String message, String warning, Button btn1, Button btn2, String style1, String style2, boolean pause){ this.message=message; this.warning=warning; this.btn1=btn1; this.btn2=btn2; this.style1=style1; this.style2=style2; this.pause=pause; } @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { if (newValue) { timer.stop(); if(pause){ timerPause.play(); } overlay.getStyleClass().setAll("game-overlay",style1); lOvrText.setText(message); lOvrText.getStyleClass().setAll("game-label",style2); lOvrSubText.setText(warning); lOvrSubText.getStyleClass().setAll("game-label","game-lblWarning"); txtOverlay.getChildren().setAll(lOvrText,lOvrSubText); buttonsOverlay.getChildren().setAll(btn1); if(btn2!=null){ buttonsOverlay.getChildren().add(btn2); } Board.this.getChildren().addAll(overlay,buttonsOverlay); layerOnProperty.set(true); } } } private void initGameProperties() { overlay.setMinSize(gridWidth, gridWidth); overlay.setAlignment(Pos.CENTER); overlay.setTranslateY(TOP_HEIGHT + GAP_HEIGHT); overlay.getChildren().setAll(txtOverlay); txtOverlay.setAlignment(Pos.CENTER); buttonsOverlay.setAlignment(Pos.CENTER); buttonsOverlay.setTranslateY(TOP_HEIGHT + GAP_HEIGHT + gridWidth / 2); buttonsOverlay.setMinSize(gridWidth, gridWidth / 2); buttonsOverlay.setSpacing(10); bTry.getStyleClass().add("game-button"); bTry.setOnTouchPressed(e -> btnTryAgain()); bTry.setOnAction(e -> btnTryAgain()); bTry.setOnKeyPressed(e->{ if(e.getCode().equals(KeyCode.ENTER) || e.getCode().equals(KeyCode.SPACE)){ btnTryAgain(); } }); bContinue.getStyleClass().add("game-button"); bContinue.setOnTouchPressed(e -> keepGoing()); bContinue.setOnMouseClicked(e -> keepGoing()); bContinue.setOnKeyPressed(e->{ if(e.getCode().equals(KeyCode.ENTER) || e.getCode().equals(KeyCode.SPACE)){ keepGoing(); } }); bContinueNo.getStyleClass().add("game-button"); bContinueNo.setOnTouchPressed(e -> keepGoing()); bContinueNo.setOnMouseClicked(e -> keepGoing()); bContinueNo.setOnKeyPressed(e->{ if(e.getCode().equals(KeyCode.ENTER) || e.getCode().equals(KeyCode.SPACE)){ keepGoing(); } }); bSave.getStyleClass().add("game-button"); bSave.setOnTouchPressed(e -> saveGame.set(true)); bSave.setOnMouseClicked(e -> saveGame.set(true)); bSave.setOnKeyPressed(e->{ if(e.getCode().equals(KeyCode.ENTER) || e.getCode().equals(KeyCode.SPACE)){ saveGame.set(true); } }); bRestore.getStyleClass().add("game-button"); bRestore.setOnTouchPressed(e -> restoreGame.set(true)); bRestore.setOnMouseClicked(e -> restoreGame.set(true)); bRestore.setOnKeyPressed(e->{ if(e.getCode().equals(KeyCode.ENTER) || e.getCode().equals(KeyCode.SPACE)){ restoreGame.set(true); } }); bQuit.getStyleClass().add("game-button"); bQuit.setOnTouchPressed(e -> quit()); bQuit.setOnMouseClicked(e -> quit()); bQuit.setOnKeyPressed(e->{ if(e.getCode().equals(KeyCode.ENTER) || e.getCode().equals(KeyCode.SPACE)){ quit(); } }); timerPause=new Timeline(new KeyFrame(Duration.seconds(1), e->time=time.plusNanos(1_000_000_000))); timerPause.setCycleCount(Animation.INDEFINITE); gameWonProperty.addListener(wonListener); gameOverProperty.addListener(new Overlay("Game over!","",bTry, null, "game-overlay-over", "game-lblOver",false)); gamePauseProperty.addListener(new Overlay("Game Paused","",bContinue, null, "game-overlay-pause", "game-lblPause",true)); gameTryAgainProperty.addListener(new Overlay("Try Again?","Current game will be deleted",bTry, bContinueNo, "game-overlay-pause", "game-lblPause",true)); gameSaveProperty.addListener(new Overlay("Save?","Previous saved data will be overwritten",bSave, bContinueNo, "game-overlay-pause", "game-lblPause",true)); gameRestoreProperty.addListener(new Overlay("Restore?","Current game will be deleted",bRestore, bContinueNo, "game-overlay-pause", "game-lblPause",true)); gameAboutProperty.addListener((observable, oldValue, newValue) -> { if (newValue) { timer.stop(); timerPause.play(); overlay.getStyleClass().setAll("game-overlay","game-overlay-quit"); TextFlow flow = new TextFlow(); flow.setTextAlignment(TextAlignment.CENTER); flow.setPadding(new Insets(10,0,0,0)); flow.setMinSize(gridWidth, gridWidth); flow.setPrefSize(gridWidth, gridWidth); flow.setMaxSize(gridWidth, gridWidth); flow.setPrefSize(BASELINE_OFFSET_SAME_AS_HEIGHT, BASELINE_OFFSET_SAME_AS_HEIGHT); Text t00 = new Text("2048"); t00.getStyleClass().setAll("game-label","game-lblAbout"); Text t01 = new Text("FX"); t01.getStyleClass().setAll("game-label","game-lblAbout2"); Text t02 = new Text(" Game\n"); t02.getStyleClass().setAll("game-label","game-lblAbout"); Text t1 = new Text("JavaFX game - "+PlatformFactory.getPlatform().getName()+" version\n\n"); t1.getStyleClass().setAll("game-label", "game-lblAboutSub"); Text t20 = new Text("Powered by "); t20.getStyleClass().setAll("game-label", "game-lblAboutSub"); Hyperlink link1 = new Hyperlink(); link1.setText("JavaFXPorts"); link1.setOnAction(e->{ PlatformService.getInstance().launchURL("http://javafxports.org/page/home"); }); link1.getStyleClass().setAll("game-label", "game-lblAboutSub2"); Text t21 = new Text(" Project \n\n"); t21.getStyleClass().setAll("game-label", "game-lblAboutSub"); Text t23 = new Text("\u00A9 "); t23.getStyleClass().setAll("game-label", "game-lblAboutSub"); Hyperlink link2 = new Hyperlink(); link2.setText("@JPeredaDnr"); link2.setOnAction(e->{ PlatformService.getInstance().launchURL("https://twitter.com/JPeredaDnr"); }); link2.getStyleClass().setAll("game-label", "game-lblAboutSub2"); Text t22 = new Text(" & "); if(PlatformFactory.getPlatform().getName().equals(PlatformFactory.IOS)){ t22.setText(", "); } t22.getStyleClass().setAll("game-label", "game-lblAboutSub"); Hyperlink link3 = new Hyperlink(); link3.setText("@brunoborges"); link3.setOnAction(e->{ PlatformService.getInstance().launchURL("https://twitter.com/brunoborges"); }); Text t32 = new Text(" & "); t32.getStyleClass().setAll("game-label", "game-lblAboutSub"); link3.getStyleClass().setAll("game-label", "game-lblAboutSub2"); Hyperlink link4 = new Hyperlink(); link4.setText("@Jerady"); link4.setOnAction(e->{ PlatformService.getInstance().launchURL("https://twitter.com/Jerady"); }); link4.getStyleClass().setAll("game-label", "game-lblAboutSub2"); Text t24 = new Text("\n\n"); t24.getStyleClass().setAll("game-label", "game-lblAboutSub"); Text t31 = new Text(" Version "+Game2048.VERSION+" - 2015\n\n"); t31.getStyleClass().setAll("game-label", "game-lblAboutSub"); flow.getChildren().setAll(t00, t01, t02, t1, t20, link1, t21, t23, link2, t22, link3); if(PlatformFactory.getPlatform().getName().equals(PlatformFactory.IOS)){ flow.getChildren().addAll(t32, link4); } flow.getChildren().addAll(t24, t31); txtOverlay.getChildren().setAll(flow); buttonsOverlay.getChildren().setAll(bContinue); this.getChildren().removeAll(overlay,buttonsOverlay); this.getChildren().addAll(overlay,buttonsOverlay); layerOnProperty.set(true); } }); gameQuitProperty.addListener(new Overlay("Quit Game?","Non saved data will be lost",bQuit, bContinueNo, "game-overlay-quit", "game-lblQuit",true)); restoreRecord(); gameScoreProperty.addListener((ov,i,i1)->{ if(i1.intValue()>gameBestProperty.get()){ gameBestProperty.set(i1.intValue()); } }); layerOnProperty.addListener((ov,b,b1)->{ if(!b1){ getChildren().removeAll(overlay, buttonsOverlay); // Keep the focus on the game when the layer is removed: getParent().requestFocus(); } else if(b1){ // Set focus on the first button buttonsOverlay.getChildren().get(0).requestFocus(); } }); } private void doClearGame() { saveRecord(); for (ListIterator<Node> iterator = gridGroup.getChildren().listIterator(); iterator.hasNext();) { if (iterator.next() instanceof Tile) { iterator.remove(); } } getChildren().removeAll(overlay, buttonsOverlay); clearGame.set(false); resetGame.set(false); restoreGame.set(false); saveGame.set(false); layerOnProperty.set(false); gameScoreProperty.set(0); gameWonProperty.set(false); gameOverProperty.set(false); gameAboutProperty.set(false); gamePauseProperty.set(false); gameTryAgainProperty.set(false); gameSaveProperty.set(false); gameRestoreProperty.set(false); gameQuitProperty.set(false); clearGame.set(true); } private void doResetGame() { doClearGame(); resetGame.set(true); } public void animateScore() { if(gameMovePoints.get()==0){ return; } final Timeline timeline = new Timeline(); lblPoints.setText("+" + gameMovePoints.getValue().toString()); lblPoints.setOpacity(1); double posX=vScore.localToScene(vScore.getWidth()/2d,0).getX(); lblPoints.setTranslateX(0); lblPoints.setTranslateX(lblPoints.sceneToLocal(posX, 0).getX()-lblPoints.getWidth()/2d); lblPoints.setLayoutY(20); final KeyValue kvO = new KeyValue(lblPoints.opacityProperty(), 0); final KeyValue kvY = new KeyValue(lblPoints.layoutYProperty(), 100); Duration animationDuration = Duration.millis(600); final KeyFrame kfO = new KeyFrame(animationDuration, kvO); final KeyFrame kfY = new KeyFrame(animationDuration, kvY); timeline.getKeyFrames().add(kfO); timeline.getKeyFrames().add(kfY); timeline.play(); } public void addTile(Tile tile){ double layoutX = tile.getLocation().getLayoutX(CELL_SIZE) - (tile.getMinWidth() / 2); double layoutY = tile.getLocation().getLayoutY(CELL_SIZE) - (tile.getMinHeight() / 2); tile.setLayoutX(layoutX); tile.setLayoutY(layoutY); gridGroup.getChildren().add(tile); } public Tile addRandomTile(Location randomLocation) { Tile tile = Tile.newRandomTile(); tile.setLocation(randomLocation); double layoutX = tile.getLocation().getLayoutX(CELL_SIZE) - (tile.getMinWidth() / 2); double layoutY = tile.getLocation().getLayoutY(CELL_SIZE) - (tile.getMinHeight() / 2); tile.setLayoutX(layoutX); tile.setLayoutY(layoutY); tile.setScaleX(0); tile.setScaleY(0); gridGroup.getChildren().add(tile); return tile; } public Group getGridGroup() { return gridGroup; } public void startGame(){ restoreRecord(); time=LocalTime.now(); timer.playFromStart(); } public void setPoints(int points){ gameMovePoints.set(points); } public int getPoints() { return gameMovePoints.get(); } public void addPoints(int points){ gameMovePoints.set(gameMovePoints.get() + points); gameScoreProperty.set(gameScoreProperty.get() + points); } public void setGameOver(boolean gameOver){ gameOverProperty.set(gameOver); } public void setGameWin(boolean won){ if(!gameWonProperty.get()){ gameWonProperty.set(won); } } public void pauseGame(){ if(!gamePauseProperty.get()){ gamePauseProperty.set(true); } } public void aboutGame(){ if(!gameAboutProperty.get()){ gameAboutProperty.set(true); } } public void quitGame(){ if(!gameQuitProperty.get()){ gameQuitProperty.set(true); } } public BooleanProperty isLayerOn(){ return layerOnProperty; } public BooleanProperty resetGameProperty() { return resetGame; } public BooleanProperty clearGameProperty() { return clearGame; } public BooleanProperty saveGameProperty() { return saveGame; } public BooleanProperty restoreGameProperty() { return restoreGame; } public boolean saveSession() { if(!gameSaveProperty.get()){ gameSaveProperty.set(true); } return true; } /* Once we have confirmation */ public void saveSession(Map<Location, Tile> gameGrid) { saveGame.set(false); sessionManager.saveSession(gameGrid, gameScoreProperty.getValue(), LocalTime.now().minusNanos(time.toNanoOfDay()).toNanoOfDay()); keepGoing(); } public boolean restoreSession() { if(!gameRestoreProperty.get()){ gameRestoreProperty.set(true); } return true; } /* Once we have confirmation */ public boolean restoreSession(Map<Location, Tile> gameGrid) { timerPause.stop(); restoreGame.set(false); doClearGame(); timer.stop(); StringProperty sTime=new SimpleStringProperty(""); int score = sessionManager.restoreSession(gameGrid, sTime); if (score >= 0) { gameScoreProperty.set(score); // check tiles>=2048 gameWonProperty.set(false); for (Map.Entry pair : gameGrid.entrySet()) { Tile t=(Tile)pair.getValue(); if(t!=null && t.getValue()>=GameManager.FINAL_VALUE_TO_WIN){ gameWonProperty.removeListener(wonListener); gameWonProperty.set(true); gameWonProperty.addListener(wonListener); } } if(!sTime.get().isEmpty()){ time = LocalTime.now().minusNanos(new Long(sTime.get())); } timer.play(); return true; } // not session found, restart again doResetGame(); return false; } public void saveRecord() { RecordManager recordManager = new RecordManager(gridOperator.getGridSize()); recordManager.saveRecord(gameScoreProperty.getValue()); } private void restoreRecord() { RecordManager recordManager = new RecordManager(gridOperator.getGridSize()); gameBestProperty.set(recordManager.restoreRecord()); } public void externalPause(boolean b, boolean b1){ if(!layerOnProperty.get()){ if(b1){ timerPause.play(); } else if(b && !b1){ timerPause.stop(); } } } }