Merge branch 'master' into story77

# Conflicts:
#	racevisionGame/src/main/java/mock/app/Event.java
#	racevisionGame/src/main/java/visualiser/Controllers/HostController.java
#	racevisionGame/src/main/java/visualiser/model/View3D.java
main
hba56 8 years ago
commit 4c2ab9cc9a

@ -15,4 +15,12 @@
<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>

@ -25,10 +25,30 @@
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<artifactId>mockito-core</artifactId>
<version>2.9.0</version>
</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>
@ -46,8 +66,6 @@
<version>15.0</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-referencing</artifactId>
@ -55,9 +73,9 @@
</dependency>
<dependency>
<groupId>InteractiveMesh</groupId>
<groupId>JavaInteractiveMesh</groupId>
<artifactId>STLImporter</artifactId>
<version>0_4_1</version>
<version>0.7</version>
</dependency>
</dependencies>
@ -79,7 +97,7 @@
<repository>
<id>interactivemesh</id>
<name>Interactive Mesh </name>
<name>Interactive Mesh</name>
<url>http://umbrasheep.com:8888/repository/internal/</url>
</repository>
@ -172,6 +190,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.8.1</version>
<configuration>
<dependencyLocationsEnabled>false</dependencyLocationsEnabled>
</configuration>
</plugin>
</plugins>
</reporting>

@ -61,12 +61,7 @@ public class ConnectionAcceptor implements Runnable {
*/
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;
@ -209,77 +204,6 @@ 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;
}
}

