Merge branch 'master' into storyA_sounds

# Conflicts:
#	racevisionGame/src/main/java/mock/model/RaceServer.java
#	racevisionGame/src/main/java/visualiser/Controllers/HostController.java
#	racevisionGame/src/main/java/visualiser/Controllers/MainController.java
#	racevisionGame/src/main/java/visualiser/Controllers/TitleController.java
main
Connor Taylor-Brown 8 years ago
commit d90b161d01

@ -106,6 +106,9 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId> <artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.8.1</version> <version>2.8.1</version>
<configuration>
<dependencyLocationsEnabled>false</dependencyLocationsEnabled>
</configuration>
</plugin> </plugin>
</plugins> </plugins>
</reporting> </reporting>

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

@ -14,4 +14,12 @@
<url>https://eng-git.canterbury.ac.nz/SENG302-2016/team-7</url> <url>https://eng-git.canterbury.ac.nz/SENG302-2016/team-7</url>
<repositories>
<repository>
<id>central</id>
<name>Maven Central</name>
<url>http://repo1.maven.org/maven2/</url>
</repository>
</repositories>
</project> </project>

@ -25,10 +25,30 @@
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all --> <!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId> <artifactId>mockito-core</artifactId>
<version>1.9.5</version> <version>2.9.0</version>
</dependency> </dependency>
<!-- Maven Dependencies block START-->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.7.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.7.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>2.6</version>
<scope>runtime</scope>
</dependency>
<!-- Maven Dependencies block END-->
<dependency> <dependency>
@ -46,14 +66,17 @@
<version>15.0</version> <version>15.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.geotools</groupId> <groupId>org.geotools</groupId>
<artifactId>gt-referencing</artifactId> <artifactId>gt-referencing</artifactId>
<version>9.0</version> <version>9.0</version>
</dependency> </dependency>
<dependency>
<groupId>JavaInteractiveMesh</groupId>
<artifactId>STLImporter</artifactId>
<version>0.7</version>
</dependency>
</dependencies> </dependencies>
@ -72,7 +95,11 @@
<url>http://download.osgeo.org/webdav/geotools/</url> <url>http://download.osgeo.org/webdav/geotools/</url>
</repository> </repository>
<repository>
<id>interactivemesh</id>
<name>Interactive Mesh</name>
<url>http://umbrasheep.com:8888/repository/internal/</url>
</repository>
</repositories> </repositories>
@ -163,6 +190,9 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId> <artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.8.1</version> <version>2.8.1</version>
<configuration>
<dependencyLocationsEnabled>false</dependencyLocationsEnabled>
</configuration>
</plugin> </plugin>
</plugins> </plugins>
</reporting> </reporting>

@ -61,12 +61,7 @@ public class ConnectionAcceptor implements Runnable {
*/ */
private SourceIdAllocator sourceIdAllocator; private SourceIdAllocator sourceIdAllocator;
//race xml sequence number
private short raceXMLSequenceNumber;
//boat xml sequence number
private short boatXMLSequenceNumber;
//regatta xml sequence number
private short regattaXMLSequenceNumber;
// //
private RaceLogic raceLogic = null; private RaceLogic raceLogic = null;
@ -210,76 +205,5 @@ public class ConnectionAcceptor implements Runnable {
} }
} }
/**
* Sets the Race XML to send.
* @param raceXml XML to send to the Client.
*/
public void setRaceXml(String raceXml) {
//Create the message.
XMLMessage message = this.createXMLMessage(raceXml, XMLMessageType.RACE);
//Place it in LatestMessages.
this.latestMessages.setRaceXMLMessage(message);
}
/**
* Sets the Regatta XMl to send.
* @param regattaXml XML to send to Client.
*/
public void setRegattaXml(String regattaXml) {
//Create the message.
XMLMessage message = this.createXMLMessage(regattaXml, XMLMessageType.REGATTA);
//Place it in LatestMessages.
this.latestMessages.setRegattaXMLMessage(message);
}
/**
* Sets the Boats XML to send.
* @param boatsXml XMl to send to the Client.
*/
public void setBoatsXml(String boatsXml) {
//Create the message.
XMLMessage message = this.createXMLMessage(boatsXml, XMLMessageType.BOAT);
//Place it in LatestMessages.
this.latestMessages.setBoatXMLMessage(message);
}
/**
* Creates an XMLMessage of a specified subtype using the xml contents string.
* @param xmlString The contents of the xml file.
* @param messageType The subtype of xml message (race, regatta, boat).
* @return The created XMLMessage object.
*/
private XMLMessage createXMLMessage(String xmlString, XMLMessageType messageType) {
//Get the correct sequence number to use, and increment it.
short sequenceNumber = 0;
if (messageType == XMLMessageType.RACE) {
sequenceNumber = this.raceXMLSequenceNumber;
this.raceXMLSequenceNumber++;
} else if (messageType == XMLMessageType.BOAT) {
sequenceNumber = this.boatXMLSequenceNumber;
this.boatXMLSequenceNumber++;
} else if (messageType == XMLMessageType.REGATTA) {
sequenceNumber = this.regattaXMLSequenceNumber;
this.regattaXMLSequenceNumber++;
}
//Create the message.
XMLMessage message = new XMLMessage(
XMLMessage.currentVersionNumber,
AckSequencer.getNextAckNum(),
System.currentTimeMillis(),
messageType,
sequenceNumber,
xmlString);
return message;
}
} }

