Merge branch 'master' into storyA_sounds
# Conflicts: # racevisionGame/src/main/java/mock/model/RaceServer.java # racevisionGame/src/main/java/visualiser/Controllers/HostController.java # racevisionGame/src/main/java/visualiser/Controllers/MainController.java # racevisionGame/src/main/java/visualiser/Controllers/TitleController.java
This commit is contained in:
@@ -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;
|
||||
@@ -15,16 +18,16 @@ import shared.exceptions.InvalidRegattaDataException;
|
||||
import shared.exceptions.XMLReaderException;
|
||||
import shared.model.Bearing;
|
||||
import shared.model.Constants;
|
||||
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;
|
||||
@@ -68,6 +71,8 @@ public class Event {
|
||||
|
||||
private Thread connectionThread;
|
||||
|
||||
private int mapIndex;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -77,11 +82,32 @@ public class Event {
|
||||
* @param singlePlayer Whether or not to create a single player event.
|
||||
* @throws EventConstructionException Thrown if we cannot create an Event for any reason.
|
||||
*/
|
||||
public Event(boolean singlePlayer) throws EventConstructionException {
|
||||
public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException {
|
||||
|
||||
String raceXMLFile = "mock/mockXML/raceTest.xml";
|
||||
// System.out.println(XMLUtilities.validateXML(this.getClass().getClassLoader().getResource("mock/mockXML/iMapLayout.xml").toString()
|
||||
// , this.getClass().getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd")));
|
||||
this.mapIndex = mapIndex;
|
||||
String raceXMLFile;
|
||||
String boatsXMLFile = "mock/mockXML/boatTest.xml";
|
||||
String regattaXMLFile = "mock/mockXML/regattaTest.xml";
|
||||
switch (mapIndex){
|
||||
case 0:raceXMLFile = "mock/mockXML/raceSixPlayers.xml";
|
||||
break;
|
||||
case 1:raceXMLFile = "mock/mockXML/oMapLayout.xml";
|
||||
break;
|
||||
case 2: raceXMLFile = "mock/mockXML/iMapLayout.xml";
|
||||
break;
|
||||
case 3: raceXMLFile = "mock/mockXML/mMapLayout.xml";
|
||||
break;
|
||||
case 4:
|
||||
raceXMLFile = "mock/mockXML/raceTutorial.xml";
|
||||
boatsXMLFile = "mock/mockXML/boatTutorial.xml";
|
||||
regattaXMLFile = "mock/mockXML/regattaTutorial.xml";
|
||||
break;
|
||||
default: raceXMLFile = "mock/mockXML/raceSixPlayers.xml";
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (singlePlayer) {
|
||||
raceXMLFile = "mock/mockXML/raceSinglePlayer.xml";
|
||||
@@ -93,7 +119,9 @@ public class Event {
|
||||
//this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90);
|
||||
this.raceXML = XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8);
|
||||
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
|
||||
|
||||
if(mapIndex==4){
|
||||
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
|
||||
}
|
||||
this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
|
||||
this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8);
|
||||
|
||||
@@ -104,6 +132,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.
|
||||
@@ -118,18 +147,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,
|
||||
@@ -146,6 +171,7 @@ public class Event {
|
||||
|
||||
|
||||
//Create connection acceptor.
|
||||
this.sourceIdAllocator = new SourceIdAllocator(newRace.getRace());
|
||||
|
||||
try {
|
||||
this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace);
|
||||
@@ -158,8 +184,6 @@ public class Event {
|
||||
this.connectionThread = new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread");
|
||||
connectionThread.start();
|
||||
|
||||
sendXMLs();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -172,18 +196,6 @@ public class Event {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sends out each xml string, via the mock output
|
||||
*/
|
||||
private void sendXMLs() {
|
||||
|
||||
connectionAcceptor.setRegattaXml(regattaXML);
|
||||
|
||||
connectionAcceptor.setRaceXml(raceXML);
|
||||
|
||||
connectionAcceptor.setBoatsXml(boatXML);
|
||||
}
|
||||
|
||||
|
||||
//TODO remove this after demo on 18th august!
|
||||
/**
|
||||
@@ -192,9 +204,15 @@ public class Event {
|
||||
* @return String containing edited xml
|
||||
*/
|
||||
public static String setRaceXMLAtCurrentTimeToNow(String raceXML) {
|
||||
return setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime);
|
||||
}
|
||||
|
||||
public static String setRaceXMLAtCurrentTimeToNow(String raceXML, long racePreStartTime, long racePreparatoryTime){
|
||||
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
|
||||
long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000);
|
||||
|
||||
long millisecondsToAdd = racePreStartTime + racePreparatoryTime;
|
||||
|
||||
|
||||
long secondsToAdd = millisecondsToAdd / 1000;
|
||||
//Scale the time using our time scalar.
|
||||
secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
|
||||
@@ -204,7 +222,6 @@ public class Event {
|
||||
raceXML = raceXML.replace("RACE_CREATION_TIME", dateFormat.format(creationTime));
|
||||
raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
|
||||
|
||||
|
||||
return raceXML;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
outgoingMessages.put(latestMessages.getBoatXMLMessage());
|
||||
|
||||
sentXMLs = true;
|
||||
}
|
||||
|
||||
if (lastSentBoatNumber != latestMessages.getBoatXMLMessage().getSequenceNumber()) {
|
||||
lastSentBoatNumber = latestMessages.getBoatXMLMessage().getSequenceNumber();
|
||||
outgoingMessages.put(latestMessages.getBoatXMLMessage());
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
@@ -49,6 +49,9 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
prestartCountdown();
|
||||
|
||||
race.initialiseBoats();
|
||||
|
||||
countdown();
|
||||
@@ -61,6 +64,37 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The countdown timer until the prestart period is finished. This timer will stop 3 minutes before the race starts, and players can no longer start participating.
|
||||
*/
|
||||
private void prestartCountdown() {
|
||||
|
||||
long previousFrameTime = System.currentTimeMillis();
|
||||
|
||||
while (((race.getRaceStatusEnum() == RaceStatusEnum.PRESTART)
|
||||
|| (race.getRaceStatusEnum() == RaceStatusEnum.NOT_ACTIVE)
|
||||
|| (race.getRaceStatusEnum() == RaceStatusEnum.WARNING)) && loopBool) {
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
//Update race time.
|
||||
race.updateRaceTime(currentTime);
|
||||
|
||||
//Update the race status based on the current time.
|
||||
race.updateRaceStatusEnum();
|
||||
|
||||
//Provide boat's with an estimated time at next mark until the race starts.
|
||||
race.setBoatsTimeNextMark(race.getRaceClock().getCurrentTime());
|
||||
|
||||
//Parse the race snapshot.
|
||||
server.parseSnapshot();
|
||||
|
||||
|
||||
waitForFramePeriod(previousFrameTime, currentTime, 50);
|
||||
previousFrameTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Countdown timer until race starts.
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
package mock.model;
|
||||
|
||||
import network.AckSequencer;
|
||||
import network.Messages.*;
|
||||
import network.Messages.Enums.BoatLocationDeviceEnum;
|
||||
import network.Messages.Enums.YachtEventEnum;
|
||||
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.
|
||||
@@ -24,6 +32,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;
|
||||
@@ -53,6 +76,8 @@ public class RaceServer {
|
||||
|
||||
latestMessages.setSnapshot(snapshotMessages);
|
||||
|
||||
updateXMLFiles();
|
||||
|
||||
//Reset collision list
|
||||
collisionEvents.clear();
|
||||
//System.out.println(collisionEvents.size());
|
||||
@@ -60,6 +85,73 @@ public class RaceServer {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the race/boat/regatta data sources have changed, and if they have, update their xml representations.
|
||||
*/
|
||||
private void updateXMLFiles() {
|
||||
updateRaceXMLFile();
|
||||
updateBoatXMLFile();
|
||||
updateRegattaXMLFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the race data source has changed, and if it has, updates LatestMessages' race xml message.
|
||||
*/
|
||||
private void updateRaceXMLFile() {
|
||||
if (raceXMLSeqNumber != race.getRaceDataSource().getSequenceNumber()) {
|
||||
|
||||
raceXMLSeqNumber = race.getRaceDataSource().getSequenceNumber();
|
||||
try {
|
||||
String raceXMLString = RaceDataSourceToXML.toString(race.getRaceDataSource());
|
||||
XMLMessage message = createXMLMessage(raceXMLString, XMLMessageType.RACE);
|
||||
latestMessages.setXMLMessage(message);
|
||||
|
||||
} catch (JAXBException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "Could not serialise: " + race.getRaceDataSource(), e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the boat data source has changed, and if it has, updates LatestMessages' boat xml message.
|
||||
*/
|
||||
private void updateBoatXMLFile() {
|
||||
if (boatXMLSeqNumber != race.getBoatDataSource().getSequenceNumber()) {
|
||||
|
||||
boatXMLSeqNumber = race.getBoatDataSource().getSequenceNumber();
|
||||
try {
|
||||
String boatXMLString = BoatDataSourceToXML.toString(race.getBoatDataSource());
|
||||
XMLMessage message = createXMLMessage(boatXMLString, XMLMessageType.BOAT);
|
||||
latestMessages.setXMLMessage(message);
|
||||
|
||||
} catch (JAXBException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "Could not serialise: " + race.getBoatDataSource(), e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the regatta data source has changed, and if it has, updates LatestMessages' regatta xml message.
|
||||
*/
|
||||
private void updateRegattaXMLFile() {
|
||||
if (regattaXMLSeqNumber != race.getRegattaDataSource().getSequenceNumber()) {
|
||||
|
||||
regattaXMLSeqNumber = race.getRegattaDataSource().getSequenceNumber();
|
||||
|
||||
try {
|
||||
String regattaXMLString = RegattaDataSourceToXML.toString(race.getRegattaDataSource());
|
||||
XMLMessage message = createXMLMessage(regattaXMLString, XMLMessageType.REGATTA);
|
||||
latestMessages.setXMLMessage(message);
|
||||
|
||||
} catch (JAXBException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "Could not serialise: " + race.getRegattaDataSource(), e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an individual marker boat, and returns it.
|
||||
* @param mark The marker boat to parse.
|
||||
@@ -191,6 +283,43 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the yacht event and return it
|
||||
* @param boat yacht with event
|
||||
|
||||
@@ -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.
|
||||
* Creates a SourceIdAllocator for a given race.
|
||||
* @param mockRace Race to allocate source IDs for.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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,25 @@ 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 +70,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
-3
@@ -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.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package mock.model;
|
||||
package mock.model.wind;
|
||||
|
||||
|
||||
import shared.model.Bearing;
|
||||
@@ -0,0 +1,152 @@
|
||||
package mock.model.wind;
|
||||
|
||||
import shared.model.Bearing;
|
||||
import shared.model.Wind;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class ShiftingWindGenerator implements WindGenerator {
|
||||
private Bearing baselineBearing;
|
||||
private double baseLineSpeed;
|
||||
private double windSpeedVariance = 5;
|
||||
private double bearingVariance = 5; // In degrees
|
||||
private double oscillationVariance = 0.25;
|
||||
private double oscillationPeriod = 1e3 * 60 * 1; // In milliseconds
|
||||
private double shiftTime = 1e3 * 60;
|
||||
private double shiftedSoFar = 0;
|
||||
|
||||
private double timeOfLastOscillationReset = 0;
|
||||
private double timeOfLastChange = 0;
|
||||
private double timeOfLastShift = 0; // Back / veer
|
||||
|
||||
private boolean anticlockwise = false;
|
||||
private boolean shiftAnticlockwise = false;//true for Back, false for veer
|
||||
private boolean shiftThisRace = Math.random() > 0.5;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param baselineBearing baseline bearing for wind
|
||||
* @param baseLineSpeed base line speed for wind
|
||||
*/
|
||||
public ShiftingWindGenerator(Bearing baselineBearing, double baseLineSpeed) {
|
||||
this.baselineBearing = baselineBearing;
|
||||
this.baseLineSpeed = baseLineSpeed;
|
||||
initialiseOscillationDirection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind generateBaselineWind() {
|
||||
return new Wind(baselineBearing, baseLineSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind generateNextWind(Wind currentWind) {
|
||||
return changeWind(currentWind);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param wind the wind to change
|
||||
* @return the changed wind
|
||||
*/
|
||||
private Wind changeWind(Wind wind) {
|
||||
Wind newWind = new Wind(wind.getWindDirection(), wind.getWindSpeed());
|
||||
oscillateWind(newWind);
|
||||
if (shiftThisRace){shiftWind(newWind);}
|
||||
changeWindSpeed(newWind);
|
||||
timeOfLastChange = System.currentTimeMillis();
|
||||
return newWind;
|
||||
}
|
||||
|
||||
/**
|
||||
* moves the wind 5 degrees up and down
|
||||
* @param wind the wind to oscillate
|
||||
*/
|
||||
private void oscillateWind(Wind wind) {
|
||||
double timeSinceLastOscillationReset = System.currentTimeMillis() - timeOfLastOscillationReset;
|
||||
double timeSinceLastChange = System.currentTimeMillis() - timeOfLastChange;
|
||||
double newBearing = wind.getWindDirection().degrees();
|
||||
double degreeChange = timeSinceLastChange * 2 * bearingVariance / oscillationPeriod;
|
||||
degreeChange = (1 - oscillationVariance) * degreeChange + (2 * oscillationVariance) * degreeChange * Math.random();
|
||||
|
||||
if (timeSinceLastOscillationReset >= oscillationPeriod) {
|
||||
timeOfLastOscillationReset = System.currentTimeMillis();
|
||||
anticlockwise = !anticlockwise;
|
||||
}
|
||||
if (anticlockwise) {
|
||||
newBearing -= degreeChange;
|
||||
if (newBearing < baselineBearing.degrees() - bearingVariance) {
|
||||
anticlockwise = !anticlockwise;
|
||||
timeOfLastOscillationReset = System.currentTimeMillis();
|
||||
} else {
|
||||
wind.setWindDirection(Bearing.fromDegrees(newBearing % 360));
|
||||
}
|
||||
} else {
|
||||
newBearing += degreeChange;
|
||||
if (newBearing > baselineBearing.degrees() + bearingVariance) {
|
||||
anticlockwise = !anticlockwise;
|
||||
timeOfLastOscillationReset = System.currentTimeMillis();
|
||||
} else {
|
||||
wind.setWindDirection(Bearing.fromDegrees(newBearing % 360));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Slowly shifts the wind up to 180 degrees from where it started
|
||||
* @param wind the wind to change
|
||||
*/
|
||||
private void shiftWind(Wind wind) {
|
||||
double timeSinceLastShift = System.currentTimeMillis() - timeOfLastShift;
|
||||
double newBearing = wind.getWindDirection().degrees();
|
||||
double degreeChange = 7;
|
||||
|
||||
if (timeSinceLastShift >= shiftTime){
|
||||
shiftedSoFar += degreeChange;
|
||||
if (shiftedSoFar >= 180){
|
||||
shiftAnticlockwise = Math.random() > 0.5;
|
||||
shiftedSoFar = 0;
|
||||
// System.out.println("Swapping");
|
||||
}
|
||||
|
||||
timeOfLastShift = System.currentTimeMillis();
|
||||
if (shiftAnticlockwise){
|
||||
newBearing -= degreeChange;
|
||||
wind.setWindDirection(Bearing.fromDegrees(newBearing % 360));
|
||||
} else {
|
||||
newBearing += degreeChange;
|
||||
wind.setWindDirection(Bearing.fromDegrees(newBearing % 360));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the wind speed
|
||||
* @param wind the wind to change
|
||||
*/
|
||||
private void changeWindSpeed(Wind wind) {
|
||||
double offsetAngle = (wind.getWindDirection().radians() - baselineBearing.radians());
|
||||
double offset = Math.sin(offsetAngle) * windSpeedVariance;
|
||||
double newWindSpeed = baseLineSpeed + offset;
|
||||
wind.setWindSpeed(newWindSpeed);
|
||||
}
|
||||
|
||||
/**
|
||||
* starts the wind oscillation direction
|
||||
*/
|
||||
private void initialiseOscillationDirection() {
|
||||
anticlockwise = new Random().nextBoolean();
|
||||
timeOfLastOscillationReset = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setBearingVariance(double maxBearingVariance) {
|
||||
this.bearingVariance = maxBearingVariance;
|
||||
}
|
||||
|
||||
public void setWindSpeedVariance(double windSpeedVariance) {
|
||||
this.windSpeedVariance = windSpeedVariance;
|
||||
}
|
||||
|
||||
public void setOscillationPeriod(double oscillationPeriod) {
|
||||
this.oscillationPeriod = oscillationPeriod;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package mock.model;
|
||||
package mock.model.wind;
|
||||
|
||||
import shared.model.Wind;
|
||||
|
||||
@@ -74,14 +74,19 @@ public class RaceXMLCreator {
|
||||
* @throws SAXException error in schema file
|
||||
* @throws ParserConfigurationException error in parsing the schema file
|
||||
*/
|
||||
public static String alterRaceToWind(String s, double degrees) throws XMLReaderException, InvalidRaceDataException, JAXBException, IOException, SAXException, ParserConfigurationException {
|
||||
public static String alterRaceToWind(String s, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException, JAXBException, IOException, SAXException, ParserConfigurationException {
|
||||
RaceXMLReader reader = new RaceXMLReader(s, XMLFileType.ResourcePath);
|
||||
|
||||
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);
|
||||
|
||||
setRaceXMLAtCurrentTimeToNow(race);
|
||||
if(tutorial){
|
||||
setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l);
|
||||
} else {
|
||||
setRaceXMLAtCurrentTimeToNow(race);
|
||||
}
|
||||
|
||||
double raceOriginalBearing = getLineAngle(getLeewardGate(reader).getMark1Position(), getWindwardGate(reader).getMark1Position());
|
||||
|
||||
@@ -89,16 +94,10 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rotate the features in a race such as the boundary, and the marks.
|
||||
* @param race the race to alter
|
||||
@@ -186,14 +185,11 @@ public class RaceXMLCreator {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
|
||||
* @param raceXML The race.xml contents.
|
||||
*/
|
||||
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) {
|
||||
|
||||
|
||||
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML, long racePrestartTime, long racePreparatoryTime){
|
||||
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
|
||||
long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000);
|
||||
long millisecondsToAdd = racePrestartTime + racePreparatoryTime;
|
||||
long secondsToAdd = millisecondsToAdd / 1000;
|
||||
//Scale the time using our time scalar.
|
||||
secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
|
||||
@@ -204,4 +200,13 @@ public class RaceXMLCreator {
|
||||
raceXML.getRaceStartTime().setTime(dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
|
||||
* @param raceXML The race.xml contents.
|
||||
*/
|
||||
public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) {
|
||||
setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user