@ -6,6 +6,10 @@ import mock.model.*;
import mock.model.commandFactory.CompositeCommand;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.HostGame;
import mock.model.wind.RandomWindGenerator;
import mock.model.wind.ShiftingWindGenerator;
import mock.model.wind.WindGenerator;
import mock.xml.RaceXMLCreator;
import network.Messages.LatestMessages;
import shared.dataInput.*;
import shared.enums.XMLFileType;
@ -16,12 +20,19 @@ import shared.exceptions.XMLReaderException;
import shared.model.Bearing;
import shared.model.Constants;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
@ -98,6 +109,7 @@ public class Event {
this.xmlFileType = XMLFileType.Contents;
this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv");
PolarParser.parseNewPolars("mock/polars/acc_polars.csv");
//Parse the XML files into data sources.
@ -112,18 +124,14 @@ public class Event {
}
this.sourceIdAllocator = new SourceIdAllocator(raceDataSource.getParticipants());
this.compositeCommand = new CompositeCommand();
this.latestMessages = new LatestMessages();
//Create and start race.
WindGenerator windGenerator = new RandomWindGenerator(
WindGenerator windGenerator = new ShiftingWindGenerator(
Bearing.fromDegrees(225),
Bearing.fromDegrees(215),
Bearing.fromDegrees(235),
12d,
8d,
16d );
12
);
RaceLogic newRace = new RaceLogic(
new MockRace(
boatDataSource,
@ -140,6 +148,7 @@ public class Event {
//Create connection acceptor.
this.sourceIdAllocator = new SourceIdAllocator(newRace.getRace());
try {
this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace);
@ -152,8 +161,6 @@ public class Event {
this.connectionThread = new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread");
connectionThread.start();
sendXMLs();
}
@ -166,18 +173,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!
/**

@ -27,7 +27,12 @@ public class MockOutput implements RunnableWithFramePeriod {
*/
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();
boolean sentXMLs = false;
@ -82,16 +86,26 @@ public class MockOutput implements RunnableWithFramePeriod {
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.getRegattaXMLMessage());
}
if (lastSentBoatNumber != latestMessages.getBoatXMLMessage().getSequenceNumber()) {
lastSentBoatNumber = latestMessages.getBoatXMLMessage().getSequenceNumber();
outgoingMessages.put(latestMessages.getBoatXMLMessage());
}
sentXMLs = true;
if (lastSentRegattaNumber != latestMessages.getRegattaXMLMessage().getSequenceNumber()) {
lastSentRegattaNumber = latestMessages.getRegattaXMLMessage().getSequenceNumber();
outgoingMessages.put(latestMessages.getRegattaXMLMessage());
}
List<AC35Data> snapshot = latestMessages.getSnapshot();
for (AC35Data message : snapshot) {
outgoingMessages.put(message);

@ -3,6 +3,7 @@ package mock.dataInput;
import mock.exceptions.InvalidPolarFileException;
import mock.model.NewPolars;
import mock.model.Polars;
import shared.model.Bearing;
@ -104,4 +105,90 @@ public class PolarParser {
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;
/**
* 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();
int allocatedSourceID = 0;
allocatedSourceID = 0;
//If they want to participate, give them a source ID number.
if (requestToJoin.getRequestType() == RequestToJoinEnum.PARTICIPANT) {
@ -283,6 +288,10 @@ public class ClientConnection implements Runnable {
if (this.controllerServerThread != null) {
this.controllerServerThread.interrupt();
}
if (allocatedSourceID != 0) {
sourceIdAllocator.returnSourceID(allocatedSourceID);
}
}
}

@ -1,6 +1,8 @@
package mock.model;
import mock.model.wind.WindGenerator;
import javafx.animation.AnimationTimer;
import mock.model.collider.ColliderRegistry;
import mock.xml.*;
import network.Messages.BoatLocation;
import network.Messages.BoatStatus;
@ -13,7 +15,6 @@ import shared.exceptions.BoatNotFoundException;
import shared.enums.RoundingType;
import shared.model.*;
import shared.model.Bearing;
import shared.model.Race;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
@ -27,7 +28,7 @@ import static java.lang.Math.cos;
* Has a course, boats, boundaries, etc...
* 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.
@ -40,6 +41,12 @@ public class MockRace extends Race {
private List<GPSCoordinate> shrinkBoundary;
/**
* Registry for all collider object in this race
*/
private ColliderRegistry colliderRegistry;
/**
* The scale factor of the race.
* See {@link Constants#RaceTimeScale}.
@ -51,6 +58,14 @@ public class MockRace extends Race {
*/
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.
* @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) {
super(boatDataSource, raceDataSource, regattaDataSource);
this.setBoatDataSource(boatDataSource);
this.setRaceDataSource(raceDataSource);
this.setRegattaDataSource(regattaDataSource);
this.polars = polars;
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;
@ -76,35 +94,53 @@ public class MockRace extends Race {
//Wind.
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);
}
/**
* Generates a list of MockBoats given a list of Boats, and a list of participating boats.
* @param boats The map of Boats describing boats that are potentially in the race. Maps boat sourceID to 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.
* Generates a MockBoat from the BoatDataSource, given a source ID. Also adds it to the participant list.
* @param sourceID The source ID to assign the boat.
*/
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...
for (int sourceID : sourceIDs) {
//Construct a MockBoat using the Boat and Polars.
MockBoat mockBoat = new MockBoat(boat, polars);
mockBoat.setCurrentLeg(this.getLegs().get(0));
//Get the boat associated with the sourceID.
Boat boat = boats.get(sourceID);
//Update participant list.
getRaceDataSource().getParticipants().add(sourceID);
//Construct a MockBoat using the Boat and Polars.
MockBoat mockBoat = new MockBoat(boat, polars);
this.boats.add(mockBoat);
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.
*/
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() {
//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) {
@ -194,7 +230,7 @@ public class MockRace extends 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.
boat.setCurrentSpeed(0d);
@ -209,7 +245,9 @@ public class MockRace extends Race {
boat.setStatus(BoatStatusEnum.PRESTART);
//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() {
//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.
GPSCoordinate mark1Position = compoundMark.getMark1Position();
@ -341,6 +379,7 @@ public class MockRace extends Race {
if (boat.getAutoVMG()) {
newOptimalVMG(boat);
boat.setAutoVMG(false);
}
} else {
@ -353,31 +392,38 @@ public class MockRace extends Race {
private void newOptimalVMG(MockBoat boat) {
long tackPeriod = 1000;
if (boat.getTimeSinceTackChange() > tackPeriod) {
//System.out.println("optim called");
//Calculate the new VMG.
VMG newVMG = boat.getPolars().calculateVMG(
this.getWindDirection(),
this.getWindSpeed(),
boat.calculateBearingToNextMarker(),
Bearing.fromDegrees(0d),
Bearing.fromDegrees(359.99999d));
// VMG newVMG = boat.getPolars().calculateVMG(
// this.getWindDirection(),
// this.getWindSpeed(),
// boat.calculateBearingToNextMarker(),
// Bearing.fromDegrees(0d),
// 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 (improvesVelocity(boat, newVMG)) {
boat.setVMG(newVMG);
}
/*if (improvesVelocity(boat, newVMG)) {
}*/
boat.setVMG(newVMG);
}
}
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.getWindSpeed(),
boat.getBearing(),
Bearing.fromDegrees(boat.getBearing().degrees() - 1),
Bearing.fromDegrees(boat.getBearing().degrees() + 1));
boat.getBearing()
), boat.getBearing()) ;
if (vmg.getSpeed() > 0) {
boat.setCurrentSpeed(vmg.getSpeed());
}
@ -485,7 +531,7 @@ public class MockRace extends Race {
roundingChecks.get(0), boat.getPosition(), legBearing) &&
gateCheck && boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) {
boat.increaseRoundingStatus();
if (boat.getCurrentLeg().getLegNumber() + 2 >= legs.size()){
if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
//boat has finished race
boat.increaseRoundingStatus();
}
@ -503,7 +549,7 @@ public class MockRace extends Race {
case 2://has traveled 180 degrees around the mark
//Move boat on to next leg.
boat.resetRoundingStatus();
Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1);
Leg nextLeg = this.getLegs().get(boat.getCurrentLeg().getLegNumber() + 1);
boat.setCurrentLeg(nextLeg);
break;
}
@ -527,10 +573,10 @@ public class MockRace extends Race {
if (boat.isStarboardSide(roundingMark) &&
GPSCoordinate.passesLine(roundingMark.getPosition(),
roundingChecks.get(0), boat.getPosition(), legBearing) &&
gateCheck &&
gateCheck &&
boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) {
boat.increaseRoundingStatus();
if (boat.getCurrentLeg().getLegNumber() + 2 >= legs.size()){
if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
//boat has finished race
boat.increaseRoundingStatus();
}
@ -547,7 +593,7 @@ public class MockRace extends Race {
case 2://has traveled 180 degrees around the mark
//Move boat on to next leg.
boat.resetRoundingStatus();
Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1);
Leg nextLeg = this.getLegs().get(boat.getCurrentLeg().getLegNumber() + 1);
boat.setCurrentLeg(nextLeg);
break;
}
@ -585,7 +631,7 @@ public class MockRace extends Race {
GPSCoordinate roundCheck2;
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 endNextDirectionLinePoint = nextLeg.getEndCompoundMark().getMark1Position();
@ -681,7 +727,7 @@ public class MockRace extends Race {
*/
public void changeWindDirection() {
Wind nextWind = windGenerator.generateNextWind(raceWind.getValue());
Wind nextWind = windGenerator.generateNextWind(windProperty().getValue());
setWind(nextWind);
}
@ -702,13 +748,10 @@ public class MockRace extends Race {
long timeFromNow = (long) (1000 * boat.calculateDistanceToNextMarker() / velocityToMark);
//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);
}
}
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 shared.model.Bearing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* 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.
* <br>

@ -48,6 +48,9 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
*/
@Override
public void run() {
prestartCountdown();
race.initialiseBoats();
countdown();
@ -60,6 +63,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.
*/

@ -1,13 +1,21 @@
package mock.model;
import network.AckSequencer;
import network.Messages.*;
import network.Messages.Enums.BoatLocationDeviceEnum;
import network.Messages.Enums.XMLMessageType;
import shared.model.Bearing;
import shared.model.CompoundMark;
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.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Created by connortaylorbrown on 2/08/17.
@ -22,6 +30,21 @@ public class RaceServer {
*/
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) {
this.race = race;
@ -45,8 +68,77 @@ public class RaceServer {
snapshotMessages.add(parseRaceStatus());
latestMessages.setSnapshot(snapshotMessages);
updateXMLFiles();
}
/**
* 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.
@ -178,4 +270,41 @@ public class RaceServer {
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;
}
}

@ -2,6 +2,7 @@ package mock.model;
import mock.exceptions.SourceIDAllocationException;
import network.Messages.Enums.RaceStatusEnum;
import java.util.ArrayList;
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.
* @param unallocatedIDs List of unallocated source IDs.
* Creates a SourceIdAllocator for a given race.
* @param mockRace Race to allocate source IDs for.
*/
public SourceIdAllocator(List<Integer> unallocatedIDs) {
//We need to copy the list.
this.unallocatedIDs.addAll(unallocatedIDs);
public SourceIdAllocator(MockRace mockRace) {
this.mockRace = mockRace;
}
@ -41,11 +36,24 @@ public class SourceIdAllocator {
*/
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()) {
int sourceID = unallocatedIDs.remove(0);
allocatedIDs.add(sourceID);
mockRace.generateMockBoat(sourceID);
return sourceID;
@ -61,10 +69,6 @@ public class SourceIdAllocator {
* @param sourceID Source ID to return.
*/
public void returnSourceID(Integer sourceID) {
//We remove an Integer, not an int, so that we remove by value not by index.
allocatedIDs.remove(sourceID);
unallocatedIDs.add(sourceID);
mockRace.removeMockBoat(sourceID);
}
}

@ -45,4 +45,8 @@ public class VMG {
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.Wind;
import java.util.Random;
/**
* This class generates Wind objects for use in a MockRace.
* Initialised with a baseline wind speed and direction, and keeps it constant.

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

@ -77,7 +77,8 @@ public class RaceXMLCreator {
public static String alterRaceToWind(String s, double degrees) throws XMLReaderException, InvalidRaceDataException, JAXBException, IOException, SAXException, ParserConfigurationException {
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"),
XMLRace.class);
@ -89,14 +90,7 @@ public class RaceXMLCreator {
alterRaceRotation(race, degreesToRotate);
JAXBContext context = JAXBContext.newInstance(XMLRace.class);
Marshaller jaxbMarshaller = context.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter sw = new StringWriter();
jaxbMarshaller.marshal(race, sw);
return sw.toString();
return XMLUtilities.classToXML(race);
}
/**

@ -71,6 +71,8 @@ public enum RaceTypeEnum {
}
/**
* 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.
*/

@ -22,4 +22,16 @@ public interface BoatDataSource {
* @return Map between source ID and mark.
*/
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 int sequenceNumber = 0;
/**
* Constructor for Boat XML using a file.
*
@ -174,4 +176,15 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource {
public Map<Integer, Mark> getMarkerBoats() {
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 int sequenceNumber = 0;
public EmptyBoatDataSource() {
}
@ -44,4 +46,15 @@ public class EmptyBoatDataSource implements BoatDataSource {
public Map<Integer, Mark> getMarkerBoats() {
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 shared.model.CompoundMark;
import shared.model.Corner;
import shared.model.GPSCoordinate;
import shared.model.Leg;
import shared.xml.Race.XMLCorner;
import java.time.ZonedDateTime;
import java.util.ArrayList;
@ -48,6 +50,12 @@ public class EmptyRaceDataSource implements RaceDataSource {
*/
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.
@ -76,6 +84,8 @@ public class EmptyRaceDataSource implements RaceDataSource {
private RaceTypeEnum raceType = RaceTypeEnum.NOT_A_RACE_TYPE;
private int sequenceNumber = 0;
public EmptyRaceDataSource() {
}
@ -98,6 +108,11 @@ public class EmptyRaceDataSource implements RaceDataSource {
return legs;
}
@Override
public List<Corner> getCorners() {
return corners;
}
public List<CompoundMark> getCompoundMarks() {
return new ArrayList<>(compoundMarkMap.values());
}
@ -126,4 +141,14 @@ public class EmptyRaceDataSource implements RaceDataSource {
public List<Integer> getParticipants() {
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() {
}
@ -119,4 +121,15 @@ public class EmptyRegattaDataSource implements RegattaDataSource {
public GPSCoordinate getGPSCoordinate() {
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 shared.model.CompoundMark;
import shared.model.Corner;
import shared.model.GPSCoordinate;
import shared.model.Leg;
import shared.xml.Race.XMLCorner;
import java.time.ZonedDateTime;
import java.util.List;
@ -27,6 +29,12 @@ public interface RaceDataSource {
*/
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.
* @return The boundary of the race.
@ -83,4 +91,16 @@ public interface RaceDataSource {
* @return Bottom right GPS coordinate.
*/
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 int sequenceNumber = 0;
/**
@ -343,7 +345,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
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.
CompoundMark lastCompoundMark = this.compoundMarkMap.get(cornerID);
@ -366,7 +368,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
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
cornerRounding = getCompoundMarkRounding(cornerElement);
@ -488,7 +490,17 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource {
return participants;
}
public List<Corner> getCornersList() {
public List<Corner> getCorners() {
return cornersList;
}
@Override
public int getSequenceNumber() {
return sequenceNumber;
}
@Override
public void incrementSequenceNumber() {
sequenceNumber++;
}
}

@ -66,5 +66,15 @@ public interface RegattaDataSource {
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 int sequenceNumber = 0;
/**
* Constructor for Regatta XML using a file.
@ -209,4 +211,14 @@ public class RegattaXMLReader extends XMLReader implements RegattaDataSource {
public GPSCoordinate getGPSCoordinate() {
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;
}
}
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,23 @@ public class Constants {
* 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.
*/
public static final int RaceTimeScale = 10;
public static final int RaceTimeScale = 2;//10;
/**
* The race pre-start time, in milliseconds. 3 minutes.
*/
public static final long RacePreStartTime = 1 * 10 * 1000;
public static final long RacePreStartTime = 3 * 60 * 1000;
/**
* The race preparatory time, in milliseconds. 1 minutes.
* The race preparatory time, in milliseconds. 1 minute.
*/
public static final long RacePreparatoryTime = 1 * 60 * 1000;
/**
* The number of milliseconds in one hour.
* <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.
*/
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;
}
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;
/**
* Created by fwy13 on 13/08/17.
* Contains utility functions to convert between xml files and xml class objects.
*/
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 {
JAXBContext context = JAXBContext.newInstance(o.getClass());
Marshaller jaxbMarshaller = context.createMarshaller();
@ -49,14 +56,26 @@ public class XMLUtilities {
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();
Document document = parser.parse(i);
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);
Unmarshaller unmarshaller = jc.createUnmarshaller();
@ -64,7 +83,7 @@ public class XMLUtilities {
Schema schema = sf.newSchema(schemaURL);
unmarshaller.setSchema(schema);
return unmarshaller.unmarshal(new DOMSource(document));
return (T) unmarshaller.unmarshal(new DOMSource(document));
}
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.
raceWinnerLabel.setText("Winner: "+ boatNameColumn.getCellObservableValue(0).getValue());
raceWinnerLabel.setWrapText(true);
if (boats.size() > 0) {
raceWinnerLabel.setText("Winner: " + boatNameColumn.getCellObservableValue(0).getValue());
raceWinnerLabel.setWrapText(true);
}
}

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

@ -51,6 +51,7 @@ public class MainController extends Controller {
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
* @param visualiserRace The object modelling the race.
* @param controllerClient Socket Client that manipulates the controller.
* @param isHost if the client is the host of a race or not.
*/
public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
raceController.startRace(visualiserRace, controllerClient, isHost);

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

@ -10,7 +10,6 @@ import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
@ -23,7 +22,6 @@ import shared.model.Leg;
import visualiser.app.App;
import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import visualiser.model.*;
import java.io.IOException;
@ -33,6 +31,7 @@ import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import static visualiser.app.App.keyFactory;
/**
@ -113,7 +112,7 @@ public class RaceController extends Controller {
@Override
public void initialize(URL location, ResourceBundle resources) {
KeyFactory keyFactory = KeyFactory.getFactory();
// KeyFactory keyFactory = KeyFactory.getFactory();
infoTableShow = true;
// Initialise keyboard handler
@ -356,7 +355,7 @@ public class RaceController extends Controller {
private void initialiseRaceCanvas(VisualiserRaceEvent race) {
//Create canvas.
raceCanvas = new ResizableRaceCanvas(race);
raceCanvas = new ResizableRaceCanvas(race.getVisualiserRaceState());
//Set properties.
raceCanvas.setMouseTransparent(true);

@ -1,5 +1,6 @@
package visualiser.Controllers;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
@ -9,6 +10,7 @@ import javafx.scene.control.RadioButton;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import visualiser.app.App;
import java.io.IOException;
@ -87,18 +89,26 @@ public class TitleController extends Controller {
* Called when control button is pressed. New pop up window displaying controls
*/
public void controlBtnPressed(){
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/visualiser/scenes/controls.fxml"));
Parent layout;
try {
layout = loader.load();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/visualiser/scenes/keyBindings.fxml"));
Parent layout = loader.load();
Scene scene = new Scene(layout);
Stage popupStage = new Stage();
popupStage.setResizable(false);
popupStage.setTitle("Game Controls");
popupStage.initModality(Modality.WINDOW_MODAL);
popupStage.centerOnScreen();
popupStage.setScene(scene);
popupStage.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){
e.printStackTrace();
}

@ -3,7 +3,6 @@ package visualiser.app;
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
@ -15,7 +14,6 @@ import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
@ -29,19 +27,17 @@ import javafx.stage.StageStyle;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
import visualiser.Controllers.MainController;
import java.io.IOException;
import visualiser.gameController.Keys.KeyFactory;
public class App extends Application {
private static Stage stage;
private Pane splashLayout;
private ProgressBar loadProgress;
private Label progressText;
private static final int SPLASH_WIDTH = 676;
private static final int SPLASH_HEIGHT = 227;
public static KeyFactory keyFactory = new KeyFactory();
public static App app;
/**
@ -55,6 +51,9 @@ public class App extends Application {
@Override
public void init() {
// load the user's personalised key bindings
keyFactory.load();
ImageView splash = new ImageView(new Image(
getClass().getClassLoader().getResourceAsStream("images/splashScreen.png")
));

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

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

@ -1,5 +1,8 @@
package visualiser.gameController.Keys;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
@ -13,39 +16,108 @@ public class KeyFactory {
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() {
this.keyState = new HashMap<>();
keyState.put("Z", new ZoomInKey("Zoom In"));
keyState.put("X", new ZoomOutKey("Zoom Out"));
keyState.put("SPACE", new VMGKey("VMG"));
keyState.put("SHIFT", new SailsToggleKey("Toggle Sails"));
keyState.put("ENTER", new TackGybeKey("Tack/Gybe"));
keyState.put("UP", new UpWindKey("Upwind"));
keyState.put("DOWN", new DownWindKey("Downwind"));
public ControlKey getKey(String key){
return keyState.get(key);
}
public Map<String, ControlKey> getKeyState() {
return keyState;
}
public void setKeyState(Map<String, ControlKey> keyState) {
this.keyState = keyState;
}
/**
* Get singleton instance of KeyFactory to interact with key state
* @return automatically constructed KeyFactory
* Update the key bound to a particular command in the keystate.
* @param newKey the new key value for the command
* @param command the command to be updated
*/
public static KeyFactory getFactory() {
return theFactory;
public void updateKey(String newKey, String command){
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
* @param key key pressed (String value of KeyCode)
* @return the Control Key behaviour of the key pressed.
* Persistently saves the keybindings the user has set.
*/
public ControlKey getKey(String key){
return keyState.get(key);
public void save(){
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
* @param name name of the key
*
*/
public SailsToggleKey(String name) {
super(name, BoatActionEnum.NOT_A_STATUS);
public SailsToggleKey() {
super("Toggle Sails", BoatActionEnum.NOT_A_STATUS);
}
/**

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

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

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

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

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

@ -39,9 +39,9 @@ public class ResizableRaceCanvas extends ResizableCanvas {
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;
@ -56,14 +56,14 @@ public class ResizableRaceCanvas extends ResizableCanvas {
/**
* 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();
this.visualiserRace = visualiserRace;
this.raceState = raceState;
RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
RaceDataSource raceData = raceState.getRaceDataSource();
double lat1 = raceData.getMapTopLeft().getLatitude();
double long1 = raceData.getMapTopLeft().getLongitude();
@ -276,8 +276,8 @@ public class ResizableRaceCanvas extends ResizableCanvas {
boat.getCountry(),
boat.getCurrentSpeed(),
this.map.convertGPS(boat.getPosition()),
boat.getTimeToNextMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()),
boat.getTimeSinceLastMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()),
boat.getTimeToNextMarkFormatted(raceState.getRaceClock().getCurrentTime()),
boat.getTimeSinceLastMarkFormatted(raceState.getRaceClock().getCurrentTime()),
Color.BLACK,
20 );
@ -291,7 +291,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
*/
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.
boats.sort(Comparator.comparingInt(Boat::getSourceID));
@ -510,7 +510,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
*/
private void drawMarks() {
for (Mark mark : new ArrayList<>(visualiserRace.getVisualiserRaceState().getMarks())) {
for (Mark mark : new ArrayList<>(raceState.getMarks())) {
drawMark(mark);
}
}
@ -574,7 +574,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
//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[] ypoints = new double[boundary.size()];
@ -601,8 +601,8 @@ public class ResizableRaceCanvas extends ResizableCanvas {
public void drawRace() {
//Update RaceMap with new GPS values of race.
this.map.setGPSTopLeft(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapTopLeft());
this.map.setGPSBotRight(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapBottomRight());
this.map.setGPSTopLeft(raceState.getRaceDataSource().getMapTopLeft());
this.map.setGPSBotRight(raceState.getRaceDataSource().getMapBottomRight());
clear();
@ -627,7 +627,7 @@ public class ResizableRaceCanvas extends ResizableCanvas {
* draws a transparent line around the course that shows the paths boats must travel
*/
public void drawRaceLine(){
List<Leg> legs = this.visualiserRace.getVisualiserRaceState().getLegs();
List<Leg> legs = raceState.getLegs();
GPSCoordinate legStartPoint = legs.get(0).getStartCompoundMark().getAverageGPSCoordinate();
GPSCoordinate nextStartPoint;
for (int i = 0; i < legs.size() -1; i++) {

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

@ -32,7 +32,6 @@
<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"/>

@ -5,12 +5,7 @@
<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" />

@ -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.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane id="anchor" fx:id="anchor" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="471.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.KeyBindingsController">
<children>
<Label fx:id="lblTitle" layoutX="172.0" layoutY="14.0" text="Key Bindings">
<font>
<Font name="Comic Sans MS" size="44.0" />
</font>
</Label>
<Button id="menu" fx:id="btnCancel" layoutX="430.0" layoutY="428.0"
mnemonicParsing="false" onAction="#cancel" prefWidth="160.0" text="Cancel" AnchorPane.bottomAnchor="18.0" AnchorPane.rightAnchor="20.0" />
<ListView fx:id="lstControl" focusTraversable="false" layoutX="27.0" layoutY="88.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="322.0" prefWidth="150.0" />
<ListView fx:id="lstKey" focusTraversable="false" layoutX="176.0" layoutY="88.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="322.0" prefWidth="150.0" />
<ListView fx:id="lstDescription" focusTraversable="false" layoutX="325.0" layoutY="88.0" prefHeight="322.0" prefWidth="250.0" />
<Button id="menu" fx:id="btnSave" layoutX="220.0" layoutY="428.0" mnemonicParsing="false" onAction="#save" prefWidth="160.0" text="Save" AnchorPane.bottomAnchor="18.0" />
<Button id="menu" fx:id="btnReset" layoutX="20.0" layoutY="428.0" mnemonicParsing="false" onAction="#reset" prefWidth="160.0" text="Reset to Default" AnchorPane.bottomAnchor="18.0" AnchorPane.leftAnchor="20.0" />
</children>
</AnchorPane>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="100.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.NotificationController">
<children>
<Button fx:id="btnOk" layoutX="110.0" layoutY="65.0" mnemonicParsing="false" onAction="#ok" prefWidth="80.0" text="Ok" />
<Text fx:id="txtMessage" fill="RED" layoutY="23.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Warning!" textAlignment="CENTER" wrappingWidth="300.0">
<font>
<Font name="System Bold" size="14.0" />
</font>
</Text>
<Label fx:id="lblDescription" alignment="CENTER" layoutY="36.0" prefHeight="17.0" prefWidth="300.0" textAlignment="CENTER" />
</children>
</Pane>

@ -1,5 +1,7 @@
package mock.model;
import mock.model.wind.ConstantWindGenerator;
import mock.model.wind.WindGenerator;
import org.junit.Before;
import org.junit.Test;
import shared.model.Bearing;

@ -3,6 +3,7 @@ package mock.model;
import org.junit.Before;
import org.junit.Test;
import shared.model.Bearing;
import shared.model.Boat;
import shared.model.GPSCoordinate;
import shared.model.Mark;
@ -13,6 +14,13 @@ public class MockBoatTest {
private Mark near;
private Mark far;
public static MockBoat createMockBoat() {
Boat boat = new Boat(121, "Test boat", "TS");
MockBoat mockBoat = new MockBoat(boat, null);
return mockBoat;
}
@Before
public void setUp() {
boat = new MockBoat(0, "Bob", "NZ", null);

@ -1,7 +1,8 @@
package mock.model;
import mock.dataInput.PolarParserTest;
import network.Messages.LatestMessages;
import mock.model.wind.ConstantWindGenerator;
import mock.model.wind.WindGenerator;
import shared.dataInput.*;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
@ -9,8 +10,6 @@ import shared.exceptions.InvalidRegattaDataException;
import shared.model.Bearing;
import shared.model.Constants;
import static org.junit.Assert.*;
public class MockRaceTest {
//TODO

@ -0,0 +1,141 @@
package mock.model;
import mock.dataInput.PolarParser;
import org.junit.Before;
import org.junit.Test;
import shared.model.Bearing;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
/**
* Created by fwy13 on 5/09/17.
*/
public class NewPolarsTest {
@Before
public void setUp(){
PolarParser.parseNewPolars("mock/polars/acc_polars.csv");
NewPolars.linearInterpolatePolars();
// Uncomment if you want to read the linear interpolation in text
// Method getPolars = null;
// try {
// getPolars = NewPolars.class.getDeclaredMethod("printOutLinearInterpolated");
// } catch (NoSuchMethodException e) {
// e.printStackTrace();
// }
// getPolars.setAccessible(true);
// try {
// getPolars.invoke(NewPolars.newPolars);
// } catch (IllegalAccessException e) {
// e.printStackTrace();
// } catch (InvocationTargetException e) {
// e.printStackTrace();
// }
}
@Test
public void testQuads() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//reflection for private class
Class[] parameterTypes = new Class[1];
parameterTypes[0] = Double.TYPE;
Method getQuads = NewPolars.class.getDeclaredMethod("getQuadrant", parameterTypes);
getQuads.setAccessible(true);
//start invoking
Object[] paras1 = new Object[1];
paras1[0] = (new Double(0)).doubleValue();
int q1 = (int) getQuads.invoke(NewPolars.newPolars, paras1);
assertEquals(q1, 1);
//start invoking
Object[] paras2 = new Object[1];
paras2[0] = (new Double(90)).doubleValue();
int q2 = (int) getQuads.invoke(NewPolars.newPolars, paras2);
assertEquals(q2, 2);
//start invoking
Object[] paras3 = new Object[1];
paras3[0] = (new Double(180)).doubleValue();
int q3 = (int) getQuads.invoke(NewPolars.newPolars, paras3);
assertEquals(q3, 3);
//start invoking
Object[] paras4 = new Object[1];
paras4[0] = (new Double(270)).doubleValue();
int q4 = (int) getQuads.invoke(NewPolars.newPolars, paras4);
assertEquals(q4, 4);
//start invoking
Object[] paras5 = new Object[1];
paras5[0] = (new Double(360)).doubleValue();
int q5 = (int) getQuads.invoke(NewPolars.newPolars, paras5);
assertEquals(q5, 1);
}
@Test
public void testEdgeSpeeds(){
//just make sure that speeds at certain angles do not throw a null exception and are not negative
double maxTWS = 30;
for (double tws = 0; tws < maxTWS; tws += 1){
for (double j = 0; j < 360; j++){
Bearing twa = Bearing.fromDegrees(j);
for (double i = 0; i < 360; i++){
Bearing boatBearing = Bearing.fromDegrees(i);
double speed = NewPolars.calculateSpeed(twa, tws, boatBearing);
assertTrue(speed >= 0);
}
}
}
}
@Test
public void testClosestSpeeds() throws NoSuchMethodException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
//reflection for private class
Method getClosest = NewPolars.class.getDeclaredMethod("getClosest", double.class, Set.class);
getClosest.setAccessible(true);
Method getPolars = NewPolars.class.getDeclaredMethod("getPolars");
getPolars.setAccessible(true);
double maxTWS = 30;
//only catches for nulls
for (double tws = 0; tws < maxTWS; tws += 1){
Map<Double, TreeMap<Double, Double>> polars = (Map<Double, TreeMap<Double, Double>>) getPolars.invoke(NewPolars.newPolars);
double speed = (double) getClosest.invoke(NewPolars.newPolars, tws, polars.keySet());
assertTrue(speed >= 0);
}
}
@Test
public void testAutoVSCalculated(){
//test that the auto chosen speed is the same speed that is calculated
double maxTWS = 30;
for (double tws = 0; tws < maxTWS; tws ++){
for (double twa = 0; twa < 360; twa ++){
Bearing TW = Bearing.fromDegrees(twa);
for (double ba = 0; ba < 360; ba ++){
Bearing boatBearing = Bearing.fromDegrees(ba);
VMG autoVMG = NewPolars.setBestVMG(TW, tws, boatBearing);
double speed = NewPolars.calculateSpeed(TW, tws, autoVMG.getBearing());
assertTrue(autoVMG.getSpeed() == speed);
}
}
}
}
}

@ -1,5 +1,6 @@
package mock.model;
import mock.model.wind.RandomWindGenerator;
import org.junit.Before;
import org.junit.Test;
import shared.model.Bearing;

@ -1,6 +1,7 @@
package mock.model;
import mock.exceptions.SourceIDAllocationException;
import network.Messages.Enums.RaceStatusEnum;
import org.junit.Before;
import org.junit.Test;
@ -15,30 +16,16 @@ import static org.junit.Assert.*;
*/
public class SourceIdAllocatorTest {
/**
* This is the list of source IDs that we start with.
*/
private List<Integer> originalSourceIDs;
/**
* Used to allocate source IDs.
*/
private MockRace mockRace;
private SourceIdAllocator sourceIdAllocator;
@Before
public void setUp() throws Exception {
originalSourceIDs = new ArrayList<>();
originalSourceIDs.add(120);
originalSourceIDs.add(121);
originalSourceIDs.add(122);
originalSourceIDs.add(123);
originalSourceIDs.add(124);
originalSourceIDs.add(125);
mockRace = MockRaceTest.createMockRace();
sourceIdAllocator = new SourceIdAllocator(originalSourceIDs);
sourceIdAllocator = new SourceIdAllocator(mockRace);
}
@ -49,11 +36,12 @@ public class SourceIdAllocatorTest {
@Test
public void emptyAllocationTest() {
SourceIdAllocator allocator = new SourceIdAllocator(new ArrayList<>());
mockRace.getRaceDataSource().getParticipants().removeAll(mockRace.getBoatDataSource().getBoats().keySet());
mockRace.getRaceDataSource().getParticipants().addAll(mockRace.getBoatDataSource().getBoats().keySet());
try {
int sourceID = allocator.allocateSourceID();
int sourceID = sourceIdAllocator.allocateSourceID();
fail("Exception should have been thrown, but wasn't.");
@ -73,6 +61,7 @@ public class SourceIdAllocatorTest {
@Test
public void allocationTest() throws Exception {
mockRace.setRaceStatusEnum(RaceStatusEnum.PRESTART);
int sourceID = sourceIdAllocator.allocateSourceID();
@ -108,10 +97,7 @@ public class SourceIdAllocatorTest {
@Test
public void reallocationTest() throws Exception {
List<Integer> sourceIDList = new ArrayList<>();
sourceIDList.add(123);
SourceIdAllocator sourceIdAllocator = new SourceIdAllocator(sourceIDList);
mockRace.setRaceStatusEnum(RaceStatusEnum.PRESTART);
//Allocate.
int sourceID = sourceIdAllocator.allocateSourceID();

@ -1,11 +1,11 @@
package mock.model.commandFactory;
import mock.exceptions.CommandConstructionException;
import mock.model.MockBoat;
import mock.model.MockRace;
import mock.model.MockRaceTest;
import mock.exceptions.SourceIDAllocationException;
import mock.model.*;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
import network.Messages.Enums.RaceStatusEnum;
import org.junit.Before;
import org.junit.Test;
import shared.exceptions.InvalidBoatDataException;
@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock;
*/
public class WindCommandTest {
private MockRace race;
private SourceIdAllocator allocator;
private MockBoat boat;
private Command upwind;
private Command downwind;
@ -29,12 +30,15 @@ public class WindCommandTest {
private double offset = 3.0;
@Before
public void setUp() throws CommandConstructionException, InvalidBoatDataException, InvalidRegattaDataException, InvalidRaceDataException {
public void setUp() throws CommandConstructionException, InvalidBoatDataException, InvalidRegattaDataException, InvalidRaceDataException, SourceIDAllocationException {
race = MockRaceTest.createMockRace();
allocator = new SourceIdAllocator(race);
race.setRaceStatusEnum(RaceStatusEnum.PRESTART);
allocator.allocateSourceID();
boat = race.getBoats().get(0);
//when(race.getWindDirection()).thenReturn(Bearing.fromDegrees(0.0));
boat.setBearing(Bearing.fromDegrees(45.0));

@ -94,7 +94,7 @@ public class ConnectionToServerParticipantTest {
incomingCommands.put(command);
//Need to wait for connection thread to execute commands.
Thread.sleep(250);
Thread.sleep(500);
assertEquals(ConnectionToServerState.CONNECTED, connectionToServer.getConnectionState());
assertTrue(connectionToServer.getJoinAcceptance() != null);

@ -5,12 +5,6 @@
<CreationTimeDate>2017-04-19T15:30:00+1200</CreationTimeDate>
<RaceStartTime Postpone="false" Time="2017-04-19T15:33:00+1200"/>
<Participants>
<Yacht SourceID="121"/>
<Yacht SourceID="122"/>
<Yacht SourceID="123"/>
<Yacht SourceID="124"/>
<Yacht SourceID="125"/>
<Yacht SourceID="126"/>
</Participants>
<CompoundMarkSequence>
<Corner CompoundMarkID="1" SeqID="1"/>
@ -54,4 +48,4 @@
<Limit Lat="32.297164" Lon="-64.856394" SeqID="10"/>
<Limit Lat="32.296148" Lon="-64.849184" SeqID="11"/>
</CourseLimit>
</Race>
</Race>

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