Merge remote-tracking branch 'remotes/origin/master' into Story_76

# Conflicts:
#	racevisionGame/src/main/java/visualiser/Controllers/HostController.java
#	racevisionGame/src/main/resources/visualiser/scenes/hostgame.fxml
main
hba56 8 years ago
commit ea1594a839

@ -106,6 +106,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>

@ -14,4 +14,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,14 +66,17 @@
<version>15.0</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-referencing</artifactId>
<version>9.0</version>
</dependency>
<dependency>
<groupId>JavaInteractiveMesh</groupId>
<artifactId>STLImporter</artifactId>
<version>0.7</version>
</dependency>
</dependencies>
@ -72,7 +95,11 @@
<url>http://download.osgeo.org/webdav/geotools/</url>
</repository>
<repository>
<id>interactivemesh</id>
<name>Interactive Mesh</name>
<url>http://umbrasheep.com:8888/repository/internal/</url>
</repository>
</repositories>
@ -163,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;
}
}

@ -4,6 +4,9 @@ import mock.dataInput.PolarParser;
import mock.exceptions.EventConstructionException;
import mock.model.*;
import mock.model.commandFactory.CompositeCommand;
import mock.model.wind.RandomWindGenerator;
import mock.model.wind.ShiftingWindGenerator;
import mock.model.wind.WindGenerator;
import mock.xml.RaceXMLCreator;
import network.Messages.LatestMessages;
import org.xml.sax.SAXException;
@ -19,13 +22,12 @@ import shared.xml.XMLUtilities;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
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;
@ -120,6 +122,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.
@ -134,18 +137,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,
@ -162,6 +161,7 @@ public class Event {
//Create connection acceptor.
this.sourceIdAllocator = new SourceIdAllocator(newRace.getRace());
try {
this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace);
@ -174,8 +174,6 @@ public class Event {
this.connectionThread = new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread");
connectionThread.start();
sendXMLs();
}
@ -188,18 +186,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);
}
/**
@ -193,7 +187,7 @@ public class RaceXMLCreator {
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) {
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000);
long millisecondsToAdd = Constants.RacePreStartTime + Constants.RacePreparatoryTime;
long secondsToAdd = millisecondsToAdd / 1000;
//Scale the time using our time scalar.
secondsToAdd = secondsToAdd / Constants.RaceTimeScale;

@ -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,18 +33,24 @@ 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.
* The race pre-start time, in milliseconds. 3 minutes (30 seconds for development).
*/
public static final long RacePreStartTime = 1 * 10 * 1000;
// public static final long RacePreStartTime = 30 * 1000;
public static final long RacePreStartTime = 1000;
/**
* The race preparatory time, in milliseconds. 1 minutes.
* The race preparatory time, in milliseconds. 1 minute.
*/
public static final long RacePreparatoryTime = 1 * 60 * 1000;
// public static final long RacePreparatoryTime = 60 * 1000;
public static final long RacePreparatoryTime = 3 * 1000;
/**

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

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

@ -3,7 +3,6 @@ package visualiser.Controllers;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
import visualiser.app.App;
import visualiser.gameController.ControllerClient;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
@ -40,6 +39,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);
}
}
}

@ -1,6 +1,6 @@
package visualiser.Controllers;
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
@ -10,21 +10,27 @@ import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Translate;
import javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.RaceDataSource;
import shared.model.Leg;
import shared.model.Mark;
import visualiser.app.App;
import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import visualiser.layout.Subject3D;
import visualiser.layout.View3D;
import visualiser.model.*;
import visualiser.utils.GPSConverter;
import java.io.IOException;
import java.net.URL;
@ -33,59 +39,41 @@ import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import static visualiser.app.App.keyFactory;
/**
* Controller used to display a running race.
*/
public class RaceController extends Controller {
/**
* The race object which describes the currently occurring race.
*/
private VisualiserRaceEvent visualiserRace;
/**
* Service for sending keystrokes to server
*/
private ControllerClient controllerClient;
private boolean isHost;
/**
* The canvas that draws the race.
*/
private ResizableRaceCanvas raceCanvas;
/**
* The sparkline graph.
*/
private Sparkline sparkline;
/**
* state of the info table
*/
private boolean infoTableShow;
private View3D view3D;
private ObservableList<Subject3D> viewSubjects;
/**
* The arrow controller.
*/
@FXML private ArrowController arrowController;
@FXML private GridPane canvasBase;
@FXML private SplitPane race;
/**
* This is the root node of the arrow control.
*/
@FXML private Pane arrow;
@FXML private SplitPane racePane;
/**
* This is the pane we place the actual arrow control inside of.
@ -101,9 +89,6 @@ public class RaceController extends Controller {
@FXML private TableColumn<VisualiserBoat, Leg> boatMarkColumn;
@FXML private TableColumn<VisualiserBoat, Number> boatSpeedColumn;
@FXML private LineChart<Number, Number> sparklineChart;
@FXML private AnchorPane annotationPane;
/**
* Ctor.
@ -113,11 +98,10 @@ public class RaceController extends Controller {
@Override
public void initialize(URL location, ResourceBundle resources) {
KeyFactory keyFactory = KeyFactory.getFactory();
infoTableShow = true;
// Initialise keyboard handler
race.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
String codeString = event.getCode().toString();
if (codeString.equals("TAB")){toggleTable();}
@ -143,7 +127,7 @@ public class RaceController extends Controller {
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
parent.endEvent();
race.setVisible(false);
racePane.setVisible(false);
App.app.showMainStage(App.getStage());
}
} else {
@ -152,7 +136,7 @@ public class RaceController extends Controller {
alert.setContentText("Do you wish to quit the race?");
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
race.setVisible(false);
racePane.setVisible(false);
App.app.showMainStage(App.getStage());
}
}
@ -176,14 +160,10 @@ public class RaceController extends Controller {
//Information table.
initialiseInfoTable(this.visualiserRace);
//Sparkline.
initialiseSparkline(this.visualiserRace);
//Arrow.
initialiseArrow(this.visualiserRace);
//Race canvas.
initialiseRaceCanvas(this.visualiserRace);
initialiseView3D(this.visualiserRace);
//Race timezone label.
initialiseRaceTimezoneLabel(this.visualiserRace);
@ -191,11 +171,79 @@ public class RaceController extends Controller {
//Race clock.
initialiseRaceClock(this.visualiserRace);
//Start the race animation timer.
raceTimer();
}
private void initialiseView3D(VisualiserRaceEvent race) {
viewSubjects = FXCollections.observableArrayList();
// Import boat mesh
URL asset = HostController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
StlMeshImporter importer = new StlMeshImporter();
importer.read(asset);
// Configure camera angles and control
view3D = new View3D();
view3D.setDistance(1050);
view3D.setYaw(0);
view3D.setPitch(60);
view3D.enableTracking();
canvasBase.add(view3D, 0, 0);
// Set up projection from GPS to view
RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);
view3D.setItems(viewSubjects);
// Position and add each mark to view
for(Mark mark: race.getVisualiserRaceState().getMarks()) {
Subject3D subject = new Subject3D(new Sphere(2));
subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
viewSubjects.add(subject);
}
// Position and add each boat to view
for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
MeshView mesh = new MeshView(importer.getImport());
Subject3D subject = new Subject3D(mesh);
viewSubjects.add(subject);
// Track this boat's movement with the new subject
AnimationTimer trackBoat = new AnimationTimer() {
@Override
public void handle(long now) {
subject.setHeading(boat.getBearing().degrees());
subject.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
}
};
trackBoat.start();
}
// Fix initial bird's-eye position
view3D.updatePivot(new Translate(250, 0, 210));
// Bind zooming to scrolling
view3D.setOnScroll(e -> {
view3D.updateDistance(e.getDeltaY());
});
// Bind zooming to keypress (Z/X default)
racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
ControlKey key = keyFactory.getKey(e.getCode().toString());
if(key != null) {
switch (key.toString()) {
case "Zoom In":
view3D.updateDistance(-10);
break;
case "Zoom Out":
view3D.updateDistance(10);
break;
}
}
});
}
/**
@ -338,42 +386,8 @@ public class RaceController extends Controller {
}
/**
* Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRaceEvent}.
* @param race The race to listen to.
*/
private void initialiseSparkline(VisualiserRaceEvent race) {
//The race.getBoats() we are passing in is sorted by position in race inside the race class.
this.sparkline = new Sparkline(this.visualiserRace.getVisualiserRaceState(), this.sparklineChart);
}
/**
* Initialises the {@link ResizableRaceCanvas}, provides the race to read data from.
* @param race Race to read data from.
*/
private void initialiseRaceCanvas(VisualiserRaceEvent race) {
//Create canvas.
raceCanvas = new ResizableRaceCanvas(race);
//Set properties.
raceCanvas.setMouseTransparent(true);
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
//Draw it and show it.
raceCanvas.draw();
raceCanvas.setVisible(true);
//Add to scene.
canvasBase.getChildren().add(0, raceCanvas);
}
/**
* Intialises the race time zone label with the race's time zone.
* Initialises the race time zone label with the race's time zone.
* @param race The race to get time zone from.
*/
private void initialiseRaceTimezoneLabel(VisualiserRaceEvent race) {
@ -411,11 +425,7 @@ public class RaceController extends Controller {
initialiseRace();
//Display this controller.
race.setVisible(true);
// set up annotation displays
new Annotations(annotationPane, raceCanvas);
racePane.setVisible(true);
}
/**
@ -423,7 +433,7 @@ public class RaceController extends Controller {
* @param boats boats there are in the race.
*/
public void finishRace(ObservableList<VisualiserBoat> boats) {
race.setVisible(false);
racePane.setVisible(false);
parent.enterFinish(boats);
}
@ -458,18 +468,13 @@ public class RaceController extends Controller {
finishRace(visualiserRace.getVisualiserRaceState().getBoats());
} else {
//Otherwise, render the canvas.
raceCanvas.drawRace();
//Sort the tableview. Doesn't automatically work for all columns.
boatInfoTable.sort();
}
//Return to main screen if we lose connection.
if (!visualiserRace.getServerConnection().isAlive()) {
race.setVisible(false);
racePane.setVisible(false);
//parent.enterTitle();
try {
App.app.showMainStage(App.getStage());
@ -491,10 +496,10 @@ public class RaceController extends Controller {
* toggles if the info table is shown
*/
private void toggleTable() {
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/race.getWidth();
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/racePane.getWidth();
if (infoTableShow){
race.setDividerPositions(tablePercent);
racePane.setDividerPositions(tablePercent);
arrowPane.setScaleX(0.5);
arrowPane.setScaleY(0.5);
@ -502,7 +507,7 @@ public class RaceController extends Controller {
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
}else{
race.setDividerPositions(1);
racePane.setDividerPositions(1);
arrowPane.setScaleX(1);
arrowPane.setScaleY(1);

@ -225,12 +225,10 @@ public class StartController extends Controller {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum();
//Display it.
raceStatusLabel.setText("Race Status: " + raceStatus.name());
//If the race has reached the preparatory phase, or has started...
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
if (raceStatus == RaceStatusEnum.WARNING
|| raceStatus == RaceStatusEnum.PREPARATORY
|| raceStatus == RaceStatusEnum.STARTED) {
//Stop this timer.
stop();

@ -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;
@ -81,25 +83,32 @@ public class TitleController extends Controller {
@Override
public void initialize(URL location, ResourceBundle resources) {
}
/**
* Called when control button is pressed. New pop up window displaying controls
*/
public void controlBtnPressed(){
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/visualiser/scenes/controls.fxml"));
Parent layout;
try {
layout = loader.load();
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")
));
@ -96,15 +95,15 @@ public class App extends Application {
);
updateMessage("Preparing ingredients . . .");
Thread.sleep(1000);
Thread.sleep(200);
for (int i = 0; i < burgerFilling.size(); i++) {
Thread.sleep(800);
Thread.sleep(100);
updateProgress(i + 1, burgerFilling.size());
String nextFilling = burgerFilling.get(i);
addedFilling.add(nextFilling);
updateMessage("Adding the " + nextFilling + " . . .");
}
Thread.sleep(400);
Thread.sleep(100);
updateMessage("Burger's done!");
return addedFilling;

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

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

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

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

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

@ -39,9 +39,9 @@ public class ResizableRaceCanvas extends ResizableCanvas {
private Image sailsLuff = new Image("/images/sailsLuff.gif", 25, 10, false, false);
/**
* 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++) {

@ -1,11 +1,10 @@
package visualiser.model;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color;
import network.Messages.Enums.BoatStatusEnum;
import shared.model.Azimuth;
import shared.model.Boat;
import shared.model.Constants;
import shared.model.GPSCoordinate;
import shared.model.*;
import java.time.Duration;
import java.time.ZonedDateTime;
@ -61,7 +60,8 @@ public class VisualiserBoat extends Boat {
private boolean isClientBoat = false;
private ObjectProperty<GPSCoordinate> positionProperty;
private ObjectProperty<Bearing> bearingProperty;
/**
@ -239,4 +239,38 @@ public class VisualiserBoat extends Boat {
public void setClientBoat(boolean clientBoat) {
isClientBoat = clientBoat;
}
@Override
public GPSCoordinate getPosition() {
return positionProperty.get();
}
@Override
public void setPosition(GPSCoordinate position) {
if(this.positionProperty == null) {
this.positionProperty = new SimpleObjectProperty<>();
}
this.positionProperty.set(position);
}
public ObjectProperty<GPSCoordinate> positionProperty() {
return positionProperty;
}
@Override
public Bearing getBearing() {
return bearingProperty.get();
}
@Override
public void setBearing(Bearing bearing) {
if(this.bearingProperty == null) {
this.bearingProperty = new SimpleObjectProperty<>();
}
this.bearingProperty.set(bearing);
}
public ObjectProperty<Bearing> bearingProperty() {
return bearingProperty;
}
}

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 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>

@ -7,7 +7,7 @@
<fx:include fx:id="start" source="start.fxml" />
<fx:include fx:id="connection" source="connect.fxml" />
<fx:include fx:id="finish" source="finish.fxml" />
<fx:include fx:id="host" source="hostgame.fxml" />
<fx:include fx:id="host" source="hostlobby.fxml" />
<fx:include fx:id="title" source="titleScreen.fxml" />
<fx:include fx:id="lobby" source="lobby.fxml" />
</children>

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

@ -22,7 +22,7 @@
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.text.Font?>
<SplitPane fx:id="race" dividerPositions="1.0" prefHeight="431.0" prefWidth="610.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.RaceController">
<SplitPane fx:id="racePane" dividerPositions="1.0" prefHeight="431.0" prefWidth="610.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.RaceController">
<items>
<GridPane fx:id="canvasBase">
<columnConstraints>

@ -1,61 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--<?xml version="1.0" encoding="UTF-8"?>-->
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="connectionWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.ConnectionController">
<children>
<GridPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="hostGameTitleBtn" maxWidth="204.0" mnemonicParsing="false" text="Host Game" GridPane.halignment="LEFT" GridPane.rowIndex="1">
<font>
<Font size="20.0" />
</font>
<GridPane.margin>
<Insets left="50.0" />
</GridPane.margin>
</Button>
<Button fx:id="connectGameBtn" maxWidth="204.0" mnemonicParsing="false" text="Connect to Game" GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="1">
<font>
<Font size="20.0" />
</font>
<GridPane.margin>
<Insets right="50.0" />
</GridPane.margin>
</Button>
<RadioButton fx:id="nightRadioBtn" mnemonicParsing="false" text="Night Mode" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowIndex="2">
<padding>
<Insets bottom="-50.0" />
</padding>
<GridPane.margin>
<Insets left="80.0" />
</GridPane.margin>
</RadioButton>
<RadioButton fx:id="dayRadioBtn" mnemonicParsing="false" text="Day Mode" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowIndex="2">
<padding>
<Insets top="-50.0" />
</padding>
<GridPane.margin>
<Insets left="80.0" />
</GridPane.margin>
</RadioButton>
<Label text="Game" textAlignment="CENTER" GridPane.columnIndex="1" GridPane.halignment="CENTER">
<font>
<Font size="60.0" />
</font>
</Label>
</children>
</GridPane>
</children>
</AnchorPane>
<!--<?import javafx.geometry.*?>-->
<!--<?import javafx.scene.control.*?>-->
<!--<?import javafx.scene.layout.*?>-->
<!--<?import javafx.scene.text.Font?>-->
<!--<AnchorPane fx:id="connectionWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.ConnectionController">-->
<!--<children>-->
<!--<GridPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">-->
<!--<columnConstraints>-->
<!--<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />-->
<!--<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />-->
<!--<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />-->
<!--</columnConstraints>-->
<!--<rowConstraints>-->
<!--<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />-->
<!--<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />-->
<!--<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />-->
<!--</rowConstraints>-->
<!--<children>-->
<!--<Button fx:id="hostGameTitleBtn" maxWidth="204.0" mnemonicParsing="false" text="Host Game" GridPane.halignment="LEFT" GridPane.rowIndex="1">-->
<!--<font>-->
<!--<Font size="20.0" />-->
<!--</font>-->
<!--<GridPane.margin>-->
<!--<Insets left="50.0" />-->
<!--</GridPane.margin>-->
<!--</Button>-->
<!--<Button fx:id="connectGameBtn" maxWidth="204.0" mnemonicParsing="false" text="Connect to Game" GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="1">-->
<!--<font>-->
<!--<Font size="20.0" />-->
<!--</font>-->
<!--<GridPane.margin>-->
<!--<Insets right="50.0" />-->
<!--</GridPane.margin>-->
<!--</Button>-->
<!--<RadioButton fx:id="nightRadioBtn" mnemonicParsing="false" text="Night Mode" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowIndex="2">-->
<!--<padding>-->
<!--<Insets bottom="-50.0" />-->
<!--</padding>-->
<!--<GridPane.margin>-->
<!--<Insets left="80.0" />-->
<!--</GridPane.margin>-->
<!--</RadioButton>-->
<!--<RadioButton fx:id="dayRadioBtn" mnemonicParsing="false" text="Day Mode" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowIndex="2">-->
<!--<padding>-->
<!--<Insets top="-50.0" />-->
<!--</padding>-->
<!--<GridPane.margin>-->
<!--<Insets left="80.0" />-->
<!--</GridPane.margin>-->
<!--</RadioButton>-->
<!--<Label text="Game" textAlignment="CENTER" GridPane.columnIndex="1" GridPane.halignment="CENTER">-->
<!--<font>-->
<!--<Font size="60.0" />-->
<!--</font>-->
<!--</Label>-->
<!--</children>-->
<!--</GridPane>-->
<!--</children>-->
<!--</AnchorPane>-->

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