@ -4,6 +4,9 @@ import mock.dataInput.PolarParser;
import mock.exceptions.EventConstructionException; import mock.exceptions.EventConstructionException;
import mock.model.*; import mock.model.*;
import mock.model.commandFactory.CompositeCommand; 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 mock.xml.RaceXMLCreator;
import network.Messages.LatestMessages; import network.Messages.LatestMessages;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@ -15,16 +18,16 @@ import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException; import shared.exceptions.XMLReaderException;
import shared.model.Bearing; import shared.model.Bearing;
import shared.model.Constants; import shared.model.Constants;
import shared.xml.XMLUtilities;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.IOException; import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -68,6 +71,8 @@ public class Event {
private Thread connectionThread; private Thread connectionThread;
private int mapIndex;
@ -77,11 +82,32 @@ public class Event {
* @param singlePlayer Whether or not to create a single player event. * @param singlePlayer Whether or not to create a single player event.
* @throws EventConstructionException Thrown if we cannot create an Event for any reason. * @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 boatsXMLFile = "mock/mockXML/boatTest.xml";
String regattaXMLFile = "mock/mockXML/regattaTest.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) { if (singlePlayer) {
raceXMLFile = "mock/mockXML/raceSinglePlayer.xml"; raceXMLFile = "mock/mockXML/raceSinglePlayer.xml";
@ -93,7 +119,9 @@ public class Event {
//this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90); //this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90);
this.raceXML = XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8); this.raceXML = XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8);
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8)); this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
if(mapIndex==4){
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
}
this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8); this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8); this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8);
@ -104,6 +132,7 @@ public class Event {
this.xmlFileType = XMLFileType.Contents; this.xmlFileType = XMLFileType.Contents;
this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv"); this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv");
PolarParser.parseNewPolars("mock/polars/acc_polars.csv");
//Parse the XML files into data sources. //Parse the XML files into data sources.
@ -118,18 +147,14 @@ public class Event {
} }
this.sourceIdAllocator = new SourceIdAllocator(raceDataSource.getParticipants());
this.compositeCommand = new CompositeCommand(); this.compositeCommand = new CompositeCommand();
this.latestMessages = new LatestMessages(); this.latestMessages = new LatestMessages();
//Create and start race. WindGenerator windGenerator = new ShiftingWindGenerator(
WindGenerator windGenerator = new RandomWindGenerator(
Bearing.fromDegrees(225), Bearing.fromDegrees(225),
Bearing.fromDegrees(215), 12
Bearing.fromDegrees(235), );
12d,
8d,
16d );
RaceLogic newRace = new RaceLogic( RaceLogic newRace = new RaceLogic(
new MockRace( new MockRace(
boatDataSource, boatDataSource,
@ -146,6 +171,7 @@ public class Event {
//Create connection acceptor. //Create connection acceptor.
this.sourceIdAllocator = new SourceIdAllocator(newRace.getRace());
try { try {
this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace); this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace);
@ -158,8 +184,6 @@ public class Event {
this.connectionThread = new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread"); this.connectionThread = new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread");
connectionThread.start(); connectionThread.start();
sendXMLs();
} }
@ -172,18 +196,6 @@ public class Event {
/**
* Sends out each xml string, via the mock output
*/
private void sendXMLs() {
connectionAcceptor.setRegattaXml(regattaXML);
connectionAcceptor.setRaceXml(raceXML);
connectionAcceptor.setBoatsXml(boatXML);
}
//TODO remove this after demo on 18th august! //TODO remove this after demo on 18th august!
/** /**
@ -192,9 +204,15 @@ public class Event {
* @return String containing edited xml * @return String containing edited xml
*/ */
public static String setRaceXMLAtCurrentTimeToNow(String raceXML) { 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. //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; long secondsToAdd = millisecondsToAdd / 1000;
//Scale the time using our time scalar. //Scale the time using our time scalar.
secondsToAdd = secondsToAdd / Constants.RaceTimeScale; secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
@ -204,7 +222,6 @@ public class Event {
raceXML = raceXML.replace("RACE_CREATION_TIME", dateFormat.format(creationTime)); raceXML = raceXML.replace("RACE_CREATION_TIME", dateFormat.format(creationTime));
raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd))); raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
return raceXML; return raceXML;
} }

@ -27,7 +27,12 @@ public class MockOutput implements RunnableWithFramePeriod {
*/ */
private LatestMessages latestMessages; private LatestMessages latestMessages;
//These sequence number track the last race/boat/regatta xml message we've sent.
private int lastSentRaceNumber = -1;
private int lastSentBoatNumber = -1;
private int lastSentRegattaNumber = -1;
@ -69,7 +74,6 @@ public class MockOutput implements RunnableWithFramePeriod {
long previousFrameTime = System.currentTimeMillis(); long previousFrameTime = System.currentTimeMillis();
boolean sentXMLs = false;
@ -82,16 +86,26 @@ public class MockOutput implements RunnableWithFramePeriod {
previousFrameTime = currentFrameTime; previousFrameTime = currentFrameTime;
//Send XML messages.
if (!sentXMLs) {
//Send XML messages if needed.
if (lastSentRaceNumber != latestMessages.getRaceXMLMessage().getSequenceNumber()) {
lastSentRaceNumber = latestMessages.getRaceXMLMessage().getSequenceNumber();
outgoingMessages.put(latestMessages.getRaceXMLMessage()); outgoingMessages.put(latestMessages.getRaceXMLMessage());
outgoingMessages.put(latestMessages.getRegattaXMLMessage()); }
if (lastSentBoatNumber != latestMessages.getBoatXMLMessage().getSequenceNumber()) {
lastSentBoatNumber = latestMessages.getBoatXMLMessage().getSequenceNumber();
outgoingMessages.put(latestMessages.getBoatXMLMessage()); outgoingMessages.put(latestMessages.getBoatXMLMessage());
}
sentXMLs = true; if (lastSentRegattaNumber != latestMessages.getRegattaXMLMessage().getSequenceNumber()) {
lastSentRegattaNumber = latestMessages.getRegattaXMLMessage().getSequenceNumber();
outgoingMessages.put(latestMessages.getRegattaXMLMessage());
} }
List<AC35Data> snapshot = latestMessages.getSnapshot(); List<AC35Data> snapshot = latestMessages.getSnapshot();
for (AC35Data message : snapshot) { for (AC35Data message : snapshot) {
outgoingMessages.put(message); outgoingMessages.put(message);

@ -3,6 +3,7 @@ package mock.dataInput;
import mock.exceptions.InvalidPolarFileException; import mock.exceptions.InvalidPolarFileException;
import mock.model.NewPolars;
import mock.model.Polars; import mock.model.Polars;
import shared.model.Bearing; import shared.model.Bearing;
@ -104,4 +105,90 @@ public class PolarParser {
return polarTable; return polarTable;
} }
/**
* Given a filename, this function parses it and generates a Polar object, which can be queried for polar information.
* @param filename The filename to load and read data from (loaded as a resource).
*/
public static void parseNewPolars(String filename) throws InvalidPolarFileException {
NewPolars newPolars = new NewPolars();
//Open the file for reading.
InputStream fileStream = PolarParser.class.getClassLoader().getResourceAsStream(filename);
if (fileStream == null) {
throw new InvalidPolarFileException("Could not open polar data file: " + filename);
}
//Wrap it with buffered input stream to set encoding and buffer.
InputStreamReader in = null;
try {
in = new InputStreamReader(fileStream, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new InvalidPolarFileException("Unsupported encoding: UTF-8", e);
}
BufferedReader inputStream = new BufferedReader(in);
//We expect the polar data file to have the column headings:
// Tws, Twa0, Bsp0, Twa1, Bsp1, UpTwa, UpBsp, Twa2, Bsp2, Twa3, Bsp3, Twa4, Bsp4, Twa5, Bsp5, Twa6, Bsp6, DnTwa, DnBsp, Twa7, Bsp7
//and to have 7 rows of data.
//Angles are expected to be in degrees, and velocities in knots.
//We read data rows, and split them into arrays of elements.
ArrayList<String[]> dataRows = new ArrayList<>(7);
try {
//Heading row.
//We skip the heading row by reading it.
String headingRow = inputStream.readLine();
//Data rows.
while (inputStream.ready()) {
//Read line.
String dataRow = inputStream.readLine();
//Split line.
String[] dataElements = dataRow.split(",");
//Add to collection.
dataRows.add(dataElements);
}
} catch (IOException e) {
throw new InvalidPolarFileException("Could not read from polar data file: " + filename, e);
}
//Finished reading in data, now we need to construct polar rows and table from it.
//For each row...
int rowNumber = 0;
for (String[] row : dataRows) {
//For each pair of columns (the pair is angle, speed).
//We start at column 1 since column 0 is the wind speed column.
for (int i = 1; i < row.length; i += 2) {
//Add angle+speed=velocity estimate to polar table.
try {
//Add the polar value to the polar table
double windSpeedKnots = Double.parseDouble(row[0]);
double angleDegrees = Double.parseDouble(row[i]);
Bearing angle = Bearing.fromDegrees(angleDegrees);
double boatSpeedKnots = Double.parseDouble(row[i + 1]);
newPolars.addPolars(windSpeedKnots, angle, boatSpeedKnots);
} catch (NumberFormatException e) {
throw new InvalidPolarFileException("Could not convert (Row,Col): (" + rowNumber + "," + i +") = " + row[i] + " to a double.", e);
}
}
//Increment row number.
rowNumber++;
}
newPolars.linearInterpolatePolars();
}
} }

@ -109,6 +109,11 @@ public class ClientConnection implements Runnable {
*/ */
private ConnectionStateEnum connectionState = ConnectionStateEnum.UNKNOWN; private ConnectionStateEnum connectionState = ConnectionStateEnum.UNKNOWN;
/**
* The source ID that has been allocated to the client.
* 0 means not allocated.
*/
private int allocatedSourceID = 0;
@ -178,7 +183,7 @@ public class ClientConnection implements Runnable {
RequestToJoin requestToJoin = waitForRequestToJoin(); RequestToJoin requestToJoin = waitForRequestToJoin();
int allocatedSourceID = 0; allocatedSourceID = 0;
//If they want to participate, give them a source ID number. //If they want to participate, give them a source ID number.
if (requestToJoin.getRequestType() == RequestToJoinEnum.PARTICIPANT) { if (requestToJoin.getRequestType() == RequestToJoinEnum.PARTICIPANT) {
@ -283,6 +288,10 @@ public class ClientConnection implements Runnable {
if (this.controllerServerThread != null) { if (this.controllerServerThread != null) {
this.controllerServerThread.interrupt(); this.controllerServerThread.interrupt();
} }
if (allocatedSourceID != 0) {
sourceIdAllocator.returnSourceID(allocatedSourceID);
}
} }
} }

@ -1,6 +1,8 @@
package mock.model; package mock.model;
import mock.model.wind.WindGenerator;
import javafx.animation.AnimationTimer; import javafx.animation.AnimationTimer;
import mock.model.collider.ColliderRegistry;
import mock.xml.*; import mock.xml.*;
import network.Messages.BoatLocation; import network.Messages.BoatLocation;
import network.Messages.BoatStatus; import network.Messages.BoatStatus;
@ -13,7 +15,6 @@ import shared.exceptions.BoatNotFoundException;
import shared.enums.RoundingType; import shared.enums.RoundingType;
import shared.model.*; import shared.model.*;
import shared.model.Bearing; import shared.model.Bearing;
import shared.model.Race;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
@ -27,7 +28,7 @@ import static java.lang.Math.cos;
* Has a course, boats, boundaries, etc... * Has a course, boats, boundaries, etc...
* Is responsible for simulating the race, and sending messages to a MockOutput instance. * Is responsible for simulating the race, and sending messages to a MockOutput instance.
*/ */
public class MockRace extends Race { public class MockRace extends RaceState {
/** /**
* An observable list of boats in the race. * An observable list of boats in the race.
@ -40,6 +41,12 @@ public class MockRace extends Race {
private List<GPSCoordinate> shrinkBoundary; private List<GPSCoordinate> shrinkBoundary;
/**
* Registry for all collider object in this race
*/
private ColliderRegistry colliderRegistry;
/** /**
* The scale factor of the race. * The scale factor of the race.
* See {@link Constants#RaceTimeScale}. * See {@link Constants#RaceTimeScale}.
@ -51,6 +58,14 @@ public class MockRace extends Race {
*/ */
private WindGenerator windGenerator; private WindGenerator windGenerator;
/**
* The polars file to use for each boat.
*/
private Polars polars;
/** /**
* Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput. * Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput.
* @param boatDataSource Data source for boat related data (yachts and marker boats). * @param boatDataSource Data source for boat related data (yachts and marker boats).
@ -62,13 +77,16 @@ public class MockRace extends Race {
*/ */
public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, Polars polars, int timeScale, WindGenerator windGenerator) { public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, Polars polars, int timeScale, WindGenerator windGenerator) {
super(boatDataSource, raceDataSource, regattaDataSource); this.setBoatDataSource(boatDataSource);
this.setRaceDataSource(raceDataSource);
this.setRegattaDataSource(regattaDataSource);
this.polars = polars;
this.scaleFactor = timeScale; this.scaleFactor = timeScale;
this.boats = this.generateMockBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), polars); this.boats = new ArrayList<>();
this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary); this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.getBoundary());
this.windGenerator = windGenerator; this.windGenerator = windGenerator;
@ -76,35 +94,53 @@ public class MockRace extends Race {
//Wind. //Wind.
this.setWind(windGenerator.generateBaselineWind()); this.setWind(windGenerator.generateBaselineWind());
// Set up colliders
this.colliderRegistry = new ColliderRegistry();
for(CompoundMark mark: this.getCompoundMarks()) {
colliderRegistry.addCollider(mark.getMark1());
if(mark.getMark2() != null) colliderRegistry.addCollider(mark.getMark2());
}
this.colliderRegistry.addAllColliders(boats); this.colliderRegistry.addAllColliders(boats);
} }
/** /**
* Generates a list of MockBoats given a list of Boats, and a list of participating boats. * Generates a MockBoat from the BoatDataSource, given a source ID. Also adds it to the participant list.
* @param boats The map of Boats describing boats that are potentially in the race. Maps boat sourceID to boat. * @param sourceID The source ID to assign the boat.
* @param sourceIDs The list of boat sourceIDs describing which specific boats are actually participating.
* @param polars The polars table to be used for boat simulation.
* @return A list of MockBoats that are participating in the race.
*/ */
private List<MockBoat> generateMockBoats(Map<Integer, Boat> boats, List<Integer> sourceIDs, Polars polars) { public void generateMockBoat(Integer sourceID) {
List<MockBoat> mockBoats = new ArrayList<>(sourceIDs.size()); //Get the boat associated with the sourceID.
Boat boat = getBoatDataSource().getBoats().get(sourceID);
//For each sourceID participating... //Construct a MockBoat using the Boat and Polars.
for (int sourceID : sourceIDs) { MockBoat mockBoat = new MockBoat(boat, polars);
mockBoat.setCurrentLeg(this.getLegs().get(0));
//Get the boat associated with the sourceID. //Update participant list.
Boat boat = boats.get(sourceID); getRaceDataSource().getParticipants().add(sourceID);
//Construct a MockBoat using the Boat and Polars. this.boats.add(mockBoat);
MockBoat mockBoat = new MockBoat(boat, polars);
mockBoats.add(mockBoat); getRaceDataSource().incrementSequenceNumber();
}
} /**
* Removes a MockBoat from the race, by sourceID. Also removes it from the participant list.
* @param sourceID Source ID of boat to remove.
*/
public void removeMockBoat(Integer sourceID) {
this.boats.removeIf(mockBoat -> mockBoat.getSourceID() == sourceID);
getRaceDataSource().getParticipants().remove(sourceID);
getRaceDataSource().incrementSequenceNumber();
}
return mockBoats;
public ColliderRegistry getColliderRegistry() {
return colliderRegistry;
} }
@ -113,7 +149,7 @@ public class MockRace extends Race {
* @param currentTime Milliseconds since unix epoch. * @param currentTime Milliseconds since unix epoch.
*/ */
public void updateRaceTime(long currentTime) { public void updateRaceTime(long currentTime) {
this.raceClock.setUTCTime(currentTime); this.getRaceClock().setUTCTime(currentTime);
} }
@ -123,7 +159,7 @@ public class MockRace extends Race {
public void updateRaceStatusEnum() { public void updateRaceStatusEnum() {
//The millisecond duration of the race. Negative means it hasn't started, so we flip sign. //The millisecond duration of the race. Negative means it hasn't started, so we flip sign.
long timeToStart = - this.raceClock.getDurationMilli(); long timeToStart = - this.getRaceClock().getDurationMilli();
if (timeToStart > Constants.RacePreStartTime) { if (timeToStart > Constants.RacePreStartTime) {
@ -194,7 +230,7 @@ public class MockRace extends Race {
//The boat starts on the first leg of the race. //The boat starts on the first leg of the race.
boat.setCurrentLeg(this.legs.get(0)); boat.setCurrentLeg(this.getLegs().get(0));
//Boats start with 0 knots speed. //Boats start with 0 knots speed.
boat.setCurrentSpeed(0d); boat.setCurrentSpeed(0d);
@ -209,7 +245,9 @@ public class MockRace extends Race {
boat.setStatus(BoatStatusEnum.PRESTART); boat.setStatus(BoatStatusEnum.PRESTART);
//We set a large time since tack change so that it calculates a new VMG when the simulation starts. //We set a large time since tack change so that it calculates a new VMG when the simulation starts.
boat.setTimeSinceTackChange(Long.MAX_VALUE); //boat.setTimeSinceTackChange(Long.MAX_VALUE);
boat.setTimeSinceTackChange(0);
} }
} }
@ -223,7 +261,7 @@ public class MockRace extends Race {
private List<GPSCoordinate> getSpreadStartingPositions() { private List<GPSCoordinate> getSpreadStartingPositions() {
//The first compound marker of the race - the starting gate. //The first compound marker of the race - the starting gate.
CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark(); CompoundMark compoundMark = this.getLegs().get(0).getStartCompoundMark();
//The position of the two markers from the compound marker. //The position of the two markers from the compound marker.
GPSCoordinate mark1Position = compoundMark.getMark1Position(); GPSCoordinate mark1Position = compoundMark.getMark1Position();
@ -341,6 +379,7 @@ public class MockRace extends Race {
if (boat.getAutoVMG()) { if (boat.getAutoVMG()) {
newOptimalVMG(boat); newOptimalVMG(boat);
boat.setAutoVMG(false);
} }
} else { } else {
@ -353,31 +392,38 @@ public class MockRace extends Race {
private void newOptimalVMG(MockBoat boat) { private void newOptimalVMG(MockBoat boat) {
long tackPeriod = 1000; long tackPeriod = 1000;
if (boat.getTimeSinceTackChange() > tackPeriod) { if (boat.getTimeSinceTackChange() > tackPeriod) {
//System.out.println("optim called");
//Calculate the new VMG. //Calculate the new VMG.
VMG newVMG = boat.getPolars().calculateVMG( // VMG newVMG = boat.getPolars().calculateVMG(
this.getWindDirection(), // this.getWindDirection(),
this.getWindSpeed(), // this.getWindSpeed(),
boat.calculateBearingToNextMarker(), // boat.calculateBearingToNextMarker(),
Bearing.fromDegrees(0d), // Bearing.fromDegrees(0d),
Bearing.fromDegrees(359.99999d)); // Bearing.fromDegrees(359.99999d));
VMG newVMG = NewPolars.setBestVMG(this.getWindDirection(), this.getWindSpeed(), boat.getBearing());
//System.out.println(newVMG);
//If the new vmg improves velocity, use it. //If the new vmg improves velocity, use it.
if (improvesVelocity(boat, newVMG)) { /*if (improvesVelocity(boat, newVMG)) {
boat.setVMG(newVMG); }*/
} boat.setVMG(newVMG);
} }
} }
private void setBoatSpeed(MockBoat boat) { private void setBoatSpeed(MockBoat boat) {
VMG vmg = boat.getPolars().calculateVMG( // VMG vmg = boat.getPolars().calculateVMG(
// this.getWindDirection(),
// this.getWindSpeed(),
// boat.getBearing(),
// Bearing.fromDegrees(boat.getBearing().degrees() - 1),
// Bearing.fromDegrees(boat.getBearing().degrees() + 1));
//VMG vmg = boat.getPolars().setBestVMG(this.getWindDirection(), this.getWindSpeed(), boat.getBearing());
VMG vmg = new VMG(NewPolars.calculateSpeed(
this.getWindDirection(), this.getWindDirection(),
this.getWindSpeed(), this.getWindSpeed(),
boat.getBearing(), boat.getBearing()
Bearing.fromDegrees(boat.getBearing().degrees() - 1), ), boat.getBearing()) ;
Bearing.fromDegrees(boat.getBearing().degrees() + 1));
if (vmg.getSpeed() > 0) { if (vmg.getSpeed() > 0) {
boat.setCurrentSpeed(vmg.getSpeed()); boat.setCurrentSpeed(vmg.getSpeed());
} }
@ -485,7 +531,7 @@ public class MockRace extends Race {
roundingChecks.get(0), boat.getPosition(), legBearing) && roundingChecks.get(0), boat.getPosition(), legBearing) &&
gateCheck && boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) { gateCheck && boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) {
boat.increaseRoundingStatus(); boat.increaseRoundingStatus();
if (boat.getCurrentLeg().getLegNumber() + 2 >= legs.size()){ if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
//boat has finished race //boat has finished race
boat.increaseRoundingStatus(); boat.increaseRoundingStatus();
} }
@ -503,7 +549,7 @@ public class MockRace extends Race {
case 2://has traveled 180 degrees around the mark case 2://has traveled 180 degrees around the mark
//Move boat on to next leg. //Move boat on to next leg.
boat.resetRoundingStatus(); boat.resetRoundingStatus();
Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); Leg nextLeg = this.getLegs().get(boat.getCurrentLeg().getLegNumber() + 1);
boat.setCurrentLeg(nextLeg); boat.setCurrentLeg(nextLeg);
break; break;
} }
@ -527,10 +573,10 @@ public class MockRace extends Race {
if (boat.isStarboardSide(roundingMark) && if (boat.isStarboardSide(roundingMark) &&
GPSCoordinate.passesLine(roundingMark.getPosition(), GPSCoordinate.passesLine(roundingMark.getPosition(),
roundingChecks.get(0), boat.getPosition(), legBearing) && roundingChecks.get(0), boat.getPosition(), legBearing) &&
gateCheck && gateCheck &&
boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) { boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) {
boat.increaseRoundingStatus(); boat.increaseRoundingStatus();
if (boat.getCurrentLeg().getLegNumber() + 2 >= legs.size()){ if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
//boat has finished race //boat has finished race
boat.increaseRoundingStatus(); boat.increaseRoundingStatus();
} }
@ -547,7 +593,7 @@ public class MockRace extends Race {
case 2://has traveled 180 degrees around the mark case 2://has traveled 180 degrees around the mark
//Move boat on to next leg. //Move boat on to next leg.
boat.resetRoundingStatus(); boat.resetRoundingStatus();
Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); Leg nextLeg = this.getLegs().get(boat.getCurrentLeg().getLegNumber() + 1);
boat.setCurrentLeg(nextLeg); boat.setCurrentLeg(nextLeg);
break; break;
} }
@ -585,7 +631,7 @@ public class MockRace extends Race {
GPSCoordinate roundCheck2; GPSCoordinate roundCheck2;
try{ try{
Leg nextLeg = legs.get(legs.indexOf(boat.getCurrentLeg()) + 1); Leg nextLeg = getLegs().get(getLegs().indexOf(boat.getCurrentLeg()) + 1);
GPSCoordinate startNextDirectionLinePoint = nextLeg.getStartCompoundMark().getMark1Position(); GPSCoordinate startNextDirectionLinePoint = nextLeg.getStartCompoundMark().getMark1Position();
GPSCoordinate endNextDirectionLinePoint = nextLeg.getEndCompoundMark().getMark1Position(); GPSCoordinate endNextDirectionLinePoint = nextLeg.getEndCompoundMark().getMark1Position();
@ -681,7 +727,7 @@ public class MockRace extends Race {
*/ */
public void changeWindDirection() { public void changeWindDirection() {
Wind nextWind = windGenerator.generateNextWind(raceWind.getValue()); Wind nextWind = windGenerator.generateNextWind(windProperty().getValue());
setWind(nextWind); setWind(nextWind);
} }
@ -702,13 +748,10 @@ public class MockRace extends Race {
long timeFromNow = (long) (1000 * boat.calculateDistanceToNextMarker() / velocityToMark); long timeFromNow = (long) (1000 * boat.calculateDistanceToNextMarker() / velocityToMark);
//Calculate time at which it will reach mark. //Calculate time at which it will reach mark.
ZonedDateTime timeAtMark = this.raceClock.getCurrentTime().plus(timeFromNow, ChronoUnit.MILLIS); ZonedDateTime timeAtMark = this.getRaceClock().getCurrentTime().plus(timeFromNow, ChronoUnit.MILLIS);
boat.setEstimatedTimeAtNextMark(timeAtMark); boat.setEstimatedTimeAtNextMark(timeAtMark);
} }
} }
public List<CompoundMark> getCompoundMarks() {
return compoundMarks;
}
} }

@ -0,0 +1,204 @@
package mock.model;
import shared.model.Bearing;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* New Polars are the revampe of the old Polars class which interpolates the data after being parsed from the Polar Parser
* There can only be one NewPolars instance stored statically however if a boat does happen to have a special case it can be assigned.
*/
public class NewPolars {
//true wind speed, <true wind angle, best boat angle>
private static Map<Double, TreeMap<Double, Double>> polars = new TreeMap<>();
public static NewPolars newPolars = null;
public NewPolars(){
newPolars = this;
}
/**
* Add polars from the polar table
* @param trueWindSpeed True Wind Speed that the true wind angle and speed corresponds to
* @param trueWindAngle True Wind Angle of the race
* @param boatSpeed The speed the boat should be going at given the true wind angle
*/
public static void addPolars(double trueWindSpeed, Bearing trueWindAngle, double boatSpeed){
double tws = trueWindSpeed;
double bs = boatSpeed;
double twa = trueWindAngle.degrees();
if (!polars.containsKey(tws)){
polars.put(tws, new TreeMap<>());
}
polars.get(tws).putIfAbsent(twa, bs);
polars.get(tws).putIfAbsent(360d - twa, bs);
}
/**
* Linearly Interpolates this should only be called once per parsing of a polar table
*/
public static void linearInterpolatePolars(){
TreeMap<Double, Double> prevTWS = null;
TreeMap<Double, TreeMap<Double, Double>> iterablePolars = new TreeMap<>(polars);
//this loop averages out the speed between tow angles
//Example: Pair one: 0 degrees, 0 knots
// Pair two: 3 degrees, 6 knots
//This loop will add
//Pair one: 0 degrees, 0 knots
//Pair two: 1 degrees, 2 knots
//Pair three: 2 degrees, 4 knots
//Pair four: 3 degrees, 6 knots
for (double windSpeed: iterablePolars.keySet()){
TreeMap<Double, Double> tws = iterablePolars.get(windSpeed);
if (prevTWS == null){
prevTWS = tws;
continue;
}
double previousTWA = -1;
TreeMap<Double, Double> iterableTWS = new TreeMap<>(tws);
for (double twa: iterableTWS.keySet()){
if (previousTWA == -1){
previousTWA = twa;
continue;
}
double twaDiff = twa - previousTWA;
double speedDiff = iterableTWS.get(twa) - iterableTWS.get(previousTWA);
double prevSpeed = iterableTWS.get(previousTWA);
double diff = speedDiff/twaDiff;
for (double i = previousTWA; i < twa; i ++){
double mult = i - previousTWA;
double newSpeed = diff * mult + prevSpeed;
tws.put(i, newSpeed);
}
previousTWA = twa;
}
}
}
private static double getClosest(double value, Set<Double> set){
double closestVal = 0;
double smallestDiff = Double.MAX_VALUE;
for (double d: set){
double diff = Math.abs(value - d);
if (diff < smallestDiff){
closestVal = d;
smallestDiff = diff;
}
}
return closestVal;
}
/**
* Determines which quadrant degrees are in
* 0/360 Degrees
* Quadrant 4 | Quadrant 1
* -----------------------
* Quadrant 3 | Quadrant 2
* @param degrees
* @return
*/
private static int getQuadrant(double degrees){
return (int) modulateAngle(degrees) / 90 + 1;
}
private static double getBestSpeedInQuadrant(int quad, Map<Double, Double> set){
double min = (quad - 1)* 90;
double max = quad * 90;
double maxAngle = 0;
double maxSpeed = 0;
double dupAngle = 0;
//DupAngle will average the angle between maxAngles that have the same speed
//Example: if 150 degrees, 180 degrees, and 210 degrees all go at 10 knots
//then the average will be taken as (150 + 210) / 2 and the angle will be returned on that.
for (Double s: set.keySet()){
if (s >= min && s < max){
if (set.get(s) > maxSpeed){
dupAngle = 0;
maxAngle = s;
maxSpeed = set.get(s);
} else if (set.get(s) == maxSpeed){
dupAngle = s;
}
}
}
if (dupAngle != 0 ){
return getClosest((dupAngle + maxAngle) / 2, set.keySet());
}
return maxAngle;
}
/**
* Returns the best VMG that the boat can change to given it's current diagonal heading direction.
* @param trueWindAngle True wind angle of the race
* @param trueWindSpeed True wind speed of the race
* @param boatAngle Angle that the boat is currently at
* @return the best vmg that the boat can change to
*/
public static VMG setBestVMG(Bearing trueWindAngle, double trueWindSpeed, Bearing boatAngle){
//System.out.println("VMG AUTO CALLED");
//speed
double closestSpeed = getClosest(trueWindSpeed, polars.keySet());
double angle = modulateAngle(boatAngle.degrees() - trueWindAngle.degrees());
int quad = getQuadrant(angle);
double bestAngle = getBestSpeedInQuadrant(quad, polars.get(closestSpeed));
double boatSpeed = polars.get(closestSpeed).get(bestAngle);
double newAngle = modulateAngle(bestAngle + trueWindAngle.degrees());
return new VMG(boatSpeed, Bearing.fromDegrees(newAngle));
}
/**
* Calculates the speed that a certain angle should be doing
* @param trueWindAngle TrueWind Angle of the race
* @param trueWindSpeed True Wind Speed of the race
* @param boatAngle Angle that the boat is current at
* @return the speed that the boat should be traveling at.
*/
public static double calculateSpeed(Bearing trueWindAngle, double trueWindSpeed, Bearing boatAngle){
//speed
double closestSpeed = getClosest(trueWindSpeed, polars.keySet());
double angleDiff = modulateAngle(boatAngle.degrees() - trueWindAngle.degrees());
double closestAngle = getClosest(angleDiff, polars.get(closestSpeed).keySet());
double boatSpeed = polars.get(closestSpeed).get(closestAngle);
return boatSpeed;
}
public static double modulateAngle(double angle){
return (angle % 360 + 360) % 360;
}
private Map<Double, TreeMap<Double, Double>> getPolars(){
//this function is just for testing so therefore it is private
return polars;
}
private void printOutLinearInterpolated(){
for (double tws: polars.keySet()){
System.out.println("==================================================");
System.out.println("Speed: " + tws);
System.out.println("==================================================");
for (double twa: polars.get(tws).keySet()){
System.out.println("TWA: " + twa + ", Boat Speed: " + polars.get(tws).get(twa));
}
}
}
}

@ -3,10 +3,7 @@ package mock.model;
import javafx.util.Pair; import javafx.util.Pair;
import shared.model.Bearing; import shared.model.Bearing;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* Encapsulates an entire polar table. Has a function to calculate VMG. * Encapsulates an entire polar table. Has a function to calculate VMG.
@ -78,8 +75,6 @@ public class Polars {
} }
/** /**
* Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds. * Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds.
* <br> * <br>

@ -49,6 +49,9 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
*/ */
@Override @Override
public void run() { public void run() {
prestartCountdown();
race.initialiseBoats(); race.initialiseBoats();
countdown(); countdown();
@ -61,6 +64,37 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
} }
/**
* The countdown timer until the prestart period is finished. This timer will stop 3 minutes before the race starts, and players can no longer start participating.
*/
private void prestartCountdown() {
long previousFrameTime = System.currentTimeMillis();
while (((race.getRaceStatusEnum() == RaceStatusEnum.PRESTART)
|| (race.getRaceStatusEnum() == RaceStatusEnum.NOT_ACTIVE)
|| (race.getRaceStatusEnum() == RaceStatusEnum.WARNING)) && loopBool) {
long currentTime = System.currentTimeMillis();
//Update race time.
race.updateRaceTime(currentTime);
//Update the race status based on the current time.
race.updateRaceStatusEnum();
//Provide boat's with an estimated time at next mark until the race starts.
race.setBoatsTimeNextMark(race.getRaceClock().getCurrentTime());
//Parse the race snapshot.
server.parseSnapshot();
waitForFramePeriod(previousFrameTime, currentTime, 50);
previousFrameTime = currentTime;
}
}
/** /**
* Countdown timer until race starts. * Countdown timer until race starts.
*/ */

@ -1,14 +1,22 @@
package mock.model; package mock.model;
import network.AckSequencer;
import network.Messages.*; import network.Messages.*;
import network.Messages.Enums.BoatLocationDeviceEnum; import network.Messages.Enums.BoatLocationDeviceEnum;
import network.Messages.Enums.YachtEventEnum; import network.Messages.Enums.YachtEventEnum;
import network.Messages.Enums.XMLMessageType;
import shared.model.Bearing; import shared.model.Bearing;
import shared.model.CompoundMark; import shared.model.CompoundMark;
import shared.model.Mark; import shared.model.Mark;
import shared.xml.Race.RaceDataSourceToXML;
import shared.xml.boats.BoatDataSourceToXML;
import shared.xml.regatta.RegattaDataSourceToXML;
import javax.xml.bind.JAXBException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Created by connortaylorbrown on 2/08/17. * Created by connortaylorbrown on 2/08/17.
@ -24,6 +32,21 @@ public class RaceServer {
*/ */
private int boatLocationSequenceNumber = 1; private int boatLocationSequenceNumber = 1;
/**
* The sequence number of race XML messages.
*/
private int raceXMLSeqNumber = -1;
/**
* The sequence number of boat XML messages.
*/
private int boatXMLSeqNumber = -1;
/**
* The sequence number of regatta XML messages.
*/
private int regattaXMLSeqNumber = -1;
public RaceServer(MockRace race, LatestMessages latestMessages) { public RaceServer(MockRace race, LatestMessages latestMessages) {
this.race = race; this.race = race;
@ -53,6 +76,8 @@ public class RaceServer {
latestMessages.setSnapshot(snapshotMessages); latestMessages.setSnapshot(snapshotMessages);
updateXMLFiles();
//Reset collision list //Reset collision list
collisionEvents.clear(); collisionEvents.clear();
//System.out.println(collisionEvents.size()); //System.out.println(collisionEvents.size());
@ -60,6 +85,73 @@ public class RaceServer {
} }
/**
* Checks if the race/boat/regatta data sources have changed, and if they have, update their xml representations.
*/
private void updateXMLFiles() {
updateRaceXMLFile();
updateBoatXMLFile();
updateRegattaXMLFile();
}
/**
* Checks if the race data source has changed, and if it has, updates LatestMessages' race xml message.
*/
private void updateRaceXMLFile() {
if (raceXMLSeqNumber != race.getRaceDataSource().getSequenceNumber()) {
raceXMLSeqNumber = race.getRaceDataSource().getSequenceNumber();
try {
String raceXMLString = RaceDataSourceToXML.toString(race.getRaceDataSource());
XMLMessage message = createXMLMessage(raceXMLString, XMLMessageType.RACE);
latestMessages.setXMLMessage(message);
} catch (JAXBException e) {
Logger.getGlobal().log(Level.WARNING, "Could not serialise: " + race.getRaceDataSource(), e);
}
}
}
/**
* Checks if the boat data source has changed, and if it has, updates LatestMessages' boat xml message.
*/
private void updateBoatXMLFile() {
if (boatXMLSeqNumber != race.getBoatDataSource().getSequenceNumber()) {
boatXMLSeqNumber = race.getBoatDataSource().getSequenceNumber();
try {
String boatXMLString = BoatDataSourceToXML.toString(race.getBoatDataSource());
XMLMessage message = createXMLMessage(boatXMLString, XMLMessageType.BOAT);
latestMessages.setXMLMessage(message);
} catch (JAXBException e) {
Logger.getGlobal().log(Level.WARNING, "Could not serialise: " + race.getBoatDataSource(), e);
}
}
}
/**
* Checks if the regatta data source has changed, and if it has, updates LatestMessages' regatta xml message.
*/
private void updateRegattaXMLFile() {
if (regattaXMLSeqNumber != race.getRegattaDataSource().getSequenceNumber()) {
regattaXMLSeqNumber = race.getRegattaDataSource().getSequenceNumber();
try {
String regattaXMLString = RegattaDataSourceToXML.toString(race.getRegattaDataSource());
XMLMessage message = createXMLMessage(regattaXMLString, XMLMessageType.REGATTA);
latestMessages.setXMLMessage(message);
} catch (JAXBException e) {
Logger.getGlobal().log(Level.WARNING, "Could not serialise: " + race.getRegattaDataSource(), e);
}
}
}
/** /**
* Parses an individual marker boat, and returns it. * Parses an individual marker boat, and returns it.
* @param mark The marker boat to parse. * @param mark The marker boat to parse.
@ -191,6 +283,43 @@ public class RaceServer {
return raceStatus; return raceStatus;
} }
/**
* Creates an XMLMessage of a specified subtype using the xml contents string.
* @param xmlString The contents of the xml file.
* @param messageType The subtype of xml message (race, regatta, boat).
* @return The created XMLMessage object.
*/
private XMLMessage createXMLMessage(String xmlString, XMLMessageType messageType) {
//Get the correct sequence number to use.
int sequenceNumber = 0;
if (messageType == XMLMessageType.RACE) {
sequenceNumber = this.raceXMLSeqNumber;
} else if (messageType == XMLMessageType.BOAT) {
sequenceNumber = this.boatXMLSeqNumber;
} else if (messageType == XMLMessageType.REGATTA) {
sequenceNumber = this.regattaXMLSeqNumber;
}
//Create the message.
XMLMessage message = new XMLMessage(
XMLMessage.currentVersionNumber,
AckSequencer.getNextAckNum(),
System.currentTimeMillis(),
messageType,
sequenceNumber,
xmlString);
return message;
}
/** /**
* Parse the yacht event and return it * Parse the yacht event and return it
* @param boat yacht with event * @param boat yacht with event

@ -2,6 +2,7 @@ package mock.model;
import mock.exceptions.SourceIDAllocationException; import mock.exceptions.SourceIDAllocationException;
import network.Messages.Enums.RaceStatusEnum;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -13,24 +14,18 @@ public class SourceIdAllocator {
/** /**
* This list contains all unallocated source IDs. * The race we are allocating for.
*/ */
List<Integer> unallocatedIDs = new ArrayList<>(); private MockRace mockRace;
/**
* This list contains all allocated source IDs.
*/
List<Integer> allocatedIDs = new ArrayList<>();
/** /**
* Creates a source ID allocator, using the given list of unallocated source IDs. * Creates a SourceIdAllocator for a given race.
* @param unallocatedIDs List of unallocated source IDs. * @param mockRace Race to allocate source IDs for.
*/ */
public SourceIdAllocator(List<Integer> unallocatedIDs) { public SourceIdAllocator(MockRace mockRace) {
//We need to copy the list. this.mockRace = mockRace;
this.unallocatedIDs.addAll(unallocatedIDs);
} }
@ -41,11 +36,25 @@ public class SourceIdAllocator {
*/ */
public synchronized int allocateSourceID() throws SourceIDAllocationException { public synchronized int allocateSourceID() throws SourceIDAllocationException {
if (!((mockRace.getRaceStatusEnum() == RaceStatusEnum.PRESTART)
|| (mockRace.getRaceStatusEnum() == RaceStatusEnum.WARNING))) {
throw new SourceIDAllocationException("Could not allocate a source ID. Can only allocate during pre-start period. It is currently: " + mockRace.getRaceStatusEnum());
}
List<Integer> allocatedIDs = mockRace.getRaceDataSource().getParticipants();
List<Integer> allIDs = new ArrayList<>(mockRace.getBoatDataSource().getBoats().keySet());
//Get list of unallocated ids.
List<Integer> unallocatedIDs = new ArrayList<>(allIDs);
unallocatedIDs.removeAll(allocatedIDs);
if (!unallocatedIDs.isEmpty()) { if (!unallocatedIDs.isEmpty()) {
int sourceID = unallocatedIDs.remove(0); int sourceID = unallocatedIDs.remove(0);
allocatedIDs.add(sourceID); mockRace.generateMockBoat(sourceID);
return sourceID; return sourceID;
@ -61,10 +70,6 @@ public class SourceIdAllocator {
* @param sourceID Source ID to return. * @param sourceID Source ID to return.
*/ */
public void returnSourceID(Integer sourceID) { public void returnSourceID(Integer sourceID) {
mockRace.removeMockBoat(sourceID);
//We remove an Integer, not an int, so that we remove by value not by index.
allocatedIDs.remove(sourceID);
unallocatedIDs.add(sourceID);
} }
} }

@ -45,4 +45,8 @@ public class VMG {
return bearing; return bearing;
} }
public String toString(){
return String.format("VMG Object: Speed %f, Bearing %f.", speed, bearing.degrees());
}
} }

@ -1,11 +1,9 @@
package mock.model; package mock.model.wind;
import shared.model.Bearing; import shared.model.Bearing;
import shared.model.Wind; import shared.model.Wind;
import java.util.Random;
/** /**
* This class generates Wind objects for use in a MockRace. * This class generates Wind objects for use in a MockRace.
* Initialised with a baseline wind speed and direction, and keeps it constant. * Initialised with a baseline wind speed and direction, and keeps it constant.

@ -1,4 +1,4 @@
package mock.model; package mock.model.wind;
import shared.model.Bearing; import shared.model.Bearing;

@ -0,0 +1,152 @@
package mock.model.wind;
import shared.model.Bearing;
import shared.model.Wind;
import java.util.Random;
public class ShiftingWindGenerator implements WindGenerator {
private Bearing baselineBearing;
private double baseLineSpeed;
private double windSpeedVariance = 5;
private double bearingVariance = 5; // In degrees
private double oscillationVariance = 0.25;
private double oscillationPeriod = 1e3 * 60 * 1; // In milliseconds
private double shiftTime = 1e3 * 60;
private double shiftedSoFar = 0;
private double timeOfLastOscillationReset = 0;
private double timeOfLastChange = 0;
private double timeOfLastShift = 0; // Back / veer
private boolean anticlockwise = false;
private boolean shiftAnticlockwise = false;//true for Back, false for veer
private boolean shiftThisRace = Math.random() > 0.5;
/**
* Constructor
* @param baselineBearing baseline bearing for wind
* @param baseLineSpeed base line speed for wind
*/
public ShiftingWindGenerator(Bearing baselineBearing, double baseLineSpeed) {
this.baselineBearing = baselineBearing;
this.baseLineSpeed = baseLineSpeed;
initialiseOscillationDirection();
}
@Override
public Wind generateBaselineWind() {
return new Wind(baselineBearing, baseLineSpeed);
}
@Override
public Wind generateNextWind(Wind currentWind) {
return changeWind(currentWind);
}
/**
* @param wind the wind to change
* @return the changed wind
*/
private Wind changeWind(Wind wind) {
Wind newWind = new Wind(wind.getWindDirection(), wind.getWindSpeed());
oscillateWind(newWind);
if (shiftThisRace){shiftWind(newWind);}
changeWindSpeed(newWind);
timeOfLastChange = System.currentTimeMillis();
return newWind;
}
/**
* moves the wind 5 degrees up and down
* @param wind the wind to oscillate
*/
private void oscillateWind(Wind wind) {
double timeSinceLastOscillationReset = System.currentTimeMillis() - timeOfLastOscillationReset;
double timeSinceLastChange = System.currentTimeMillis() - timeOfLastChange;
double newBearing = wind.getWindDirection().degrees();
double degreeChange = timeSinceLastChange * 2 * bearingVariance / oscillationPeriod;
degreeChange = (1 - oscillationVariance) * degreeChange + (2 * oscillationVariance) * degreeChange * Math.random();
if (timeSinceLastOscillationReset >= oscillationPeriod) {
timeOfLastOscillationReset = System.currentTimeMillis();
anticlockwise = !anticlockwise;
}
if (anticlockwise) {
newBearing -= degreeChange;
if (newBearing < baselineBearing.degrees() - bearingVariance) {
anticlockwise = !anticlockwise;
timeOfLastOscillationReset = System.currentTimeMillis();
} else {
wind.setWindDirection(Bearing.fromDegrees(newBearing % 360));
}
} else {
newBearing += degreeChange;
if (newBearing > baselineBearing.degrees() + bearingVariance) {
anticlockwise = !anticlockwise;
timeOfLastOscillationReset = System.currentTimeMillis();
} else {
wind.setWindDirection(Bearing.fromDegrees(newBearing % 360));
}
}
}
/**
* Slowly shifts the wind up to 180 degrees from where it started
* @param wind the wind to change
*/
private void shiftWind(Wind wind) {
double timeSinceLastShift = System.currentTimeMillis() - timeOfLastShift;
double newBearing = wind.getWindDirection().degrees();
double degreeChange = 7;
if (timeSinceLastShift >= shiftTime){
shiftedSoFar += degreeChange;
if (shiftedSoFar >= 180){
shiftAnticlockwise = Math.random() > 0.5;
shiftedSoFar = 0;
// System.out.println("Swapping");
}
timeOfLastShift = System.currentTimeMillis();
if (shiftAnticlockwise){
newBearing -= degreeChange;
wind.setWindDirection(Bearing.fromDegrees(newBearing % 360));
} else {
newBearing += degreeChange;
wind.setWindDirection(Bearing.fromDegrees(newBearing % 360));
}
}
}
/**
* Change the wind speed
* @param wind the wind to change
*/
private void changeWindSpeed(Wind wind) {
double offsetAngle = (wind.getWindDirection().radians() - baselineBearing.radians());
double offset = Math.sin(offsetAngle) * windSpeedVariance;
double newWindSpeed = baseLineSpeed + offset;
wind.setWindSpeed(newWindSpeed);
}
/**
* starts the wind oscillation direction
*/
private void initialiseOscillationDirection() {
anticlockwise = new Random().nextBoolean();
timeOfLastOscillationReset = System.currentTimeMillis();
}
public void setBearingVariance(double maxBearingVariance) {
this.bearingVariance = maxBearingVariance;
}
public void setWindSpeedVariance(double windSpeedVariance) {
this.windSpeedVariance = windSpeedVariance;
}
public void setOscillationPeriod(double oscillationPeriod) {
this.oscillationPeriod = oscillationPeriod;
}
}

@ -1,4 +1,4 @@
package mock.model; package mock.model.wind;
import shared.model.Wind; import shared.model.Wind;

@ -74,14 +74,19 @@ public class RaceXMLCreator {
* @throws SAXException error in schema file * @throws SAXException error in schema file
* @throws ParserConfigurationException error in parsing the schema file * @throws ParserConfigurationException error in parsing the schema file
*/ */
public static String alterRaceToWind(String s, double degrees) throws XMLReaderException, InvalidRaceDataException, JAXBException, IOException, SAXException, ParserConfigurationException { public static String alterRaceToWind(String s, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException, JAXBException, IOException, SAXException, ParserConfigurationException {
RaceXMLReader reader = new RaceXMLReader(s, XMLFileType.ResourcePath); RaceXMLReader reader = new RaceXMLReader(s, XMLFileType.ResourcePath);
XMLRace race = (XMLRace) XMLUtilities.xmlToClass(RaceXMLCreator.class.getClassLoader().getResourceAsStream(s), XMLRace race = XMLUtilities.xmlToClass(
RaceXMLCreator.class.getClassLoader().getResourceAsStream(s),
RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"), RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"),
XMLRace.class); XMLRace.class);
setRaceXMLAtCurrentTimeToNow(race); if(tutorial){
setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l);
} else {
setRaceXMLAtCurrentTimeToNow(race);
}
double raceOriginalBearing = getLineAngle(getLeewardGate(reader).getMark1Position(), getWindwardGate(reader).getMark1Position()); double raceOriginalBearing = getLineAngle(getLeewardGate(reader).getMark1Position(), getWindwardGate(reader).getMark1Position());
@ -89,16 +94,10 @@ public class RaceXMLCreator {
alterRaceRotation(race, degreesToRotate); alterRaceRotation(race, degreesToRotate);
JAXBContext context = JAXBContext.newInstance(XMLRace.class); return XMLUtilities.classToXML(race);
Marshaller jaxbMarshaller = context.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter sw = new StringWriter();
jaxbMarshaller.marshal(race, sw);
return sw.toString();
} }
/** /**
* Rotate the features in a race such as the boundary, and the marks. * Rotate the features in a race such as the boundary, and the marks.
* @param race the race to alter * @param race the race to alter
@ -186,14 +185,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. //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; long secondsToAdd = millisecondsToAdd / 1000;
//Scale the time using our time scalar. //Scale the time using our time scalar.
secondsToAdd = secondsToAdd / Constants.RaceTimeScale; secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
@ -204,4 +200,13 @@ public class RaceXMLCreator {
raceXML.getRaceStartTime().setTime(dateFormat.format(creationTime.plusSeconds(secondsToAdd))); 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);
}
} }

@ -71,6 +71,8 @@ public enum RaceTypeEnum {
} }
/** /**
* Stores a mapping between Byte values and RaceStatusEnum values. * Stores a mapping between Byte values and RaceStatusEnum values.
*/ */
@ -107,4 +109,14 @@ public enum RaceTypeEnum {
} }
@Override
public String toString() {
if (fromByte(value) == FLEET_RACE) {
return "fleet";
} else if (fromByte(value) == MATCH_RACE) {
return "match";
} else {
return super.toString();
}
}
} }

@ -35,6 +35,7 @@ public class LatestMessages extends Observable {
/** /**
* Ctor. * Ctor.
*/ */

@ -22,4 +22,16 @@ public interface BoatDataSource {
* @return Map between source ID and mark. * @return Map between source ID and mark.
*/ */
Map<Integer, Mark> getMarkerBoats(); Map<Integer, Mark> getMarkerBoats();
/**
* Returns the sequence number associated with this data source. Used to indicate when it has changed.
* @return Sequence number.
*/
int getSequenceNumber();
/**
* Increments the sequence number for this data source. Used to indicate that it has changed.
*/
void incrementSequenceNumber();
} }

@ -29,6 +29,8 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource {
private final Map<Integer, Mark> markerMap = new HashMap<>(); private final Map<Integer, Mark> markerMap = new HashMap<>();
private int sequenceNumber = 0;
/** /**
* Constructor for Boat XML using a file. * Constructor for Boat XML using a file.
* *
@ -174,4 +176,15 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource {
public Map<Integer, Mark> getMarkerBoats() { public Map<Integer, Mark> getMarkerBoats() {
return markerMap; return markerMap;
} }
@Override
public int getSequenceNumber() {
return sequenceNumber;
}
@Override
public void incrementSequenceNumber() {
sequenceNumber++;
}
} }

@ -22,6 +22,8 @@ public class EmptyBoatDataSource implements BoatDataSource {
private final Map<Integer, Mark> markerMap = new HashMap<>(); private final Map<Integer, Mark> markerMap = new HashMap<>();
private int sequenceNumber = 0;
public EmptyBoatDataSource() { public EmptyBoatDataSource() {
} }
@ -44,4 +46,15 @@ public class EmptyBoatDataSource implements BoatDataSource {
public Map<Integer, Mark> getMarkerBoats() { public Map<Integer, Mark> getMarkerBoats() {
return markerMap; return markerMap;
} }
@Override
public int getSequenceNumber() {
return sequenceNumber;
}
@Override
public void incrementSequenceNumber() {
sequenceNumber++;
}
} }

@ -2,8 +2,10 @@ package shared.dataInput;
import network.Messages.Enums.RaceTypeEnum; import network.Messages.Enums.RaceTypeEnum;
import shared.model.CompoundMark; import shared.model.CompoundMark;
import shared.model.Corner;
import shared.model.GPSCoordinate; import shared.model.GPSCoordinate;
import shared.model.Leg; import shared.model.Leg;
import shared.xml.Race.XMLCorner;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@ -48,6 +50,12 @@ public class EmptyRaceDataSource implements RaceDataSource {
*/ */
private final List<Leg> legs = new ArrayList<>(); private final List<Leg> legs = new ArrayList<>();
/**
* Corners in race.
*/
private final List<Corner> corners = new ArrayList<>();
/** /**
* The time that the race.xml file was created. * The time that the race.xml file was created.
@ -76,6 +84,8 @@ public class EmptyRaceDataSource implements RaceDataSource {
private RaceTypeEnum raceType = RaceTypeEnum.NOT_A_RACE_TYPE; private RaceTypeEnum raceType = RaceTypeEnum.NOT_A_RACE_TYPE;
private int sequenceNumber = 0;
public EmptyRaceDataSource() { public EmptyRaceDataSource() {
} }
@ -98,6 +108,11 @@ public class EmptyRaceDataSource implements RaceDataSource {
return legs; return legs;
} }
@Override
public List<Corner> getCorners() {
return corners;
}
public List<CompoundMark> getCompoundMarks() { public List<CompoundMark> getCompoundMarks() {
return new ArrayList<>(compoundMarkMap.values()); return new ArrayList<>(compoundMarkMap.values());
} }
@ -126,4 +141,14 @@ public class EmptyRaceDataSource implements RaceDataSource {
public List<Integer> getParticipants() { public List<Integer> getParticipants() {
return participants; return participants;
} }
@Override
public int getSequenceNumber() {
return sequenceNumber;
}
@Override
public void incrementSequenceNumber() {
sequenceNumber++;
}
} }

@ -60,6 +60,8 @@ public class EmptyRegattaDataSource implements RegattaDataSource {
private int sequenceNumber = 0;
public EmptyRegattaDataSource() { public EmptyRegattaDataSource() {
} }
@ -119,4 +121,15 @@ public class EmptyRegattaDataSource implements RegattaDataSource {
public GPSCoordinate getGPSCoordinate() { public GPSCoordinate getGPSCoordinate() {
return new GPSCoordinate(centralLatitude, centralLongitude); return new GPSCoordinate(centralLatitude, centralLongitude);
} }
@Override
public int getSequenceNumber() {
return sequenceNumber;
}
@Override
public void incrementSequenceNumber() {
sequenceNumber++;
}
} }

@ -2,8 +2,10 @@ package shared.dataInput;
import network.Messages.Enums.RaceTypeEnum; import network.Messages.Enums.RaceTypeEnum;
import shared.model.CompoundMark; import shared.model.CompoundMark;
import shared.model.Corner;
import shared.model.GPSCoordinate; import shared.model.GPSCoordinate;
import shared.model.Leg; import shared.model.Leg;
import shared.xml.Race.XMLCorner;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
@ -27,6 +29,12 @@ public interface RaceDataSource {
*/ */
List<Leg> getLegs(); List<Leg> getLegs();
/**
* Returns the list of corners in the race - two adjacent corners form a leg.
* @return List of corners in race.
*/
List<Corner> getCorners();
/** /**
* Returns a list of coordinates representing the boundary of the race. * Returns a list of coordinates representing the boundary of the race.
* @return The boundary of the race. * @return The boundary of the race.
@ -83,4 +91,16 @@ public interface RaceDataSource {
* @return Bottom right GPS coordinate. * @return Bottom right GPS coordinate.
*/ */
GPSCoordinate getMapBottomRight(); GPSCoordinate getMapBottomRight();
/**
* Returns the sequence number associated with this data source. Used to indicate when it has changed.
* @return Sequence number.
*/
int getSequenceNumber();
/**
* Increments the sequence number for this data source. Used to indicate that it has changed.
*/
void incrementSequenceNumber();
} }

@ -84,6 +84,8 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
private RaceTypeEnum raceType; private RaceTypeEnum raceType;
private int sequenceNumber = 0;
/** /**
@ -343,7 +345,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
int cornerSeq = Integer.parseInt(getAttribute(cornerElement, "SeqID")); int cornerSeq = Integer.parseInt(getAttribute(cornerElement, "SeqID"));
cornersList.add(new Corner(cornerID, cornerSeq, "SP", 3)); cornersList.add(new Corner(cornerID, cornerSeq, cornerRounding, 3));
//Gets the CompoundMark associated with this corner. //Gets the CompoundMark associated with this corner.
CompoundMark lastCompoundMark = this.compoundMarkMap.get(cornerID); CompoundMark lastCompoundMark = this.compoundMarkMap.get(cornerID);
@ -366,7 +368,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
cornerSeq = Integer.parseInt(getAttribute(cornerElement, "SeqID")); cornerSeq = Integer.parseInt(getAttribute(cornerElement, "SeqID"));
cornersList.add(new Corner(cornerID, cornerSeq, "Port", 3)); cornersList.add(new Corner(cornerID, cornerSeq, getCompoundMarkRounding(cornerElement), 3));
//gets the Rounding of this corner element //gets the Rounding of this corner element
cornerRounding = getCompoundMarkRounding(cornerElement); cornerRounding = getCompoundMarkRounding(cornerElement);
@ -488,7 +490,17 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
return participants; return participants;
} }
public List<Corner> getCornersList() { public List<Corner> getCorners() {
return cornersList; return cornersList;
} }
@Override
public int getSequenceNumber() {
return sequenceNumber;
}
@Override
public void incrementSequenceNumber() {
sequenceNumber++;
}
} }

@ -66,5 +66,15 @@ public interface RegattaDataSource {
float getMagneticVariation(); float getMagneticVariation();
/**
* Returns the sequence number associated with this data source. Used to indicate when it has changed.
* @return Sequence number.
*/
int getSequenceNumber();
/**
* Increments the sequence number for this data source. Used to indicate that it has changed.
*/
void incrementSequenceNumber();
} }

@ -59,6 +59,8 @@ public class RegattaXMLReader extends XMLReader implements RegattaDataSource {
private float magneticVariation; private float magneticVariation;
private int sequenceNumber = 0;
/** /**
* Constructor for Regatta XML using a file. * Constructor for Regatta XML using a file.
@ -209,4 +211,14 @@ public class RegattaXMLReader extends XMLReader implements RegattaDataSource {
public GPSCoordinate getGPSCoordinate() { public GPSCoordinate getGPSCoordinate() {
return new GPSCoordinate(centralLatitude, centralLongitude); return new GPSCoordinate(centralLatitude, centralLongitude);
} }
@Override
public int getSequenceNumber() {
return sequenceNumber;
}
@Override
public void incrementSequenceNumber() {
sequenceNumber++;
}
} }

@ -46,4 +46,19 @@ public enum RoundingType {
return null; return null;
} }
} }
public static String getStringOf(RoundingType value) {
switch (value) {
case Port:
return "Port";
case Starboard:
return "Starboard";
case SP:
return "SP";
case PS:
return "PS";
default:
return null;
}
}
} }

@ -33,20 +33,26 @@ public class Constants {
* Frame periods are multiplied by this to get the amount of time a single frame represents. * Frame periods are multiplied by this to get the amount of time a single frame represents.
* E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred. * E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred.
*/ */
public static final int RaceTimeScale = 10; public static final int RaceTimeScale = 2;//10;
/** /**
* The race pre-start time, in milliseconds. 3 minutes. * The race pre-start time, in milliseconds. 3 minutes (30 seconds for development).
*/ */
public static final long RacePreStartTime = 1 * 10 * 1000; // public static final long RacePreStartTime = 30 * 1000;
public static final long RacePreStartTime = 1000;
/** /**
* The race preparatory time, in milliseconds. 1 minutes. * The race preparatory time, in milliseconds. 1 minute.
*/ */
// public static final long RacePreparatoryTime = 60 * 1000;
public static final long RacePreparatoryTime = 1 * 60 * 1000; public static final long RacePreparatoryTime = 1 * 60 * 1000;
/** /**
* The number of milliseconds in one hour. * The number of milliseconds in one hour.
* <br> * <br>

@ -1,318 +0,0 @@
package shared.model;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import mock.model.collider.ColliderRegistry;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RaceTypeEnum;
import shared.dataInput.BoatDataSource;
import shared.dataInput.RaceDataSource;
import shared.dataInput.RegattaDataSource;
import java.util.List;
/**
* Represents a yacht race.
* Has a course, state, wind, boundaries, etc.... Boats are added by inheriting classes (see {@link Boat}, {@link mock.model.MockBoat}, {@link visualiser.model.VisualiserBoat}.
*/
public abstract class Race {
/**
* The source of race related data.
*/
protected RaceDataSource raceDataSource;
/**
* The source of boat related data.
*/
protected BoatDataSource boatDataSource;
/**
* The source of regatta related data.
*/
protected RegattaDataSource regattaDataSource;
/**
* A list of compound marks in the race.
*/
protected List<CompoundMark> compoundMarks;
/**
* A list of legs in the race.
*/
protected List<Leg> legs;
/**
* A list of coordinates describing the boundary of the course.
*/
protected List<GPSCoordinate> boundary;
/**
* The clock which tracks the race's start time, current time, and elapsed duration.
*/
protected RaceClock raceClock;
/**
* The race ID of the course.
*/
protected int raceId;
/**
* The name of the regatta.
*/
protected String regattaName;
/**
* The current status of the race.
*/
protected RaceStatusEnum raceStatusEnum;
/**
* The type of race this is.
*/
protected RaceTypeEnum raceType;
/**
* The race's wind.
*/
protected Property<Wind> raceWind = new SimpleObjectProperty<>();
/**
* Registry for all collider object in this race
*/
protected ColliderRegistry colliderRegistry;
/**
* The number of frames per second.
* We essentially track the number of frames generated per second, over a one second period. When {@link #lastFpsResetTime} reaches 1 second, currentFps is reset.
*/
private int currentFps = 0;
/**
* The number of frames per second we generated over the last 1 second period.
*/
private IntegerProperty lastFps = new SimpleIntegerProperty(0);
/**
* The time, in milliseconds, since we last reset our {@link #currentFps} counter.
*/
private long lastFpsResetTime;
/**
* Constructs a race object with a given BoatDataSource, RaceDataSource, and RegattaDataSource.
* @param boatDataSource Data source for boat related data (yachts and marker boats).
* @param raceDataSource Data source for race related data (participating boats, legs, etc...).
* @param regattaDataSource Data source for race related data (course name, location, timezone, etc...).
*/
public Race(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource) {
//Keep a reference to data sources.
this.raceDataSource = raceDataSource;
this.boatDataSource = boatDataSource;
this.regattaDataSource = regattaDataSource;
//Marks.
this.compoundMarks = raceDataSource.getCompoundMarks();
//Boundaries.
this.boundary = raceDataSource.getBoundary();
//Legs.
this.useLegsList(raceDataSource.getLegs());
//Race ID.
this.raceId = raceDataSource.getRaceId();
//Regatta name.
this.regattaName = regattaDataSource.getRegattaName();
//Race clock.
this.raceClock = new RaceClock(this.raceDataSource.getStartDateTime());
//Race status.
this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);
//Race type.
this.raceType = raceDataSource.getRaceType();
//Wind.
this.setWind(Bearing.fromDegrees(0), 0);
// Set up colliders
this.colliderRegistry = new ColliderRegistry();
for(CompoundMark mark: compoundMarks) {
colliderRegistry.addCollider(mark.getMark1());
if(mark.getMark2() != null) colliderRegistry.addCollider(mark.getMark2());
}
}
public ColliderRegistry getColliderRegistry() {
return colliderRegistry;
}
/**
* Initialise the boats in the race.
* This sets their starting positions and current legs.
*/
protected abstract void initialiseBoats();
/**
* Updates the race to use a new list of legs, and adds a dummy "Finish" leg at the end.
* @param legs The new list of legs to use.
*/
protected void useLegsList(List<Leg> legs) {
//We add a "dummy" leg at the end of the race.
this.legs = legs;
this.legs.add(new Leg("Finish", this.legs.size()));
}
/**
* Determines whether or not a specific leg is the last leg in the race.
* @param leg The leg to check.
* @return Returns true if it is the last, false otherwise.
*/
protected boolean isLastLeg(Leg leg) {
//Get the last leg.
Leg lastLeg = this.legs.get(this.legs.size() - 1);
//Check its ID.
int lastLegID = lastLeg.getLegNumber();
//Get the specified leg's ID.
int legID = leg.getLegNumber();
//Check if they are the same.
return legID == lastLegID;
}
/**
* Returns the current race status.
* @return The current race status.
*/
public RaceStatusEnum getRaceStatusEnum() {
return raceStatusEnum;
}
/**
* Sets the current race status.
* @param raceStatusEnum The new status of the race.
*/
public void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) {
this.raceStatusEnum = raceStatusEnum;
}
/**
* Returns the type of race this is.
* @return The type of race this is.
*/
public RaceTypeEnum getRaceType() {
return raceType;
}
/**
* Returns the name of the regatta.
* @return The name of the regatta.
*/
public String getRegattaName() {
return regattaName;
}
/**
* Updates the race to have a specified wind bearing and speed.
* @param windBearing New wind bearing.
* @param windSpeedKnots New wind speed, in knots.
*/
protected void setWind(Bearing windBearing, double windSpeedKnots) {
Wind wind = new Wind(windBearing, windSpeedKnots);
setWind(wind);
}
/**
* Updates the race to have a specified wind (bearing and speed).
* @param wind New wind.
*/
protected void setWind(Wind wind) {
this.raceWind.setValue(wind);
}
/**
* Returns the wind bearing.
* @return The wind bearing.
*/
public Bearing getWindDirection() {
return raceWind.getValue().getWindDirection();
}
/**
* Returns the wind speed.
* Measured in knots.
* @return The wind speed.
*/
public double getWindSpeed() {
return raceWind.getValue().getWindSpeed();
}
/**
* Returns the RaceClock for this race.
* This is used to track the start time, current time, and elapsed duration of the race.
* @return The RaceClock for the race.
*/
public RaceClock getRaceClock() {
return raceClock;
}
/**
* Returns the RaceDataSource used for the race.
* @return The RaceDataSource used for the race.
*/
public RaceDataSource getRaceDataSource() {
return raceDataSource;
}
/**
* Returns the number of legs in the race.
* @return The number of legs in the race.
*/
public int getLegCount() {
//We minus one, as we have added an extra "dummy" leg.
return legs.size() - 1;
}
/**
* Returns the race boundary.
* @return The race boundary.
*/
public List<GPSCoordinate> getBoundary() {
return boundary;
}
/**
* Returns the marks of the race.
* @return Marks of the race.
*/
public List<CompoundMark> getCompoundMarks() {
return compoundMarks;
}
/**
* Returns the legs of the race.
* @return Legs of the race.
*/
public List<Leg> getLegs() {
return legs;
}
/**
* Returns the fps property.
* @return The fps property.
*/
public IntegerProperty fpsProperty() {
return lastFps;
}
/**
* Increments the FPS counter, and adds timePeriod milliseconds to our FPS reset timer.
* @param timePeriod Time, in milliseconds, to add to {@link #lastFpsResetTime}.
*/
protected void incrementFps(long timePeriod) {
//Increment.
this.currentFps++;
//Add period to timer.
this.lastFpsResetTime += timePeriod;
//If we have reached 1 second period, snapshot the framerate and reset.
if (this.lastFpsResetTime > 1000) {
this.lastFps.set(this.currentFps);
this.currentFps = 0;
this.lastFpsResetTime = 0;
}
}
public int getRaceId() {
return raceId;
}
}

@ -188,7 +188,20 @@ public abstract class RaceState {
* @return List of mark boats. * @return List of mark boats.
*/ */
public List<Mark> getMarks() { public List<Mark> getMarks() {
return new ArrayList<>(boatDataSource.getMarkerBoats().values()); //BoatDataSource contains a collection of Marks, and RaceDataSource contains a collection of Compound marks (which contain marks). RaceDataSource is the "definitive" source of mark data.
List<Mark> marks = new ArrayList<>(getCompoundMarks().size() * 2);
for (CompoundMark compoundMark : getCompoundMarks()) {
if (compoundMark.getMark1() != null) {
marks.add(compoundMark.getMark1());
}
if (compoundMark.getMark2() != null) {
marks.add(compoundMark.getMark2());
}
}
return marks;
} }
/** /**

@ -48,4 +48,11 @@ public class Wind {
return windSpeed; return windSpeed;
} }
public void setWindSpeed(double windSpeed) {
this.windSpeed = windSpeed;
}
public void setWindDirection(Bearing windDirection) {
this.windDirection = windDirection;
}
} }

@ -0,0 +1,131 @@
package shared.xml.Race;
import shared.dataInput.RaceDataSource;
import shared.enums.RoundingType;
import shared.model.CompoundMark;
import shared.model.Corner;
import shared.model.GPSCoordinate;
import shared.model.Leg;
import shared.xml.XMLUtilities;
import javax.xml.bind.JAXBException;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
/**
* Has functions to convert a {@link shared.dataInput.RaceDataSource} to an {@link XMLRace} object.
*/
public class RaceDataSourceToXML {
/**
* Converts a race data source to an XMLRace object.
* @param raceDataSource The data source to convert.
* @return The XMLRace file.
*/
public static XMLRace toXML(RaceDataSource raceDataSource) {
//Kind of ugly. Could be refactored/split up a bit.
XMLRace race = new XMLRace();
race.setRaceID(raceDataSource.getRaceId());
race.setRaceType(raceDataSource.getRaceType().toString());
race.setCreationTimeDate(raceDataSource.getCreationDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ")));
XMLRaceStartTime startTime = new XMLRaceStartTime();
startTime.setPostpone(String.valueOf(raceDataSource.getPostponed()));
startTime.setTime(raceDataSource.getStartDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ")));
race.setRaceStartTime(startTime);
XMLParticipants participants = new XMLParticipants();
participants.yacht = new ArrayList<>();
for (int i : raceDataSource.getParticipants()) {
XMLYacht yacht = new XMLYacht();
yacht.setSourceID(i);
participants.yacht.add(yacht);
}
race.setParticipants(participants);
XMLCourseLimit courseLimit = new XMLCourseLimit();
courseLimit.limit = new ArrayList<>();
for (int i = 0; i < raceDataSource.getBoundary().size(); i++) {
XMLLimit limit = new XMLLimit();
limit.setLat(raceDataSource.getBoundary().get(i).getLatitude());
limit.setLon(raceDataSource.getBoundary().get(i).getLongitude());
limit.setSeqID(i + 1);
courseLimit.limit.add(limit);
}
race.setCourseLimit(courseLimit);
XMLCourse course = new XMLCourse();
course.compoundMark = new ArrayList<>();
for (CompoundMark compoundMark : raceDataSource.getCompoundMarks()) {
XMLCompoundMark xmlCompoundMark = new XMLCompoundMark();
xmlCompoundMark.setCompoundMarkID(compoundMark.getCompoundMarkID());
xmlCompoundMark.setName(compoundMark.getName());
if (compoundMark.getMark1() != null) {
XMLMark xmlMark = new XMLMark();
xmlMark.setName(compoundMark.getMark1().getName());
xmlMark.setSourceID(compoundMark.getMark1().getSourceID());
xmlMark.setSeqId(1);
xmlMark.setTargetLat(compoundMark.getMark1().getPosition().getLatitude());
xmlMark.setTargetLng(compoundMark.getMark1().getPosition().getLongitude());
xmlCompoundMark.getMark().add(xmlMark);
}
if (compoundMark.getMark2() != null) {
XMLMark xmlMark = new XMLMark();
xmlMark.setName(compoundMark.getMark2().getName());
xmlMark.setSourceID(compoundMark.getMark2().getSourceID());
xmlMark.setSeqId(2);
xmlMark.setTargetLat(compoundMark.getMark2().getPosition().getLatitude());
xmlMark.setTargetLng(compoundMark.getMark2().getPosition().getLongitude());
xmlCompoundMark.getMark().add(xmlMark);
}
course.compoundMark.add(xmlCompoundMark);
}
race.setCourse(course);
XMLCompoundMarkSequence compoundMarkSequence = new XMLCompoundMarkSequence();
compoundMarkSequence.corner = new ArrayList<>();
for (Corner corner : raceDataSource.getCorners()) {
XMLCorner xmlCorner = new XMLCorner();
xmlCorner.setZoneSize(corner.getZoneSize());
xmlCorner.setSeqID(corner.getSeqID());
xmlCorner.setCompoundMarkID(corner.getCompoundMarkID());
xmlCorner.setRounding(corner.getRounding());
compoundMarkSequence.corner.add(xmlCorner);
}
race.setCompoundMarkSequence(compoundMarkSequence);
return race;
}
/**
* Converts a race data source to an xml string.
* @param raceDataSource Data source to convert.
* @return String containing xml file.
* @throws JAXBException Thrown if it cannot be converted.
*/
public static String toString(RaceDataSource raceDataSource) throws JAXBException {
XMLRace race = toXML(raceDataSource);
return XMLUtilities.classToXML(race);
}
}

@ -20,10 +20,17 @@ import java.io.*;
import java.net.URL; import java.net.URL;
/** /**
* Created by fwy13 on 13/08/17. * Contains utility functions to convert between xml files and xml class objects.
*/ */
public class XMLUtilities { public class XMLUtilities {
/**
* Converts an XML class object to an XML string.
* @param o The XML class object to convert.
* @return String containing the serialised XML data.
* @throws JAXBException Thrown if the object is cannot be serialised to XML.
*/
public static String classToXML(Object o) throws JAXBException { public static String classToXML(Object o) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(o.getClass()); JAXBContext context = JAXBContext.newInstance(o.getClass());
Marshaller jaxbMarshaller = context.createMarshaller(); Marshaller jaxbMarshaller = context.createMarshaller();
@ -49,14 +56,26 @@ public class XMLUtilities {
return xmlToClass(document, schemaURL, c); return xmlToClass(document, schemaURL, c);
} }
public static Object xmlToClass(InputStream i, URL schemaURL, Class c) throws ParserConfigurationException, IOException, SAXException, JAXBException { /**
* Converts an XML file to an XML class (e.g., {@link shared.xml.Race.XMLRace}).
* @param i The input stream for the XML file.
* @param schemaURL URL for the XML schema.
* @param c The XML class to convert to.
* @param <T> The XML class to convert to.
* @return The XML class object.
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @throws JAXBException
*/
public static <T> T xmlToClass(InputStream i, URL schemaURL, Class<T> c) throws ParserConfigurationException, IOException, SAXException, JAXBException {
DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = parser.parse(i); Document document = parser.parse(i);
return xmlToClass(document, schemaURL, c); return xmlToClass(document, schemaURL, c);
} }
public static Object xmlToClass(Document document, URL schemaURL, Class c) throws ParserConfigurationException, IOException, SAXException, JAXBException { public static <T> T xmlToClass(Document document, URL schemaURL, Class<T> c) throws ParserConfigurationException, IOException, SAXException, JAXBException {
JAXBContext jc = JAXBContext.newInstance(c); JAXBContext jc = JAXBContext.newInstance(c);
Unmarshaller unmarshaller = jc.createUnmarshaller(); Unmarshaller unmarshaller = jc.createUnmarshaller();
@ -64,7 +83,7 @@ public class XMLUtilities {
Schema schema = sf.newSchema(schemaURL); Schema schema = sf.newSchema(schemaURL);
unmarshaller.setSchema(schema); unmarshaller.setSchema(schema);
return unmarshaller.unmarshal(new DOMSource(document)); return (T) unmarshaller.unmarshal(new DOMSource(document));
} }
public static boolean validateXML(String file, URL schemaURL){ public static boolean validateXML(String file, URL schemaURL){

@ -0,0 +1,533 @@
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2017.09.01 at 11:12:43 PM NZST
//
package shared.xml.boats;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;sequence&gt;
* &lt;element name="Boats"&gt;
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;sequence&gt;
* &lt;element name="Boat" maxOccurs="unbounded"&gt;
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;sequence&gt;
* &lt;element name="GPSposition"&gt;
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;attribute name="X" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;attribute name="Y" use="required" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;attribute name="Z" use="required" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* &lt;/element&gt;
* &lt;/sequence&gt;
* &lt;attribute name="Type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="BoatName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="SourceID" use="required" type="{http://www.w3.org/2001/XMLSchema}int" /&gt;
* &lt;attribute name="HullNum" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="ShapeID" type="{http://www.w3.org/2001/XMLSchema}int" /&gt;
* &lt;attribute name="StoweName" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* &lt;/element&gt;
* &lt;/sequence&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* &lt;/element&gt;
* &lt;/sequence&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"boats"
})
@XmlRootElement(name = "BoatConfig")
public class BoatConfig {
@XmlElement(name = "Boats", required = true)
protected BoatConfig.Boats boats;
/**
* Gets the value of the boats property.
*
* @return
* possible object is
* {@link BoatConfig.Boats }
*
*/
public BoatConfig.Boats getBoats() {
return boats;
}
/**
* Sets the value of the boats property.
*
* @param value
* allowed object is
* {@link BoatConfig.Boats }
*
*/
public void setBoats(BoatConfig.Boats value) {
this.boats = value;
}
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;sequence&gt;
* &lt;element name="Boat" maxOccurs="unbounded"&gt;
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;sequence&gt;
* &lt;element name="GPSposition"&gt;
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;attribute name="X" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;attribute name="Y" use="required" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;attribute name="Z" use="required" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* &lt;/element&gt;
* &lt;/sequence&gt;
* &lt;attribute name="Type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="BoatName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="SourceID" use="required" type="{http://www.w3.org/2001/XMLSchema}int" /&gt;
* &lt;attribute name="HullNum" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="ShapeID" type="{http://www.w3.org/2001/XMLSchema}int" /&gt;
* &lt;attribute name="StoweName" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* &lt;/element&gt;
* &lt;/sequence&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"boat"
})
public static class Boats {
@XmlElement(name = "Boat", required = true)
protected List<BoatConfig.Boats.Boat> boat;
/**
* Gets the value of the boat property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the boat property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getBoat().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link BoatConfig.Boats.Boat }
*
*
*/
public List<BoatConfig.Boats.Boat> getBoat() {
if (boat == null) {
boat = new ArrayList<BoatConfig.Boats.Boat>();
}
return this.boat;
}
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;sequence&gt;
* &lt;element name="GPSposition"&gt;
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;attribute name="X" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;attribute name="Y" use="required" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;attribute name="Z" use="required" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* &lt;/element&gt;
* &lt;/sequence&gt;
* &lt;attribute name="Type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="BoatName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="SourceID" use="required" type="{http://www.w3.org/2001/XMLSchema}int" /&gt;
* &lt;attribute name="HullNum" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;attribute name="ShapeID" type="{http://www.w3.org/2001/XMLSchema}int" /&gt;
* &lt;attribute name="StoweName" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"gpSposition"
})
public static class Boat {
@XmlElement(name = "GPSposition", required = true)
protected BoatConfig.Boats.Boat.GPSposition gpSposition;
@XmlAttribute(name = "Type", required = true)
protected String type;
@XmlAttribute(name = "BoatName", required = true)
protected String boatName;
@XmlAttribute(name = "SourceID", required = true)
protected int sourceID;
@XmlAttribute(name = "HullNum")
protected String hullNum;
@XmlAttribute(name = "ShortName")
protected String shortName;
@XmlAttribute(name = "ShapeID")
protected Integer shapeID;
@XmlAttribute(name = "StoweName")
protected String stoweName;
/**
* Gets the value of the gpSposition property.
*
* @return
* possible object is
* {@link BoatConfig.Boats.Boat.GPSposition }
*
*/
public BoatConfig.Boats.Boat.GPSposition getGPSposition() {
return gpSposition;
}
/**
* Sets the value of the gpSposition property.
*
* @param value
* allowed object is
* {@link BoatConfig.Boats.Boat.GPSposition }
*
*/
public void setGPSposition(BoatConfig.Boats.Boat.GPSposition value) {
this.gpSposition = value;
}
/**
* Gets the value of the type property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getType() {
return type;
}
/**
* Sets the value of the type property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setType(String value) {
this.type = value;
}
/**
* Gets the value of the boatName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getBoatName() {
return boatName;
}
/**
* Sets the value of the boatName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setBoatName(String value) {
this.boatName = value;
}
/**
* Gets the value of the sourceID property.
*
*/
public int getSourceID() {
return sourceID;
}
/**
* Sets the value of the sourceID property.
*
*/
public void setSourceID(int value) {
this.sourceID = value;
}
/**
* Gets the value of the hullNum property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getHullNum() {
return hullNum;
}
/**
* Sets the value of the hullNum property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setHullNum(String value) {
this.hullNum = value;
}
/**
* Gets the value of the shortName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getShortName() {
return shortName;
}
/**
* Sets the value of the shortName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setShortName(String value) {
this.shortName = value;
}
/**
* Gets the value of the shapeID property.
*
* @return
* possible object is
* {@link Integer }
*
*/
public Integer getShapeID() {
return shapeID;
}
/**
* Sets the value of the shapeID property.
*
* @param value
* allowed object is
* {@link Integer }
*
*/
public void setShapeID(Integer value) {
this.shapeID = value;
}
/**
* Gets the value of the stoweName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getStoweName() {
return stoweName;
}
/**
* Sets the value of the stoweName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setStoweName(String value) {
this.stoweName = value;
}
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;attribute name="X" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;attribute name="Y" use="required" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;attribute name="Z" use="required" type="{http://www.w3.org/2001/XMLSchema}double" /&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "")
public static class GPSposition {
@XmlAttribute(name = "X", required = true)
protected Double x;
@XmlAttribute(name = "Y", required = true)
protected double y;
@XmlAttribute(name = "Z", required = true)
protected double z;
/**
* Gets the value of the x property.
*
* @return
* possible object is
* {@link Double }
*
*/
public Double getX() {
return x;
}
/**
* Sets the value of the x property.
*
* @param value
* allowed object is
* {@link Double }
*
*/
public void setX(Double value) {
this.x = value;
}
/**
* Gets the value of the y property.
*
*/
public double getY() {
return y;
}
/**
* Sets the value of the y property.
*
*/
public void setY(double value) {
this.y = value;
}
/**
* Gets the value of the z property.
*
*/
public double getZ() {
return z;
}
/**
* Sets the value of the z property.
*
*/
public void setZ(double value) {
this.z = value;
}
}
}
}
}

@ -0,0 +1,86 @@
package shared.xml.boats;
import shared.dataInput.BoatDataSource;
import shared.dataInput.RaceDataSource;
import shared.enums.RoundingType;
import shared.model.Boat;
import shared.model.CompoundMark;
import shared.model.Leg;
import shared.model.Mark;
import shared.xml.Race.*;
import shared.xml.XMLUtilities;
import javax.xml.bind.JAXBException;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
/**
* Has functions to convert a {@link shared.dataInput.BoatDataSource} to an {@link BoatConfig} object.
*/
public class BoatDataSourceToXML {
/**
* Converts a boat data source to an XMLRace object.
* @param boatDataSource The data source to convert.
* @return The XMLRace file.
*/
public static BoatConfig toXML(BoatDataSource boatDataSource) {
BoatConfig boatConfig = new BoatConfig();
boatConfig.boats = new BoatConfig.Boats();
boatConfig.boats.boat = new ArrayList<>();
for (Boat boat : boatDataSource.getBoats().values()) {
BoatConfig.Boats.Boat xmlBoat = new BoatConfig.Boats.Boat();
xmlBoat.setType("Yacht");
xmlBoat.setBoatName(boat.getName());
xmlBoat.setSourceID(boat.getSourceID());
xmlBoat.setStoweName(boat.getCountry());
xmlBoat.setShortName(boat.getCountry());
BoatConfig.Boats.Boat.GPSposition position = new BoatConfig.Boats.Boat.GPSposition();
position.setX(boat.getPosition().getLongitude());
position.setY(boat.getPosition().getLatitude());
position.setZ(0);
xmlBoat.setGPSposition(position);
boatConfig.boats.boat.add(xmlBoat);
}
for (Mark mark : boatDataSource.getMarkerBoats().values()) {
BoatConfig.Boats.Boat xmlBoat = new BoatConfig.Boats.Boat();
xmlBoat.setType("Mark");
xmlBoat.setBoatName(mark.getName());
xmlBoat.setSourceID(mark.getSourceID());
BoatConfig.Boats.Boat.GPSposition position = new BoatConfig.Boats.Boat.GPSposition();
position.setX(mark.getPosition().getLongitude());
position.setY(mark.getPosition().getLatitude());
position.setZ(0);
xmlBoat.setGPSposition(position);
boatConfig.boats.boat.add(xmlBoat);
}
return boatConfig;
}
/**
* Converts a boat data source to an xml string.
* @param boatDataSource Data source to convert.
* @return String containing xml file.
* @throws JAXBException Thrown if it cannot be converted.
*/
public static String toString(BoatDataSource boatDataSource) throws JAXBException {
BoatConfig boats = toXML(boatDataSource);
return XMLUtilities.classToXML(boats);
}
}

@ -0,0 +1,71 @@
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2017.09.01 at 11:12:43 PM NZST
//
package shared.xml.boats;
import javax.xml.bind.annotation.XmlRegistry;
/**
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the aaa package.
* <p>An ObjectFactory allows you to programatically
* construct new instances of the Java representation
* for XML content. The Java representation of XML
* content can consist of schema derived interfaces
* and classes representing the binding of schema
* type definitions, element declarations and model
* groups. Factory methods for each of these are
* provided in this class.
*
*/
@XmlRegistry
public class ObjectFactory {
/**
* Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: aaa
*
*/
public ObjectFactory() {
}
/**
* Create an instance of {@link BoatConfig }
*
*/
public BoatConfig createBoatConfig() {
return new BoatConfig();
}
/**
* Create an instance of {@link BoatConfig.Boats }
*
*/
public BoatConfig.Boats createBoatConfigBoats() {
return new BoatConfig.Boats();
}
/**
* Create an instance of {@link BoatConfig.Boats.Boat }
*
*/
public BoatConfig.Boats.Boat createBoatConfigBoatsBoat() {
return new BoatConfig.Boats.Boat();
}
/**
* Create an instance of {@link BoatConfig.Boats.Boat.GPSposition }
*
*/
public BoatConfig.Boats.Boat.GPSposition createBoatConfigBoatsBoatGPSposition() {
return new BoatConfig.Boats.Boat.GPSposition();
}
}

@ -0,0 +1,47 @@
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2017.09.01 at 10:37:23 PM NZST
//
package shared.xml.regatta;
import javax.xml.bind.annotation.XmlRegistry;
/**
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the aaa package.
* <p>An ObjectFactory allows you to programatically
* construct new instances of the Java representation
* for XML content. The Java representation of XML
* content can consist of schema derived interfaces
* and classes representing the binding of schema
* type definitions, element declarations and model
* groups. Factory methods for each of these are
* provided in this class.
*
*/
@XmlRegistry
public class ObjectFactory {
/**
* Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: aaa
*
*/
public ObjectFactory() {
}
/**
* Create an instance of {@link RegattaConfig }
*
*/
public RegattaConfig createRegattaConfig() {
return new RegattaConfig();
}
}

@ -0,0 +1,219 @@
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2017.09.01 at 10:37:23 PM NZST
//
package shared.xml.regatta;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;sequence&gt;
* &lt;element name="RegattaID" type="{http://www.w3.org/2001/XMLSchema}int"/&gt;
* &lt;element name="RegattaName" type="{http://www.w3.org/2001/XMLSchema}string"/&gt;
* &lt;element name="CourseName" type="{http://www.w3.org/2001/XMLSchema}string"/&gt;
* &lt;element name="CentralLatitude" type="{http://www.w3.org/2001/XMLSchema}double"/&gt;
* &lt;element name="CentralLongitude" type="{http://www.w3.org/2001/XMLSchema}double"/&gt;
* &lt;element name="CentralAltitude" type="{http://www.w3.org/2001/XMLSchema}double"/&gt;
* &lt;element name="UtcOffset" type="{http://www.w3.org/2001/XMLSchema}double"/&gt;
* &lt;element name="MagneticVariation" type="{http://www.w3.org/2001/XMLSchema}double"/&gt;
* &lt;/sequence&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"regattaID",
"regattaName",
"courseName",
"centralLatitude",
"centralLongitude",
"centralAltitude",
"utcOffset",
"magneticVariation"
})
@XmlRootElement(name = "RegattaConfig")
public class RegattaConfig {
@XmlElement(name = "RegattaID")
protected int regattaID;
@XmlElement(name = "RegattaName", required = true)
protected String regattaName;
@XmlElement(name = "CourseName", required = true)
protected String courseName;
@XmlElement(name = "CentralLatitude")
protected double centralLatitude;
@XmlElement(name = "CentralLongitude")
protected double centralLongitude;
@XmlElement(name = "CentralAltitude")
protected double centralAltitude;
@XmlElement(name = "UtcOffset")
protected double utcOffset;
@XmlElement(name = "MagneticVariation")
protected double magneticVariation;
/**
* Gets the value of the regattaID property.
*
*/
public int getRegattaID() {
return regattaID;
}
/**
* Sets the value of the regattaID property.
*
*/
public void setRegattaID(int value) {
this.regattaID = value;
}
/**
* Gets the value of the regattaName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getRegattaName() {
return regattaName;
}
/**
* Sets the value of the regattaName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setRegattaName(String value) {
this.regattaName = value;
}
/**
* Gets the value of the courseName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getCourseName() {
return courseName;
}
/**
* Sets the value of the courseName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setCourseName(String value) {
this.courseName = value;
}
/**
* Gets the value of the centralLatitude property.
*
*/
public double getCentralLatitude() {
return centralLatitude;
}
/**
* Sets the value of the centralLatitude property.
*
*/
public void setCentralLatitude(double value) {
this.centralLatitude = value;
}
/**
* Gets the value of the centralLongitude property.
*
*/
public double getCentralLongitude() {
return centralLongitude;
}
/**
* Sets the value of the centralLongitude property.
*
*/
public void setCentralLongitude(double value) {
this.centralLongitude = value;
}
/**
* Gets the value of the centralAltitude property.
*
*/
public double getCentralAltitude() {
return centralAltitude;
}
/**
* Sets the value of the centralAltitude property.
*
*/
public void setCentralAltitude(double value) {
this.centralAltitude = value;
}
/**
* Gets the value of the utcOffset property.
*
*/
public double getUtcOffset() {
return utcOffset;
}
/**
* Sets the value of the utcOffset property.
*
*/
public void setUtcOffset(double value) {
this.utcOffset = value;
}
/**
* Gets the value of the magneticVariation property.
*
*/
public double getMagneticVariation() {
return magneticVariation;
}
/**
* Sets the value of the magneticVariation property.
*
*/
public void setMagneticVariation(double value) {
this.magneticVariation = value;
}
}

@ -0,0 +1,54 @@
package shared.xml.regatta;
import shared.dataInput.RegattaDataSource;
import shared.xml.Race.XMLRace;
import shared.xml.XMLUtilities;
import javax.xml.bind.JAXBException;
/**
* Has functions to convert a {@link shared.dataInput.RegattaDataSource} to an {@link RegattaConfig} object.
*/
public class RegattaDataSourceToXML {
/**
* Converts a regatta data source to an XMLRace object.
* @param regattaDataSource The data source to convert.
* @return The XMLRace file.
*/
public static RegattaConfig toXML(RegattaDataSource regattaDataSource) {
RegattaConfig regatta = new RegattaConfig();
regatta.setCentralAltitude(regattaDataSource.getCentralAltitude());
regatta.setCentralLatitude(regattaDataSource.getCentralLatitude());
regatta.setCentralLongitude(regattaDataSource.getCentralLongitude());
regatta.setCourseName(regattaDataSource.getCourseName());
regatta.setRegattaName(regattaDataSource.getRegattaName());
regatta.setMagneticVariation(regattaDataSource.getMagneticVariation());
regatta.setRegattaID(regattaDataSource.getRegattaID());
regatta.setUtcOffset(regattaDataSource.getUtcOffset());
return regatta;
}
/**
* Converts a regatta data source to an xml string.
* @param regattaDataSource Data source to convert.
* @return String containing xml file.
* @throws JAXBException Thrown if it cannot be converted.
*/
public static String toString(RegattaDataSource regattaDataSource) throws JAXBException {
RegattaConfig regatta = toXML(regattaDataSource);
return XMLUtilities.classToXML(regatta);
}
}

@ -71,8 +71,10 @@ public class FinishController extends Controller {
//Winner label. //Winner label.
raceWinnerLabel.setText("Winner: "+ boatNameColumn.getCellObservableValue(0).getValue()); if (boats.size() > 0) {
raceWinnerLabel.setWrapText(true); raceWinnerLabel.setText("Winner: " + boatNameColumn.getCellObservableValue(0).getValue());
raceWinnerLabel.setWrapText(true);
}
} }

@ -1,8 +1,13 @@
package visualiser.Controllers; package visualiser.Controllers;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.scene.media.AudioClip; import javafx.scene.media.AudioClip;
import mock.app.Event; import mock.app.Event;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@ -17,6 +22,8 @@ import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -36,11 +43,30 @@ public class HostController extends Controller {
@FXML @FXML
AnchorPane hostWrapper; AnchorPane hostWrapper;
@FXML
Button previousButton;
@FXML
Button nextButton;
@FXML
ImageView mapImage;
private Event game; private Event game;
private ArrayList<Image> listOfMaps;
private int currentMapIndex = 0;
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources){
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));
} }
/** /**
@ -49,7 +75,7 @@ public class HostController extends Controller {
*/ */
public void hostGamePressed() throws IOException{ public void hostGamePressed() throws IOException{
try { try {
this.game = new Event(false); this.game = new Event(false, currentMapIndex);
connectSocket("localhost", 4942); connectSocket("localhost", 4942);
} catch (EventConstructionException e) { } catch (EventConstructionException e) {
Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e); Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e);
@ -82,9 +108,13 @@ public class HostController extends Controller {
* Hosts a game. * Hosts a game.
*/ */
public void hostGame(){ public void hostGame(){
mapImage.fitWidthProperty().bind(((Stage) mapImage.getScene().getWindow()).widthProperty().multiply(0.6));
hostWrapper.setVisible(true); hostWrapper.setVisible(true);
} }
/**
* Menu button pressed. Prompt alert then return to menu
*/
public void menuBtnPressed(){ public void menuBtnPressed(){
AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm()); AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play(); sound.play();
@ -92,4 +122,27 @@ public class HostController extends Controller {
parent.enterTitle(); parent.enterTitle();
} }
public void nextImage(){
increaseIndex();
mapImage.setImage(listOfMaps.get(currentMapIndex));
}
public void previousImage(){
decreaseIndex();
mapImage.setImage(listOfMaps.get(currentMapIndex));
}
private void increaseIndex(){
currentMapIndex = (currentMapIndex + 1)%listOfMaps.size();
}
private void decreaseIndex(){
currentMapIndex = ((((currentMapIndex - 1)%listOfMaps.size())+listOfMaps.size())%listOfMaps.size());
}
public void setGameType(int gameType){
this.currentMapIndex = gameType;
}
public int getGameType(){ return this.currentMapIndex; }
} }

@ -0,0 +1,247 @@
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;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
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 {
private @FXML Button btnSave;
private @FXML Button btnCancel;
private @FXML Button btnReset;
private @FXML ListView lstControl;
private @FXML ListView lstKey;
private @FXML ListView lstDescription;
private @FXML AnchorPane anchor;
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
newKeyFactory = copyExistingFactory();
initializeTable();
populateTable();
setKeyListener();
}
/**
* Sets up table before populating it.
* Set up includes headings, CSS styling and modifying default properties.
*/
public void initializeTable(){
// set the headings for each column
lstKey.getItems().add("Key");
lstControl.getItems().add("Command");
lstDescription.getItems().add("Description");
lstKey.getSelectionModel().select(0);
lstControl.getSelectionModel().select(0);
lstDescription.getSelectionModel().select(0);
// add CSS stylesheet once the scene has been created
lstKey.sceneProperty().addListener((obs, oldScene, newScene) -> {
if (newScene != null) {
newScene.getStylesheets().add("/css/keyBindings.css");
}
});
// stop the columns from being selectable, so only the buttons are
lstKey.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) ->
Platform.runLater(() ->
lstKey.getSelectionModel().select(0)));
lstDescription.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) ->
Platform.runLater(() ->
lstDescription.getSelectionModel().select(0)));
lstControl.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) ->
Platform.runLater(() ->
lstControl.getSelectionModel().select(0)));
}
/**
* Populates the table with commands and their key binding details.
*/
public void populateTable(){
// add each command to the table
for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet()) {
// create button for command
Button button = new Button(entry.getKey());
button.setMinWidth(120);
button.setId(entry.getValue().toString());
button.setOnAction(e -> currentButton = button);
// display details for command in table
lstControl.getItems().add(entry.getValue());
lstKey.getItems().add(button);
lstDescription.getItems().add(entry.getValue().getProtocolCode());
}
}
/**
* Makes a copy of the {@link KeyFactory} that does not modify the original.
* @return new keyfactory to be modified
*/
public KeyFactory copyExistingFactory(){
newKeyFactory = new KeyFactory();
Map<String, ControlKey> oldKeyState = keyFactory.getKeyState();
Map<String, ControlKey> newKeyState = new HashMap<>();
// copy over commands and their keys
for (Map.Entry<String, ControlKey> entry : oldKeyState.entrySet()){
newKeyState.put(entry.getKey(), entry.getValue());
}
newKeyFactory.setKeyState(newKeyState);
return newKeyFactory;
}
/**
* 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(){
anchor.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
// if esc, cancel current button click
if (event.getCode() == KeyCode.ESCAPE){
btnCancel.requestFocus();
currentButton = null;
}
// if a button was clicked
else if (currentButton != null) {
// check if a button is already mapped to this key
for (int i = 1; i < lstKey.getItems().size(); i++) {
Button button = (Button)lstKey.getItems().get(i);
// update buttons text and remove key binding from command
if (button.getText().equals(event.getCode().toString())) {
button.setText("");
newKeyFactory.updateKey(button.getId(), button.getId());
}
}
// update text on the button
currentButton.setText(event.getCode().toString());
// update the control key
newKeyFactory.updateKey(event.getCode().toString(),
currentButton.getId());
// remove current button selection
currentButton = null;
changed = true;
btnCancel.requestFocus();
}
event.consume();
});
}
/**
* Cancel and exits the key bindings menu. Changes are not forced to be
* saved or fixed if invalid, and instead are defaulted back to the last
* successful saved state.
*/
public void cancel(){
((Stage)btnCancel.getScene().getWindow()).close();
}
/**
* Resets all key bindings to the built-in defaults.
*/
public void reset(){
lstKey.getItems().clear();
lstControl.getItems().clear();
lstDescription.getItems().clear();
newKeyFactory = new KeyFactory();
initializeTable();
populateTable();
changed = true;
}
/**
* Replace existing {@link KeyFactory} with the modified key bindings.
*/
public void save(){
if (isFactoryValid()) {
keyFactory = newKeyFactory;
newKeyFactory = new KeyFactory();
changed = false;
keyFactory.save(); // save persistently
loadNotification("Key bindings were successfully saved.", false);
} else {
loadNotification("One or more key bindings are missing. " +
"Failed to save.", true);
}
}
/**
* Checks the {@link KeyFactory} being modified is valid and that no
* commands are missing a key binding.
* @return True if valid, false if invalid
*/
public Boolean isFactoryValid(){
for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet
()) {
if (entry.getKey().toString()==entry.getValue().toString()){
return false;
}
}
return true;
}
/**
* Method used to stop a user from exiting key bindings without saving
* their changes to the {@link KeyFactory}.
* @param we {@link WindowEvent} close request to be consumed if settings
* have not been successfully saved.
*/
public void onExit(WindowEvent we){
// if modified KeyFactory hasn't been saved
if (changed){
loadNotification("Please cancel or save your changes before exiting" +
".", true);
we.consume();
}
}
/**
* Loads a popup window giving confirmation/warning of user activity.
* @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"));
try {
root = loader.load();
} 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);
}
}

@ -22,7 +22,7 @@ import java.util.ResourceBundle;
public class LobbyController extends Controller { public class LobbyController extends Controller {
@FXML @FXML
AnchorPane lobbyWrapper; private AnchorPane lobbyWrapper;
@FXML @FXML
private TableView<RaceConnection> lobbyTable; private TableView<RaceConnection> lobbyTable;
@FXML @FXML

@ -43,6 +43,7 @@ public class MainController extends Controller {
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race). * Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
* @param visualiserRace The object modelling the race. * @param visualiserRace The object modelling the race.
* @param controllerClient Socket Client that manipulates the controller. * @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) { public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
raceController.startRace(visualiserRace, controllerClient, isHost); raceController.startRace(visualiserRace, controllerClient, isHost);
@ -97,6 +98,20 @@ public class MainController extends Controller {
*/ */
public void startCss(){titleController.setDayMode();} public void startCss(){titleController.setDayMode();}
/**
* host controller host a game
* @throws IOException throws exception
*/
public void beginGame() throws IOException {
hostController.hostGamePressed();
}
public void setGameType(int gameType){
hostController.setGameType(gameType);
}
public int getGameType(){ return hostController.getGameType(); }
/** /**
* Main Controller for the applications will house the menu and the displayed pane. * Main Controller for the applications will house the menu and the displayed pane.
* *

@ -0,0 +1,35 @@
package visualiser.Controllers;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
/**
* Controller for a popup notification regarding user activity.
*/
public class NotificationController {
private @FXML Label lblDescription;
private @FXML Text txtMessage;
/**
* Closes the popup window once clicked.
*/
public void ok(){
((Stage)lblDescription.getScene().getWindow()).close();
}
/**
* Displays the appropriate popup notification.
* @param message message for the user
* @param warning if true warning text shown, if false success text shown
*/
public void setMessage(String message, Boolean warning){
lblDescription.setText(message);
if (!warning){
txtMessage.setText("Success!");
txtMessage.setFill(Color.GREEN);
}
}
}

