Merge remote-tracking branch 'origin/master' into story_51_rounding_fix

# Conflicts:
#	racevisionGame/src/main/java/mock/app/Event.java
#	racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java

Also added keybindings.xml to git ignore.
#story[1185]
main
fjc40 8 years ago
commit 55402136ed

1
.gitignore vendored

@ -183,3 +183,4 @@ local.properties
# IntelliJDEA ignore
*.iml
dedicatedServer/.idea/
settings/keyBindings.xml

@ -18,3 +18,4 @@
Erika Savell <esa46@uclive.ac.nz>
Connor Taylor-Brown <cbt24@cs17086jp.canterbury.ac.nz> <cbt24@uclive.canterbury.ac.nz>
Fraser Cope <fjc40@uclive.ac.nz>
Jessica Syder <jam339@uclive.ac.nz> Jessica Syder <doctorjess@live.com>

@ -21,7 +21,7 @@ public class App extends Application {
public void start(Stage primaryStage) {
try {
//TODO should read a configuration file to configure server?
Event raceEvent = new Event(false);
Event raceEvent = new Event(false, 0);
} catch (Exception e) {

@ -2,14 +2,15 @@ package mock.app;
import mock.dataInput.PolarParser;
import mock.exceptions.EventConstructionException;
import mock.model.*;
import mock.model.MockRace;
import mock.model.Polars;
import mock.model.RaceLogic;
import mock.model.SourceIdAllocator;
import mock.model.commandFactory.CompositeCommand;
import mock.model.wind.RandomWindGenerator;
import mock.model.wind.ShiftingWindGenerator;
import mock.model.wind.WindGenerator;
import mock.xml.RaceXMLCreator;
import network.Messages.LatestMessages;
import org.xml.sax.SAXException;
import shared.dataInput.*;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidBoatDataException;
@ -19,17 +20,10 @@ import shared.exceptions.XMLReaderException;
import shared.model.Bearing;
import shared.model.Constants;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
@ -70,6 +64,8 @@ public class Event {
private Thread connectionThread;
private int mapIndex;
@ -79,11 +75,32 @@ public class Event {
* @param singlePlayer Whether or not to create a single player event.
* @throws EventConstructionException Thrown if we cannot create an Event for any reason.
*/
public Event(boolean singlePlayer) throws EventConstructionException {
public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException {
String raceXMLFile = "mock/mockXML/raceTest.xml";
// System.out.println(XMLUtilities.validateXML(this.getClass().getClassLoader().getResource("mock/mockXML/iMapLayout.xml").toString()
// , this.getClass().getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd")));
this.mapIndex = mapIndex;
String raceXMLFile;
String boatsXMLFile = "mock/mockXML/boatTest.xml";
String regattaXMLFile = "mock/mockXML/regattaTest.xml";
switch (mapIndex){
case 0:raceXMLFile = "mock/mockXML/raceSixPlayers.xml";
break;
case 1:raceXMLFile = "mock/mockXML/oMapLayout.xml";
break;
case 2: raceXMLFile = "mock/mockXML/iMapLayout.xml";
break;
case 3: raceXMLFile = "mock/mockXML/mMapLayout.xml";
break;
case 4:
raceXMLFile = "mock/mockXML/raceTutorial.xml";
boatsXMLFile = "mock/mockXML/boatTutorial.xml";
regattaXMLFile = "mock/mockXML/regattaTutorial.xml";
break;
default: raceXMLFile = "mock/mockXML/raceSixPlayers.xml";
}
if (singlePlayer) {
raceXMLFile = "mock/mockXML/raceSinglePlayer.xml";
@ -95,9 +112,10 @@ public class Event {
//this.raceXML = XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8);
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, 300);
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, 300, false);
if(mapIndex==4){
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
}
this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8);
@ -180,9 +198,15 @@ public class Event {
* @return String containing edited xml
*/
public static String setRaceXMLAtCurrentTimeToNow(String raceXML) {
return setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime);
}
public static String setRaceXMLAtCurrentTimeToNow(String raceXML, long racePreStartTime, long racePreparatoryTime){
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000);
long millisecondsToAdd = racePreStartTime + racePreparatoryTime;
long secondsToAdd = millisecondsToAdd / 1000;
//Scale the time using our time scalar.
secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
@ -192,7 +216,6 @@ public class Event {
raceXML = raceXML.replace("RACE_CREATION_TIME", dateFormat.format(creationTime));
raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
return raceXML;
}

@ -23,6 +23,7 @@ import java.util.logging.Logger;
public class RaceServer {
private MockRace race;
private LatestMessages latestMessages;
private static RaceServer server;
/**
@ -47,10 +48,17 @@ public class RaceServer {
public RaceServer(MockRace race, LatestMessages latestMessages) {
server = this;
this.race = race;
this.latestMessages = latestMessages;
}
public static void staticUpdateXML() {
if (server != null) {
server.updateXMLFiles();
}
}
/**
* Parses the race to create a snapshot, and places it in latestMessages.
*/

@ -42,6 +42,7 @@ public class SourceIdAllocator {
}
List<Integer> allocatedIDs = mockRace.getRaceDataSource().getParticipants();
List<Integer> allIDs = new ArrayList<>(mockRace.getBoatDataSource().getBoats().keySet());
//Get list of unallocated ids.

@ -1,35 +1,23 @@
package mock.xml;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import shared.dataInput.RaceXMLReader;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.XMLReaderException;
import shared.model.*;
import shared.xml.Race.*;
import shared.model.CompoundMark;
import shared.model.Constants;
import shared.model.GPSCoordinate;
import shared.xml.Race.XMLCompoundMark;
import shared.xml.Race.XMLLimit;
import shared.xml.Race.XMLMark;
import shared.xml.Race.XMLRace;
import shared.xml.XMLUtilities;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.util.JAXBSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.math.BigInteger;
import java.net.URL;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@ -72,7 +60,7 @@ public class RaceXMLCreator {
* @throws XMLReaderException if the xml is not readable
* @throws InvalidRaceDataException if the race is invalid
*/
public static String alterRaceToWind(String s, XMLFileType fileType, double degrees) throws XMLReaderException, InvalidRaceDataException {
public static String alterRaceToWind(String s, XMLFileType fileType, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException {
RaceXMLReader reader = new RaceXMLReader(s, fileType);
@ -83,9 +71,23 @@ public class RaceXMLCreator {
RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"),
XMLRace.class);
setRaceXMLAtCurrentTimeToNow(race);
if(tutorial){
setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l);
} else {
setRaceXMLAtCurrentTimeToNow(race);
}
CompoundMark leewardGate = getLeewardGate(reader);
CompoundMark windwardGate = getWindwardGate(reader);
double raceOriginalBearing = getLineAngle(getLeewardGate(reader).getMark1Position(), getWindwardGate(reader).getMark1Position());
double raceOriginalBearing = 0;
/*if (leewardGate != null && windwardGate != null) {
raceOriginalBearing = getLineAngle(
leewardGate.getMark1Position(),
windwardGate.getMark1Position() );
}*/
double degreesToRotate = degrees - raceOriginalBearing;
@ -98,6 +100,7 @@ public class RaceXMLCreator {
}
}
/**
* Rotate the features in a race such as the boundary, and the marks.
* @param race the race to alter
@ -185,14 +188,11 @@ public class RaceXMLCreator {
}
/**
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
* @param raceXML The race.xml contents.
*/
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) {
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML, long racePrestartTime, long racePreparatoryTime){
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000);
long millisecondsToAdd = racePrestartTime + racePreparatoryTime;
long secondsToAdd = millisecondsToAdd / 1000;
//Scale the time using our time scalar.
secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
@ -203,4 +203,13 @@ public class RaceXMLCreator {
raceXML.getRaceStartTime().setTime(dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
}
/**
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
* @param raceXML The race.xml contents.
*/
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) {
setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime);
}
}

@ -1,5 +1,6 @@
package network.Messages;
import mock.model.RaceServer;
import network.Messages.Enums.XMLMessageType;
import java.util.*;
@ -33,9 +34,6 @@ public class LatestMessages extends Observable {
private XMLMessage regattaXMLMessage;
/**
* Ctor.
*/
@ -45,6 +43,7 @@ public class LatestMessages extends Observable {
/**
* Returns a copy of the race snapshot.
*
* @return Copy of the race snapshot.
*/
public List<AC35Data> getSnapshot() {
@ -54,6 +53,7 @@ public class LatestMessages extends Observable {
/**
* Sets the snapshot of the race.
*
* @param snapshot New snapshot of race.
*/
public void setSnapshot(List<AC35Data> snapshot) {
@ -61,12 +61,9 @@ public class LatestMessages extends Observable {
}
/**
* Returns the latest race xml message.
*
* @return The latest race xml message.
*/
public XMLMessage getRaceXMLMessage() {
@ -75,6 +72,7 @@ public class LatestMessages extends Observable {
/**
* Sets the latest race xml message to a specified race XML message.
*
* @param raceXMLMessage The new race XML message to use.
*/
public void setRaceXMLMessage(XMLMessage raceXMLMessage) {
@ -87,6 +85,7 @@ public class LatestMessages extends Observable {
/**
* Returns the latest boat xml message.
*
* @return The latest boat xml message.
*/
public XMLMessage getBoatXMLMessage() {
@ -95,6 +94,7 @@ public class LatestMessages extends Observable {
/**
* Sets the latest boat xml message to a specified boat XML message.
*
* @param boatXMLMessage The new boat XML message to use.
*/
public void setBoatXMLMessage(XMLMessage boatXMLMessage) {
@ -107,6 +107,7 @@ public class LatestMessages extends Observable {
/**
* Returns the latest regatta xml message.
*
* @return The latest regatta xml message.
*/
public XMLMessage getRegattaXMLMessage() {
@ -115,6 +116,7 @@ public class LatestMessages extends Observable {
/**
* Sets the latest regatta xml message to a specified regatta XML message.
*
* @param regattaXMLMessage The new regatta XML message to use.
*/
public void setRegattaXMLMessage(XMLMessage regattaXMLMessage) {
@ -126,6 +128,7 @@ public class LatestMessages extends Observable {
/**
* Checks the type of xml message, and places it in this LatestMessages object.
*
* @param xmlMessage The new xml message to use.
*/
public void setXMLMessage(XMLMessage xmlMessage) {
@ -145,17 +148,20 @@ public class LatestMessages extends Observable {
/**
* Returns whether or not there is an xml message for each message type.
*
* @return True if race, boat, and regatta have an xml message, false otherwise.
*/
public boolean hasAllXMLMessages() {
if (this.regattaXMLMessage == null || this.boatXMLMessage == null || this.raceXMLMessage == null) {
if (this.regattaXMLMessage == null || this.boatXMLMessage == null ||
this.raceXMLMessage == null) {
return false;
} else {
RaceServer.staticUpdateXML();
return true;
}
}
}
}

@ -3,6 +3,7 @@ package shared.model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.jetbrains.annotations.Nullable;
import visualiser.Controllers.RaceStartController;
import visualiser.model.ResizableRaceCanvas;
import java.time.Duration;
@ -15,8 +16,8 @@ import java.util.Date;
* This class is used to implement a clock which keeps track of and
* displays times relevant to a race. This is displayed on the
* {@link ResizableRaceCanvas} via the
* {@link visualiser.Controllers.RaceController} and the
* {@link visualiser.Controllers.StartController}.
* {@link visualiser.Controllers.RaceViewController} and the
* {@link RaceStartController}.
*/
public class RaceClock {

@ -1,65 +1,31 @@
package visualiser.Controllers;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import shared.model.Bearing;
import shared.model.Wind;
/**
* Controller for the arrow.fxml view.
* Controller for the wind direction arrow on the race screen.
*/
public class ArrowController {
@FXML
private Pane compass;
@FXML
private StackPane arrowStackPane;
@FXML
private ImageView arrowImage;
@FXML
private Circle circle;
@FXML
private Label northLabel;
@FXML
private Label windLabel;
@FXML
private Label speedLabel;
/**
* This is the property our arrow control binds to.
*/
private Property<Wind> wind;
/**
* Constructor.
*/
public ArrowController() {
}
private @FXML StackPane arrowStackPane;
private @FXML ImageView arrowImage;
private @FXML Label speedLabel;
private final static Integer MIN_KNOTS = 2; // knots for min_height
private final static Integer MAX_KNOTS = 30; // knots for max_height
private final static Integer MIN_HEIGHT = 25; // min arrow height
private final static Integer MAX_HEIGHT = 75; // max arrow height
/**
* Sets which wind property the arrow control should bind to.
* @param wind The wind property to bind to.
*/
public void setWindProperty(Property<Wind> wind) {
this.wind = wind;
wind.addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
Platform.runLater(() -> updateWind(newValue));
@ -67,7 +33,6 @@ public class ArrowController {
});
}
/**
* Updates the control to use the new wind value.
* This updates the arrow direction (due to bearing), arrow length (due to speed), and label (due to speed).
@ -78,7 +43,6 @@ public class ArrowController {
updateWindSpeed(wind.getWindSpeed());
}
/**
* Updates the control to account for the new wind speed.
* This changes the length (height) of the wind arrow, and updates the speed label.
@ -94,29 +58,22 @@ public class ArrowController {
* @param speedKnots Wind speed, in knots.
*/
private void updateWindArrowLength(double speedKnots) {
//At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height.
double minKnots = 2;
double maxKnots = 30;
double deltaKnots = maxKnots - minKnots;
double minHeight = 25;
double maxHeight = 75;
double deltaHeight = maxHeight - minHeight;
double deltaKnots = MAX_KNOTS - MIN_KNOTS;
double deltaHeight = MAX_HEIGHT - MIN_HEIGHT;
//Clamp speed.
if (speedKnots > maxKnots) {
speedKnots = maxKnots;
} else if (speedKnots < minKnots) {
speedKnots = minKnots;
if (speedKnots > MAX_KNOTS) {
speedKnots = MAX_KNOTS;
} else if (speedKnots < MIN_KNOTS) {
speedKnots = MIN_KNOTS;
}
//How far between the knots bounds is the current speed?
double currentDeltaKnots = speedKnots - minKnots;
double currentDeltaKnots = speedKnots - MIN_KNOTS;
double currentKnotsScalar = currentDeltaKnots / deltaKnots;
//Thus, how far between the pixel height bounds should the arrow height be?
double newHeight = minHeight + (currentKnotsScalar * deltaHeight);
double newHeight = MIN_HEIGHT + (currentKnotsScalar * deltaHeight);
arrowImage.setFitHeight(newHeight);
}
@ -129,7 +86,6 @@ public class ArrowController {
speedLabel.setText(String.format("%.1fkn", speedKnots));
}
/**
* Updates the control to account for a new wind bearing.
* This rotates the arrow according to the bearing.
@ -140,7 +96,4 @@ public class ArrowController {
arrowStackPane.setRotate(bearing.degrees());
}
}
}

@ -1,147 +0,0 @@
package visualiser.Controllers;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import mock.app.Event;
import org.xml.sax.SAXException;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import visualiser.model.RaceConnection;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.Text;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ResourceBundle;
//TODO it appears this view/controller was replaced by Lobby.fxml. Remove?
/**
* Controls the connection that the VIsualiser can connect to.
*/
public class ConnectionController extends Controller {
@FXML
private AnchorPane connectionWrapper;
@FXML
private TableView<RaceConnection> connectionTable;
@FXML
private TableColumn<RaceConnection, String> hostnameColumn;
@FXML
private TableColumn<RaceConnection, String> statusColumn;
@FXML
private Button connectButton;
@FXML
private TextField urlField;
@FXML
private TextField portField;
/*Title Screen fxml items*/
@FXML
private Button hostGameTitleBtn;
@FXML
private Button connectGameBtn;
@FXML
private RadioButton nightRadioBtn;
@FXML
private RadioButton dayRadioButton;
/*Lobby fxml items*/
@FXML
private TableView lobbyTable;
@FXML
private TableColumn gameNameColumn;
@FXML
private TableColumn hostNameColumn;
@FXML
private TableColumn playerCountColumn;
@FXML
private TextField playerNameField;
@FXML
private Button joinGameBtn;
/*Host game fxml items*/
@FXML
private TextField gameNameField;
@FXML
private TextField hostNameField;
@FXML
private TextField hostGameBtn;
private ObservableList<RaceConnection> connections;
@Override
public void initialize(URL location, ResourceBundle resources) {
// TODO - replace with config file
connections = FXCollections.observableArrayList();
connectionTable.setItems(connections);
hostnameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty());
statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty());
connectionTable.getSelectionModel().selectedItemProperty().addListener((obs, prev, curr) -> {
if (curr != null && curr.check()) connectButton.setDisable(false);
else connectButton.setDisable(true);
});
connectButton.setDisable(true);
}
/**
* Sets current status of all connections.
*/
public void checkConnections() {
for(RaceConnection connection: connections) {
connection.check();
}
}
public AnchorPane startWrapper(){
return connectionWrapper;
}
/**
* Connects to host currently selected in table. Button enabled only if host is ready.
*/
public void connectSocket() {
try{
RaceConnection connection = connectionTable.getSelectionModel().getSelectedItem();
Socket socket = new Socket(connection.getHostname(), connection.getPort());
socket.setKeepAlive(true);
connectionWrapper.setVisible(false);
//parent.enterLobby(socket);
} catch (IOException e) { /* Never reached */ }
}
/**
* adds a new connection
*/
public void addConnection(){
String hostName = urlField.getText();
String portString = portField.getText();
try{
int port = Integer.parseInt(portString);
connections.add(new RaceConnection(hostName, port, null));
}catch(NumberFormatException e){
System.err.println("Port number entered is not a number");
}
}
}

@ -1,32 +1,108 @@
package visualiser.Controllers;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Modality;
import javafx.stage.Stage;
import visualiser.app.App;
import java.io.IOException;
/**
* Controller parent for app controllers.
* Created by fwy13 on 15/03/2017.
* Abstract controller class to give each subclass the functionality to load
* a new scene into the existing stage, or create a new popup window.
*/
public abstract class Controller implements Initializable {
protected MainController parent;
public abstract class Controller {
private Stage stage = App.getStage();
/**
* Loads the title screen again when app is already running.
* @throws IOException if a problem with the title.fxml
*/
protected void loadTitleScreen() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/visualiser/scenes/title.fxml"));
Parent root = loader.load();
stage.setResizable(false);
Scene scene = new Scene(root);
addCssStyle(scene);
stage.setScene(scene);
stage.show();
}
/**
* Used to load a new scene in the currently open stage.
* @param fxmlUrl the URL of the FXML file to be loaded
* @return the controller of the new scene
* @throws IOException if there is an issue with the fxmlUrl
*/
protected Controller loadScene(String fxmlUrl) throws IOException {
// load the correct fxml file
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource
("/visualiser/scenes/"+fxmlUrl));
Parent root = loader.load();
// reuse previous stage and it's window size
Stage stage = App.getStage();
Double stageHeight = stage.getHeight();
Double stageWidth = stage.getWidth();
// set new scene into existing window
Scene scene = new Scene(root, stageWidth, stageHeight);
addCssStyle(scene);
stage.setScene(scene);
stage.setResizable(true);
stage.show();
stage.setHeight(stageHeight);
stage.setWidth(stageWidth);
stage.sizeToScene();
// return controller for the loaded fxml scene
return loader.getController();
}
/**
* Sets the parent of the application
*
* @param parent controller
* Used to load a scene in a new separate popup stage.
* @param fxmlUrl the URL of the FXML file to be loaded
* @param title title for the new window
* @param modality modality settings for popup window
* @return the controller of the new scene
* @throws IOException if there is an issue with the fxmlUrl
*/
public void setParent(MainController parent) {
this.parent = parent;
protected Controller loadPopupScene(String fxmlUrl, String title, Modality
modality) throws IOException {
// load the correct fxml scene
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource(
"/visualiser/scenes/" + fxmlUrl));
Parent root = loader.load();
// create a new 'pop-up' window
Stage stage = new Stage();
stage.initModality(modality);
stage.setTitle(title);
stage.centerOnScreen();
stage.getIcons().add(new Image(getClass().getClassLoader().getResourceAsStream("images/SailIcon.png")));
Scene scene = new Scene(root);
addCssStyle(scene);
stage.setScene(scene);
stage.show();
// return controller for the loaded fxml scene
return loader.getController();
}
/**
* Initialisation class that is run on start up.
*
* @param location resources location
* @param resources resources bundle
* Adds the relevant CSS styling to the scene being loaded.
* @param scene new scene to be loaded and displayed
*/
@Override
public abstract void initialize(URL location, ResourceBundle resources);
private void addCssStyle(Scene scene){
if (App.dayMode) {
scene.getStylesheets().add("/css/dayMode.css");
} else {
scene.getStylesheets().add("/css/nightMode.css");
}
}
}

@ -1,92 +0,0 @@
package visualiser.Controllers;
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 visualiser.model.VisualiserBoat;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Finish Screen for when the race finishes.
*/
public class FinishController extends Controller {
@FXML
AnchorPane finishWrapper;
@FXML
TableView<VisualiserBoat> boatInfoTable;
@FXML
TableColumn<VisualiserBoat, String> boatRankColumn;
@FXML
TableColumn<VisualiserBoat, String> boatNameColumn;
@FXML
Label raceWinnerLabel;
/**
* The boats to display on the table.
*/
private ObservableList<VisualiserBoat> boats;
/**
* Ctor.
*/
public FinishController() {
}
@Override
public void initialize(URL location, ResourceBundle resources){
}
/**
* Sets up the finish table
* @param boats Boats to display
*/
private void setFinishTable(ObservableList<VisualiserBoat> boats) {
this.boats = boats;
//Set contents.
boatInfoTable.setItems(boats);
//Name.
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
//Rank/position.
boatRankColumn.setCellValueFactory(cellData -> cellData.getValue().placingProperty());
//Winner label.
if (boats.size() > 0) {
raceWinnerLabel.setText("Winner: " + boatNameColumn.getCellObservableValue(0).getValue());
raceWinnerLabel.setWrapText(true);
}
}
/**
* Display the table
* @param boats boats to display on the table.
*/
public void enterFinish(ObservableList<VisualiserBoat> boats){
finishWrapper.setVisible(true);
setFinishTable(boats);
}
}

@ -1,165 +0,0 @@
package visualiser.Controllers;
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
import javafx.animation.AnimationTimer;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.SplitPane;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.shape.Box;
import javafx.scene.shape.Mesh;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Rotate;
import mock.app.Event;
import mock.exceptions.EventConstructionException;
import visualiser.model.View3D;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Controller for Hosting a game.
*/
public class HostController extends Controller {
// @FXML
// TextField gameNameField;
//
// @FXML
// TextField hostNameField;
@FXML
private ImageView imageView;
@FXML
AnchorPane hostWrapper;
@FXML
AnchorPane imagePane;
@FXML
SplitPane splitPane;
@FXML
AnchorPane specPane;
@FXML
GridPane playerContainer;
private Event game;
private View3D view3D;
@Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList<Shape3D> shapes = FXCollections.observableArrayList();
view3D = new View3D();
view3D.setItems(shapes);
playerContainer.add(view3D, 0,0);
URL asset = HostController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
StlMeshImporter importer = new StlMeshImporter();
importer.read(asset);
MeshView mesh = new MeshView(importer.getImport());
shapes.add(mesh);
view3D.setPivot(mesh);
view3D.setDistance(50);
view3D.setYaw(45);
view3D.setPitch(20);
Rotate rotation = new Rotate(0, Rotate.Y_AXIS);
mesh.getTransforms().addAll(rotation, new Rotate(-90, Rotate.X_AXIS));
AnimationTimer rotate = new AnimationTimer() {
@Override
public void handle(long now) {
rotation.setAngle(rotation.getAngle() + 0.1);
}
};
rotate.start();
}
/**
* Hosts a game
*/
public void hostGamePressed() {
try {
this.game = new Event(false);
connectSocket("localhost", 4942);
} catch (EventConstructionException e) {
Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e);
throw new RuntimeException(e);
}
}
public void endEvent() throws IOException {
game.endEvent();
}
/**
* Connect to a socket
* @param address address of the server
* @param port port that the server is run off
*/
public void connectSocket(String address, int port) {
try{
Socket socket = new Socket(address, port);
hostWrapper.setVisible(false);
parent.enterLobby(socket, true);
} catch (IOException e) { /* Never reached */ }
}
public AnchorPane startWrapper(){
return hostWrapper;
}
/**
* Hosts a game.
*/
public void hostGame(){
splitPane.setResizableWithParent(specPane, false);
splitPane.lookupAll(".split-pane-divider").stream().forEach(div -> div.setMouseTransparent(true));
imageView.fitWidthProperty().bind(imagePane.widthProperty());
imageView.fitHeightProperty().bind(imagePane.heightProperty());
hostWrapper.setVisible(true);
}
/**
* Menu button pressed. Prompt alert then return to menu
*/
public void menuBtnPressed(){
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Quitting race");
alert.setContentText("Do you wish to quit the race?");
alert.setHeaderText("You are about to quit the race");
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.OK){
hostWrapper.setVisible(false);
parent.enterTitle();
}
}
/**
* Start button pressed. Currently only prints out start
*/
public void startBtnPressed(){
//System.out.println("Should start the race. This button is only visible for the host");
hostGamePressed();
}
}

@ -0,0 +1,118 @@
package visualiser.Controllers;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import mock.app.Event;
import mock.exceptions.EventConstructionException;
import visualiser.app.App;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Controller for Hosting a game.
*/
public class HostGameController extends Controller {
private @FXML ImageView mapImage;
private ArrayList<Image> listOfMaps;
private int currentMapIndex = 0;
public void initialize() {
loadMaps();
}
/**
* Loads in the list of playable maps to be selected from.
*/
private void loadMaps(){
// image preview of maps
Image ac35Map = new Image(getClass().getClassLoader().getResourceAsStream("images/AC35_Racecourse_MAP.png"));
Image oMap = new Image(getClass().getClassLoader().getResourceAsStream("images/oMapLayout.png"));
Image iMap = new Image(getClass().getClassLoader().getResourceAsStream("images/iMapLayout.png"));
Image mMap = new Image(getClass().getClassLoader().getResourceAsStream("images/mMapLayout.png"));
listOfMaps = new ArrayList(Arrays.asList(ac35Map, oMap, iMap, mMap));
mapImage.setImage(listOfMaps.get(currentMapIndex));
Platform.runLater(() -> {
mapImage.fitWidthProperty()
.bind(mapImage.getScene().getWindow().widthProperty().multiply(0.6));
});
}
/**
* Hosts a game
*/
public void hostGamePressed() {
try {
App.game = new Event(false, currentMapIndex);
App.gameType = currentMapIndex;
connectSocket("localhost", 4942);
} catch (EventConstructionException e) {
Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e);
throw new RuntimeException(e);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Connect to a socket
* @param address address of the server
* @param port port that the server is run off
*/
public void connectSocket(String address, int port) throws IOException {
Socket socket = new Socket(address, port);
RaceStartController rsc = (RaceStartController)loadScene("raceStart.fxml");
rsc.enterLobby(socket, true);
}
/**
* Menu button pressed. Prompt alert then return to menu
*/
public void menuBtnPressed() throws Exception {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Quitting race");
alert.setContentText("Do you wish to quit the race?");
alert.setHeaderText("You are about to quit the race");
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.OK){
loadTitleScreen();
}
}
/**
* Method called when the 'next' arrow button is pressed. It is used to
* change the currently displayed map preview to the next in the list.
*/
public void nextImage(){
// increase index
currentMapIndex = (currentMapIndex + 1) % listOfMaps.size();
// update map preview
mapImage.setImage(listOfMaps.get(currentMapIndex));
}
/**
* Method called when the 'previous' arrow button is pressed. It is used to
* change the currently displayed map preview to the previous in the list.
*/
public void previousImage(){
// decrease index
currentMapIndex = ((((currentMapIndex - 1) % listOfMaps.size()) +
listOfMaps.size()) % listOfMaps.size());
// update map preview
mapImage.setImage(listOfMaps.get(currentMapIndex));
}
public void setCurrentMapIndex(Integer index){
this.currentMapIndex = index;
}
}

@ -2,9 +2,6 @@ package visualiser.Controllers;
import javafx.application.Platform;
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.ListView;
import javafx.scene.input.KeyCode;
@ -20,12 +17,10 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static visualiser.app.App.keyFactory;
/**
* Controller for the scene used to display and update current key bindings.
*/
public class KeyBindingsController {
public class KeyBindingsController extends Controller {
private @FXML Button btnSave;
private @FXML Button btnCancel;
private @FXML Button btnReset;
@ -33,23 +28,27 @@ public class KeyBindingsController {
private @FXML ListView lstKey;
private @FXML ListView lstDescription;
private @FXML AnchorPane anchor;
private KeyFactory existingKeyFactory;
private KeyFactory newKeyFactory;
private Boolean changed = false; // keyBindings have been modified
private Button currentButton = null; // last button clicked
public void initialize(){
// create new key factory to modify, keeping the existing one safe
existingKeyFactory = new KeyFactory();
existingKeyFactory.load();
newKeyFactory = copyExistingFactory();
initializeTable();
populateTable();
setKeyListener();
setClosedListener();
}
/**
* Sets up table before populating it.
* Set up includes headings, CSS styling and modifying default properties.
*/
public void initializeTable(){
private void initializeTable(){
// set the headings for each column
lstKey.getItems().add("Key");
lstControl.getItems().add("Command");
@ -67,15 +66,15 @@ public class KeyBindingsController {
// stop the columns from being selectable, so only the buttons are
lstKey.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) ->
.addListener((observable, oldValue, newValue) ->
Platform.runLater(() ->
lstKey.getSelectionModel().select(0)));
lstDescription.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) ->
.addListener((observable, oldValue, newValue) ->
Platform.runLater(() ->
lstDescription.getSelectionModel().select(0)));
lstControl.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) ->
.addListener((observable, oldValue, newValue) ->
Platform.runLater(() ->
lstControl.getSelectionModel().select(0)));
}
@ -83,7 +82,7 @@ public class KeyBindingsController {
/**
* Populates the table with commands and their key binding details.
*/
public void populateTable(){
private void populateTable(){
// add each command to the table
for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet()) {
// create button for command
@ -100,11 +99,11 @@ public class KeyBindingsController {
/**
* Makes a copy of the {@link KeyFactory} that does not modify the original.
* @return new keyfactory to be modified
* @return new keyFactory to be modified
*/
public KeyFactory copyExistingFactory(){
private KeyFactory copyExistingFactory(){
newKeyFactory = new KeyFactory();
Map<String, ControlKey> oldKeyState = keyFactory.getKeyState();
Map<String, ControlKey> oldKeyState = existingKeyFactory.getKeyState();
Map<String, ControlKey> newKeyState = new HashMap<>();
// copy over commands and their keys
@ -116,11 +115,32 @@ public class KeyBindingsController {
}
/**
* Creates a listener for the base anchorpane for key presses.
* Creates a listener for when a user tries to close the current window.
*/
private void setClosedListener(){
anchor.sceneProperty().addListener((obsS, oldS, newS) -> {
if (newS != null) {
newS.windowProperty().addListener((obsW, oldW, newW) -> {
if (newW != null) {
Stage stage = (Stage)newW;
// WE is processed by onExit method
stage.setOnCloseRequest(we -> {
if (we.getEventType() == WindowEvent.WINDOW_CLOSE_REQUEST) {
onExit(we);
}
});
}
});
}
});
}
/**
* Creates a listener for the base anchorPane for key presses.
* It updates the current key bindings of the {@link KeyFactory} if
* required.
*/
public void setKeyListener(){
private void setKeyListener(){
anchor.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
// if esc, cancel current button click
if (event.getCode() == KeyCode.ESCAPE){
@ -179,10 +199,10 @@ public class KeyBindingsController {
*/
public void save(){
if (isFactoryValid()) {
keyFactory = newKeyFactory;
existingKeyFactory = newKeyFactory;
newKeyFactory = new KeyFactory();
changed = false;
keyFactory.save(); // save persistently
existingKeyFactory.save(); // save persistently
loadNotification("Key bindings were successfully saved.", false);
} else {
loadNotification("One or more key bindings are missing. " +
@ -195,10 +215,10 @@ public class KeyBindingsController {
* commands are missing a key binding.
* @return True if valid, false if invalid
*/
public Boolean isFactoryValid(){
private Boolean isFactoryValid(){
for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet
()) {
if (entry.getKey().toString()==entry.getValue().toString()){
if (entry.getKey().equals(entry.getValue().toString())){
return false;
}
}
@ -211,7 +231,7 @@ public class KeyBindingsController {
* @param we {@link WindowEvent} close request to be consumed if settings
* have not been successfully saved.
*/
public void onExit(WindowEvent we){
private void onExit(WindowEvent we){
// if modified KeyFactory hasn't been saved
if (changed){
loadNotification("Please cancel or save your changes before exiting" +
@ -225,23 +245,15 @@ public class KeyBindingsController {
* @param message the message to be displayed to the user
* @param warning true if the message to be displayed is due to user error
*/
public void loadNotification(String message, Boolean warning){
Parent root = null;
FXMLLoader loader = new FXMLLoader(getClass().getResource
("/visualiser/scenes/notification.fxml"));
private void loadNotification(String message, Boolean warning){
try {
root = loader.load();
NotificationController nc = (NotificationController)
loadPopupScene("notification.fxml",
"", Modality.APPLICATION_MODAL);
nc.setMessage(message, warning);
} catch (IOException e) {
e.printStackTrace();
}
NotificationController controller = loader.getController();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.centerOnScreen();
stage.initModality(Modality.APPLICATION_MODAL);
stage.show();
// displays given message in the window
controller.setMessage(message, warning);
}
}

@ -7,46 +7,29 @@ import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import visualiser.model.RaceConnection;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Controller for the Lobby for entering games
*/
public class LobbyController extends Controller {
@FXML
private AnchorPane lobbyWrapper;
@FXML
private TableView<RaceConnection> lobbyTable;
@FXML
private TableColumn<RaceConnection, String> gameNameColumn;
@FXML
private TableColumn<RaceConnection, String> hostNameColumn;
@FXML
private TableColumn<RaceConnection, String> statusColumn;
@FXML
private Button joinGameBtn;
@FXML
private TextField addressFld;
@FXML
private TextField portFld;
private @FXML TableView<RaceConnection> lobbyTable;
private @FXML TableColumn<RaceConnection, String> gameNameColumn;
private @FXML TableColumn<RaceConnection, String> hostNameColumn;
private @FXML TableColumn<RaceConnection, String> statusColumn;
private @FXML Button joinGameBtn;
private @FXML TextField addressFld;
private @FXML TextField portFld;
private ObservableList<RaceConnection> connections;
@Override
public void initialize(URL location, ResourceBundle resources) {
public void initialize() {
// set up the connection table
connections = FXCollections.observableArrayList();
//connections.add(new RaceConnection("localhost", 4942, "Local Game"));
lobbyTable.setItems(connections);
gameNameColumn.setCellValueFactory(cellData -> cellData.getValue().gamenameProperty());
hostNameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty());
statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty());
@ -54,8 +37,7 @@ public class LobbyController extends Controller {
lobbyTable.getSelectionModel().selectedItemProperty().addListener((obs, prev, curr) -> {
if (curr != null && curr.statusProperty().getValue().equals("Ready")) {
joinGameBtn.setDisable(false);
}
else {
} else {
joinGameBtn.setDisable(true);
}
});
@ -75,26 +57,21 @@ public class LobbyController extends Controller {
} else {
joinGameBtn.setDisable(true);
}
} catch (Exception e){}
} catch (Exception ignored){}
}
/**
* Connect to a connection.
*/
public void connectSocket() {
try{
RaceConnection connection = lobbyTable.getSelectionModel().getSelectedItem();
Socket socket = new Socket(connection.getHostname(), connection.getPort());
lobbyWrapper.setVisible(false);
parent.enterLobby(socket, false);
} catch (IOException e) { /* Never reached */
e.printStackTrace();
}
public void connectSocket() throws IOException {
RaceConnection connection = lobbyTable.getSelectionModel().getSelectedItem();
Socket socket = new Socket(connection.getHostname(), connection.getPort());
RaceStartController rsc = (RaceStartController)loadScene("raceStart.fxml");
rsc.enterLobby(socket, false);
}
public void menuBtnPressed(){
lobbyWrapper.setVisible(false);
parent.enterTitle();
public void menuBtnPressed() throws IOException {
loadScene("title.fxml");
}
/**
@ -103,24 +80,14 @@ public class LobbyController extends Controller {
public void addConnectionPressed(){
String hostName = addressFld.getText();
String portString = portFld.getText();
try{
try {
int port = Integer.parseInt(portString);
connections.add(new RaceConnection(hostName, port, "Boat Game"));
addressFld.clear();
portFld.clear();
}catch(NumberFormatException e){
} catch (NumberFormatException e) {
System.err.println("Port number entered is not a number");
}
}
public AnchorPane startWrapper(){
return lobbyWrapper;
}
/**
* Enter the lobby page.
*/
public void enterLobby(){
lobbyWrapper.setVisible(true);
}
}
}

@ -1,132 +0,0 @@
package visualiser.Controllers;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
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;
/**
* Controller that everything is overlayed onto. This makes it so that changing scenes does not resize your stage.
*/
public class MainController extends Controller {
@FXML private StartController startController;
@FXML private RaceController raceController;
@FXML private ConnectionController connectionController;
@FXML private FinishController finishController;
@FXML private TitleController titleController;
@FXML private HostController hostController;
@FXML private LobbyController lobbyController;
/**
* Ctor.
*/
public MainController() {
}
/**
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
* @param visualiserRace The object modelling the race.
* @param controllerClient Socket Client that manipulates the controller.
* @param isHost if the client is the host of a race or not.
*/
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, Boolean isHost) {
startController.enterLobby(socket, isHost);
}
/**
* Transitions from the {@link RaceController} screen to the {@link FinishController} screen.
* @param boats The boats to display on the finish screen.
*/
public void enterFinish(ObservableList<VisualiserBoat> boats) {
finishController.enterFinish(boats);
}
/**
* Transitions into the title screen
*/
public void enterTitle() {
titleController.enterTitle();
}
/**
* Transitions into lobby screen
*/
public void enterLobby(){ lobbyController.enterLobby(); }
/**
* Transitions into host game screen
*/
public void hostGame(){ hostController.hostGame(); }
/**
* Sets up the css for the start of the program
*/
public void startCss(){titleController.setDayMode();}
/**
* Main Controller for the applications will house the menu and the displayed pane.
*
* @param location of resources
* @param resources bundle
*/
@Override
public void initialize(URL location, ResourceBundle resources) {
startController.setParent(this);
raceController.setParent(this);
connectionController.setParent(this);
finishController.setParent(this);
titleController.setParent(this);
hostController.setParent(this);
lobbyController.setParent(this);
AnchorPane.setTopAnchor(startController.startWrapper(), 0.0);
AnchorPane.setBottomAnchor(startController.startWrapper(), 0.0);
AnchorPane.setLeftAnchor(startController.startWrapper(), 0.0);
AnchorPane.setRightAnchor(startController.startWrapper(), 0.0);
AnchorPane.setTopAnchor(lobbyController.startWrapper(), 0.0);
AnchorPane.setBottomAnchor(lobbyController.startWrapper(), 0.0);
AnchorPane.setLeftAnchor(lobbyController.startWrapper(), 0.0);
AnchorPane.setRightAnchor(lobbyController.startWrapper(), 0.0);
AnchorPane.setTopAnchor(hostController.startWrapper(), 0.0);
AnchorPane.setBottomAnchor(hostController.startWrapper(), 0.0);
AnchorPane.setLeftAnchor(hostController.startWrapper(), 0.0);
AnchorPane.setRightAnchor(hostController.startWrapper(), 0.0);
AnchorPane.setTopAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setBottomAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setLeftAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setRightAnchor(finishController.finishWrapper, 0.0);
AnchorPane.setTopAnchor(titleController.titleWrapper, 0.0);
AnchorPane.setBottomAnchor(titleController.titleWrapper, 0.0);
AnchorPane.setLeftAnchor(titleController.titleWrapper, 0.0);
AnchorPane.setRightAnchor(titleController.titleWrapper, 0.0);
}
}

@ -9,7 +9,7 @@ import javafx.stage.Stage;
/**
* Controller for a popup notification regarding user activity.
*/
public class NotificationController {
public class NotificationController extends Controller{
private @FXML Label lblDescription;
private @FXML Text txtMessage;

@ -1,516 +0,0 @@
package visualiser.Controllers;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
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.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;
import static visualiser.app.App.keyFactory;
/**
* Controller used to display a running race.
*/
public class RaceController extends Controller {
/**
* The race object which describes the currently occurring race.
*/
private VisualiserRaceEvent visualiserRace;
/**
* Service for sending keystrokes to server
*/
private ControllerClient controllerClient;
private boolean isHost;
/**
* The canvas that draws the race.
*/
private ResizableRaceCanvas raceCanvas;
/**
* The sparkline graph.
*/
private Sparkline sparkline;
/**
* state of the info table
*/
private boolean infoTableShow;
/**
* The arrow controller.
*/
@FXML private ArrowController arrowController;
@FXML private GridPane canvasBase;
@FXML private SplitPane race;
/**
* This is the root node of the arrow control.
*/
@FXML private Pane arrow;
/**
* This is the pane we place the actual arrow control inside of.
*/
@FXML private StackPane arrowPane;
@FXML private Label timer;
@FXML private Label FPS;
@FXML private Label timeZone;
@FXML private CheckBox showFPS;
@FXML private TableView<VisualiserBoat> boatInfoTable;
@FXML private TableColumn<VisualiserBoat, String> boatPlacingColumn;
@FXML private TableColumn<VisualiserBoat, String> boatTeamColumn;
@FXML private TableColumn<VisualiserBoat, Leg> boatMarkColumn;
@FXML private TableColumn<VisualiserBoat, Number> boatSpeedColumn;
@FXML private LineChart<Number, Number> sparklineChart;
@FXML private AnchorPane annotationPane;
/**
* Ctor.
*/
public RaceController() {
}
@Override
public void initialize(URL location, ResourceBundle resources) {
// KeyFactory keyFactory = KeyFactory.getFactory();
infoTableShow = true;
// Initialise keyboard handler
race.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
String codeString = event.getCode().toString();
if (codeString.equals("TAB")){toggleTable();}
ControlKey controlKey = keyFactory.getKey(codeString);
if(controlKey != null) {
try {
controlKey.onAction(); // Change key state if applicable
controllerClient.sendKey(controlKey);
event.consume();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
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<ButtonType> 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<ButtonType> 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();
}
}
});
}
/**
* Initialises the various UI components to listen to the {@link #visualiserRace}.
*/
private void initialiseRace() {
//Fps display.
initialiseFps(this.visualiserRace);
//Information table.
initialiseInfoTable(this.visualiserRace);
//Sparkline.
initialiseSparkline(this.visualiserRace);
//Arrow.
initialiseArrow(this.visualiserRace);
//Race canvas.
initialiseRaceCanvas(this.visualiserRace);
//Race timezone label.
initialiseRaceTimezoneLabel(this.visualiserRace);
//Race clock.
initialiseRaceClock(this.visualiserRace);
//Start the race animation timer.
raceTimer();
}
/**
* Initialises the frame rate functionality. This allows for toggling the frame rate, and connect the fps label to the race's fps property.
* @param visualiserRace The race to connect the fps label to.
*/
private void initialiseFps(VisualiserRaceEvent visualiserRace) {
//On/off toggle.
initialiseFpsToggle();
//Label value.
initialiseFpsLabel(visualiserRace);
}
/**
* Initialises a listener for the fps toggle.
*/
private void initialiseFpsToggle() {
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) {
FPS.setVisible(true);
} else {
FPS.setVisible(false);
}
});
}
/**
* Initialises the fps label to update when the race fps changes.
* @param visualiserRace The race to monitor the frame rate of.
*/
private void initialiseFpsLabel(VisualiserRaceEvent visualiserRace) {
visualiserRace.getFrameRateProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> this.FPS.setText("FPS: " + newValue.toString()));
});
}
/**
* Initialises the information table view to listen to a given race.
* @param race Race to listen to.
*/
public void initialiseInfoTable(VisualiserRaceEvent race) {
//Copy list of boats.
ObservableList<VisualiserBoat> boats = FXCollections.observableArrayList(race.getVisualiserRaceState().getBoats());
SortedList<VisualiserBoat> sortedBoats = new SortedList<>(boats);
sortedBoats.comparatorProperty().bind(boatInfoTable.comparatorProperty());
//Update copy when original changes.
race.getVisualiserRaceState().getBoats().addListener((ListChangeListener.Change<? extends VisualiserBoat> c) -> Platform.runLater(() -> {
boats.setAll(race.getVisualiserRaceState().getBoats());
}));
//Set up table.
boatInfoTable.setItems(sortedBoats);
//Set up each column.
//Name.
boatTeamColumn.setCellValueFactory(
cellData -> cellData.getValue().nameProperty() );
//Speed.
boatSpeedColumn.setCellValueFactory(
cellData -> cellData.getValue().currentSpeedProperty() );
//Kind of ugly, but allows for formatting an observed speed.
boatSpeedColumn.setCellFactory(
//Callback object.
new Callback<TableColumn<VisualiserBoat, Number>, TableCell<VisualiserBoat, Number>>() {
//Callback function.
@Override
public TableCell<VisualiserBoat, Number> call(TableColumn<VisualiserBoat, Number> param) {
//We return a table cell that populates itself with a Number, and formats it.
return new TableCell<VisualiserBoat, Number>(){
//Function to update the cell text.
@Override
protected void updateItem(Number item, boolean empty) {
if (item != null) {
super.updateItem(item, empty);
setText(String.format("%.2fkn", item.doubleValue()));
}
}
};
}
} );
//Last mark.
boatMarkColumn.setCellValueFactory(
cellData -> cellData.getValue().legProperty() );
//Kind of ugly, but allows for turning an observed Leg into a string.
boatMarkColumn.setCellFactory(
//Callback object.
new Callback<TableColumn<VisualiserBoat, Leg>, TableCell<VisualiserBoat, Leg>>() {
//Callback function.
@Override
public TableCell<VisualiserBoat, Leg> call(TableColumn<VisualiserBoat, Leg> param) {
//We return a table cell that populates itself with a Leg's name.
return new TableCell<VisualiserBoat, Leg>(){
//Function to update the cell text.
@Override
protected void updateItem(Leg item, boolean empty) {
if (item != null) {
super.updateItem(item, empty);
setText(item.getName());
}
}
};
}
} );
//Current place within race.
boatPlacingColumn.setCellValueFactory(
cellData -> cellData.getValue().placingProperty() );
}
/**
* Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRaceEvent}.
* @param race The race to listen to.
*/
private void initialiseSparkline(VisualiserRaceEvent race) {
//The race.getBoats() we are passing in is sorted by position in race inside the race class.
this.sparkline = new Sparkline(this.visualiserRace.getVisualiserRaceState(), this.sparklineChart);
}
/**
* Initialises the {@link ResizableRaceCanvas}, provides the race to read data from.
* @param race Race to read data from.
*/
private void initialiseRaceCanvas(VisualiserRaceEvent race) {
//Create canvas.
raceCanvas = new ResizableRaceCanvas(race.getVisualiserRaceState());
//Set properties.
raceCanvas.setMouseTransparent(true);
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
//Draw it and show it.
raceCanvas.draw();
raceCanvas.setVisible(true);
//Add to scene.
canvasBase.getChildren().add(0, raceCanvas);
}
/**
* Intialises the race time zone label with the race's time zone.
* @param race The race to get time zone from.
*/
private void initialiseRaceTimezoneLabel(VisualiserRaceEvent race) {
timeZone.setText(race.getVisualiserRaceState().getRaceClock().getTimeZone());
}
/**
* Initialises the race clock to listen to the specified race.
* @param race The race to listen to.
*/
private void initialiseRaceClock(VisualiserRaceEvent race) {
//RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update.
race.getVisualiserRaceState().getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
}
/**
* 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, Boolean isHost) {
this.visualiserRace = visualiserRace;
this.controllerClient = controllerClient;
this.isHost = isHost;
initialiseRace();
//Display this controller.
race.setVisible(true);
// set up annotation displays
new Annotations(annotationPane, raceCanvas);
}
/**
* Transition from the race view to the finish view.
* @param boats boats there are in the race.
*/
public void finishRace(ObservableList<VisualiserBoat> boats) {
race.setVisible(false);
parent.enterFinish(boats);
}
/**
* Initialises the arrow controller with data from the race to observe.
* @param race The race to observe.
*/
private void initialiseArrow(VisualiserRaceEvent race) {
arrowController.setWindProperty(race.getVisualiserRaceState().windProperty());
}
/**
* Timer which monitors the race.
*/
private void raceTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRace.getVisualiserRaceState().getRaceStatusEnum();
//If the race has finished, go to finish view.
if (raceStatus == RaceStatusEnum.FINISHED) {
//Stop this timer.
stop();
//Hide this, and display the finish controller.
finishRace(visualiserRace.getVisualiserRaceState().getBoats());
} else {
//Otherwise, render the canvas.
raceCanvas.drawRace();
//Sort the tableview. Doesn't automatically work for all columns.
boatInfoTable.sort();
}
//Return to main screen if we lose connection.
if (!visualiserRace.getServerConnection().isAlive()) {
race.setVisible(false);
//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...).
}
}
}.start();
}
/**
* toggles if the info table is shown
*/
private void toggleTable() {
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/race.getWidth();
if (infoTableShow){
race.setDividerPositions(tablePercent);
arrowPane.setScaleX(0.5);
arrowPane.setScaleY(0.5);
arrowPane.setTranslateX(0 + (arrowPane.getScene().getWidth()/4)*tablePercent);
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
}else{
race.setDividerPositions(1);
arrowPane.setScaleX(1);
arrowPane.setScaleY(1);
arrowPane.setTranslateX(0);
arrowPane.setTranslateY(0);
}
boatInfoTable.refresh();
infoTableShow = !infoTableShow;
}
}

@ -0,0 +1,39 @@
package visualiser.Controllers;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import visualiser.model.VisualiserBoat;
/**
* Finish Screen for when the race finishes.
*/
public class RaceFinishController extends Controller {
private @FXML TableView<VisualiserBoat> boatInfoTable;
private @FXML TableColumn<VisualiserBoat, String> boatRankColumn;
private @FXML TableColumn<VisualiserBoat, String> boatNameColumn;
private @FXML Label raceWinnerLabel;
/**
* Display the table
* @param boats boats to display on the table.
*/
public void loadFinish(ObservableList<VisualiserBoat> boats) {
// set table contents
boatInfoTable.setItems(boats);
//Name.
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
//Rank/position.
boatRankColumn.setCellValueFactory(cellData -> cellData.getValue().placingProperty());
//Winner label.
if (boats.size() > 0) {
raceWinnerLabel.setText("Winner: " +
boatNameColumn.getCellObservableValue(0).getValue());
raceWinnerLabel.setWrapText(true);
}
}
}

@ -0,0 +1,141 @@
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 network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RequestToJoinEnum;
import visualiser.gameController.ControllerClient;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceState;
import java.io.IOException;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Controller to for waiting for the race to start.
*/
public class RaceStartController extends Controller {
private @FXML Label raceTitleLabel;
private @FXML Label raceStartLabel;
private @FXML Label timeZoneTime;
private @FXML Label timer;
private @FXML TableView<VisualiserBoat> boatNameTable;
private @FXML TableColumn<VisualiserBoat, String> boatNameColumn;
private @FXML TableColumn<VisualiserBoat, String> boatCodeColumn;
private @FXML Label raceStatusLabel;
private VisualiserRaceEvent visualiserRaceEvent;
private VisualiserRaceState raceState;
private ControllerClient controllerClient;
private boolean isHost;
/**
* Show starting information for a race given a socket.
* Intended to be called on loading the scene.
* @param socket network source of information
* @param isHost is user a host
*/
public void enterLobby(Socket socket, Boolean isHost) {
try {
this.isHost = isHost;
this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT);
this.controllerClient = visualiserRaceEvent.getControllerClient();
this.raceState = visualiserRaceEvent.getVisualiserRaceState();
showRaceDetails();
} catch (IOException e) {
//TODO should probably let this propagate, so that we only enter this scene if everything works
Logger.getGlobal()
.log(Level.WARNING, "Could not connect to server.", e);
}
}
/**
* Displays details and starts the timer for the race being started
*/
private void showRaceDetails() {
raceTitleLabel.setText(this.raceState.getRegattaName());
initialiseBoatTable();
initialiseRaceClock();
countdownTimer();
}
/**
* Initialises the boat table that is to be shown on the pane.
*/
private void initialiseBoatTable() {
//Get the boats.
ObservableList<VisualiserBoat> boats =
this.raceState.getBoats();
//Populate table.
boatNameTable.setItems(boats);
boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty());
}
/**
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
*/
private void initialiseRaceClock() {
raceStartLabel.setText(
this.raceState.getRaceClock().getStartingTimeString());
// init clock start time
this.raceState.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
raceStartLabel.setText(newValue);
});
});
// init clock current time
this.raceState.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
timeZoneTime.setText(newValue);
});
});
// init clock remaining time
this.raceState.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) {
// display current race status
RaceStatusEnum raceStatus = raceState.getRaceStatusEnum();
raceStatusLabel.setText("Race Status: " + raceStatus.name());
// if race is in PREPARATORY or STARTED status
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
stop(); // stop this timer
// load up the race scene
try {
RaceViewController rvc = (RaceViewController)
loadScene("raceView.fxml");
rvc.startRace(visualiserRaceEvent, controllerClient,
isHost);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
}

@ -0,0 +1,567 @@
package visualiser.Controllers;
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Translate;
import javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.RaceDataSource;
import shared.model.Leg;
import shared.model.Mark;
import visualiser.app.App;
import visualiser.enums.TutorialState;
import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import visualiser.layout.Subject3D;
import visualiser.layout.View3D;
import visualiser.model.Sparkline;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceState;
import visualiser.utils.GPSConverter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Controller used to display a running race.
*/
public class RaceViewController extends Controller {
private VisualiserRaceEvent visualiserRace;
private VisualiserRaceState raceState;
private ControllerClient controllerClient;
private KeyFactory keyFactory = new KeyFactory();
private boolean infoTableShow = true; // shown or hidden
private boolean isHost;
private TutorialState currentState;
private ArrayList<TutorialState> tutorialStates;
private boolean isTutorial = false;
private String keyToPress;
private View3D view3D;
private ObservableList<Subject3D> viewSubjects;
// note: it says it's not used but it is! do not remove :)
private @FXML ArrowController arrowController;
private @FXML GridPane canvasBase;
private @FXML SplitPane racePane;
private @FXML StackPane arrowPane;
private @FXML Label timer;
private @FXML Label FPS;
private @FXML Label timeZone;
private @FXML CheckBox showFPS;
private @FXML TableView<VisualiserBoat> boatInfoTable;
private @FXML TableColumn<VisualiserBoat, String> boatPlacingColumn;
private @FXML TableColumn<VisualiserBoat, String> boatTeamColumn;
private @FXML TableColumn<VisualiserBoat, Leg> boatMarkColumn;
private @FXML TableColumn<VisualiserBoat, Number> boatSpeedColumn;
private @FXML LineChart<Number, Number> sparklineChart;
private @FXML Label tutorialText;
/**
* Displays a specified race.
* Intended to be called on loading the scene.
* @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, Boolean isHost) {
this.visualiserRace = visualiserRace;
this.raceState = visualiserRace.getVisualiserRaceState();
this.controllerClient = controllerClient;
this.isHost = isHost;
keyFactory.load();
tutorialCheck();
initKeypressHandler();
initialiseRaceVisuals();
}
/**
* Checks if the current game is a tutorial race and sets up initial
* tutorial displays if it is.
*/
private void tutorialCheck(){
if (App.gameType == 4) {
isTutorial = true;
tutorialText.setVisible(true);
tutorialStates = new ArrayList<>(Arrays.asList(TutorialState.values()));
currentState = tutorialStates.get(0);
tutorialStates.remove(0);
searchMapForKey("Upwind");
tutorialText.setText(
"Welcome to the tutorial! Exit at anytime with ESC. \nWe will first learn how to turn upwind. Press " +
keyToPress + " to turn upwind.");
} else {
isTutorial = false;
tutorialText.setVisible(false);
}
}
/**
* Sets up the listener and actions that occur when a key is pressed.
*/
private void initKeypressHandler() {
racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
String codeString = event.getCode().toString();
// tab key
if (codeString.equals("TAB")){toggleTable();}
// any key pressed
ControlKey controlKey = keyFactory.getKey(codeString);
if(controlKey != null) {
try {
controlKey.onAction(); // Change key state if applicable
//Check if current race is a tutorial
if (isTutorial){
//Check if current tutorial state has the same boat protocol code as key press
if (controlKey.getProtocolCode().equals(currentState.getAction())){
//Update tutorial
checkTutorialState();
}
}
controllerClient.sendKey(controlKey);
event.consume();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Logger.getGlobal().log(Level.WARNING, "RaceViewController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e);
Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e);
} catch (Exception e) {
e.printStackTrace();
}
}
// escape key
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<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
App.game.endEvent();
loadTitleScreen();
}
} else {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Exit Race");
alert.setContentText("Do you wish to quit the race?");
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
loadTitleScreen();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Initialises the various UI components to listen to the {@link #visualiserRace}.
*/
private void initialiseRaceVisuals() {
// initialise displays
initialiseFps();
initialiseInfoTable();
initialiseView3D();
initialiseRaceClock();
raceTimer(); // start the timer
new Sparkline(this.raceState, this.sparklineChart);
timeZone.setText(this.raceState.getRaceClock().getTimeZone());
arrowController.setWindProperty(this.raceState.windProperty());
}
private void initialiseView3D() {
viewSubjects = FXCollections.observableArrayList();
// Import boat mesh
URL asset = RaceViewController.class.getClassLoader().getResource("assets/V1.2 " +
"Complete Boat.stl");
StlMeshImporter importer = new StlMeshImporter();
importer.read(asset);
// Configure camera angles and control
view3D = new View3D();
view3D.setDistance(1050);
view3D.setYaw(0);
view3D.setPitch(60);
view3D.enableTracking();
canvasBase.add(view3D, 0, 0);
// Set up projection from GPS to view
RaceDataSource raceData = raceState.getRaceDataSource();
final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);
view3D.setItems(viewSubjects);
// Position and add each mark to view
for (Mark mark : raceState.getMarks()) {
Subject3D subject = new Subject3D(new Sphere(2));
subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
viewSubjects.add(subject);
}
// Position and add each boat to view
for (VisualiserBoat boat : raceState.getBoats()) {
MeshView mesh = new MeshView(importer.getImport());
Subject3D subject = new Subject3D(mesh);
viewSubjects.add(subject);
// Track this boat's movement with the new subject
AnimationTimer trackBoat = new AnimationTimer() {
@Override public void handle(long now) {
subject.setHeading(boat.getBearing().degrees());
subject.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
}
};
trackBoat.start();
}
// Fix initial bird's-eye position
view3D.updatePivot(new Translate(250, 0, 210));
// Bind zooming to scrolling
view3D.setOnScroll(e -> {
view3D.updateDistance(e.getDeltaY());
});
// Bind zooming to keypress (Z/X default)
racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
ControlKey key = keyFactory.getKey(e.getCode().toString());
if (key != null) {
switch (key.toString()) {
case "Zoom In":
//Check if race is a tutorial
if (isTutorial) {
//Check if the current tutorial state is zoom-in
if (currentState.equals(TutorialState.ZOOMIN)) {
try {
//Update tutorial
checkTutorialState();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
view3D.updateDistance(-10);
break;
case "Zoom Out":
//Check if race is a tutorial
if(isTutorial) {
//Check if current tutorial state is zoom-out
if (currentState.equals(TutorialState.ZOOMOUT)) {
try {
//Update tutorial
checkTutorialState();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
view3D.updateDistance(10);
break;
}
}
});
}
/**
* Initialises the frame rate functionality. This allows for toggling the
* frame rate, and connect the fps label to the race's fps property.
*/
private void initialiseFps() {
// fps toggle listener
showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
if (showFPS.isSelected()) {
FPS.setVisible(true);
} else {
FPS.setVisible(false);
}
});
// fps label display
this.visualiserRace.getFrameRateProperty().addListener((observable,
oldValue, newValue) -> {
Platform.runLater(() ->
this.FPS.setText("FPS: " + newValue.toString()));
});
}
/**
* Initialises the information table view to listen to a given race.
*/
private void initialiseInfoTable() {
// list of boats to display data for
ObservableList<VisualiserBoat> boats = FXCollections
.observableArrayList(this.visualiserRace.getVisualiserRaceState().getBoats());
SortedList<VisualiserBoat> sortedBoats = new SortedList<>(boats);
sortedBoats.comparatorProperty().bind(boatInfoTable.comparatorProperty());
// update list when boat information changes
this.visualiserRace.getVisualiserRaceState().getBoats().addListener(
(ListChangeListener.Change<? extends VisualiserBoat> c) -> Platform.runLater(() -> {
boats.setAll(this.visualiserRace.getVisualiserRaceState().getBoats());
}));
// set table data
boatInfoTable.setItems(sortedBoats);
boatTeamColumn.setCellValueFactory(
cellData -> cellData.getValue().nameProperty());
boatSpeedColumn.setCellValueFactory(
cellData -> cellData.getValue().currentSpeedProperty());
boatMarkColumn.setCellValueFactory(
cellData -> cellData.getValue().legProperty());
boatPlacingColumn.setCellValueFactory(
cellData -> cellData.getValue().placingProperty());
//Kind of ugly, but allows for formatting an observed speed.
boatSpeedColumn.setCellFactory(
new Callback<TableColumn<VisualiserBoat, Number>, TableCell<VisualiserBoat, Number>>() {
@Override
public TableCell<VisualiserBoat, Number> call(TableColumn<VisualiserBoat, Number> param) {
//We return a table cell that populates itself with a Number, and formats it.
return new TableCell<VisualiserBoat, Number>(){
//Function to update the cell text.
@Override
protected void updateItem(Number item, boolean empty) {
if (item != null) {
super.updateItem(item, empty);
setText(String.format("%.2fkn", item.doubleValue()));
}
}
};
}
});
//Kind of ugly, but allows for turning an observed Leg into a string.
boatMarkColumn.setCellFactory(
new Callback<TableColumn<VisualiserBoat, Leg>, TableCell<VisualiserBoat, Leg>>() {
@Override
public TableCell<VisualiserBoat, Leg> call(TableColumn<VisualiserBoat, Leg> param) {
//We return a table cell that populates itself with a Leg's name.
return new TableCell<VisualiserBoat, Leg>(){
//Function to update the cell text.
@Override
protected void updateItem(Leg item, boolean empty) {
if (item != null) {
super.updateItem(item, empty);
setText(item.getName());
}
}
};
}
});
}
/**
* Initialises the race clock to listen to the specified race.
*/
private void initialiseRaceClock() {
raceState.getRaceClock().durationProperty().addListener((observable,
oldValue, newValue) -> {
Platform.runLater(() -> {
timer.setText(newValue);
});
});
}
/**
* Transition from the race view to the finish view.
*/
private void finishRace() throws IOException {
RaceFinishController fc =
(RaceFinishController)loadScene("raceFinish.fxml");
fc.loadFinish(raceState.getBoats());
}
/**
* Timer which monitors the race.
*/
private void raceTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//If the race has finished, go to finish view.
if (raceState.getRaceStatusEnum() == RaceStatusEnum.FINISHED) {
stop(); // stop the timer
try {
finishRace();
} catch (IOException e) {
e.printStackTrace();
}
} else {
boatInfoTable.sort();
}
//Return to main screen if we lose connection.
if (!visualiserRace.getServerConnection().isAlive()) {
try {
loadTitleScreen();
} catch (Exception e) {
e.printStackTrace();
}
//TODO we should display an error to the user
//TODO also need to "reset" any state (race, connections, etc...).
}
}
}.start();
}
/**
* toggles if the info table is shown
*/
private void toggleTable() {
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() +
boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth()
+ boatSpeedColumn.getPrefWidth())/racePane.getWidth();
if (infoTableShow) {
racePane.setDividerPositions(tablePercent);
arrowPane.setScaleX(0.5);
arrowPane.setScaleY(0.5);
arrowPane.setTranslateX(0 + (arrowPane.getScene().getWidth()/4)*tablePercent);
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
} else {
racePane.setDividerPositions(1);
arrowPane.setScaleX(1);
arrowPane.setScaleY(1);
arrowPane.setTranslateX(0);
arrowPane.setTranslateY(0);
}
boatInfoTable.refresh();
infoTableShow = !infoTableShow;
}
/**
* Get the next tutorial state
*/
private void updateTutorialState(){
//Next tutorial state is popped from list
currentState = tutorialStates.get(0);
tutorialStates.remove(0);
}
/**
* Search key map for key given string of command
* @param command the command of the key
*/
private void searchMapForKey(String command){
//For loop through keyFactory
for (Map.Entry<String, ControlKey> entry: keyFactory.getKeyState().entrySet()){
if(entry.getValue().toString().equals(command)){
//Found next key required to press
keyToPress = entry.getKey();
}
}
}
/**
* Updates tutorial state and gui display for tutorial text
* @throws Exception Exception thrown
*/
private void checkTutorialState() throws Exception {
//Switch statement to check what the current tutorial state is
switch (currentState){
case UPWIND:
//Set next key to press as the downwind key
searchMapForKey("Downwind");
//Update tutorial text
tutorialText.setText("Nice! To turn downwind press " + keyToPress + ".");
updateTutorialState();
break;
case DOWNWIND:
//Set next key to press as the tack/gybe key
searchMapForKey("Tack/Gybe");
//Update tutorial text
tutorialText.setText("Nice! To tack or gybe press " + keyToPress + ".");
updateTutorialState();
break;
case TACKGYBE:
//Set next key to press as the VMG key
searchMapForKey("VMG");
//Update tutorial text
tutorialText.setText("Nice! To use VMG press " + keyToPress + ". This will turn the boat.");
updateTutorialState();
break;
case VMG:
//Set next key to press as the sails-in key
searchMapForKey("Toggle Sails");
//Update tutorial text
tutorialText.setText("Nice! To sails in press " + keyToPress + ". This will stop the boat.");
updateTutorialState();
break;
case SAILSIN:
//Set next key to press as the sails-out key
searchMapForKey("Toggle Sails");
//Update tutorial text
tutorialText.setText("Nice! To sails out press " + keyToPress + " again. The will start moving again.");
updateTutorialState();
break;
case SAILSOUT:
//Set next key to press as the zoom-in key
searchMapForKey("Zoom In");
//Update tutorial text
tutorialText.setText("Nice! To zoom in press " + keyToPress + ".");
updateTutorialState();
break;
case ZOOMIN:
//Set next key to press as the zoom-out key
searchMapForKey("Zoom Out");
//Update tutorial text
tutorialText.setText("Nice! You will also be able to zoom into boats and marks by clicking them. To zoom out press " + keyToPress + ".");
updateTutorialState();
break;
case ZOOMOUT:
//Finished tutorial. Display pop-up for exiting the tutorial
tutorialText.setText("Congratulations! You're done!");
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Finished Tutorial");
alert.setHeaderText("You have finished the tutorial.");
alert.setContentText("Now you know the controls you are ready to race!");
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
App.game.endEvent();
loadTitleScreen();
}
break;
default:
//State not found. Exit tutorial to title menu
App.game.endEvent();
loadTitleScreen();
break;
}
}
}

@ -1,275 +0,0 @@
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 mock.model.commandFactory.CompositeCommand;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RequestToJoinEnum;
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.gameController.ControllerClient;
import visualiser.model.VisualiserRaceState;
import visualiser.network.ServerConnection;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Controller to for waiting for the race to start.
*/
public class StartController extends Controller {
@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<VisualiserBoat> boatNameTable;
@FXML private TableColumn<VisualiserBoat, String> boatNameColumn;
@FXML private TableColumn<VisualiserBoat, String> boatCodeColumn;
/**
* The status of the race.
*/
@FXML private Label raceStatusLabel;
/**
* The race + connection to server.
*/
private VisualiserRaceEvent visualiserRaceEvent;
/**
* Writes BoatActions to outgoing message queue.
*/
private ControllerClient controllerClient;
private boolean isHost;
/**
* Ctor.
*/
public StartController() {
}
@Override
public void initialize(URL location, ResourceBundle resources) {
}
/**
* Starts the race.
*/
private void startRace() {
//Initialise the boat table.
initialiseBoatTable(this.visualiserRaceEvent.getVisualiserRaceState());
//Initialise the race name.
initialiseRaceName(this.visualiserRaceEvent.getVisualiserRaceState());
//Initialises the race clock.
initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState());
//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(VisualiserRaceState visualiserRace) {
//Get the boats.
ObservableList<VisualiserBoat> 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(VisualiserRaceState 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(VisualiserRaceState 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(VisualiserRaceState 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(VisualiserRaceState 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(VisualiserRaceState 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) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().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);//TODO is this needed?
parent.beginRace(visualiserRaceEvent, controllerClient, isHost);
}
}
}.start();
}
/**
* 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, Boolean isHost) {
try {
this.isHost = isHost;
this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT);
this.controllerClient = visualiserRaceEvent.getControllerClient();
startWrapper.setVisible(true);
startRace();
} catch (IOException e) {
//TODO should probably let this propagate, so that we only enter this scene if everything works
Logger.getGlobal().log(Level.WARNING, "Could not connect to server.", e);
}
}
}

@ -1,21 +1,16 @@
package visualiser.Controllers;
import javafx.event.EventHandler;
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.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.layout.AnchorPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import mock.exceptions.EventConstructionException;
import visualiser.app.App;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Controller for the opening title window.
@ -24,15 +19,11 @@ import java.util.ResourceBundle;
* the game.
*/
public class TitleController extends Controller {
//FXML stuff
@FXML
Button btnJoin;
@FXML
AnchorPane titleWrapper;
@FXML
RadioButton dayModeRD;
@FXML
RadioButton nightModeRD;
private @FXML RadioButton dayModeRD;
private @FXML RadioButton nightModeRD;
private @FXML Label tutorialLabel;
private @FXML Pane menuPane;
private @FXML ImageView imgSun;
/**
* Method called when the 'host a game' button is pressed.
@ -41,26 +32,15 @@ public class TitleController extends Controller {
* @throws IOException if main has problems
*/
public void hostAGame() throws IOException {
titleWrapper.setVisible(false);
parent.hostGame();
App.getStage().setResizable(true);
}
/**
* Switch the scene to the title page.
*/
public void enterTitle(){
titleWrapper.setVisible(true);
loadScene("hostGame.fxml");
}
/**
* To be implemented at a later date- will open the next scene displaying
* games a player can join. Place holder method for now!
*/
public void joinAGame() {
titleWrapper.setVisible(false);
parent.enterLobby();
App.getStage().setResizable(true);
public void joinAGame() throws IOException {
loadScene("lobby.fxml");
}
/**
@ -68,8 +48,12 @@ public class TitleController extends Controller {
*/
public void setDayMode(){
dayModeRD.getScene().getStylesheets().clear();
menuPane.getStylesheets().clear();
imgSun.setImage(new Image(getClass().getResource("/visualiser/images/sun.png").toExternalForm()));
dayModeRD.getScene().getStylesheets().add("/css/dayMode.css");
menuPane.setStyle("-fx-background-color: #6be6ff;");
nightModeRD.setSelected(false);
App.dayMode = true;
}
/**
@ -77,41 +61,32 @@ public class TitleController extends Controller {
*/
public void setNightMode(){
nightModeRD.getScene().getStylesheets().clear();
menuPane.getStylesheets().clear();
imgSun.setImage(new Image(getClass().getResource("/visualiser/images/sunsleep.png").toExternalForm()));
nightModeRD.getScene().getStylesheets().add("/css/nightMode.css");
menuPane.setStyle("-fx-background-color: #1f2c60;");
dayModeRD.setSelected(false);
}
@Override
public void initialize(URL location, ResourceBundle resources) {
App.dayMode = false;
}
/**
* Called when control button is pressed. New pop up window displaying controls
*/
public void controlBtnPressed(){
public void showControls(){
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/visualiser/scenes/keyBindings.fxml"));
Parent 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.centerOnScreen();
popupStage.setScene(scene);
popupStage.show();
KeyBindingsController controller = loader.getController();
popupStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
public void handle(WindowEvent we) {
if (we.getEventType() == WindowEvent.WINDOW_CLOSE_REQUEST) {
controller.onExit(we);
}
}
});
} catch (Exception e){
loadPopupScene("keyBindings.fxml",
"Game Controls", Modality.WINDOW_MODAL);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void tutorialStartPressed() throws IOException,
EventConstructionException {
App.gameType = 4;
HostGameController hgc = new HostGameController();
hgc.setCurrentMapIndex(4);
hgc.hostGamePressed();
}
}

@ -7,7 +7,6 @@ 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;
@ -24,25 +23,22 @@ 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 visualiser.gameController.Keys.KeyFactory;
import mock.app.Event;
public class App extends Application {
private static Stage stage;
public static Event game;
public static Boolean dayMode = true;
public static Integer gameType = 0;
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 KeyFactory keyFactory = new KeyFactory();
public static App app;
/**
* Entry point for running the programme
*
* @param args for starting the programme
*/
public static void main(String[] args) {
@ -51,9 +47,6 @@ public class App extends Application {
@Override
public void init() {
// load the user's personalised key bindings
keyFactory.load();
ImageView splash = new ImageView(new Image(
getClass().getClassLoader().getResourceAsStream("images/splashScreen.png")
));
@ -79,15 +72,15 @@ public class App extends Application {
/**
* Method that sets up and displays the splash screen
* @param initStage the inital stage
* @param stage the initial stage
* @throws Exception if something wrong with title screen.
*/
public void start(Stage initStage) throws Exception {
public void start(Stage stage) throws Exception {
final Task<ObservableList<String>> boatTask = new Task<ObservableList<String>>() {
@Override
protected ObservableList<String> call() throws InterruptedException {
ObservableList<String> addedFilling =
FXCollections.<String>observableArrayList();
FXCollections.observableArrayList();
ObservableList<String> burgerFilling =
FXCollections.observableArrayList(
"Buns", "Patties", "Lettuce", "Onions", "Tomato",
@ -95,7 +88,7 @@ public class App extends Application {
);
updateMessage("Preparing ingredients . . .");
Thread.sleep(200);
Thread.sleep(100);
for (int i = 0; i < burgerFilling.size(); i++) {
Thread.sleep(100);
updateProgress(i + 1, burgerFilling.size());
@ -105,69 +98,49 @@ public class App extends Application {
}
Thread.sleep(100);
updateMessage("Burger's done!");
return addedFilling;
}
};
showSplash(
initStage,
stage,
boatTask,
() -> {
try {
showMainStage(new Stage());
loadTitleScreen();
} catch (Exception e) {
e.printStackTrace();
}
}
);
new Thread(boatTask).start();
}
/**
* Get main stage
* @return main stage
* Get the main stage to be shared for all regular game play scenes.
* @return shared stage
*/
public static Stage getStage() {
return App.stage;
}
/**
* Set main stage
* @param stage stage to set main stage
* Loads the title screen for the first time on app start.
* @throws Exception if there is a problem with a resource loaded
*/
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"));
private void loadTitleScreen() throws Exception {
stage = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource
("/visualiser/scenes/title.fxml"));
Parent root = loader.load();
stage.setResizable(false);
MainController mc = (MainController) loader.getController();
mc.enterTitle();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("RaceVision - Team 7");
stage.setTitle("The Boat Game - Burgers & Boats");
stage.getIcons().add(new Image(getClass().getClassLoader().getResourceAsStream("images/SailIcon.png")));
mc.startCss();
setStage(stage);
stage.setScene(scene);
stage.show();
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
Platform.exit();
System.exit(0);
}
stage.setOnCloseRequest(event -> {
Platform.exit();
System.exit(0);
});
}

@ -0,0 +1,60 @@
package visualiser.enums;
import network.Messages.Enums.BoatActionEnum;
/**
* State of which stage the tutorial is currently in
*/
public enum TutorialState {
/**
* State for upwind in tutorial
*/
UPWIND(BoatActionEnum.UPWIND),
/**
* State for downwind in tutorial
*/
DOWNWIND(BoatActionEnum.DOWNWIND),
/**
* State for tacking/gybing in tutorial
*/
TACKGYBE(BoatActionEnum.TACK_GYBE),
/**
* State for vmg in tutorial
*/
VMG(BoatActionEnum.AUTO_PILOT),
/**
* State for sails-in in tutorial
*/
SAILSIN(BoatActionEnum.SAILS_IN),
/**
* State for sails-out in tutorial
*/
SAILSOUT(BoatActionEnum.SAILS_OUT),
/**
* State for zoom-in in tutorial
*/
ZOOMIN(null),
/**
* State for zoom-out in tutorial
*/
ZOOMOUT(null);
private BoatActionEnum action;
TutorialState(BoatActionEnum action){
this.action = action;
}
public BoatActionEnum getAction(){
return action;
}
}

@ -3,15 +3,14 @@ package visualiser.gameController;
import javafx.animation.AnimationTimer;
import javafx.scene.Scene;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import java.util.HashMap;
import static visualiser.app.App.keyFactory;
/**
* Class for checking what keys are currently being used
*/
public class InputChecker {
private KeyFactory keyFactory;
private HashMap<String, Boolean> currentlyActiveKeys = new HashMap<>();
/**
@ -19,7 +18,8 @@ public class InputChecker {
* @param scene Scene the controller is to run in parallel with.
*/
public void runWithScene(Scene scene){
// KeyFactory keyFactory = KeyFactory.getFactory();
KeyFactory keyFactory = new KeyFactory();
keyFactory.load();
scene.setOnKeyPressed(event -> {
String codeString = event.getCode().toString();

@ -1,6 +1,5 @@
package visualiser.gameController.Keys;
import javafx.scene.input.KeyCode;
import network.Messages.Enums.BoatActionEnum;
/**
@ -45,7 +44,7 @@ public abstract class ControlKey {
/**
* What this key should do when the command is issued for it to do its job.
*/
public abstract void onAction();//may want to make it take in a visualiser and stuff in the future.
public abstract void onAction();
/**
* What to do when the key is held

@ -0,0 +1,65 @@
package visualiser.layout;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
/**
* Wrapper for controlling the position and heading of rendered 3D models.
*/
public class Subject3D {
/**
* Rendered mesh
*/
private Shape3D mesh;
/**
* Position translation updated by state listeners
*/
private Translate position;
/**
* Heading rotation updated by state listeners
*/
private Rotate heading;
/**
* Constructor for view subject wrapper
* @param mesh to be rendered
*/
public Subject3D(Shape3D mesh) {
this.mesh = mesh;
this.position = new Translate();
this.heading = new Rotate(0, Rotate.Y_AXIS);
this.mesh.getTransforms().addAll(position, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS));
}
public Shape3D getMesh() {
return mesh;
}
public Translate getPosition() {
return this.position;
}
public Rotate getHeading() {
return heading;
}
public void setX(double x) {
position.setX(x);
}
public void setY(double y) {
position.setY(y);
}
public void setZ(double z) {
position.setZ(z);
}
public void setHeading(double angle) {
heading.setAngle(angle);
}
}

@ -0,0 +1,260 @@
package visualiser.layout;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SubScene;
import javafx.scene.input.PickResult;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import java.util.HashMap;
import java.util.Map;
/**
* Control for rendering 3D objects visible through a PerspectiveCamera. Implements Adapter Pattern to
* interface with camera, and allows clients to add shapes to the scene. All scenes contain sea plane and
* sky box, whose textures are set with special methods.
*/
public class View3D extends Pane {
/**
* Container for group and camera
*/
private SubScene scene;
/**
* Observable list of renderable items
*/
private ObservableList<Subject3D> items;
/**
* Map for selecting Subject3D from Shape3D
*/
private Map<Shape3D, Subject3D> selectionMap;
/**
* Subject tracked by camera
*/
private Subject3D target;
/**
* Rendering container for shapes
*/
private Group world;
/**
* Near limit of view frustum
*/
private double nearClip;
/**
* Far limit of view frustum
*/
private double farClip;
/**
* Camera origin
*/
private Translate pivot;
/**
* Distance of camera from pivot point
*/
private Translate distance;
/**
* Angle along ground between z-axis and camera
*/
private Rotate yaw;
/**
* Angle between ground plane and camera direction
*/
private Rotate pitch;
/**
* Single listener for subject heading changes
*/
private ChangeListener<? super Number> pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr);
/**
* Single listener for subject position (x) changes
*/
private ChangeListener<? super Number> pivotX = (o, prev, curr) -> pivot.setX((double)curr);
/**
* Single listener for subject position (y) changes
*/
private ChangeListener<? super Number> pivotY = (o, prev, curr) -> pivot.setY((double)curr);
/**
* Single listener for subject position (z) changes
*/
private ChangeListener<? super Number> pivotZ = (o, prev, curr) -> pivot.setZ((double)curr);
/**
* Distance to switch from third person to bird's eye
*/
private double THIRD_PERSON_LIMIT = 100;
/**
* Default constructor for View3D. Sets up Scene and PerspectiveCamera.
*/
public View3D() {
this.world = new Group();
this.selectionMap = new HashMap<>();
this.target = null;
this.scene = new SubScene(world, 300, 300);
scene.widthProperty().bind(this.widthProperty());
scene.heightProperty().bind(this.heightProperty());
scene.setFill(new Color(0.2, 0.6, 1, 1));
scene.setCamera(buildCamera());
this.getChildren().add(scene);
}
/**
* Sets up camera view frustum and binds transformations
* @return perspective camera
*/
private PerspectiveCamera buildCamera() {
PerspectiveCamera camera = new PerspectiveCamera(true);
// Set up view frustum
nearClip = 0.1;
farClip = 3000.0;
camera.setNearClip(nearClip);
camera.setFarClip(farClip);
// Set up transformations
pivot = new Translate();
distance = new Translate();
yaw = new Rotate(0, Rotate.Y_AXIS);
pitch = new Rotate(0, Rotate.X_AXIS);
camera.getTransforms().addAll(pivot, yaw, pitch, distance);
return camera;
}
/**
* Provide the list of subjects to be automatically added or removed from the view as the list
* changes.
* @param items list managed by client
*/
public void setItems(ObservableList<Subject3D> items) {
this.items = items;
this.items.addListener((ListChangeListener<? super Subject3D>) c -> {
while(c.next()) {
if (c.wasRemoved() || c.wasAdded()) {
for (Subject3D shape : c.getRemoved()) {
world.getChildren().remove(shape.getMesh());
selectionMap.remove(shape.getMesh());
}
for (Subject3D shape : c.getAddedSubList()) {
world.getChildren().add(shape.getMesh());
selectionMap.put(shape.getMesh(), shape);
}
}
}
});
}
/**
* Intercept mouse clicks on subjects in view. The applied listener cannot be removed.
*/
public void enableTracking() {
scene.setOnMousePressed(e -> {
PickResult result = e.getPickResult();
if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) {
trackSubject(selectionMap.get(result.getIntersectedNode()));
}
});
}
/**
* Stop camera from following the last selected subject
*/
private void untrackSubject() {
if(target != null) {
target.getPosition().xProperty().removeListener(pivotX);
target.getPosition().yProperty().removeListener(pivotY);
target.getPosition().zProperty().removeListener(pivotZ);
target.getHeading().angleProperty().removeListener(pivotHeading);
}
}
/**
* Set camera to follow the selected subject
* @param subject to track
*/
private void trackSubject(Subject3D subject) {
untrackSubject();
target = subject;
updatePivot(target.getPosition());
setYaw(target.getHeading().getAngle());
target.getPosition().xProperty().addListener(pivotX);
target.getPosition().yProperty().addListener(pivotY);
target.getPosition().zProperty().addListener(pivotZ);
target.getHeading().angleProperty().addListener(pivotHeading);
this.setDistance(THIRD_PERSON_LIMIT);
this.setPitch(20);
}
public void setNearClip(double nearClip) {
this.nearClip = nearClip;
}
public void setFarClip(double farClip) {
this.farClip = farClip;
}
/**
* Sets the coordinates of the camera pivot once.
* @param pivot source of coordinates
*/
public void updatePivot(Translate pivot) {
this.pivot.setX(pivot.getX());
this.pivot.setY(pivot.getY());
this.pivot.setZ(pivot.getZ());
}
/**
* Set distance of camera from pivot
* @param distance in units
*/
public void setDistance(double distance) {
this.distance.setZ(-distance);
}
/**
* Adds delta to current distance and changes camera mode if applicable.
* Third person limit specifies the distance at which a third person camera
* switches to bird's-eye, remaining focused on the same position.
* @param delta amount to change distance by
*/
public void updateDistance(double delta) {
double distance = -this.distance.getZ() + delta;
if(distance <= 0) {
this.setDistance(0);
} else if(distance > THIRD_PERSON_LIMIT) {
untrackSubject();
this.setYaw(0);
this.setPitch(60);
this.setDistance(distance);
} else {
this.setDistance(distance);
}
}
/**
* Set angle of camera from z-axis along ground
* @param yaw in degrees
*/
public void setYaw(double yaw) {
this.yaw.setAngle(yaw);
}
/**
* Set elevation of camera
* @param pitch in degrees
*/
public void setPitch(double pitch) {
this.pitch.setAngle(-pitch);
}
}

@ -15,7 +15,7 @@ import java.util.Map;
* Class that processes user selected annotation visibility options to
* display the requested information on the
* {@link ResizableRaceCanvas}. These are displayed
* via the {@link visualiser.Controllers.RaceController}. <br>
* via the {@link visualiser.Controllers.RaceViewController}. <br>
* Annotation options for a {@link VisualiserBoat} include: its name,
* abbreviation, speed, the time since it passed the last
* {@link shared.model.Mark}, estimated time to the next marker,

@ -1,18 +0,0 @@
package visualiser.model;
import com.interactivemesh.jfx.importer.Importer;
import javafx.scene.layout.Pane;
/**
* Created by fwy13 on 29/08/17.
*/
public class BoatDisplay3D extends Pane {
public BoatDisplay3D(String filePath){
super();
// Shape3D
// this.getChildren().add();
}
}

@ -18,7 +18,7 @@ import java.util.List;
/**
* This JavaFX Canvas is used to update and display details for a
* {@link RaceMap} via the
* {@link visualiser.Controllers.RaceController}.<br>
* {@link visualiser.Controllers.RaceViewController}.<br>
* It fills it's parent and cannot be downsized. <br>
* Details displayed include:
* {@link VisualiserBoat}s (and their

@ -21,7 +21,7 @@ import java.util.Map;
* placing position as they complete each {@link shared.model.Leg} by
* passing a course {@link shared.model.Mark}. <br>
* This sparkline is displayed using the
* {@link visualiser.Controllers.RaceController}.
* {@link visualiser.Controllers.RaceViewController}.
*/
public class Sparkline {

@ -8,8 +8,8 @@ import shared.model.GPSCoordinate;
* {@link VisualiserBoat Boat} has travelled in a race. <br>
* TrackPoints are displayed on a
* {@link ResizableRaceCanvas}, via the
* {@link visualiser.Controllers.RaceController}. <br>
* Track points can be made visible or hidden via the RaceController's
* {@link visualiser.Controllers.RaceViewController}. <br>
* Track points can be made visible or hidden via the RaceViewController's
* {@link Annotations}.
*/
public class TrackPoint {

@ -1,145 +0,0 @@
package visualiser.model;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SubScene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
/**
* Control for rendering 3D objects visible through a PerspectiveCamera. Implements Adapter Pattern to
* interface with camera, and allows clients to add shapes to the scene. All scenes contain sea plane and
* sky box, whose textures are set with special methods.
*/
public class View3D extends Pane {
/**
* Observable list of renderable items
*/
private ObservableList<Shape3D> items;
/**
* Rendering container for shapes
*/
private Group world;
/**
* Near limit of view frustum
*/
private double nearClip;
/**
* Far limit of view frustum
*/
private double farClip;
/**
* Position camera pivots around
*/
private Translate pivot;
/**
* Distance of camera from pivot point
*/
private Translate distance;
/**
* Angle along ground between z-axis and camera
*/
private Rotate yaw;
/**
* Angle between ground plane and camera direction
*/
private Rotate pitch;
/**
* Default constructor for View3D. Sets up Scene and PerspectiveCamera.
*/
public View3D() {
world = new Group();
SubScene scene = new SubScene(world, 300, 300);
scene.widthProperty().bind(this.widthProperty());
scene.heightProperty().bind(this.heightProperty());
scene.setFill(Color.BLACK);
scene.setCamera(buildCamera());
this.getChildren().add(scene);
}
/**
* Sets up camera view frustum and binds transformations
* @return perspective camera
*/
private PerspectiveCamera buildCamera() {
PerspectiveCamera camera = new PerspectiveCamera(true);
// Set up view frustum
nearClip = 0.1;
farClip = 1000.0;
camera.setNearClip(nearClip);
camera.setFarClip(farClip);
// Set up transformations
pivot = new Translate();
distance = new Translate();
yaw = new Rotate(0, Rotate.Y_AXIS);
pitch = new Rotate(0, Rotate.X_AXIS);
camera.getTransforms().addAll(pivot, yaw, pitch, distance);
return camera;
}
public void setItems(ObservableList<Shape3D> items) {
this.items = items;
this.items.addListener((ListChangeListener<? super Shape3D>) c -> {
while(c.next()) {
if (c.wasRemoved() || c.wasAdded()) {
for (Shape3D shape : c.getRemoved()) world.getChildren().remove(shape);
for (Shape3D shape : c.getAddedSubList()) world.getChildren().add(shape);
}
}
});
}
public void setNearClip(double nearClip) {
this.nearClip = nearClip;
}
public void setFarClip(double farClip) {
this.farClip = farClip;
}
/**
* Set object to centre on camera
* @param pivot centred object
*/
public void setPivot(Shape3D pivot) {
this.pivot.setX(pivot.getTranslateX());
this.pivot.setY(pivot.getTranslateY());
this.pivot.setZ(pivot.getTranslateZ());
}
/**
* Set distance of camera from pivot
* @param distance in units
*/
public void setDistance(double distance) {
this.distance.setZ(-distance);
}
/**
* Set angle of camera from z-axis along ground
* @param yaw in degrees
*/
public void setYaw(double yaw) {
this.yaw.setAngle(yaw);
}
/**
* Set elevation of camera
* @param pitch in degrees
*/
public void setPitch(double pitch) {
this.pitch.setAngle(-pitch);
}
}

@ -1,11 +1,10 @@
package visualiser.model;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color;
import network.Messages.Enums.BoatStatusEnum;
import shared.model.Azimuth;
import shared.model.Boat;
import shared.model.Constants;
import shared.model.GPSCoordinate;
import shared.model.*;
import java.time.Duration;
import java.time.ZonedDateTime;
@ -18,7 +17,7 @@ import java.util.List;
* This class is used to represent and store information about a boat which may
* travel around in a race. It is displayed on the
* {@link ResizableRaceCanvas ResizableRaceCanvas} via the
* {@link visualiser.Controllers.RaceController RaceController}.
* {@link visualiser.Controllers.RaceViewController RaceViewController}.
*/
public class VisualiserBoat extends Boat {
@ -61,7 +60,8 @@ public class VisualiserBoat extends Boat {
private boolean isClientBoat = false;
private ObjectProperty<GPSCoordinate> positionProperty;
private ObjectProperty<Bearing> bearingProperty;
/**
@ -239,4 +239,38 @@ public class VisualiserBoat extends Boat {
public void setClientBoat(boolean clientBoat) {
isClientBoat = clientBoat;
}
@Override
public GPSCoordinate getPosition() {
return positionProperty.get();
}
@Override
public void setPosition(GPSCoordinate position) {
if(this.positionProperty == null) {
this.positionProperty = new SimpleObjectProperty<>();
}
this.positionProperty.set(position);
}
public ObjectProperty<GPSCoordinate> positionProperty() {
return positionProperty;
}
@Override
public Bearing getBearing() {
return bearingProperty.get();
}
@Override
public void setBearing(Bearing bearing) {
if(this.bearingProperty == null) {
this.bearingProperty = new SimpleObjectProperty<>();
}
this.bearingProperty.set(bearing);
}
public ObjectProperty<Bearing> bearingProperty() {
return bearingProperty;
}
}

@ -66,7 +66,7 @@ public class VisualiserRaceEvent {
this.serverConnection = new ServerConnection(socket, visualiserRaceState, raceCommands, requestType);
this.serverConnectionThread = new Thread(serverConnection, "StartController.enterLobby()->serverConnection thread " + serverConnection);
this.serverConnectionThread = new Thread(serverConnection, "RaceStartController.enterLobby()->serverConnection thread " + serverConnection);
this.serverConnectionThread.start();

@ -7,14 +7,12 @@ import network.MessageRouters.MessageRouter;
import network.Messages.AC35Data;
import network.Messages.Enums.MessageType;
import network.Messages.Enums.RequestToJoinEnum;
import network.Messages.LatestMessages;
import network.StreamRelated.MessageDeserialiser;
import network.StreamRelated.MessageSerialiser;
import shared.model.RunnableWithFramePeriod;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceController;
import visualiser.enums.ConnectionToServerState;
import visualiser.gameController.ControllerClient;
import visualiser.model.VisualiserRaceController;
import visualiser.model.VisualiserRaceState;
import java.io.IOException;
@ -310,7 +308,7 @@ public class ServerConnection implements RunnableWithFramePeriod {
}
//TODO create input controller here. RaceController should query for it, if it exists.
//TODO create input controller here. RaceViewController should query for it, if it exists.
private void createPlayerInputController() {
this.messageRouter.addRoute(MessageType.BOATACTION, messageSerialiser.getMessagesToSend());

@ -0,0 +1,104 @@
package visualiser.utils;
import shared.dataInput.RaceDataSource;
import shared.model.GPSCoordinate;
import visualiser.model.GraphCoordinate;
/**
* Converts GPS coordinates to view volume coordinates. Longitudes are equally spaced at all latitudes,
* which leads to inaccurate distance measurements close to the poles. This is acceptable as races are
* not likely to be set there.
*/
public class GPSConverter {
private double longRight;
private double longLeft;
private double latBottom;
private double latTop;
/**
* Conversion factor from longitude to view units
*/
private double longitudeFactor;
/**
* Conversion factor from latitude to view units
*/
private double latitudeFactor;
/**
* Set up projection with default view boundaries from RaceDataSource
* @param source for view boundaries
* @param longitudeFactor separation of a degree of longitude in view units
* @param latitudeFactor separation of a degree of latitude in view units
*/
public GPSConverter(RaceDataSource source, double longitudeFactor, double latitudeFactor) {
this.latTop = source.getMapTopLeft().getLatitude();
this.longLeft = source.getMapTopLeft().getLongitude();
this.latBottom = source.getMapBottomRight().getLatitude();
this.longRight = source.getMapBottomRight().getLongitude();
this.longitudeFactor = longitudeFactor;
this.latitudeFactor = latitudeFactor;
}
/**
* Converts GPS coordinates to coordinates for container.
* It is assumed that the provided GPSCoordinate will always be within the GPSCoordinate boundaries of the RaceMap.
*
* @param lat GPS latitude
* @param lon GPS longitude
* @return GraphCoordinate (pair of doubles)
* @see GraphCoordinate
*/
private GraphCoordinate convertGPS(double lat, double lon) {
//Calculate the width/height, in gps coordinates, of the map.
double longWidth = longRight - longLeft;
double latHeight = latBottom - latTop;
//Calculate the distance between the specified coordinate and the edge of the map.
double longDelta = lon - longLeft;
double latDelta = lat - latTop;
//Calculate the proportion along horizontally, from the left, the coordinate should be.
double longProportion = longDelta / longWidth;
//Calculate the proportion along vertically, from the top, the coordinate should be.
double latProportion = latDelta / latHeight;
//Check which metric dimension of our map is smaller. We use this to ensure that any rendered stuff retains its correct aspect ratio, and that everything is visible on screen.
double smallerDimension = Math.min(longitudeFactor, latitudeFactor);
//Calculate the x and y pixel coordinates.
//We take the complement of latProportion to flip it.
int x = (int) (longProportion * smallerDimension);
int y = (int) (latProportion * smallerDimension);
//Because we try to maintain the correct aspect ratio, we will end up with "spare" pixels along the larger dimension (e.g., width 800, height 600, 200 extra pixels along width).
double extraDistance = Math.abs(longitudeFactor - latitudeFactor);
//We therefore "center" the coordinates along this larger dimension, by adding half of the extra pixels.
if (longitudeFactor > latitudeFactor) {
x += extraDistance / 2;
} else {
y += extraDistance / 2;
}
//Finally, create the GraphCoordinate.
GraphCoordinate graphCoordinate = new GraphCoordinate(x, y);
return graphCoordinate;
}
/**
* Converts the GPS Coordinate to GraphCoordinate.
* It is assumed that the provided GPSCoordinate will always be within the GPSCoordinate boundaries of the RaceMap.
*
* @param coordinate GPSCoordinate representation of Latitude and Longitude.
* @return GraphCoordinate that the GPS is coordinates are to be displayed on the map.
* @see GraphCoordinate
* @see GPSCoordinate
*/
public GraphCoordinate convertGPS(GPSCoordinate coordinate) {
return convertGPS(coordinate.getLatitude(), coordinate.getLongitude());
}
}

@ -53,5 +53,39 @@
}
#arrowImage {
-fx-image: url("/visualiser/images/arrow.png");
-fx-graphic: url("/visualiser/images/arrow.png");
}
#nextButton {
-fx-background-image: url("/visualiser/images/ArrowRoundRight.png");
-fx-background-size: 60px;
-fx-background-repeat: no-repeat;
-fx-background-position: center center;
-fx-focus-color: transparent;
-fx-background-color: transparent;
}
#nextButton:pressed {
-fx-background-image: url("/visualiser/images/ArrowRoundRightClicked.png");
-fx-background-size: 60px;
-fx-background-repeat: no-repeat;
-fx-background-position: center center;
-fx-focus-color: transparent;
-fx-background-color: transparent;
}
#previousButton {
-fx-background-image: url("/visualiser/images/ArrowRoundLeft.png");
-fx-background-size: 60px;
-fx-background-repeat: no-repeat;
-fx-background-position: center center;
-fx-focus-color: transparent;
-fx-background-color: transparent;
}
#previousButton:pressed {
-fx-background-image: url("/visualiser/images/ArrowRoundLeftClicked.png");
-fx-background-size: 60px;
-fx-background-repeat: no-repeat;
-fx-background-position: center center;
-fx-focus-color: transparent;
-fx-background-color: transparent;
}

@ -57,3 +57,37 @@
#arrowImage {
-fx-image: url("/visualiser/images/arrowLight.png");
}
#nextButton {
-fx-background-image: url("/visualiser/images/ArrowRoundRight.png");
-fx-background-size: 60px;
-fx-background-repeat: no-repeat;
-fx-background-position: center center;
-fx-focus-color: transparent;
-fx-background-color: transparent;
}
#nextButton:pressed {
-fx-background-image: url("/visualiser/images/ArrowRoundRightClicked.png");
-fx-background-size: 60px;
-fx-background-repeat: no-repeat;
-fx-background-position: center center;
-fx-focus-color: transparent;
-fx-background-color: transparent;
}
#previousButton {
-fx-background-image: url("/visualiser/images/ArrowRoundLeft.png");
-fx-background-size: 60px;
-fx-background-repeat: no-repeat;
-fx-background-position: center center;
-fx-focus-color: transparent;
-fx-background-color: transparent;
}
#previousButton:pressed {
-fx-background-image: url("/visualiser/images/ArrowRoundLeftClicked.png");
-fx-background-size: 60px;
-fx-background-repeat: no-repeat;
-fx-background-position: center center;
-fx-focus-color: transparent;
-fx-background-color: transparent;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<BoatConfig>
<Boats>
<!--Mark Boats-->
<Boat Type="Mark" BoatName="PRO" SourceID="101" >
<GPSposition X= "-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="PIN" SourceID="102" >
<GPSposition X= "-64.855242" Y="32.293771" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="FL" SourceID="108" >
<GPSposition X= "-64.839291" Y="32.317379" Z="0"/>
</Boat>
<Boat Type="Mark" BoatName="FR" SourceID="109" >
<GPSposition X= "-64.83626" Y="32.317257" Z="0"/>
</Boat>
<!--Participants-->
<!--Participants-->
<Boat BoatName="Emirates Team New Zealand" HullNum="RG01" ShapeID="0" ShortName="NZL" SourceID="121" StoweName="NZL" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
<!--<Boat BoatName="Land Rover BAR" HullNum="RG01" ShapeID="0" ShortName="GBR" SourceID="122" StoweName="GBR" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat BoatName="SoftBank Team Japan" HullNum="RG01" ShapeID="0" ShortName="JPN" SourceID="123" StoweName="JPN" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat BoatName="Groupama Team France" HullNum="RG01" ShapeID="0" ShortName="FRA" SourceID="124" StoweName="FRA" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat BoatName="Artemis Racing" HullNum="RG01" ShapeID="0" ShortName="SWE" SourceID="125" StoweName="SWE" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>
<Boat BoatName="ORACLE TEAM USA" HullNum="RG01" ShapeID="0" ShortName="USA" SourceID="126" StoweName="USA" Type="Yacht">
<GPSposition X="-64.854304" Y="32.296577" Z="0"/>
</Boat>-->
</Boats>
</BoatConfig>

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Race>
<RaceID>5326</RaceID>
<RaceType>FLEET</RaceType>
<CreationTimeDate>RACE_CREATION_TIME</CreationTimeDate>
<RaceStartTime Postpone="false" Time="RACE_START_TIME"/>
<Participants>
</Participants>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3" />
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3" />
<Corner SeqID="3" CompoundMarkID="4" Rounding="Starboard" ZoneSize="3" />
<Corner SeqID="4" CompoundMarkID="2" Rounding="Port" ZoneSize="3" />
<Corner SeqID="5" CompoundMarkID="3" Rounding="Port" ZoneSize="3" />
<Corner SeqID="7" CompoundMarkID="5" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<Course>
<CompoundMark CompoundMarkID="1" Name="Start Line">
<Mark SeqId="1" Name="PRO" TargetLat="1.681354" TargetLng="1.132354" SourceID="101"/>
<Mark SeqId="2" Name="PIN" TargetLat="1.681354" TargetLng="1.135604" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="Marker 1">
<Mark Name="Marker1" TargetLat="1.728713" TargetLng="1.131345" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="Marker 2">
<Mark Name="Marker2" TargetLat="1.725120" TargetLng="1.109701" SourceID="104"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Gate 1">
<Mark Name="LGL" SeqId="1" TargetLat="1.707354" TargetLng="1.130505" SourceID="105"/>
<Mark Name="LGR" SeqId="2" TargetLat="1.707404" TargetLng="1.133304" SourceID="106"/>
</CompoundMark>
<CompoundMark CompoundMarkID="5" Name="Finish Line">
<Mark Name="FL" SeqId="1" TargetLat="1.690202" TargetLng="1.109606" SourceID="107"/>
<Mark Name="FR" SeqId="2" TargetLat="1.692202" TargetLng="1.112629" SourceID="108"/>
</CompoundMark>
</Course>
<CourseLimit>
<Limit Lat="1.73558" Lon="1.105505" SeqID="1"/>
<Limit Lat="1.732444" Lon="1.137105" SeqID="2"/>
<Limit Lat="1.680404" Lon="1.140505" SeqID="3"/>
<Limit Lat="1.675232" Lon="1.122202" SeqID="4"/>
<Limit Lat="1.685101" Lon="1.120222" SeqID="5"/>
<Limit Lat="1.680101" Lon="1.102505" SeqID="6"/>
</CourseLimit>
</Race>

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Race>
<RaceID>5326</RaceID>
<RaceType>FLEET</RaceType>
<CreationTimeDate>RACE_CREATION_TIME</CreationTimeDate>
<RaceStartTime Postpone="false" Time="RACE_START_TIME"/>
<Participants>
</Participants>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3" />
<Corner SeqID="2" CompoundMarkID="2" Rounding="Starboard" ZoneSize="3" />
<Corner SeqID="3" CompoundMarkID="3" Rounding="Starboard" ZoneSize="3" />
<Corner SeqID="4" CompoundMarkID="2" Rounding="Starboard" ZoneSize="3" />
<Corner SeqID="5" CompoundMarkID="4" Rounding="SP" ZoneSize="3" />
</CompoundMarkSequence>
<Course>
<CompoundMark CompoundMarkID="1" Name="Start Line">
<Mark SeqId="1" Name="PRO" TargetLat="1.681354" TargetLng="1.132354" SourceID="101"/>
<Mark SeqId="2" Name="PIN" TargetLat="1.680354" TargetLng="1.135604" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="Marker 1">
<Mark Name="Marker1" TargetLat="1.780354" TargetLng="1.140600" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="Marker 2">
<Mark Name="Marker2" TargetLat="1.730354" TargetLng="1.137604" SourceID="104"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Finish Line">
<Mark SeqId="1" Name="FL" TargetLat="1.681354" TargetLng="1.132354" SourceID="105"/>
<Mark SeqId="2" Name="FR" TargetLat="1.680354" TargetLng="1.135604" SourceID="106"/>
</CompoundMark>
</Course>
<CourseLimit>
<Limit Lat="1.675354" Lon="1.122354" SeqID="1"/>
<Limit Lat="1.680354" Lon="1.141600" SeqID="2"/>
<Limit Lat="1.790354" Lon="1.146600" SeqID="3"/>
<Limit Lat="1.785354" Lon="1.127354" SeqID="4"/>
</CourseLimit>
</Race>

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Race>
<RaceID>5326</RaceID>
<RaceType>FLEET</RaceType>
<CreationTimeDate>RACE_CREATION_TIME</CreationTimeDate>
<RaceStartTime Postpone="false" Time="RACE_START_TIME"/>
<Participants>
</Participants>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3" />
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3" />
<Corner SeqID="3" CompoundMarkID="3" Rounding="Port" ZoneSize="3" />
<Corner SeqID="4" CompoundMarkID="4" Rounding="Starboard" ZoneSize="3" />
<Corner SeqID="5" CompoundMarkID="3" Rounding="Starboard" ZoneSize="3" />
<Corner SeqID="6" CompoundMarkID="4" Rounding="Port" ZoneSize="3" />
<Corner SeqID="7" CompoundMarkID="5" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<Course>
<CompoundMark CompoundMarkID="1" Name="Start Line">
<Mark SeqId="1" Name="PRO" TargetLat="-40.681354" TargetLng="174.132354" SourceID="101"/>
<Mark SeqId="2" Name="PIN" TargetLat="-40.608415" TargetLng="174.272430" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="Marker 1">
<Mark Name="Marker1" TargetLat="-40.118713" TargetLng="173.541839" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="Marker 2">
<Mark Name="Marker2" TargetLat="-40.53120" TargetLng="173.250701" SourceID="104"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Gate">
<Mark Name="WGL" SeqId="1" TargetLat="-40.722999" TargetLng="173.420989" SourceID="106"/>
<Mark Name="WGR" SeqId="2" TargetLat="-40.789575" TargetLng="173.294647" SourceID="107"/>
</CompoundMark>
<CompoundMark CompoundMarkID="5" Name="Finish Line">
<Mark Name="FL" SeqId="1" TargetLat="-40.968169" TargetLng="173.692901" SourceID="108"/>
<Mark Name="FR" SeqId="2" TargetLat="-40.878932" TargetLng="173.695648" SourceID="109"/>
</CompoundMark>
</Course>
<CourseLimit>
<Limit Lat="-40.984758" Lon="173.736846" SeqID="1"/>
<Limit Lat="-41.160757" Lon="173.140838" SeqID="2"/>
<Limit Lat="-39.937843" Lon="173.085906" SeqID="3"/>
<Limit Lat="-40.244616" Lon="174.420745" SeqID="4"/>
<Limit Lat="-40.627178" Lon="174.412505" SeqID="5"/>
<Limit Lat="-40.783337" Lon="174.093902" SeqID="6"/>
<Limit Lat="-40.612585" Lon="173.893401" SeqID="7"/>
<Limit Lat="-40.752134" Lon="173.723113" SeqID="8"/>
</CourseLimit>
</Race>

@ -5,7 +5,6 @@
<CreationTimeDate>RACE_CREATION_TIME</CreationTimeDate>
<RaceStartTime Postpone="false" Time="RACE_START_TIME"/>
<Participants>
<Yacht SourceID="126"/>
</Participants>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3" />

@ -1,54 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Race>
<RaceID>5326</RaceID>
<RaceType>FLEET</RaceType>
<CreationTimeDate>RACE_CREATION_TIME</CreationTimeDate>
<RaceStartTime Postpone="false" Time="RACE_START_TIME"/>
<Participants>
<Yacht SourceID="124"/>
<Yacht SourceID="125"/>
<Yacht SourceID="126"/>
</Participants>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3" />
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3" />
<Corner SeqID="3" CompoundMarkID="4" Rounding="Port" ZoneSize="3" />
<Corner SeqID="4" CompoundMarkID="3" Rounding="Starboard" ZoneSize="3" />
<Corner SeqID="5" CompoundMarkID="4" Rounding="Port" ZoneSize="3" />
<Corner SeqID="6" CompoundMarkID="5" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<Course>
<CompoundMark CompoundMarkID="1" Name="Start Line">
<Mark SeqId="1" Name="PRO" TargetLat="32.296577" TargetLng="-64.854304" SourceID="101"/>
<Mark SeqId="2" Name="PIN" TargetLat="32.293771" TargetLng="-64.855242" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="Marker 1">
<Mark Name="Marker1" TargetLat="32.293039" TargetLng="-64.843983" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="Windward Gate">
<Mark Name="WGL" SeqId="1" TargetLat="32.28468" TargetLng="-64.850045" SourceID="104"/>
<Mark Name="WGR" SeqId="2" TargetLat="32.280164" TargetLng="-64.847591" SourceID="105"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Leeward Gate">
<Mark Name="LGL" SeqId="1" TargetLat="32.309693" TargetLng="-64.835249" SourceID="106"/>
<Mark Name="LGR" SeqId="2" TargetLat="32.308046" TargetLng="-64.831785" SourceID="107"/>
</CompoundMark>
<CompoundMark CompoundMarkID="5" Name="Finish Line">
<Mark Name="FL" SeqId="1" TargetLat="32.317379" TargetLng="-64.839291" SourceID="108"/>
<Mark Name="FR" SeqId="2" TargetLat="32.317257" TargetLng="-64.83626" SourceID="109"/>
</CompoundMark>
</Course>
<CourseLimit>
<Limit Lat="32.313922" Lon="-64.837168" SeqID="1"/>
<Limit Lat="32.317379" Lon="-64.839291" SeqID="2"/>
<Limit Lat="32.317911" Lon="-64.836996" SeqID="3"/>
<Limit Lat="32.317257" Lon="-64.83626" SeqID="4"/>
<Limit Lat="32.304273" Lon="-64.822834" SeqID="5"/>
<Limit Lat="32.279097" Lon="-64.841545" SeqID="6"/>
<Limit Lat="32.279604" Lon="-64.849871" SeqID="7"/>
<Limit Lat="32.289545" Lon="-64.854162" SeqID="8"/>
<Limit Lat="32.290198" Lon="-64.858711" SeqID="9"/>
<Limit Lat="32.297164" Lon="-64.856394" SeqID="10"/>
<Limit Lat="32.296148" Lon="-64.849184" SeqID="11"/>
</CourseLimit>
</Race>

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Race>
<RaceID>9999</RaceID>
<RaceType>FLEET</RaceType>
<CreationTimeDate>RACE_CREATION_TIME</CreationTimeDate>
<RaceStartTime Postpone="false" Time="RACE_START_TIME"/>
<Participants>
<!--<Yacht SourceID="121"/>-->
</Participants>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3" />
<Corner SeqID="2" CompoundMarkID="2" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<Course>
<CompoundMark CompoundMarkID="1" Name="Start Line">
<Mark SeqId="1" Name="PRO" TargetLat="32.288148" TargetLng="-64.852996" SourceID="101"/>
<Mark SeqId="2" Name="PIN" TargetLat="32.290148" TargetLng="-64.854996" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="Finish Line">
<Mark Name="FL" SeqId="1" TargetLat="42.315911" TargetLng="-64.846996" SourceID="108"/>
<Mark Name="FR" SeqId="2" TargetLat="42.315911" TargetLng="-64.848996" SourceID="109"/>
</CompoundMark>
</Course>
<CourseLimit>
<Limit Lat="32.317911" Lon="-64.836996" SeqID="1"/>
<Limit Lat="32.286148" Lon="-64.836996" SeqID="2"/>
<Limit Lat="32.286148" Lon="-64.856996" SeqID="3"/>
<Limit Lat="32.317911" Lon="-64.856996" SeqID="4"/>
<!--<Limit Lat="32.313922" Lon="-64.837168" SeqID="1"/>
<Limit Lat="32.317379" Lon="-64.839291" SeqID="2"/>
<Limit Lat="32.317911" Lon="-64.836996" SeqID="3"/>
<Limit Lat="32.317257" Lon="-64.83626" SeqID="4"/>
<Limit Lat="32.304273" Lon="-64.822834" SeqID="5"/>
<Limit Lat="32.279097" Lon="-64.841545" SeqID="6"/>
<Limit Lat="32.279604" Lon="-64.849871" SeqID="7"/>
<Limit Lat="32.289545" Lon="-64.854162" SeqID="8"/>
<Limit Lat="32.290198" Lon="-64.858711" SeqID="9"/>
<Limit Lat="32.297164" Lon="-64.856394" SeqID="10"/>
<Limit Lat="32.296148" Lon="-64.849184" SeqID="11"/>-->
</CourseLimit>
</Race>

@ -0,0 +1,10 @@
<RegattaConfig>
<RegattaID>0</RegattaID>
<RegattaName>Race Tutorial</RegattaName>
<CourseName>Tutorial</CourseName>
<CentralLatitude>-36.82791529</CentralLatitude>
<CentralLongitude>174.81218919</CentralLongitude>
<CentralAltitude>0.00</CentralAltitude>
<UtcOffset>12</UtcOffset>
<MagneticVariation>14.1</MagneticVariation>
</RegattaConfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<Race>
<RaceID>99999999</RaceID>
<RaceType>Match</RaceType>
<CreationTimeDate>2011-08-06T13:25:00-0000</CreationTimeDate>
<RaceStartTime Time="2011-08-06T13:30:00-0700" Postpone="false"/>
<Participants>
<Yacht SourceID="101" Entry="Port"/>
<Yacht SourceID="108" Entry="Stbd"/>
</Participants>
<Course>
<CompoundMark CompoundMarkID="1" Name="StartLine">
<Mark SeqID="1" Name="PRO" TargetLat="-36.83" TargetLng="174.83" SourceID="101"/>
<Mark SeqID="2" Name="PIN" TargetLat="-36.84" TargetLng="174.81" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="M1">
<Mark Name="M1" TargetLat="-36.63566590" TargetLng="174.88543944" SourceID="103"/>
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="M2">
<Mark Name="M2" TargetLat="-36.83" TargetLng="174.80" SourceID="102"/>
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Gate">
<Mark SeqID="1" Name="G1" TargetLat="-36.63566590" TargetLng="174.97205159" SourceID="104"/>
<Mark SeqID="2" Name="G2" TargetLat="-36.64566590" TargetLng="174.98205159" SourceID="105"/>
</CompoundMark>
</Course>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3"/>
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3"/>
<Corner SeqID="3" CompoundMarkID="3" Rounding="Stbd" ZoneSize="6"/>
<Corner SeqID="4" CompoundMarkID="4" Rounding="PS" ZoneSize="6"/>
<Corner SeqID="5" CompoundMarkID="1" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<CourseLimit>
<Limit SeqID="1" Lat="-36.8325" Lon="174.8325"/>
<Limit SeqID="2" Lat="-36.82883" Lon="174.81983"/>
<Limit SeqID="3" Lat="-36.82067" Lon="174.81983"/>
<Limit SeqID="4" Lat="-36.811" Lon="174.8265"/>
<Limit SeqID="5" Lat="-36.81033" Lon="174.83833"/>
<Limit SeqID="6" Lat="-36.81533" Lon="174.8525"/>
<Limit SeqID="7" Lat="-36.81533" Lon="174.86733"/>
<Limit SeqID="8" Lat="-36.81633" Lon="174.88217"/>
<Limit SeqID="9" Lat="-36.83383" Lon="174.87117"/>
<Limit SeqID="10" Lat="-36.83417" Lon="174.84767"/>
</CourseLimit>
</Race>

@ -1,82 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="connectionWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" visible="false" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.ConnectionController">
<children>
<GridPane fx:id="connection" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="600.0" minWidth="10.0" prefWidth="600.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="600.0" minWidth="10.0" prefWidth="600.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="182.0" minHeight="10.0" prefHeight="182.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="434.0" minHeight="10.0" prefHeight="434.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="174.0" minHeight="10.0" prefHeight="174.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="80.0" minHeight="50.0" prefHeight="80.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TableView fx:id="connectionTable" prefHeight="200.0" prefWidth="1080.0" GridPane.columnSpan="2" GridPane.rowIndex="1">
<columns>
<TableColumn fx:id="hostnameColumn" prefWidth="453.99998474121094" text="Host" />
<TableColumn fx:id="statusColumn" prefWidth="205.0" text="Status" />
</columns>
<GridPane.margin>
<Insets left="50.0" right="50.0" />
</GridPane.margin>
</TableView>
<Button mnemonicParsing="false" onAction="#checkConnections" text="Refresh" GridPane.halignment="RIGHT" GridPane.rowIndex="3">
<GridPane.margin>
<Insets right="20.0" />
</GridPane.margin>
</Button>
<Button fx:id="connectButton" mnemonicParsing="false" onAction="#connectSocket" text="Connect" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowIndex="3">
<GridPane.margin>
<Insets left="20.0" />
</GridPane.margin>
</Button>
<Label text="Welcome to RaceVision" GridPane.columnSpan="2" GridPane.halignment="CENTER">
<font>
<Font size="36.0" />
</font>
</Label>
<GridPane GridPane.columnSpan="2" GridPane.rowIndex="2">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TextField fx:id="urlField" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="portField" GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<Button mnemonicParsing="false" onAction="#addConnection" text="Add New Connection" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<Label text="Host Name:" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM" />
<Label text="Port:" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM" />
</children>
</GridPane>
</children>
</GridPane>
</children>
</AnchorPane>

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="350.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<ImageView fitHeight="385.0" fitWidth="600.0" layoutY="-2.0" pickOnBounds="true" preserveRatio="true" AnchorPane.bottomAnchor="-1.9456787109375" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-2.0">
<image>
<Image url="@../images/game_controls.png" />
</image>
</ImageView>
</children>
</AnchorPane>

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="hostWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.HostGameController">
<children>
<GridPane layoutY="14.0" AnchorPane.bottomAnchor="-14.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="14.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="170.0" minWidth="10.0" prefWidth="170.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="170.0" minWidth="10.0" prefWidth="170.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="60.0" minHeight="60.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="435.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="80.0" minHeight="80.0" prefHeight="80.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="hostGameBtn" mnemonicParsing="false" onAction="#hostGamePressed" text="Start Game" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<font>
<Font size="20.0" />
</font>
<GridPane.margin>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</GridPane.margin>
</Button>
<Label text="Address: 127.0.0.1" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="TOP">
<font>
<Font size="17.0" />
</font>
</Label>
<Label text="Port: 4942" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<font>
<Font size="17.0" />
</font>
</Label>
<Button mnemonicParsing="false" onAction="#menuBtnPressed" text="Main Menu" GridPane.halignment="LEFT" GridPane.valignment="TOP">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</Button>
<ImageView fx:id="mapImage" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.valignment="CENTER" GridPane.vgrow="ALWAYS" />
<Button fx:id="previousButton" maxHeight="80.0" maxWidth="80.0" mnemonicParsing="false" onAction="#previousImage" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
<Button fx:id="nextButton" maxHeight="80.0" maxWidth="80.0" mnemonicParsing="false" onAction="#nextImage" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER" />
</children>
</GridPane>
</children>
</AnchorPane>

@ -1,44 +0,0 @@
<!--<?xml version="1.0" encoding="UTF-8"?>-->
<!--<?import java.lang.*?>-->
<!--<?import javafx.scene.control.*?>-->
<!--<?import javafx.scene.text.*?>-->
<!--<?import javafx.scene.control.Button?>-->
<!--<?import javafx.scene.control.Label?>-->
<!--<?import javafx.scene.layout.*?>-->
<!--<?import javafx.scene.text.Font?>-->
<!--<AnchorPane fx:id="hostWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" visible="false" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">-->
<!--<children>-->
<!--<GridPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">-->
<!--<columnConstraints>-->
<!--<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />-->
<!--<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />-->
<!--<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />-->
<!--</columnConstraints>-->
<!--<rowConstraints>-->
<!--<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />-->
<!--<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />-->
<!--<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />-->
<!--</rowConstraints>-->
<!--<children>-->
<!--<Button fx:id="hostGameBtn" mnemonicParsing="false" onAction="#hostGamePressed" text="Start Game" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2">-->
<!--<font>-->
<!--<Font size="20.0" />-->
<!--</font>-->
<!--</Button>-->
<!--<Label text="Address: 127.0.0.1" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="TOP">-->
<!--<font>-->
<!--<Font size="17.0" />-->
<!--</font>-->
<!--</Label>-->
<!--<Label text="Port: 4942" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1">-->
<!--<font>-->
<!--<Font size="17.0" />-->
<!--</font>-->
<!--</Label>-->
<!--<Button mnemonicParsing="false" onAction="#menuBtnPressed" text="Main Menu" GridPane.halignment="CENTER" />-->
<!--</children>-->
<!--</GridPane>-->
<!--</children>-->
<!--</AnchorPane>-->

@ -1,89 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="hostWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.HostController">
<children>
<SplitPane fx:id="splitPane" dividerPositions="0.7724935732647815" layoutX="580.0" layoutY="129.0" prefHeight="160.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<AnchorPane fx:id="imagePane" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<ImageView fx:id="imageView" fitHeight="376.0" fitWidth="597.0" nodeOrientation="INHERIT" pickOnBounds="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<image>
<Image url="@../images/lobby.gif" />
</image></ImageView>
<GridPane style="-fx-background-color: rgba(0, 0, 0, 0.5);" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="50.0" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" prefHeight="173.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="50.0" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button mnemonicParsing="false" onAction="#menuBtnPressed" text="Quit" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="20.0" />
</GridPane.margin>
</Button>
<Button alignment="CENTER_RIGHT" contentDisplay="RIGHT" mnemonicParsing="false" onAction="#startBtnPressed" text="Start Game" GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="2">
<GridPane.margin>
<Insets right="20.0" />
</GridPane.margin>
</Button>
<Label alignment="CENTER" contentDisplay="CENTER" text="Map: MapNameHere" textFill="WHITE" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2">
<font>
<Font size="16.0" />
</font>
</Label>
<GridPane fx:id="playerContainer" GridPane.columnSpan="3" GridPane.rowIndex="1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
</children>
</GridPane>
<Label alignment="TOP_CENTER" text="Get Ready For The Next Race" textFill="#fffdfd" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="20.0">
<font>
<Font size="22.0" />
</font>
</Label>
</children>
</AnchorPane>
<AnchorPane fx:id="specPane" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<TableView prefHeight="200.0" prefWidth="200.0" style="-fx-background-color: rgba(60, 60, 60, 1);" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn prefWidth="173.0" text="Spectators" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</AnchorPane>
</items>
</SplitPane>
</children>
</AnchorPane>

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
@ -17,7 +12,7 @@
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="lobbyWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" visible="false" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.LobbyController">
<AnchorPane fx:id="lobbyWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.LobbyController">
<children>
<GridPane fx:id="connection" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="main" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.MainController">
<children>
<fx:include fx:id="race" source="race.fxml" />
<fx:include fx:id="start" source="start.fxml" />
<fx:include fx:id="connection" source="connect.fxml" />
<fx:include fx:id="finish" source="finish.fxml" />
<fx:include fx:id="host" source="hostlobby.fxml" />
<fx:include fx:id="title" source="titleScreen.fxml" />
<fx:include fx:id="lobby" source="lobby.fxml" />
</children>
</AnchorPane>

@ -1,127 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Accordion?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.text.Font?>
<SplitPane fx:id="race" dividerPositions="1.0" prefHeight="431.0" prefWidth="610.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.RaceController">
<items>
<GridPane fx:id="canvasBase">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Pane prefHeight="200.0" prefWidth="400.0" GridPane.halignment="LEFT" GridPane.valignment="TOP">
<children>
<Accordion>
<panes>
<TitledPane animated="false" prefHeight="395.0" prefWidth="222.0" text="Annotation Control">
<content>
<AnchorPane fx:id="annotationPane" minHeight="0.0" minWidth="0.0">
<children>
<CheckBox fx:id="showName" layoutY="39.0" mnemonicParsing="false" selected="true" text="Show Boat Name" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
<CheckBox fx:id="showAbbrev" layoutY="61.0" mnemonicParsing="false" selected="true" text="Show Boat Abbreviation" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="25.0" />
<CheckBox fx:id="showSpeed" layoutY="90.0" mnemonicParsing="false" selected="true" text="Show Boat Speed" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="50.0" />
<CheckBox fx:id="showBoatPath" mnemonicParsing="false" selected="true" text="Show Boat Paths" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="75.0" />
<CheckBox fx:id="showTime" mnemonicParsing="false" selected="true" text="Show Boat Leg Time" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="100.0" />
<CheckBox fx:id="showEstTime" mnemonicParsing="false" selected="true" text="Show Est. Time to Next Mark" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="125.0" />
<CheckBox fx:id="showGuideline" mnemonicParsing="false" text="Show Guideline" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="150.0" />
<Separator layoutX="19.6" layoutY="175.6" prefHeight="0.0" prefWidth="200.0" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="175.0" />
<Label text="Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="175.0" />
<RadioButton fx:id="hideAnnoRBtn" mnemonicParsing="false" text="Hidden" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="200.0">
<toggleGroup>
<ToggleGroup fx:id="annoToggleGroup" />
</toggleGroup></RadioButton>
<RadioButton fx:id="showAnnoRBtn" mnemonicParsing="false" text="Visible" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="225.0" />
<RadioButton fx:id="partialAnnoRBtn" mnemonicParsing="false" text="Partial" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="250.0" />
<RadioButton fx:id="importantAnnoRBtn" mnemonicParsing="false" text="Important" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="275.0" />
<Button fx:id="saveAnno" layoutX="11.0" layoutY="126.0" mnemonicParsing="false" text="Save Important Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="300.0" />
</children>
</AnchorPane>
</content>
</TitledPane>
<TitledPane animated="false" text="FPS Control">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<CheckBox fx:id="showFPS" layoutX="-14.0" layoutY="13.0" mnemonicParsing="false" selected="true" text="Show FPS" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>
</TitledPane>
</panes>
</Accordion>
</children>
</Pane>
<Label fx:id="timer" layoutX="45.0" layoutY="146.0" maxHeight="20.0" text="0:0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" GridPane.halignment="RIGHT" GridPane.valignment="BOTTOM">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="FPS" text="FPS: 0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" GridPane.halignment="LEFT" GridPane.valignment="BOTTOM">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="timeZone" text="Label" GridPane.halignment="RIGHT" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="20.0" />
</GridPane.margin>
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<StackPane fx:id="arrowPane" alignment="TOP_RIGHT" mouseTransparent="true" prefHeight="150.0" prefWidth="150.0" snapToPixel="false">
<children>
<fx:include fx:id="arrow" source="arrow.fxml" />
</children>
</StackPane>
</children>
</GridPane>
<AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0" GridPane.columnIndex="1">
<children>
<TableView fx:id="boatInfoTable" layoutX="-2.0" prefHeight="265.0" prefWidth="242.0" AnchorPane.bottomAnchor="164.0" AnchorPane.leftAnchor="-2.0" AnchorPane.rightAnchor="-62.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn fx:id="boatPlacingColumn" prefWidth="50.0" text="Place" />
<TableColumn fx:id="boatTeamColumn" prefWidth="200.0" text="Team" />
<TableColumn fx:id="boatMarkColumn" prefWidth="150.0" text="Mark" />
<TableColumn fx:id="boatSpeedColumn" prefWidth="75.0" text="Speed" />
</columns>
</TableView>
<AnchorPane layoutY="265.0" prefHeight="167.0" prefWidth="178.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<children>
<LineChart fx:id="sparklineChart" layoutX="-211.0" layoutY="-186.0" mouseTransparent="true" prefHeight="167.0" prefWidth="178.0" titleSide="LEFT" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<xAxis>
<NumberAxis side="BOTTOM" fx:id="xAxis" />
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" side="LEFT" />
</yAxis>
</LineChart>
</children>
</AnchorPane>
</children>
</AnchorPane>
</items>
</SplitPane>

@ -1,9 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="finishWrapper" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" visible="false" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.FinishController">
<AnchorPane fx:id="finishWrapper" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.RaceFinishController">
<children>
<GridPane fx:id="start" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>

@ -1,9 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="startWrapper" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" visible="false" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.StartController">
<AnchorPane fx:id="startWrapper" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.RaceStartController">
<children>
<GridPane fx:id="start" prefHeight="600.0" prefWidth="780.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.chart.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Accordion?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.text.Font?>
<SplitPane fx:id="racePane" dividerPositions="1.0" prefHeight="431.0"
prefWidth="610.0" visible="true" AnchorPane.bottomAnchor="0.0"
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8.0.65"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.RaceViewController">
<items>
<StackPane fx:id="newPane" prefHeight="150.0" prefWidth="200.0">
<children>
<AnchorPane>
<children>
<GridPane fx:id="canvasBase" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
<Pane prefHeight="200.0" prefWidth="400.0" visible="false">
<children>
<Accordion>
<panes>
<TitledPane animated="false" prefHeight="395.0" prefWidth="222.0" text="Annotation Control">
<content>
<AnchorPane fx:id="annotationPane" minHeight="0.0" minWidth="0.0">
<children>
<CheckBox fx:id="showName" layoutY="39.0" mnemonicParsing="false" selected="true" text="Show Boat Name" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
<CheckBox fx:id="showAbbrev" layoutY="61.0" mnemonicParsing="false" selected="true" text="Show Boat Abbreviation" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="25.0" />
<CheckBox fx:id="showSpeed" layoutY="90.0" mnemonicParsing="false" selected="true" text="Show Boat Speed" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="50.0" />
<CheckBox fx:id="showBoatPath" mnemonicParsing="false" selected="true" text="Show Boat Paths" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="75.0" />
<CheckBox fx:id="showTime" mnemonicParsing="false" selected="true" text="Show Boat Leg Time" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="100.0" />
<CheckBox fx:id="showEstTime" mnemonicParsing="false" selected="true" text="Show Est. Time to Next Mark" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="125.0" />
<CheckBox fx:id="showGuideline" mnemonicParsing="false" text="Show Guideline" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="150.0" />
<Separator layoutX="19.6" layoutY="175.6" prefHeight="0.0" prefWidth="200.0" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="175.0" />
<Label text="Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="175.0" />
<RadioButton fx:id="hideAnnoRBtn" mnemonicParsing="false" text="Hidden" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="200.0">
<toggleGroup>
<ToggleGroup fx:id="annoToggleGroup" />
</toggleGroup>
</RadioButton>
<RadioButton fx:id="showAnnoRBtn" mnemonicParsing="false" text="Visible" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="225.0" />
<RadioButton fx:id="partialAnnoRBtn" mnemonicParsing="false" text="Partial" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="250.0" />
<RadioButton fx:id="importantAnnoRBtn" mnemonicParsing="false" text="Important" toggleGroup="$annoToggleGroup" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="275.0" />
<Button fx:id="saveAnno" layoutX="11.0" layoutY="126.0" mnemonicParsing="false" text="Save Important Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="300.0" />
</children>
</AnchorPane>
</content>
</TitledPane>
<TitledPane animated="false" text="FPS Control">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<CheckBox fx:id="showFPS" layoutX="-14.0" layoutY="13.0" mnemonicParsing="false" selected="true" text="Show FPS" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>
</TitledPane>
</panes>
</Accordion>
</children>
</Pane>
<Label fx:id="timer" maxHeight="20.0" text="0:0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="FPS" text="FPS: 0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="timeZone" text="Label" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0">
<font>
<Font name="System Bold" size="15.0" />
</font>
<padding>
<Insets bottom="20.0" />
</padding>
</Label>
<StackPane fx:id="arrowPane" alignment="TOP_RIGHT" mouseTransparent="true" prefHeight="150.0" prefWidth="150.0" snapToPixel="false" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<fx:include fx:id="arrow" source="arrow.fxml" />
</children>
</StackPane>
<Label fx:id="tutorialText" alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" style="-fx-border-color: orange; -fx-border-radius: 5px; -fx-background-color: #ffffcc; -fx-text-fill: #3399ff; -fx-border-width: 3; -fx-border-insets: -3;" text="This is the tutorial text" visible="false" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="150.0" AnchorPane.topAnchor="100.0" />
</children>
</AnchorPane>
</children>
</StackPane>
<AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0" GridPane.columnIndex="1">
<children>
<TableView fx:id="boatInfoTable" layoutX="-2.0" prefHeight="265.0" prefWidth="242.0" AnchorPane.bottomAnchor="164.0" AnchorPane.leftAnchor="-2.0" AnchorPane.rightAnchor="-62.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn fx:id="boatPlacingColumn" prefWidth="50.0" text="Place" />
<TableColumn fx:id="boatTeamColumn" prefWidth="200.0" text="Team" />
<TableColumn fx:id="boatMarkColumn" prefWidth="150.0" text="Mark" />
<TableColumn fx:id="boatSpeedColumn" prefWidth="75.0" text="Speed" />
</columns>
</TableView>
<AnchorPane layoutY="265.0" prefHeight="167.0" prefWidth="178.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<children>
<LineChart fx:id="sparklineChart" layoutX="-211.0" layoutY="-186.0" mouseTransparent="true" prefHeight="167.0" prefWidth="178.0" titleSide="LEFT" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<xAxis>
<NumberAxis side="BOTTOM" fx:id="xAxis" />
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" side="LEFT" />
</yAxis>
</LineChart>
</children>
</AnchorPane>
</children>
</AnchorPane>
</items>
</SplitPane>

@ -1,61 +1,94 @@
<!--<?xml version="1.0" encoding="UTF-8"?>-->
<?xml version="1.0" encoding="UTF-8"?>
<!--<?import javafx.geometry.*?>-->
<!--<?import javafx.scene.control.*?>-->
<!--<?import javafx.scene.layout.*?>-->
<!--<?import javafx.scene.text.Font?>-->
<!--<AnchorPane fx:id="connectionWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.ConnectionController">-->
<!--<children>-->
<!--<GridPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">-->
<!--<columnConstraints>-->
<!--<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />-->
<!--<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />-->
<!--<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />-->
<!--</columnConstraints>-->
<!--<rowConstraints>-->
<!--<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />-->
<!--<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />-->
<!--<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />-->
<!--</rowConstraints>-->
<!--<children>-->
<!--<Button fx:id="hostGameTitleBtn" maxWidth="204.0" mnemonicParsing="false" text="Host Game" GridPane.halignment="LEFT" GridPane.rowIndex="1">-->
<!--<font>-->
<!--<Font size="20.0" />-->
<!--</font>-->
<!--<GridPane.margin>-->
<!--<Insets left="50.0" />-->
<!--</GridPane.margin>-->
<!--</Button>-->
<!--<Button fx:id="connectGameBtn" maxWidth="204.0" mnemonicParsing="false" text="Connect to Game" GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="1">-->
<!--<font>-->
<!--<Font size="20.0" />-->
<!--</font>-->
<!--<GridPane.margin>-->
<!--<Insets right="50.0" />-->
<!--</GridPane.margin>-->
<!--</Button>-->
<!--<RadioButton fx:id="nightRadioBtn" mnemonicParsing="false" text="Night Mode" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowIndex="2">-->
<!--<padding>-->
<!--<Insets bottom="-50.0" />-->
<!--</padding>-->
<!--<GridPane.margin>-->
<!--<Insets left="80.0" />-->
<!--</GridPane.margin>-->
<!--</RadioButton>-->
<!--<RadioButton fx:id="dayRadioBtn" mnemonicParsing="false" text="Day Mode" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowIndex="2">-->
<!--<padding>-->
<!--<Insets top="-50.0" />-->
<!--</padding>-->
<!--<GridPane.margin>-->
<!--<Insets left="80.0" />-->
<!--</GridPane.margin>-->
<!--</RadioButton>-->
<!--<Label text="Game" textAlignment="CENTER" GridPane.columnIndex="1" GridPane.halignment="CENTER">-->
<!--<font>-->
<!--<Font size="60.0" />-->
<!--</font>-->
<!--</Label>-->
<!--</children>-->
<!--</GridPane>-->
<!--</children>-->
<!--</AnchorPane>-->
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<AnchorPane fx:id="titleWrapper" maxHeight="600.0" maxWidth="800.0" minHeight="600.0" minWidth="800.0" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.TitleController">
<children>
<GridPane layoutY="39.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="800.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Pane prefHeight="20.0" prefWidth="20.0" style="-fx-background-color: #0061ff;" GridPane.columnSpan="4" GridPane.rowIndex="4" GridPane.rowSpan="2">
<children>
<Text fx:id="txtTitle" layoutX="167.0" layoutY="136.0" strokeType="OUTSIDE" strokeWidth="0.0" text="The Boat Game!">
<font>
<Font name="Comic Sans MS" size="64.0" />
</font>
</Text>
<Text layoutX="690.0" layoutY="80.0" strokeType="OUTSIDE" strokeWidth="0.0" text="TM">
<font>
<Font name="Comic Sans MS" size="12.0" />
</font>
</Text>
<RadioButton fx:id="nightModeRD" layoutX="681.0" layoutY="168.0" mnemonicParsing="false" onAction="#setNightMode" text="Night Mode" />
<RadioButton fx:id="dayModeRD" layoutX="574.0" layoutY="168.0" mnemonicParsing="false" onAction="#setDayMode" selected="true" text="Day Mode" />
<Button layoutX="28.0" layoutY="152.0" mnemonicParsing="false" onAction="#showControls" text="Controls" />
</children>
</Pane>
<Pane fx:id="menuPane" prefHeight="20.0" prefWidth="20.0" style="-fx-background-color: #6be6ff;" GridPane.columnSpan="4" GridPane.rowSpan="4">
<children>
<ImageView fx:id="imgBoat" fitHeight="404.0" fitWidth="296.0" layoutX="268.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/boat.png" />
</image>
</ImageView>
<ImageView fx:id="imgCloud1" fitHeight="291.0" fitWidth="307.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/cloud.png" />
</image>
</ImageView>
<ImageView fx:id="imgWhale" fitHeight="113.0" fitWidth="98.0" layoutX="69.0" layoutY="302.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/whale.png" />
</image>
</ImageView>
<ImageView fx:id="imgCloud2" fitHeight="291.0" fitWidth="307.0" layoutX="501.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/cloud.png" />
</image>
</ImageView>
<ImageView fx:id="imgSun" fitHeight="154.0" fitWidth="145.0" layoutX="701.0" layoutY="-39.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/sun.png" />
</image>
</ImageView>
<Button fx:id="btnJoin" layoutX="78.0" layoutY="149.0" mnemonicParsing="false" onAction="#joinAGame" prefHeight="31.0" prefWidth="130.0" text="Join a Game">
<font>
<Font name="Comic Sans MS Bold" size="16.0" />
</font>
</Button>
<Button layoutX="578.0" layoutY="150.0" mnemonicParsing="false" onAction="#hostAGame" prefHeight="31.0" prefWidth="130.0" text="Host a Game">
<font>
<Font name="Comic Sans MS Bold" size="16.0" />
</font>
</Button>
<Label fx:id="tutorialLabel" alignment="CENTER" layoutX="94.0" layoutY="223.0" onMouseClicked="#tutorialStartPressed" prefHeight="167.0" prefWidth="206.0" style="-fx-shape: &quot;M 45.673,0 C 67.781,0 85.703,12.475 85.703,27.862 C 85.703,43.249 67.781,55.724 45.673,55.724 C 38.742,55.724 32.224,54.497 26.539,52.34 C 15.319,56.564 0,64.542 0,64.542 C 0,64.542 9.989,58.887 14.107,52.021 C 15.159,50.266 15.775,48.426 16.128,46.659 C 9.618,41.704 5.643,35.106 5.643,27.862 C 5.643,12.475 23.565,0 45.673,0 M 45.673,2.22 C 24.824,2.22 7.862,13.723 7.862,27.863 C 7.862,34.129 11.275,40.177 17.472,44.893 L 18.576,45.734 L 18.305,47.094 C 17.86,49.324 17.088,51.366 16.011,53.163 C 15.67,53.73 15.294,54.29 14.891,54.837 C 18.516,53.191 22.312,51.561 25.757,50.264 L 26.542,49.968 L 27.327,50.266 C 32.911,52.385 39.255,53.505 45.673,53.505 C 66.522,53.505 83.484,42.002 83.484,27.862 C 83.484,13.722 66.522,2.22 45.673,2.22 L 45.673,2.22 z &quot;; -fx-background-color: black, white; -fx-background-insets: 0,1; -fx-padding: 50;" text="How do you play this game? Click here!" textAlignment="CENTER" wrapText="true" />
</children>
</Pane>
</children>
</GridPane>
</children>
</AnchorPane>

@ -1,85 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<AnchorPane fx:id="titleWrapper" maxHeight="600.0" maxWidth="800.0" minHeight="600.0" minWidth="800.0" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.TitleController">
<children>
<GridPane layoutY="39.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="800.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Pane prefHeight="20.0" prefWidth="20.0" style="-fx-background-color: #0061ff;" GridPane.columnSpan="4" GridPane.rowIndex="4" GridPane.rowSpan="2">
<children>
<Text fx:id="txtTitle" layoutX="167.0" layoutY="136.0" strokeType="OUTSIDE" strokeWidth="0.0" text="The Boat Game!">
<font>
<Font name="Comic Sans MS" size="64.0" />
</font>
</Text>
<Text layoutX="690.0" layoutY="80.0" strokeType="OUTSIDE" strokeWidth="0.0" text="TM">
<font>
<Font name="Comic Sans MS" size="12.0" />
</font>
</Text>
<RadioButton fx:id="nightModeRD" layoutX="681.0" layoutY="168.0" mnemonicParsing="false" onAction="#setNightMode" text="Night Mode" />
<RadioButton fx:id="dayModeRD" layoutX="574.0" layoutY="168.0" mnemonicParsing="false" onAction="#setDayMode" selected="true" text="Day Mode" />
<Button layoutX="28.0" layoutY="152.0" mnemonicParsing="false" onAction="#controlBtnPressed" text="Controls" />
</children>
</Pane>
<Pane prefHeight="20.0" prefWidth="20.0" style="-fx-background-color: #6be6ff;" GridPane.columnSpan="4" GridPane.rowSpan="4">
<children>
<ImageView fx:id="imgBoat" fitHeight="404.0" fitWidth="296.0" layoutX="268.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/boat.png" />
</image>
</ImageView>
<ImageView fx:id="imgCloud1" fitHeight="291.0" fitWidth="307.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/cloud.png" />
</image>
</ImageView>
<ImageView fx:id="imgWhale" fitHeight="113.0" fitWidth="98.0" layoutX="69.0" layoutY="302.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/whale.png" />
</image>
</ImageView>
<ImageView fx:id="imgCloud2" fitHeight="291.0" fitWidth="307.0" layoutX="501.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/cloud.png" />
</image>
</ImageView>
<ImageView fx:id="imgSun" fitHeight="154.0" fitWidth="145.0" layoutX="701.0" layoutY="-39.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/sun.png" />
</image>
</ImageView>
<Button fx:id="btnJoin" layoutX="78.0" layoutY="149.0" mnemonicParsing="false" onAction="#joinAGame" prefHeight="31.0" prefWidth="130.0" text="Join a Game">
<font>
<Font name="Comic Sans MS Bold" size="16.0" />
</font>
</Button>
<Button layoutX="578.0" layoutY="150.0" mnemonicParsing="false" onAction="#hostAGame" prefHeight="31.0" prefWidth="130.0" text="Host a Game">
<font>
<Font name="Comic Sans MS Bold" size="16.0" />
</font>
</Button>
</children>
</Pane>
</children>
</GridPane>
</children>
</AnchorPane>

@ -1,47 +0,0 @@
//package seng302.Mock;
//
//import org.junit.Before;
//import org.junit.Test;
//
//import java.util.HashMap;
//import java.util.Map;
//
//import static org.junit.Assert.assertEquals;
//
///**
// * Created by jjg64 on 21/04/17.
// */
//public class BoatsXMLTest {
// private BoatXMLReader boatXMLReader;
//
// @Before
// public void setup() {
// try {
// boatXMLReader = new BoatXMLReader("mockXML/boatXML/boatTest.xml", false);
// } catch (Exception e) {
// e.printStackTrace();
// //fail("Cannot find mockXML/raceXML/raceTest.xml in the resources folder");
// }
// }
//
// @Test
// public void testInvalidParticipant() {
// Map<Integer, StreamedBoat> inputParticipants = new HashMap<>();
// inputParticipants.put(420, new StreamedBoat(420));
// boatXMLReader.setParticipants(inputParticipants);
// boatXMLReader.read();
// assertEquals(boatXMLReader.getBoats().size(), 0);
// }
//
// @Test
// public void testValidParticipant() {
// Map<Integer, StreamedBoat> inputParticipants = new HashMap<>();
// inputParticipants.put(101, new StreamedBoat(101));
// boatXMLReader.setParticipants(inputParticipants);
// boatXMLReader.read();
// assertEquals(boatXMLReader.getBoats().size(), 1);
// StreamedBoat boat = (StreamedBoat) boatXMLReader.getBoats().get(0);
// assertEquals(boat.getSourceID(), 101);
// }
//
//}

@ -1,26 +0,0 @@
//package seng302.Mock;
//
//import org.junit.Test;
//import org.xml.sax.SAXException;
//
//import javax.xml.parsers.ParserConfigurationException;
//import java.io.IOException;
//import java.text.ParseException;
//
///**
// * Created by jjg64 on 1/05/17.
// */
//public class FailBoatXMLTest {
// private final String path = "mockXML/boatXML/";
//
// @Test(expected = NumberFormatException.class)
// public void invalidSourceID() throws SAXException, ParserConfigurationException, ParseException, IOException {
// new BoatXMLReader(path + "invalidSourceID.xml");
// }
//
// @Test(expected = NullPointerException.class)
// public void insufficientInformation() throws SAXException, ParserConfigurationException, ParseException, IOException {
// new BoatXMLReader(path + "insufficientInformation.xml");
// }
//
//}

@ -1,345 +0,0 @@
//package shared.model;
//
//
//import mock.model.Polars;
//import org.junit.Before;
//import org.junit.Ignore;
//import org.junit.Test;
//import org.mockito.Mockito;
//import org.xml.sax.SAXException;
//
//import javax.xml.parsers.ParserConfigurationException;
//import java.io.IOException;
//import java.text.ParseException;
//import java.util.ArrayList;
//
//import static org.junit.Assert.*;
//import static org.mockito.Mockito.*;
//
///**
// * Created by esa46 on 15/03/17.
// */
//public class RaceTest{
//
//
// private CompoundMark ORIGIN;
//
// private CompoundMark THREE_NM_FROM_ORIGIN;
//
// private CompoundMark FIFTEEN_NM_FROM_ORIGIN;
//
// private ArrayList<Leg> TEST_LEGS = new ArrayList<>();
//
// private int START_LEG_DISTANCE = 3;
// private int MIDDLE_LEG_DISTANCE = 12;
//
// private Leg START_LEG;
//
// private Leg MIDDLE_LEG;
//
// private Leg FINISH_LEG;
//
//
// @Before
// public void setUp() {
//
// ORIGIN = new CompoundMark(
// 1,
// "origin compound",
// new Mark(1, "test origin 1", new GPSCoordinate(0, 0)) );
//
//
// THREE_NM_FROM_ORIGIN = new CompoundMark(
// 2,
// "3 NM from origin compound",
// new Mark(2, "test mark 2", new GPSCoordinate(0.050246769, 0)) );
//
//
// FIFTEEN_NM_FROM_ORIGIN = new CompoundMark(
// 3,
// "15 NM from origin compound",
// new Mark(3, "test mark 3", new GPSCoordinate(0.251233845, 0)) );
//
//
// START_LEG = new Leg("Start", ORIGIN, THREE_NM_FROM_ORIGIN, 0);
//
//
// MIDDLE_LEG = new Leg("Middle", THREE_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 1);
//
//
// FINISH_LEG = new Leg("Finish", FIFTEEN_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 2);
//
//
// TEST_LEGS.add(START_LEG);
// TEST_LEGS.add(MIDDLE_LEG);
// TEST_LEGS.add(FINISH_LEG);
// }
//
// @Ignore
// @Test
// public void countdownTimerSendsBoatLocations() {
//
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
// Race testRace = new Race(raceDataSource, mockOutput);
// testRace.initialiseBoats();
// testRace.countdownTimer.handle(1);
// verify(mockOutput, atLeast(boatDataSource.getBoats().size())).parseBoatLocation(anyInt(), anyDouble(), anyDouble(), anyDouble(), anyDouble(), anyLong());
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
// @Ignore
// @Test
// public void countdownTimerSendsRaceStatusMessages() {
//
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
// Race testRace = new Race(dataSource, mockOutput);
// testRace.initialiseBoats();
// testRace.countdownTimer.handle(1);
// verify(mockOutput, atLeast(1)).parseRaceStatus(any());
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
// @Ignore
// @Test
// public void checkPositionFinishedUpdatesNumberFinishedBoats() {
//
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
// Race testRace = new Race(dataSource, mockOutput);
// testRace.initialiseBoats();
// Boat testBoat = testRace.getBoats().get(0);
// testBoat.setCurrentLeg(FINISH_LEG);
// testBoat.setDistanceTravelledInLeg(1);
// testRace.checkPosition(testBoat, 1);
//
// assertEquals(testRace.getNumberOfActiveBoats(), 0);
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
// @Ignore
// @Test
// public void checkPositionSetFinishedBoatVelocityTo0() {
//
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
// Race testRace = new Race(dataSource, mockOutput);
// testRace.initialiseBoats();
// Boat testBoat = testRace.getBoats().get(0);
// testBoat.setCurrentLeg(FINISH_LEG);
// testBoat.setDistanceTravelledInLeg(1);
// testRace.checkPosition(testBoat, 1);
//
// assertEquals(testBoat.getCurrentSpeed(), 0, 1e-8);
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
// @Ignore
// @Test
// public void checkPositionSetsFinishTime() {
//
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
// Race testRace = new Race(dataSource, mockOutput);
// testRace.initialiseBoats();
// Boat testBoat = testRace.getBoats().get(0);
// testBoat.setCurrentLeg(FINISH_LEG);
// testBoat.setDistanceTravelledInLeg(1);
// testRace.checkPosition(testBoat, 1);
//
// assertEquals(testBoat.getTimeFinished(), 1, 1e-8);
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
// @Ignore
// @Test
// public void checkPositionUnfinishedDoesntUpdateNumberFinishedBoats() {
//
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
// Race testRace = new Race(dataSource, mockOutput);
// testRace.initialiseBoats();
// Boat testBoat = testRace.getBoats().get(0);
// testBoat.setCurrentLeg(START_LEG);
// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE);
// testRace.checkPosition(testBoat, 1);
//
// assertEquals(testRace.getNumberOfActiveBoats(), 1);
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
//
// @Ignore
// @Test
// public void distanceTravelledBeforeUpdatingLegIsRetained() {
//
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars()));
// Race testRace = new Race(dataSource, mockOutput);
// testRace.initialiseBoats();
// Boat testBoat = testRace.getBoats().get(0);
// testBoat.setCurrentLeg(START_LEG);
// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1);
// testRace.checkPosition(testBoat, 0);
//
// assertEquals(testBoat.getDistanceTravelledInLeg(), 1, 1e-7);
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
// @Ignore
// @Test
// public void doNotFinishAnswersYesIf100PercentChance() {
//
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
// Race testRace = new Race(raceDataSource, mockOutput);
//
// testRace.setDnfChance(100);
// assertTrue(testRace.doNotFinish());
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
// @Ignore
// @Test
// public void doNotFinishAnswersNoIf0PercentChance() {
//
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
// Race testRace = new Race(raceDataSource, mockOutput);
// testRace.setDnfChance(0);
// assertFalse(testRace.doNotFinish());
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
// @Ignore
// @Test
// public void boatsAreSetToDNF() {
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
// Race testRace = new Race(raceDataSource, mockOutput);
// testRace.setDnfChance(100);
// Boat testBoat = testRace.getBoats().get(0);
// testBoat.setCurrentLeg(START_LEG);
// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1);
// testRace.checkPosition(testBoat, 1);
// assertEquals(testBoat.getCurrentLeg().getName(), "DNF");
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
//
// }
//
// @Ignore
// @Test
// public void updatePositionIgnoresFinishedBoats() {
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
// Race testRace = new Race(raceDataSource, mockOutput);
// Boat testBoat = testRace.getBoats().get(0);
// testBoat.setCurrentLeg(FINISH_LEG);
// testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate());
// testRace.updatePosition(testBoat, 1, 1);
// assertEquals(testBoat.getCurrentPosition(), ORIGIN.getAverageGPSCoordinate());
//
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
// @Ignore
// @Test
// public void updatePositionChangesBoatPosition() {
// try {
// MockOutput mockOutput = Mockito.mock(MockOutput.class);
// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars());
// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
// Race testRace = new Race(raceDataSource, mockOutput);
// testRace.initialiseBoats();
// Boat testBoat = testRace.getBoats().get(0);
// testBoat.setCurrentLeg(START_LEG);
// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE - 1);
// testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate());
// testRace.updatePosition(testBoat, 100, 100);
// assertFalse(testBoat.getCurrentPosition() == ORIGIN.getAverageGPSCoordinate());
// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
// e.printStackTrace();
// fail();
// }
// }
//
// @Ignore
// @Test
// public void windDirectionCorrectValues(){
//// try {
//// MockOutput mockOutput = Mockito.mock(MockOutput.class);
//// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml");
//// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource);
//// Race testRace = new Race(raceDataSource, mockOutput);
//// testRace.setChangeWind(1);
//// testRace.setWindDir(65535);
//// testRace.changeWindDir();
//// assertEquals(100, testRace.getWind());
//// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) {
//// e.printStackTrace();
//// fail();
//// }
// }
//
//
//}

@ -1,100 +0,0 @@
//package visualiser.model;
//
//
//import org.junit.Before;
//import org.junit.Test;
//import shared.dataInput.RaceXMLReader;
//import shared.dataInput.XMLReader;
//import shared.exceptions.InvalidRaceDataException;
//import shared.model.GPSCoordinate;
//
//import java.nio.charset.StandardCharsets;
//import java.util.List;
//
//import static org.junit.Assert.assertEquals;
//import static org.junit.Assert.fail;
//
///**
// * Tests only work on the current version of mockXML/raceXML/raceTest.xml
// */
//public class VisualiserRaceTest {
// private RaceXMLReader streamedCourseXMLReader;
// private List<GPSCoordinate> boundary;
//
// @Before
// public void setup() {
// try {
// streamedCourseXMLReader = new RaceXMLReader(XMLReader.readXMLFileToString("mockXML/raceXML/raceTest.xml", StandardCharsets.UTF_8));
// boundary = streamedCourseXMLReader.getBoundary();
// } catch (InvalidRaceDataException e) {
// e.printStackTrace();
// fail("Cannot find mockXML/raceXML/raceTest.xml in the resources folder");
// }
// }
//
// @Test
// public void testAllBoundaryPointsRead() {
// assertEquals(boundary.size(), 10);
// }
//
// @Test
// public void testBoundaryPointData() {
// // First point
// assertEquals(boundary.get(0).getLatitude(), -36.8325, 1e-6);
// assertEquals(boundary.get(0).getLongitude(), 174.8325, 1e-6);
//
// // Last point
// assertEquals(boundary.get(boundary.size() - 1).getLatitude(), -36.83417, 1e-6);
// assertEquals(boundary.get(boundary.size() - 1).getLongitude(), 174.84767, 1e-6);
// }
//
// @Test
// public void testMapEdges() {
// double maxLatitude = streamedCourseXMLReader.getMapBottomRight().getLatitude() - streamedCourseXMLReader.getPadding();
// double maxLongitude = streamedCourseXMLReader.getMapBottomRight().getLongitude() - streamedCourseXMLReader.getPadding();
// double minLatitude = streamedCourseXMLReader.getMapTopLeft().getLatitude() - streamedCourseXMLReader.getPadding();
// double minLongitude = streamedCourseXMLReader.getMapTopLeft().getLongitude() - streamedCourseXMLReader.getPadding();
//
// assertEquals(maxLatitude, -36.81033, 1e-6);
// assertEquals(maxLongitude, 174.88217, 1e-6);
// assertEquals(minLatitude, -36.83417, 1e-6);
// assertEquals(minLongitude, 174.81983, 1e-6);
// }
//
// @Test
// public void testRaceSettings() {
//
// }
//
// @Test
// public void correctLegSequence() {
// List<Leg> legs = streamedCourseXMLReader.getLegs();
// String[] expectedNames = {
// "StartLine",
// "M1",
// "M2",
// "Gate"
// };
// for(int i = 0; i < legs.size(); i++) {
// assertEquals(expectedNames[i], legs.get(i).getName());
// }
// }
//
// /**
// * raceTest.xml is not compliant with this test. Markers are positioned far out of bounds.
// */
// @Test
// @Ignore
// public void markersWithinRaceBoundaries() {
// GPSCoordinate topLeft = streamedCourseXMLReader.getMapTopLeft();
// GPSCoordinate bottomRight = streamedCourseXMLReader.getMapBottomRight();
//
// for(Marker compoundMark : streamedCourseXMLReader.getMarkers()) {
// GPSCoordinate centre = compoundMark.getAverageGPSCoordinate();
// assertTrue(centre.getLatitude() < bottomRight.getLatitude());
// assertTrue(centre.getLatitude() > topLeft.getLatitude());
// assertTrue(centre.getLongitude() > bottomRight.getLongitude());
// assertTrue(centre.getLongitude() < topLeft.getLongitude());
// }
// }
//}

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_111" class="java.beans.XMLDecoder">
<object class="java.util.HashMap">
<void method="put">
<string>SPACE</string>
<object class="visualiser.gameController.Keys.VMGKey"/>
</void>
<void method="put">
<string>SHIFT</string>
<object class="visualiser.gameController.Keys.SailsToggleKey"/>
</void>
<void method="put">
<string>DOWN</string>
<object class="visualiser.gameController.Keys.DownWindKey"/>
</void>
<void method="put">
<string>X</string>
<object class="visualiser.gameController.Keys.ZoomOutKey"/>
</void>
<void method="put">
<string>ENTER</string>
<object class="visualiser.gameController.Keys.TackGybeKey"/>
</void>
<void method="put">
<string>Z</string>
<object class="visualiser.gameController.Keys.ZoomInKey"/>
</void>
<void method="put">
<string>UP</string>
<object class="visualiser.gameController.Keys.UpWindKey"/>
</void>
</object>
</java>
Loading…
Cancel
Save