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:
Connor Taylor-Brown
2017-09-12 11:11:39 +12:00
119 changed files with 4467 additions and 1518 deletions
@@ -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,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,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,4 +1,4 @@
package mock.model;
package mock.model.wind;
import shared.model.Wind;