diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index da5c50f8..ee8ea216 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -38,6 +38,8 @@ public class ConnectionAcceptor implements Runnable { */ private ServerSocket serverSocket; + private Socket mockSocket = null; + /** * List of client connections. @@ -97,20 +99,32 @@ public class ConnectionAcceptor implements Runnable { return serverPort; } + public void closeConnection() throws IOException { + this.raceLogic.boolFalse(); + if(!this.serverSocket.isClosed()){ + this.serverSocket.close(); + } + } + /** * Run the Acceptor */ @Override - public void run() { + public void run(){ while(clientConnections.remainingCapacity() > 0) { try { + if(Thread.currentThread().isInterrupted()){ + break; + } - Socket mockSocket = serverSocket.accept(); + try { + this.mockSocket = serverSocket.accept(); + } catch (Exception e){} Logger.getGlobal().log(Level.INFO, String.format("Client connected. client ip/port = %s. Local ip/port = %s.", mockSocket.getRemoteSocketAddress(), mockSocket.getLocalSocketAddress())); diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index 47696875..228534c2 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -64,6 +64,10 @@ public class Event { */ private SourceIdAllocator sourceIdAllocator; + private Thread raceThread; + + private Thread connectionThread; + @@ -144,7 +148,8 @@ public class Event { this.latestMessages, this.compositeCommand); - new Thread(newRace, "Event.Start()->RaceLogic thread").start(); + this.raceThread = new Thread(newRace, "Event.Start()->RaceLogic thread"); + raceThread.start(); //Create connection acceptor. @@ -157,13 +162,20 @@ public class Event { } - new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread").start(); + this.connectionThread = new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread"); + connectionThread.start(); sendXMLs(); } + public void endEvent() throws IOException { + this.connectionThread.interrupt(); + this.connectionAcceptor.closeConnection(); + this.raceThread.interrupt(); + } + /** diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index 9536507b..63ff1dd1 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -15,8 +15,6 @@ import java.util.logging.Logger; */ public class MockOutput implements RunnableWithFramePeriod { - - /** * A queue to send messages to client. */ @@ -103,6 +101,8 @@ public class MockOutput implements RunnableWithFramePeriod { Logger.getGlobal().log(Level.WARNING, "MockOutput.run() interrupted while putting message in queue.", e); Thread.currentThread().interrupt(); return; + } catch (Exception e) { + e.printStackTrace(); } } diff --git a/racevisionGame/src/main/java/mock/model/HeartBeatService.java b/racevisionGame/src/main/java/mock/model/HeartBeatService.java index 0028a575..88544340 100644 --- a/racevisionGame/src/main/java/mock/model/HeartBeatService.java +++ b/racevisionGame/src/main/java/mock/model/HeartBeatService.java @@ -78,7 +78,7 @@ public class HeartBeatService implements RunnableWithFramePeriod { * Puts a HeartBeat message on the message queue. * @throws InterruptedException Thrown if the thread is interrupted. */ - private void sendHeartBeat() throws InterruptedException { + private void sendHeartBeat() throws Exception { HeartBeat heartBeat = createHeartbeatMessage(); @@ -92,13 +92,17 @@ public class HeartBeatService implements RunnableWithFramePeriod { while (!Thread.interrupted()) { long currentFrameTime = System.currentTimeMillis(); - waitForFramePeriod(lastHeartbeatTime, currentFrameTime, heartbeatPeriod); + try { + waitForFramePeriod(lastHeartbeatTime, currentFrameTime, heartbeatPeriod); + } catch (Exception e) { + e.printStackTrace(); + } lastHeartbeatTime = currentFrameTime; try { sendHeartBeat(); - } catch (InterruptedException e) { + } catch (Exception e) { Logger.getGlobal().log(Level.WARNING, "HeartBeatService: " + this + " sendHeartBeat() was interrupted on thread: " + Thread.currentThread(), e); Thread.currentThread().interrupt(); return; diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index 5f0c4191..60dca0d0 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -27,6 +27,8 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer { private CompositeCommand commands; + private boolean loopBool = true; + /** * Initialises race loop with state and server message queue * @param race state of race to modify @@ -48,20 +50,32 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer { public void run() { race.initialiseBoats(); - countdown(); + try { + countdown(); + } catch (Exception e) { + e.printStackTrace(); + } + + try { + raceLoop(); + } catch (Exception e) { + e.printStackTrace(); + } + } - raceLoop(); + public void boolFalse(){ + loopBool = false; } /** * Countdown timer until race starts. */ - private void countdown() { + private void countdown() throws Exception { long previousFrameTime = System.currentTimeMillis(); - while (race.getRaceStatusEnum() != RaceStatusEnum.STARTED) { + while (race.getRaceStatusEnum() != RaceStatusEnum.STARTED && loopBool) { long currentTime = System.currentTimeMillis(); @@ -96,11 +110,11 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer { /** * Timer that runs for the duration of the race, until all boats finish. */ - private void raceLoop() { + private void raceLoop() throws Exception { long previousFrameTime = System.currentTimeMillis(); - while (race.getRaceStatusEnum() != RaceStatusEnum.FINISHED) { + while (race.getRaceStatusEnum() != RaceStatusEnum.FINISHED && loopBool) { //Get the current time. long currentTime = System.currentTimeMillis(); diff --git a/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java b/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java index 84bddf78..de165402 100644 --- a/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java +++ b/racevisionGame/src/main/java/network/StreamRelated/MessageSerialiser.java @@ -93,7 +93,11 @@ public class MessageSerialiser implements RunnableWithFramePeriod { long currentFrameTime = System.currentTimeMillis(); - waitForFramePeriod(previousFrameTime, currentFrameTime, 16); + try { + waitForFramePeriod(previousFrameTime, currentFrameTime, 16); + } catch (Exception e) { + e.printStackTrace(); + } previousFrameTime = currentFrameTime; diff --git a/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java b/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java index fd5827f0..10667eca 100644 --- a/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java +++ b/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java @@ -20,7 +20,7 @@ public interface RunnableWithFramePeriod extends Runnable { * @param currentFrameTime The timestamp of the current frame. * @param minimumFramePeriod The minimum period the frame must be. */ - default void waitForFramePeriod(long previousFrameTime, long currentFrameTime, long minimumFramePeriod) { + default void waitForFramePeriod(long previousFrameTime, long currentFrameTime, long minimumFramePeriod) throws Exception { //This is the time elapsed, in milliseconds, since the last server "frame". @@ -39,10 +39,10 @@ public interface RunnableWithFramePeriod extends Runnable { } catch (InterruptedException e) { //If we get interrupted, exit the function. - Logger.getGlobal().log(Level.SEVERE, "RunnableWithFramePeriod.waitForFramePeriod().sleep(framePeriod) was interrupted on thread: " + Thread.currentThread(), e); + /*Logger.getGlobal().log(Level.SEVERE, "RunnableWithFramePeriod.waitForFramePeriod().sleep(framePeriod) was interrupted on thread: " + Thread.currentThread(), e); //Re-set the interrupt flag. Thread.currentThread().interrupt(); - return; + return;*/ } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java index de2e4bc9..28692488 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java @@ -125,7 +125,7 @@ public class ConnectionController extends Controller { Socket socket = new Socket(connection.getHostname(), connection.getPort()); socket.setKeepAlive(true); connectionWrapper.setVisible(false); - parent.enterLobby(socket); + //parent.enterLobby(socket); } catch (IOException e) { /* Never reached */ } } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java index d383a974..c947aa2c 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java @@ -35,6 +35,8 @@ public class HostController extends Controller { @FXML AnchorPane hostWrapper; + private Event game; + @Override public void initialize(URL location, ResourceBundle resources) { @@ -46,7 +48,7 @@ public class HostController extends Controller { */ public void hostGamePressed() throws IOException{ try { - Event game = new Event(false); + this.game = new Event(false); game.start(); connectSocket("localhost", 4942); } catch (EventConstructionException e) { @@ -55,6 +57,10 @@ public class HostController extends Controller { } } + public void endEvent() throws IOException { + game.endEvent(); + } + /** * Connect to a socket * @param address address of the server @@ -64,7 +70,7 @@ public class HostController extends Controller { try{ Socket socket = new Socket(address, port); hostWrapper.setVisible(false); - parent.enterLobby(socket); + parent.enterLobby(socket, true); } catch (IOException e) { /* Never reached */ } } @@ -79,4 +85,9 @@ public class HostController extends Controller { hostWrapper.setVisible(true); } + public void menuBtnPressed(){ + hostWrapper.setVisible(false); + parent.enterTitle(); + } + } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java index 8a2c8713..07af2b49 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java @@ -86,12 +86,17 @@ public class LobbyController extends Controller { RaceConnection connection = lobbyTable.getSelectionModel().getSelectedItem(); Socket socket = new Socket(connection.getHostname(), connection.getPort()); lobbyWrapper.setVisible(false); - parent.enterLobby(socket); + parent.enterLobby(socket, false); } catch (IOException e) { /* Never reached */ e.printStackTrace(); } } + public void menuBtnPressed(){ + lobbyWrapper.setVisible(false); + parent.enterTitle(); + } + /** * adds a new connection */ diff --git a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java index 7cdd0e73..57d18830 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java @@ -3,10 +3,12 @@ package visualiser.Controllers; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.layout.AnchorPane; +import visualiser.app.App; import visualiser.gameController.ControllerClient; import visualiser.model.VisualiserBoat; import visualiser.model.VisualiserRaceEvent; +import java.io.IOException; import java.net.Socket; import java.net.URL; import java.util.ResourceBundle; @@ -39,16 +41,19 @@ public class MainController extends Controller { * @param visualiserRace The object modelling the race. * @param controllerClient Socket Client that manipulates the controller. */ - public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient) { - raceController.startRace(visualiserRace, controllerClient); + public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) { + raceController.startRace(visualiserRace, controllerClient, isHost); } + public void endEvent() throws IOException { hostController.endEvent(); } + /** * Transitions from the server selection screen to the pre-race lobby for a given server. * @param socket The server to read data from. + * @param isHost is connection a host */ - public void enterLobby(Socket socket) { - startController.enterLobby(socket); + public void enterLobby(Socket socket, Boolean isHost) { + startController.enterLobby(socket, isHost); } /** @@ -62,7 +67,9 @@ public class MainController extends Controller { /** * Transitions into the title screen */ - public void enterTitle(){ titleController.enterTitle(); } + public void enterTitle() { + titleController.enterTitle(); + } /** * Transitions into lobby screen diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index 0fc5f765..4fcda581 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -10,6 +10,8 @@ import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.scene.chart.LineChart; import javafx.scene.control.*; +import javafx.scene.control.Label; +import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; @@ -18,12 +20,15 @@ import javafx.scene.layout.StackPane; import javafx.util.Callback; import network.Messages.Enums.RaceStatusEnum; import shared.model.Leg; +import visualiser.app.App; import visualiser.gameController.ControllerClient; import visualiser.gameController.Keys.ControlKey; import visualiser.gameController.Keys.KeyFactory; import visualiser.model.*; +import java.io.IOException; import java.net.URL; +import java.util.Optional; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; @@ -48,7 +53,7 @@ public class RaceController extends Controller { private ControllerClient controllerClient; - + private boolean isHost; /** * The canvas that draws the race. @@ -128,7 +133,36 @@ public class RaceController extends Controller { Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e); } } - }); + if(event.getCode() == KeyCode.ESCAPE) { + + try { + if (isHost) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("Exit Race"); + alert.setContentText("Do you wish to quit the race? You are the host"); + Optional result = alert.showAndWait(); + if (result.get() == ButtonType.OK) { + parent.endEvent(); + race.setVisible(false); + App.app.showMainStage(App.getStage()); + } + } else { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("Exit Race"); + alert.setContentText("Do you wish to quit the race?"); + Optional result = alert.showAndWait(); + if (result.get() == ButtonType.OK) { + race.setVisible(false); + App.app.showMainStage(App.getStage()); + } + } + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); } @@ -366,11 +400,13 @@ public class RaceController extends Controller { * Displays a specified race. * @param visualiserRace Object modelling the race. * @param controllerClient Socket Client that manipulates the controller. + * @param isHost is user a host */ - public void startRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient) { + public void startRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) { this.visualiserRace = visualiserRace; this.controllerClient = controllerClient; + this.isHost = isHost; initialiseRace(); @@ -434,7 +470,14 @@ public class RaceController extends Controller { //Return to main screen if we lose connection. if (!visualiserRace.getServerConnection().isAlive()) { race.setVisible(false); - parent.enterTitle(); + //parent.enterTitle(); + try { + App.app.showMainStage(App.getStage()); + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } //TODO currently this doesn't work correctly - the title screen remains visible after clicking join game //TODO we should display an error to the user //TODO also need to "reset" any state (race, connections, etc...). diff --git a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java index b2d6b2b7..890eb816 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java @@ -82,7 +82,7 @@ public class StartController extends Controller { */ private ControllerClient controllerClient; - + private boolean isHost; @@ -238,7 +238,7 @@ public class StartController extends Controller { startWrapper.setVisible(false); //start.setVisible(false);//TODO is this needed? - parent.beginRace(visualiserRaceEvent, controllerClient); + parent.beginRace(visualiserRaceEvent, controllerClient, isHost); } } @@ -250,10 +250,13 @@ public class StartController extends Controller { /** * Show starting information for a race given a socket. * @param socket network source of information + * @param isHost is user a host */ - public void enterLobby(Socket socket) { + public void enterLobby(Socket socket, Boolean isHost) { try { + this.isHost = isHost; + this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT); this.controllerClient = visualiserRaceEvent.getControllerClient(); diff --git a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java index 887d3f07..32e033c8 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java @@ -1,9 +1,14 @@ package visualiser.Controllers; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.RadioButton; import javafx.scene.layout.AnchorPane; +import javafx.stage.Modality; +import javafx.stage.Stage; import visualiser.app.App; import java.io.IOException; @@ -17,6 +22,7 @@ import java.util.ResourceBundle; * the game. */ public class TitleController extends Controller { + //FXML stuff @FXML Button btnJoin; @FXML @@ -77,4 +83,26 @@ public class TitleController extends Controller { public void initialize(URL location, ResourceBundle resources) { } + + /** + * Called when control button is pressed. New pop up window displaying controls + */ + public void controlBtnPressed(){ + FXMLLoader loader = new FXMLLoader(); + loader.setLocation(getClass().getResource("/visualiser/scenes/controls.fxml")); + Parent layout; + try { + layout = loader.load(); + Scene scene = new Scene(layout); + Stage popupStage = new Stage(); + popupStage.setResizable(false); + popupStage.setTitle("Game Controls"); + popupStage.initModality(Modality.WINDOW_MODAL); + popupStage.setScene(scene); + popupStage.showAndWait(); + } catch (Exception e){ + e.printStackTrace(); + } + } + } diff --git a/racevisionGame/src/main/java/visualiser/app/App.java b/racevisionGame/src/main/java/visualiser/app/App.java index 8c75ee97..eeae5a5d 100644 --- a/racevisionGame/src/main/java/visualiser/app/App.java +++ b/racevisionGame/src/main/java/visualiser/app/App.java @@ -1,18 +1,48 @@ package visualiser.app; +import javafx.animation.FadeTransition; import javafx.application.Application; import javafx.application.Platform; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.concurrent.Worker; import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.geometry.Rectangle2D; import javafx.scene.Parent; import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.ProgressBar; +import javafx.scene.effect.DropShadow; import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.stage.Screen; import javafx.stage.Stage; +import javafx.stage.StageStyle; import javafx.stage.WindowEvent; +import javafx.util.Duration; import visualiser.Controllers.MainController; +import java.io.IOException; + public class App extends Application { + + private static Stage stage; + private Pane splashLayout; + private ProgressBar loadProgress; + private Label progressText; + private static final int SPLASH_WIDTH = 676; + private static final int SPLASH_HEIGHT = 227; + + public static App app; /** * Entry point for running the programme @@ -23,19 +53,103 @@ public class App extends Application { launch(args); } + @Override + public void init() { + ImageView splash = new ImageView(new Image( + getClass().getClassLoader().getResourceAsStream("images/splashScreen.png") + )); + loadProgress = new ProgressBar(); + loadProgress.setPrefWidth(SPLASH_WIDTH - 20); + progressText = new Label("Preparing application . . ."); + splashLayout = new VBox(); + splashLayout.getChildren().addAll(splash, loadProgress, progressText); + progressText.setAlignment(Pos.CENTER); + splashLayout.setStyle( + "-fx-padding: 5; " + + "-fx-background-color: cornsilk; " + + "-fx-border-width:5; " + + "-fx-border-color: " + + "linear-gradient(" + + "to bottom, " + + "chocolate, " + + "derive(chocolate, 50%)" + + ");" + ); + splashLayout.setEffect(new DropShadow()); + } + /** - * Method that displays the visualiser, starting with the title screen. - * @param stage the stage to be displayed. + * Method that sets up and displays the splash screen + * @param initStage the inital stage * @throws Exception if something wrong with title screen. */ - public void start(Stage stage) throws Exception { - stage.setOnCloseRequest(new EventHandler() { + public void start(Stage initStage) throws Exception { + final Task> boatTask = new Task>() { @Override - public void handle(WindowEvent event) { - Platform.exit(); - System.exit(0); + protected ObservableList call() throws InterruptedException { + ObservableList addedFilling = + FXCollections.observableArrayList(); + ObservableList burgerFilling = + FXCollections.observableArrayList( + "Buns", "Patties", "Lettuce", "Onions", "Tomato", + "Sauces" + ); + + updateMessage("Preparing ingredients . . ."); + Thread.sleep(1000); + for (int i = 0; i < burgerFilling.size(); i++) { + Thread.sleep(800); + updateProgress(i + 1, burgerFilling.size()); + String nextFilling = burgerFilling.get(i); + addedFilling.add(nextFilling); + updateMessage("Adding the " + nextFilling + " . . ."); + } + Thread.sleep(400); + updateMessage("Burger's done!"); + + return addedFilling; } - }); + }; + + showSplash( + initStage, + boatTask, + () -> { + try { + showMainStage(new Stage()); + } catch (Exception e) { + e.printStackTrace(); + } + } + ); + new Thread(boatTask).start(); + + } + + /** + * Get main stage + * @return main stage + */ + public static Stage getStage() { + return App.stage; + } + + /** + * Set main stage + * @param stage stage to set main stage + */ + public static void setStage(Stage stage) { + App.stage = stage; + } + + /** + * Show the main stage after the splash screen + * @param stage main stage for application + * @throws Exception Throws an exception on error + */ + public void showMainStage(Stage stage) throws Exception { + App.stage = stage; + App.app = this; FXMLLoader loader = new FXMLLoader(getClass().getResource("/visualiser/scenes/main.fxml")); Parent root = loader.load(); stage.setResizable(false); @@ -49,13 +163,57 @@ public class App extends Application { mc.startCss(); setStage(stage); stage.show(); + stage.setOnCloseRequest(new EventHandler() { + @Override + public void handle(WindowEvent event) { + Platform.exit(); + System.exit(0); + } + }); } - public static Stage getStage() { - return App.stage; + /** + * Show the splash screen + * @param initStage Initial stage + * @param task Task for splash screen + * @param initCompletionHandler initCompletionHandler interface + */ + private void showSplash( + final Stage initStage, + Task task, + InitCompletionHandler initCompletionHandler + ) { + progressText.textProperty().bind(task.messageProperty()); + loadProgress.progressProperty().bind(task.progressProperty()); + task.stateProperty().addListener((observableValue, oldState, newState) -> { + if (newState == Worker.State.SUCCEEDED) { + loadProgress.progressProperty().unbind(); + loadProgress.setProgress(1); + initStage.toFront(); + FadeTransition fadeSplash = new FadeTransition(Duration.seconds(2), splashLayout); + fadeSplash.setFromValue(1.0); + fadeSplash.setToValue(0.0); + fadeSplash.setOnFinished(actionEvent -> initStage.hide()); + fadeSplash.play(); + + initCompletionHandler.complete(); + } + }); + + Scene splashScene = new Scene(splashLayout, Color.TRANSPARENT); + final Rectangle2D bounds = Screen.getPrimary().getBounds(); + initStage.setScene(splashScene); + initStage.setX(bounds.getMinX() + bounds.getWidth() / 2 - SPLASH_WIDTH / 2); + initStage.setY(bounds.getMinY() + bounds.getHeight() / 2 - SPLASH_HEIGHT / 2); + initStage.initStyle(StageStyle.TRANSPARENT); + initStage.setAlwaysOnTop(true); + initStage.show(); } - public static void setStage(Stage stage) { - App.stage = stage; + /** + * InnitCompletionHandler interface + */ + public interface InitCompletionHandler { + void complete(); } } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceService.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceService.java index 21be26d9..e08f1463 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceService.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceService.java @@ -69,7 +69,11 @@ public class VisualiserRaceService implements RunnableWithFramePeriod { long currentFrameTime = System.currentTimeMillis(); - waitForFramePeriod(previousFrameTime, currentFrameTime, 16); + try { + waitForFramePeriod(previousFrameTime, currentFrameTime, 16); + } catch (Exception e) { + e.printStackTrace(); + } previousFrameTime = currentFrameTime; diff --git a/racevisionGame/src/main/java/visualiser/network/ServerConnection.java b/racevisionGame/src/main/java/visualiser/network/ServerConnection.java index ed540566..b0266962 100644 --- a/racevisionGame/src/main/java/visualiser/network/ServerConnection.java +++ b/racevisionGame/src/main/java/visualiser/network/ServerConnection.java @@ -330,7 +330,11 @@ public class ServerConnection implements RunnableWithFramePeriod { while (!Thread.interrupted()) { long currentFrameTime = System.currentTimeMillis(); - waitForFramePeriod(previousFrameTime, currentFrameTime, 100); + try { + waitForFramePeriod(previousFrameTime, currentFrameTime, 100); + } catch (Exception e) { + e.printStackTrace(); + } previousFrameTime = currentFrameTime; diff --git a/racevisionGame/src/main/resources/images/splashScreen.png b/racevisionGame/src/main/resources/images/splashScreen.png new file mode 100644 index 00000000..1d60605e Binary files /dev/null and b/racevisionGame/src/main/resources/images/splashScreen.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/game_controls.png b/racevisionGame/src/main/resources/visualiser/images/game_controls.png new file mode 100644 index 00000000..ac2c9ae0 Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/game_controls.png differ diff --git a/racevisionGame/src/main/resources/visualiser/scenes/controls.fxml b/racevisionGame/src/main/resources/visualiser/scenes/controls.fxml new file mode 100644 index 00000000..f82ffa17 --- /dev/null +++ b/racevisionGame/src/main/resources/visualiser/scenes/controls.fxml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/racevisionGame/src/main/resources/visualiser/scenes/hostgame.fxml b/racevisionGame/src/main/resources/visualiser/scenes/hostgame.fxml index 5a5d1c7b..ced36627 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/hostgame.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/hostgame.fxml @@ -33,6 +33,7 @@ + diff --git a/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml b/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml index b7aaabcf..007ef599 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml @@ -37,6 +37,7 @@ +