package visualiser.Controllers; import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.paint.Color; import network.Messages.Enums.RaceStatusEnum; import network.Messages.LatestMessages; import shared.dataInput.*; import shared.enums.XMLFileType; import shared.exceptions.InvalidBoatDataException; import shared.exceptions.InvalidRaceDataException; import shared.exceptions.InvalidRegattaDataException; import shared.exceptions.XMLReaderException; import visualiser.app.VisualiserInput; import visualiser.model.VisualiserBoat; import visualiser.model.VisualiserRace; import java.io.IOException; import java.net.Socket; import java.net.URL; import java.util.*; /** * Controller to for waiting for the race to start. */ public class StartController extends Controller implements Observer { @FXML private GridPane start; @FXML private AnchorPane startWrapper; /** * The name of the race/regatta. */ @FXML private Label raceTitleLabel; /** * The time the race starts at. */ @FXML private Label raceStartLabel; /** * The current time at the race location. */ @FXML private Label timeZoneTime; /** * Time until the race starts. */ @FXML private Label timer; @FXML private TableView boatNameTable; @FXML private TableColumn boatNameColumn; @FXML private TableColumn boatCodeColumn; /** * The status of the race. */ @FXML private Label raceStatusLabel; /** * The object used to read packets from the connected server. */ private VisualiserInput visualiserInput; /** * The race object which describes the currently occurring race. */ private VisualiserRace visualiserRace; /** * An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor. */ List colors = new ArrayList<>(Arrays.asList( Color.BLUEVIOLET, Color.BLACK, Color.RED, Color.ORANGE, Color.DARKOLIVEGREEN, Color.LIMEGREEN, Color.PURPLE, Color.DARKGRAY, Color.YELLOW )); /** * Ctor. */ public StartController() { } @Override public void initialize(URL location, ResourceBundle resources) { } /** * Starts the race. * Called once we have received all XML files from the server. * @param latestMessages The set of latest race messages to use for race. * @throws XMLReaderException Thrown if XML file cannot be parsed. * @throws InvalidRaceDataException Thrown if XML file cannot be parsed. * @throws InvalidBoatDataException Thrown if XML file cannot be parsed. * @throws InvalidRegattaDataException Thrown if XML file cannot be parsed. */ private void startRace(LatestMessages latestMessages) throws XMLReaderException, InvalidRaceDataException, InvalidBoatDataException, InvalidRegattaDataException { //Create data sources from latest messages for the race. RaceDataSource raceDataSource = new RaceXMLReader(latestMessages.getRaceXMLMessage().getXmlMessage(), XMLFileType.ResourcePath); BoatDataSource boatDataSource = new BoatXMLReader(latestMessages.getBoatXMLMessage().getXmlMessage(), XMLFileType.ResourcePath); RegattaDataSource regattaDataSource = new RegattaXMLReader(latestMessages.getRegattaXMLMessage().getXmlMessage(), XMLFileType.ResourcePath); //Create race. this.visualiserRace = new VisualiserRace(boatDataSource, raceDataSource, regattaDataSource, latestMessages, this.colors); new Thread(this.visualiserRace).start(); //Initialise the boat table. initialiseBoatTable(this.visualiserRace); //Initialise the race name. initialiseRaceName(this.visualiserRace); //Initialises the race clock. initialiseRaceClock(this.visualiserRace); //Starts the race countdown timer. countdownTimer(); } public AnchorPane startWrapper(){ return startWrapper; } /** * Initialises the boat table that is to be shown on the pane. * @param visualiserRace The race to get data from. */ private void initialiseBoatTable(VisualiserRace visualiserRace) { //Get the boats. ObservableList boats = visualiserRace.getBoats(); //Populate table. boatNameTable.setItems(boats); boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty()); } /** * Initialises the race name which is shown on the pane. * @param visualiserRace The race to get data from. */ private void initialiseRaceName(VisualiserRace visualiserRace) { raceTitleLabel.setText(visualiserRace.getRegattaName()); } /** * Initialises the race clock/timer labels for the start time, current time, and remaining time. * @param visualiserRace The race to get data from. */ private void initialiseRaceClock(VisualiserRace visualiserRace) { //Start time. initialiseRaceClockStartTime(visualiserRace); //Current time. initialiseRaceClockCurrentTime(visualiserRace); //Remaining time. initialiseRaceClockDuration(visualiserRace); } /** * Initialises the race current time label. * @param visualiserRace The race to get data from. */ private void initialiseRaceClockStartTime(VisualiserRace visualiserRace) { raceStartLabel.setText(visualiserRace.getRaceClock().getStartingTimeString()); visualiserRace.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> { Platform.runLater(() -> { raceStartLabel.setText(newValue); }); }); } /** * Initialises the race current time label. * @param visualiserRace The race to get data from. */ private void initialiseRaceClockCurrentTime(VisualiserRace visualiserRace) { visualiserRace.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> { Platform.runLater(() -> { timeZoneTime.setText(newValue); }); }); } /** * Initialises the race duration label. * @param visualiserRace The race to get data from. */ private void initialiseRaceClockDuration(VisualiserRace visualiserRace) { visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> { Platform.runLater(() -> { timer.setText(newValue); }); }); } /** * Countdown timer until race starts. */ private void countdownTimer() { new AnimationTimer() { @Override public void handle(long arg0) { //TODO instead of having an AnimationTimer checking the race status, we could provide a Property, and connect a listener to that. //Get the current race status. RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum(); //Display it. raceStatusLabel.setText("Race Status: " + raceStatus.name()); //If the race has reached the preparatory phase, or has started... if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) { //Stop this timer. stop(); //Hide this, and display the race controller. startWrapper.setVisible(false); start.setVisible(false); parent.beginRace(visualiserInput, visualiserRace); } } }.start(); } /** * Function to handle changes in objects we observe. * We observe LatestMessages. * @param o The observed object. * @param arg The {@link Observable#notifyObservers(Object)} parameter. */ @Override public void update(Observable o, Object arg) { //Check that we actually have LatestMessages. if (o instanceof LatestMessages) { LatestMessages latestMessages = (LatestMessages) o; //If we've received all of the xml files, start the race. Only start it if it hasn't already been created. if (latestMessages.hasAllXMLMessages() && this.visualiserRace == null) { //Need to handle it in the javafx thread. Platform.runLater(() -> { try { this.startRace(latestMessages); } catch (XMLReaderException | InvalidBoatDataException | InvalidRaceDataException | InvalidRegattaDataException e) { //We currently don't handle this in meaningful way, as it should never occur. //If we reach this point it means that malformed XML files were sent. e.printStackTrace(); } }); } } } /** * Show starting information for a race given a socket. * @param socket network source of information */ public void enterLobby(Socket socket) { startWrapper.setVisible(true); try { //Begin reading packets from the socket/server. this.visualiserInput = new VisualiserInput(socket); //Store a reference to latestMessages so that we can observe it. LatestMessages latestMessages = this.visualiserInput.getLatestMessages(); latestMessages.addObserver(this); new Thread(this.visualiserInput).start(); } catch (IOException e) { e.printStackTrace(); } } }