@ -1,6 +1,6 @@
package visualiser.Controllers; package visualiser.Controllers;
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
import javafx.animation.AnimationTimer; import javafx.animation.AnimationTimer;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@ -10,82 +10,81 @@ import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.chart.LineChart; import javafx.scene.chart.LineChart;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane; 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 javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.RaceDataSource;
import shared.model.Leg; import shared.model.Leg;
import shared.model.Mark;
import visualiser.app.App; import visualiser.app.App;
import visualiser.enums.TutorialState;
import visualiser.gameController.ControllerClient; import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey; import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory; import visualiser.layout.Subject3D;
import visualiser.layout.View3D;
import visualiser.model.*; import visualiser.model.*;
import visualiser.utils.GPSConverter;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.Optional; import java.util.*;
import java.util.ResourceBundle;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import static visualiser.app.App.keyFactory;
/** /**
* Controller used to display a running race. * Controller used to display a running race.
*/ */
public class RaceController extends Controller { public class RaceController extends Controller {
/** /**
* The race object which describes the currently occurring race. * The race object which describes the currently occurring race.
*/ */
private VisualiserRaceEvent visualiserRace; private VisualiserRaceEvent visualiserRace;
/** /**
* Service for sending keystrokes to server * Service for sending keystrokes to server
*/ */
private ControllerClient controllerClient; private ControllerClient controllerClient;
private boolean isHost; private boolean isHost;
/** private TutorialState currentState;
* The canvas that draws the race.
*/ private ArrayList<TutorialState> tutorialStates;
private ResizableRaceCanvas raceCanvas;
private boolean isTutorial = false;
private String keyToPress;
/**
* The sparkline graph.
*/
private Sparkline sparkline;
/** /**
* state of the info table * state of the info table
*/ */
private boolean infoTableShow; private boolean infoTableShow;
private View3D view3D;
private ObservableList<Subject3D> viewSubjects;
/** /**
* The arrow controller. * The arrow controller.
*/ */
@FXML private ArrowController arrowController; @FXML private ArrowController arrowController;
@FXML private GridPane canvasBase; @FXML private GridPane canvasBase;
@FXML private SplitPane racePane;
@FXML private SplitPane race; @FXML private Label tutorialText;
/**
* 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. * This is the pane we place the actual arrow control inside of.
@ -101,9 +100,6 @@ public class RaceController extends Controller {
@FXML private TableColumn<VisualiserBoat, Leg> boatMarkColumn; @FXML private TableColumn<VisualiserBoat, Leg> boatMarkColumn;
@FXML private TableColumn<VisualiserBoat, Number> boatSpeedColumn; @FXML private TableColumn<VisualiserBoat, Number> boatSpeedColumn;
@FXML private LineChart<Number, Number> sparklineChart; @FXML private LineChart<Number, Number> sparklineChart;
@FXML private AnchorPane annotationPane;
/** /**
* Ctor. * Ctor.
@ -113,11 +109,10 @@ public class RaceController extends Controller {
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
KeyFactory keyFactory = KeyFactory.getFactory();
infoTableShow = true; infoTableShow = true;
// Initialise keyboard handler // Initialise keyboard handler
race.addEventFilter(KeyEvent.KEY_PRESSED, event -> { racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
String codeString = event.getCode().toString(); String codeString = event.getCode().toString();
if (codeString.equals("TAB")){toggleTable();} if (codeString.equals("TAB")){toggleTable();}
@ -126,13 +121,27 @@ public class RaceController extends Controller {
if(controlKey != null) { if(controlKey != null) {
try { try {
controlKey.onAction(); // Change key state if applicable 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); controllerClient.sendKey(controlKey);
event.consume(); event.consume();
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
Logger.getGlobal().log(Level.WARNING, "RaceController 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();
} }
} }
if(event.getCode() == KeyCode.ESCAPE) { if(event.getCode() == KeyCode.ESCAPE) {
try { try {
@ -143,7 +152,7 @@ public class RaceController extends Controller {
Optional<ButtonType> result = alert.showAndWait(); Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) { if (result.get() == ButtonType.OK) {
parent.endEvent(); parent.endEvent();
race.setVisible(false); racePane.setVisible(false);
App.app.showMainStage(App.getStage()); App.app.showMainStage(App.getStage());
} }
} else { } else {
@ -152,7 +161,7 @@ public class RaceController extends Controller {
alert.setContentText("Do you wish to quit the race?"); alert.setContentText("Do you wish to quit the race?");
Optional<ButtonType> result = alert.showAndWait(); Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) { if (result.get() == ButtonType.OK) {
race.setVisible(false); racePane.setVisible(false);
App.app.showMainStage(App.getStage()); App.app.showMainStage(App.getStage());
} }
} }
@ -176,14 +185,10 @@ public class RaceController extends Controller {
//Information table. //Information table.
initialiseInfoTable(this.visualiserRace); initialiseInfoTable(this.visualiserRace);
//Sparkline.
initialiseSparkline(this.visualiserRace);
//Arrow. //Arrow.
initialiseArrow(this.visualiserRace); initialiseArrow(this.visualiserRace);
//Race canvas. initialiseView3D(this.visualiserRace);
initialiseRaceCanvas(this.visualiserRace);
//Race timezone label. //Race timezone label.
initialiseRaceTimezoneLabel(this.visualiserRace); initialiseRaceTimezoneLabel(this.visualiserRace);
@ -191,11 +196,106 @@ public class RaceController extends Controller {
//Race clock. //Race clock.
initialiseRaceClock(this.visualiserRace); initialiseRaceClock(this.visualiserRace);
//Start the race animation timer. //Start the race animation timer.
raceTimer(); raceTimer();
} }
private void initialiseView3D(VisualiserRaceEvent race) {
viewSubjects = FXCollections.observableArrayList();
// Import boat mesh
URL asset = HostController.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();
//newPane.getChildren().add(view3D);
canvasBase.add(view3D, 0, 0);
// Set up projection from GPS to view
RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);
view3D.setItems(viewSubjects);
// Position and add each mark to view
for(Mark mark: race.getVisualiserRaceState().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: race.getVisualiserRaceState().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;
}
}
});
}
/** /**
@ -338,42 +438,8 @@ public class RaceController extends Controller {
} }
/**
* 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. * Initialises the race time zone label with the race's time zone.
* @param race Race to read data from.
*/
private void initialiseRaceCanvas(VisualiserRaceEvent race) {
//Create canvas.
raceCanvas = new ResizableRaceCanvas(race);
//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. * @param race The race to get time zone from.
*/ */
private void initialiseRaceTimezoneLabel(VisualiserRaceEvent race) { private void initialiseRaceTimezoneLabel(VisualiserRaceEvent race) {
@ -408,14 +474,29 @@ public class RaceController extends Controller {
this.controllerClient = controllerClient; this.controllerClient = controllerClient;
this.isHost = isHost; this.isHost = isHost;
initialiseRace();
//Display this controller. //Check if the game is a tutorial
race.setVisible(true); if (parent.getGameType()==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);
}
initialiseRace();
// set up annotation displays //Display this controller.
new Annotations(annotationPane, raceCanvas); racePane.setVisible(true);
} }
/** /**
@ -423,7 +504,7 @@ public class RaceController extends Controller {
* @param boats boats there are in the race. * @param boats boats there are in the race.
*/ */
public void finishRace(ObservableList<VisualiserBoat> boats) { public void finishRace(ObservableList<VisualiserBoat> boats) {
race.setVisible(false); racePane.setVisible(false);
parent.enterFinish(boats); parent.enterFinish(boats);
} }
@ -458,18 +539,13 @@ public class RaceController extends Controller {
finishRace(visualiserRace.getVisualiserRaceState().getBoats()); finishRace(visualiserRace.getVisualiserRaceState().getBoats());
} else { } else {
//Otherwise, render the canvas.
raceCanvas.drawRace();
//Sort the tableview. Doesn't automatically work for all columns. //Sort the tableview. Doesn't automatically work for all columns.
boatInfoTable.sort(); boatInfoTable.sort();
} }
//Return to main screen if we lose connection. //Return to main screen if we lose connection.
if (!visualiserRace.getServerConnection().isAlive()) { if (!visualiserRace.getServerConnection().isAlive()) {
race.setVisible(false); racePane.setVisible(false);
//parent.enterTitle(); //parent.enterTitle();
try { try {
App.app.showMainStage(App.getStage()); App.app.showMainStage(App.getStage());
@ -491,10 +567,10 @@ public class RaceController extends Controller {
* toggles if the info table is shown * toggles if the info table is shown
*/ */
private void toggleTable() { private void toggleTable() {
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/race.getWidth(); double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/racePane.getWidth();
if (infoTableShow){ if (infoTableShow){
race.setDividerPositions(tablePercent); racePane.setDividerPositions(tablePercent);
arrowPane.setScaleX(0.5); arrowPane.setScaleX(0.5);
arrowPane.setScaleY(0.5); arrowPane.setScaleY(0.5);
@ -502,7 +578,7 @@ public class RaceController extends Controller {
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4); arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
}else{ }else{
race.setDividerPositions(1); racePane.setDividerPositions(1);
arrowPane.setScaleX(1); arrowPane.setScaleX(1);
arrowPane.setScaleY(1); arrowPane.setScaleY(1);
@ -514,4 +590,108 @@ public class RaceController extends Controller {
infoTableShow = !infoTableShow; 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) {
parent.endEvent();
racePane.setVisible(false);
App.app.showMainStage(App.getStage());
}
break;
default:
//State not found. Exit tutorial to title menu
parent.endEvent();
racePane.setVisible(false);
App.app.showMainStage(App.getStage());
break;
}
}
} }

@ -86,9 +86,6 @@ public class StartController extends Controller {
/** /**
* Ctor. * Ctor.
*/ */
@ -218,31 +215,30 @@ public class StartController extends Controller {
* Countdown timer until race starts. * Countdown timer until race starts.
*/ */
private void countdownTimer() { private void countdownTimer() {
new AnimationTimer() { new AnimationTimer() {
@Override @Override
public void handle(long arg0) { public void handle(long arg0) {
//Get the current race status. //Get the current race status.
RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum(); RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum();
//Display it. //If the race has reached the preparatory phase, or has started...
raceStatusLabel.setText("Race Status: " + raceStatus.name()); if (raceStatus == RaceStatusEnum.WARNING
|| 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?
//If the race has reached the preparatory phase, or has started... parent.beginRace(visualiserRaceEvent, controllerClient, isHost);
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();
}.start();
} }

@ -1,17 +1,25 @@
package visualiser.Controllers; package visualiser.Controllers;
import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton; import javafx.scene.control.RadioButton;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.media.AudioClip; import javafx.scene.media.AudioClip;
import javafx.scene.media.Media; import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer; import javafx.scene.media.MediaPlayer;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import mock.app.Event;
import mock.exceptions.EventConstructionException;
import javafx.stage.WindowEvent;
import visualiser.app.App; import visualiser.app.App;
import java.io.File; import java.io.File;
@ -19,6 +27,8 @@ import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Controller for the opening title window. * Controller for the opening title window.
@ -36,6 +46,12 @@ public class TitleController extends Controller {
RadioButton dayModeRD; RadioButton dayModeRD;
@FXML @FXML
RadioButton nightModeRD; RadioButton nightModeRD;
@FXML
Label tutorialLabel;
@FXML
Pane menuPane;
@FXML
ImageView imgSun;
/** /**
* Method called when the 'host a game' button is pressed. * Method called when the 'host a game' button is pressed.
@ -44,8 +60,8 @@ public class TitleController extends Controller {
* @throws IOException if main has problems * @throws IOException if main has problems
*/ */
public void hostAGame() throws IOException, URISyntaxException { public void hostAGame() throws IOException, URISyntaxException {
titleWrapper.setVisible(false); titleWrapper.setVisible(false);
parent.setGameType(0);
parent.hostGame(); parent.hostGame();
App.getStage().setResizable(true); App.getStage().setResizable(true);
} }
@ -72,7 +88,10 @@ public class TitleController extends Controller {
*/ */
public void setDayMode(){ public void setDayMode(){
dayModeRD.getScene().getStylesheets().clear(); 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"); dayModeRD.getScene().getStylesheets().add("/css/dayMode.css");
menuPane.setStyle("-fx-background-color: #6be6ff;");
nightModeRD.setSelected(false); nightModeRD.setSelected(false);
} }
@ -81,12 +100,16 @@ public class TitleController extends Controller {
*/ */
public void setNightMode(){ public void setNightMode(){
nightModeRD.getScene().getStylesheets().clear(); 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"); nightModeRD.getScene().getStylesheets().add("/css/nightMode.css");
menuPane.setStyle("-fx-background-color: #1f2c60;");
dayModeRD.setSelected(false); dayModeRD.setSelected(false);
} }
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
tutorialLabel.setWrapText(true);
} }
@ -94,23 +117,39 @@ public class TitleController extends Controller {
* Called when control button is pressed. New pop up window displaying controls * Called when control button is pressed. New pop up window displaying controls
*/ */
public void controlBtnPressed(){ public void controlBtnPressed(){
AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/visualiser/scenes/controls.fxml"));
Parent layout;
try { try {
layout = loader.load(); AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/visualiser/scenes/keyBindings.fxml"));
Parent layout = loader.load();
Scene scene = new Scene(layout); Scene scene = new Scene(layout);
Stage popupStage = new Stage(); Stage popupStage = new Stage();
popupStage.setResizable(false); popupStage.setResizable(false);
popupStage.setTitle("Game Controls"); popupStage.setTitle("Game Controls");
popupStage.initModality(Modality.WINDOW_MODAL); popupStage.initModality(Modality.WINDOW_MODAL);
popupStage.centerOnScreen();
popupStage.setScene(scene); popupStage.setScene(scene);
popupStage.showAndWait(); 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){ } catch (Exception e){
e.printStackTrace(); e.printStackTrace();
} }
} }
public void tutorialStartPressed() throws IOException {
titleWrapper.setVisible(false);
parent.setGameType(4);
parent.beginGame();
}
} }

@ -29,19 +29,17 @@ import javafx.stage.StageStyle;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
import javafx.util.Duration; import javafx.util.Duration;
import visualiser.Controllers.MainController; import visualiser.Controllers.MainController;
import visualiser.gameController.Keys.KeyFactory;
import java.io.IOException;
public class App extends Application { public class App extends Application {
private static Stage stage; private static Stage stage;
private Pane splashLayout; private Pane splashLayout;
private ProgressBar loadProgress; private ProgressBar loadProgress;
private Label progressText; private Label progressText;
private static final int SPLASH_WIDTH = 676; private static final int SPLASH_WIDTH = 676;
private static final int SPLASH_HEIGHT = 227; private static final int SPLASH_HEIGHT = 227;
public static KeyFactory keyFactory = new KeyFactory();
public static App app; public static App app;
/** /**
@ -55,6 +53,9 @@ public class App extends Application {
@Override @Override
public void init() { public void init() {
// load the user's personalised key bindings
keyFactory.load();
ImageView splash = new ImageView(new Image( ImageView splash = new ImageView(new Image(
getClass().getClassLoader().getResourceAsStream("images/splashScreen.png") getClass().getClassLoader().getResourceAsStream("images/splashScreen.png")
)); ));

@ -0,0 +1,66 @@
package visualiser.enums;
import javafx.scene.input.KeyCode;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
import visualiser.gameController.Keys.ControlKey;
import static visualiser.app.App.keyFactory;
import java.util.ArrayList;
/**
* 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,10 +3,11 @@ package visualiser.gameController;
import javafx.animation.AnimationTimer; import javafx.animation.AnimationTimer;
import javafx.scene.Scene; import javafx.scene.Scene;
import visualiser.gameController.Keys.ControlKey; import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import java.util.HashMap; import java.util.HashMap;
import static visualiser.app.App.keyFactory;
/** /**
* Class for checking what keys are currently being used * Class for checking what keys are currently being used
*/ */
@ -18,7 +19,7 @@ public class InputChecker {
* @param scene Scene the controller is to run in parallel with. * @param scene Scene the controller is to run in parallel with.
*/ */
public void runWithScene(Scene scene){ public void runWithScene(Scene scene){
KeyFactory keyFactory = KeyFactory.getFactory(); // KeyFactory keyFactory = KeyFactory.getFactory();
scene.setOnKeyPressed(event -> { scene.setOnKeyPressed(event -> {
String codeString = event.getCode().toString(); String codeString = event.getCode().toString();

@ -1,6 +1,5 @@
package visualiser.gameController.Keys; package visualiser.gameController.Keys;
import javafx.scene.input.KeyCode;
import network.Messages.Enums.BoatActionEnum; 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. * 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 * What to do when the key is held

@ -9,11 +9,9 @@ public class DownWindKey extends ControlKey {
/** /**
* Constructor for Control * Constructor for Control
* @param name name of the key
*
*/ */
public DownWindKey(String name) { public DownWindKey() {
super(name, BoatActionEnum.DOWNWIND); super("Downwind", BoatActionEnum.DOWNWIND);
} }
@Override @Override

@ -1,5 +1,8 @@
package visualiser.gameController.Keys; package visualiser.gameController.Keys;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -13,39 +16,108 @@ public class KeyFactory {
private Map<String, ControlKey> keyState; private Map<String, ControlKey> keyState;
/** /**
* Singleton instance to enforce consistent key state * Constructor for key state, set up initial state of each action.
*/ */
private static KeyFactory theFactory = new KeyFactory(); public KeyFactory() {
this.keyState = new HashMap<>();
keyState.put("Z", new ZoomInKey());
keyState.put("X", new ZoomOutKey());
keyState.put("SPACE", new VMGKey());
keyState.put("SHIFT", new SailsToggleKey());
keyState.put("ENTER", new TackGybeKey());
keyState.put("UP", new UpWindKey());
keyState.put("DOWN", new DownWindKey());
}
/** /**
* Singleton constructor for key state, set up initial state of each action. * Get the Control Key in charge of a key press
* @param key key pressed (String value of KeyCode)
* @return the Control Key behaviour of the key pressed.
*/ */
private KeyFactory() { public ControlKey getKey(String key){
this.keyState = new HashMap<>(); return keyState.get(key);
keyState.put("Z", new ZoomInKey("Zoom In")); }
keyState.put("X", new ZoomOutKey("Zoom Out"));
keyState.put("SPACE", new VMGKey("VMG")); public Map<String, ControlKey> getKeyState() {
keyState.put("SHIFT", new SailsToggleKey("Toggle Sails")); return keyState;
keyState.put("ENTER", new TackGybeKey("Tack/Gybe")); }
keyState.put("UP", new UpWindKey("Upwind"));
keyState.put("DOWN", new DownWindKey("Downwind")); public void setKeyState(Map<String, ControlKey> keyState) {
this.keyState = keyState;
} }
/** /**
* Get singleton instance of KeyFactory to interact with key state * Update the key bound to a particular command in the keystate.
* @return automatically constructed KeyFactory * @param newKey the new key value for the command
* @param command the command to be updated
*/ */
public static KeyFactory getFactory() { public void updateKey(String newKey, String command){
return theFactory; ControlKey controlKey = null;
String oldKey = null;
for (Map.Entry<String, ControlKey> entry : keyState.entrySet()) {
// if this is the correct command
if (entry.getValue().toString()==command){
controlKey = entry.getValue();
oldKey = entry.getKey();
}
}
keyState.remove(oldKey, controlKey);
keyState.put(newKey, controlKey);
} }
/** /**
* Get the Control Key in charge of a key press * Persistently saves the keybindings the user has set.
* @param key key pressed (String value of KeyCode)
* @return the Control Key behaviour of the key pressed.
*/ */
public ControlKey getKey(String key){ public void save(){
return keyState.get(key); try {
// open the filestream and write to it
FileOutputStream fos = new FileOutputStream(
System.getProperty("user.dir")+
"/settings/keyBindings.xml");
XMLEncoder xmlEncoder = new XMLEncoder(fos);
xmlEncoder.writeObject(this.keyState);
xmlEncoder.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* Loads the persistently saved keybindings the user has set.
*/
public void load(){
try {
// access settings folder, create if it doesn't exist
File settingsFolder = new File(
System.getProperty("user.dir")+"/settings");
if (!settingsFolder.exists()){
settingsFolder.mkdir();
}
// access keybindings xml file, create if it doesn't exist
File savedFile = new File(
settingsFolder+"/keyBindings.xml");
if (!savedFile.exists()){
savedFile.createNewFile();
FileOutputStream fos = new FileOutputStream(savedFile);
XMLEncoder xmlEncoder = new XMLEncoder(fos);
xmlEncoder.writeObject(this.keyState);
xmlEncoder.close();
}
// load the saved settings into the game
InputStream is = new FileInputStream(savedFile);
XMLDecoder xmlDecoder = new XMLDecoder(is);
Map<String, ControlKey> savedKeyState
= (Map<String, ControlKey>)xmlDecoder.readObject();
xmlDecoder.close();
this.keyState = savedKeyState;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} }
} }

@ -10,11 +10,9 @@ public class SailsToggleKey extends ControlKey {
/** /**
* Constructor for Control * Constructor for Control
* @param name name of the key
*
*/ */
public SailsToggleKey(String name) { public SailsToggleKey() {
super(name, BoatActionEnum.NOT_A_STATUS); super("Toggle Sails", BoatActionEnum.NOT_A_STATUS);
} }
/** /**

@ -9,11 +9,9 @@ public class TackGybeKey extends ControlKey {
/** /**
* Constructor for Control * Constructor for Control
* @param name name of the key
*
*/ */
public TackGybeKey(String name) { public TackGybeKey() {
super(name, BoatActionEnum.TACK_GYBE); super("Tack/Gybe", BoatActionEnum.TACK_GYBE);
} }
@Override @Override

@ -9,11 +9,9 @@ public class UpWindKey extends ControlKey {
/** /**
* Constructor for Control * Constructor for Control
* @param name name of the key
*
*/ */
public UpWindKey(String name) { public UpWindKey() {
super(name, BoatActionEnum.UPWIND); super("Upwind", BoatActionEnum.UPWIND);
} }
@Override @Override

@ -1,6 +1,5 @@
package visualiser.gameController.Keys; package visualiser.gameController.Keys;
import javafx.scene.input.KeyCode;
import network.Messages.Enums.BoatActionEnum; import network.Messages.Enums.BoatActionEnum;
/** /**
@ -10,11 +9,9 @@ public class VMGKey extends ControlKey{
/** /**
* Constructor for Control * Constructor for Control
*
* @param name name of the key
*/ */
public VMGKey(String name) { public VMGKey() {
super(name, BoatActionEnum.AUTO_PILOT); super("VMG", BoatActionEnum.AUTO_PILOT);
} }
@Override @Override

@ -5,8 +5,8 @@ package visualiser.gameController.Keys;
*/ */
public class ZoomInKey extends ControlKey { public class ZoomInKey extends ControlKey {
public ZoomInKey(String name) { public ZoomInKey() {
super(name); super("Zoom In");
} }
@Override @Override

@ -7,11 +7,9 @@ public class ZoomOutKey extends ControlKey{
/** /**
* Constructor for Control * Constructor for Control
* @param name name of the key
*
*/ */
public ZoomOutKey(String name) { public ZoomOutKey() {
super(name); super("Zoom Out");
} }
@Override @Override

@ -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);
}
}

@ -39,9 +39,9 @@ public class ResizableRaceCanvas extends ResizableCanvas {
private Image sailsLuff = new Image("/images/sailsLuff.gif", 25, 10, false, false); private Image sailsLuff = new Image("/images/sailsLuff.gif", 25, 10, false, false);
/** /**
* The race we read data from and draw. * The race state we read data from and draw.
*/ */
private VisualiserRaceEvent visualiserRace; private VisualiserRaceState raceState;
private boolean annoName = true; private boolean annoName = true;
@ -56,14 +56,14 @@ public class ResizableRaceCanvas extends ResizableCanvas {
/** /**
* Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRaceEvent}. * Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRaceEvent}.
* @param visualiserRace The race that data is read from in order to be drawn. * @param raceState The race state to be drawn.
*/ */
public ResizableRaceCanvas(VisualiserRaceEvent visualiserRace) { public ResizableRaceCanvas(VisualiserRaceState raceState) {
super(); super();
this.visualiserRace = visualiserRace; this.raceState = raceState;
RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource(); RaceDataSource raceData = raceState.getRaceDataSource();
double lat1 = raceData.getMapTopLeft().getLatitude(); double lat1 = raceData.getMapTopLeft().getLatitude();
double long1 = raceData.getMapTopLeft().getLongitude(); double long1 = raceData.getMapTopLeft().getLongitude();
@ -276,8 +276,8 @@ public class ResizableRaceCanvas extends ResizableCanvas {
boat.getCountry(), boat.getCountry(),
boat.getCurrentSpeed(), boat.getCurrentSpeed(),
this.map.convertGPS(boat.getPosition()), this.map.convertGPS(boat.getPosition()),
boat.getTimeToNextMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()), boat.getTimeToNextMarkFormatted(raceState.getRaceClock().getCurrentTime()),
boat.getTimeSinceLastMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()), boat.getTimeSinceLastMarkFormatted(raceState.getRaceClock().getCurrentTime()),
Color.BLACK, Color.BLACK,
20 ); 20 );
@ -291,7 +291,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
*/ */
private void drawBoats() { private void drawBoats() {
List<VisualiserBoat> boats = new ArrayList<>(visualiserRace.getVisualiserRaceState().getBoats()); List<VisualiserBoat> boats = new ArrayList<>(raceState.getBoats());
//Sort to ensure we draw boats in consistent order. //Sort to ensure we draw boats in consistent order.
boats.sort(Comparator.comparingInt(Boat::getSourceID)); boats.sort(Comparator.comparingInt(Boat::getSourceID));
@ -510,7 +510,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
*/ */
private void drawMarks() { private void drawMarks() {
for (Mark mark : new ArrayList<>(visualiserRace.getVisualiserRaceState().getMarks())) { for (Mark mark : new ArrayList<>(raceState.getMarks())) {
drawMark(mark); drawMark(mark);
} }
} }
@ -574,7 +574,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//Calculate the screen coordinates of the boundary. //Calculate the screen coordinates of the boundary.
List<GPSCoordinate> boundary = new ArrayList<>(visualiserRace.getVisualiserRaceState().getBoundary()); List<GPSCoordinate> boundary = new ArrayList<>(raceState.getBoundary());
double[] xpoints = new double[boundary.size()]; double[] xpoints = new double[boundary.size()];
double[] ypoints = new double[boundary.size()]; double[] ypoints = new double[boundary.size()];
@ -601,8 +601,8 @@ public class ResizableRaceCanvas extends ResizableCanvas {
public void drawRace() { public void drawRace() {
//Update RaceMap with new GPS values of race. //Update RaceMap with new GPS values of race.
this.map.setGPSTopLeft(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapTopLeft()); this.map.setGPSTopLeft(raceState.getRaceDataSource().getMapTopLeft());
this.map.setGPSBotRight(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapBottomRight()); this.map.setGPSBotRight(raceState.getRaceDataSource().getMapBottomRight());
clear(); clear();
@ -627,7 +627,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
* draws a transparent line around the course that shows the paths boats must travel * draws a transparent line around the course that shows the paths boats must travel
*/ */
public void drawRaceLine(){ public void drawRaceLine(){
List<Leg> legs = this.visualiserRace.getVisualiserRaceState().getLegs(); List<Leg> legs = raceState.getLegs();
GPSCoordinate legStartPoint = legs.get(0).getStartCompoundMark().getAverageGPSCoordinate(); GPSCoordinate legStartPoint = legs.get(0).getStartCompoundMark().getAverageGPSCoordinate();
GPSCoordinate nextStartPoint; GPSCoordinate nextStartPoint;
for (int i = 0; i < legs.size() -1; i++) { for (int i = 0; i < legs.size() -1; i++) {

@ -1,11 +1,10 @@
package visualiser.model; package visualiser.model;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import network.Messages.Enums.BoatStatusEnum; import network.Messages.Enums.BoatStatusEnum;
import shared.model.Azimuth; import shared.model.*;
import shared.model.Boat;
import shared.model.Constants;
import shared.model.GPSCoordinate;
import java.time.Duration; import java.time.Duration;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@ -61,7 +60,8 @@ public class VisualiserBoat extends Boat {
private boolean isClientBoat = false; 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) { public void setClientBoat(boolean clientBoat) {
isClientBoat = 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;
}
} }

@ -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 { #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;
} }

@ -0,0 +1,45 @@
.list-view .list-cell {
-fx-cell-size: 40;
-fx-font-family: "Tahoma";
-fx-background-color: #fffff0;
-fx-alignment: center;
}
.list-view .list-cell:even {
-fx-background-color: #ffffdc;
}
.list-view .list-cell:selected {
-fx-background-color: #f9e5c3;
-fx-font-family: "Comic Sans MS";
-fx-font-weight: bold;
-fx-font-size: 18;
}
.button {
-fx-background-color: linear-gradient(#acdeff 50%, #a9c8ff 100%);
-fx-background-radius: 4px;
-fx-border-radius: 4px;
-fx-text-fill: #242d35;
-fx-font-size: 12px;
-fx-font-family: "Courier New";
-fx-border-color: #a9c8ff;
}
.button:focused {
-fx-background-color: white;
-fx-border-color: #0056bd;
}
#anchor {
-fx-background-color: #fdfac3;
}
#menu{
-fx-background-color: linear-gradient(#acdeff 50%, #a9c8ff 100%);
-fx-background-radius: 4px;
-fx-border-radius: 4px;
-fx-text-fill: #242d35;
-fx-font-size: 12px;
-fx-font-family: "Verdana";
-fx-border-color: #a9c8ff;
}

@ -57,3 +57,37 @@
#arrowImage { #arrowImage {
-fx-image: url("/visualiser/images/arrowLight.png"); -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

@ -32,7 +32,6 @@
<GPSposition X= "-64.83626" Y="32.317257" Z="0"/> <GPSposition X= "-64.83626" Y="32.317257" Z="0"/>
</Boat> </Boat>
<!--Participants-->
<!--Participants--> <!--Participants-->
<Boat BoatName="Emirates Team New Zealand" HullNum="RG01" ShapeID="0" ShortName="NZL" SourceID="121" StoweName="NZL" Type="Yacht"> <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"/> <GPSposition X="-64.854304" Y="32.296577" Z="0"/>

@ -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> <CreationTimeDate>RACE_CREATION_TIME</CreationTimeDate>
<RaceStartTime Postpone="false" Time="RACE_START_TIME"/> <RaceStartTime Postpone="false" Time="RACE_START_TIME"/>
<Participants> <Participants>
<Yacht SourceID="126"/>
</Participants> </Participants>
<CompoundMarkSequence> <CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3" /> <Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3" />

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

@ -1,57 +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="121"/>
<Yacht SourceID="122"/>
<Yacht SourceID="123"/>
<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>

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="BoatConfig">
<xs:complexType>
<xs:sequence>
<xs:element name="Boats">
<xs:complexType>
<xs:sequence>
<xs:element name="Boat" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="GPSposition" maxOccurs="1">
<xs:complexType>
<xs:attribute name="X" type="xs:double" use="required"/>
<xs:attribute name="Y" type="xs:double" use="required"/>
<xs:attribute name="Z" type="xs:double" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="Type" type="xs:string" use="required"/>
<xs:attribute name="BoatName" type="xs:string" use="required"/>
<xs:attribute name="SourceID" type="xs:int" use="required"/>
<xs:attribute name="HullNum" type="xs:string"/>
<xs:attribute name="ShortName" type="xs:string"/>
<xs:attribute name="ShapeID" type="xs:int"/>
<xs:attribute name="StoweName" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="RegattaConfig">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:int" name="RegattaID" minOccurs="1"/>
<xs:element type="xs:string" name="RegattaName" minOccurs="1"/>
<xs:element type="xs:string" name="CourseName" minOccurs="1"/>
<xs:element name="CentralLatitude" type="xs:double" minOccurs="1"/>
<xs:element name="CentralLongitude" type="xs:double" minOccurs="1"/>
<xs:element name="CentralAltitude" type="xs:double" minOccurs="1"/>
<xs:element type="xs:double" name="UtcOffset" minOccurs="1"/>
<xs:element name="MagneticVariation" type="xs:double" minOccurs="1"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

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.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 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,39 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?> <?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?> <?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" fx:controller="visualiser.Controllers.HostController"> <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" fx:controller="visualiser.Controllers.HostController">
<children> <children>
<GridPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <GridPane layoutY="14.0" AnchorPane.bottomAnchor="-14.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="14.0">
<columnConstraints> <columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> <ColumnConstraints hgrow="SOMETIMES" maxWidth="170.0" minWidth="10.0" prefWidth="170.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 hgrow="SOMETIMES" maxWidth="170.0" minWidth="10.0" prefWidth="170.0" />
</columnConstraints> </columnConstraints>
<rowConstraints> <rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints maxHeight="60.0" minHeight="60.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="435.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints maxHeight="80.0" minHeight="80.0" prefHeight="80.0" vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<Button fx:id="hostGameBtn" mnemonicParsing="false" onAction="#hostGamePressed" text="Start Game" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2"> <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>
<Font size="20.0" /> <Font size="20.0" />
</font> </font>
<GridPane.margin>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</GridPane.margin>
</Button> </Button>
<Label text="Address: 127.0.0.1" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="TOP"> <Label text="Address: 127.0.0.1" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="TOP">
<font> <font>
<Font size="17.0" /> <Font size="17.0" />
</font> </font>
</Label> </Label>
<Label text="Port: 4942" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1"> <Label text="Port: 4942" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<font> <font>
<Font size="17.0" /> <Font size="17.0" />
</font> </font>
</Label> </Label>
<Button mnemonicParsing="false" onAction="#menuBtnPressed" text="Main Menu" GridPane.halignment="CENTER" /> <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> </children>
</GridPane> </GridPane>
</children> </children>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save