diff --git a/racevisionGame/pom.xml b/racevisionGame/pom.xml
index 8b195d5f..98ccaf82 100644
--- a/racevisionGame/pom.xml
+++ b/racevisionGame/pom.xml
@@ -25,10 +25,30 @@
org.mockito
- mockito-all
- 1.9.5
+ mockito-core
+ 2.9.0
+
+
+ net.bytebuddy
+ byte-buddy
+ 1.7.0
+ runtime
+
+
+ net.bytebuddy
+ byte-buddy-agent
+ 1.7.0
+ runtime
+
+
+ org.objenesis
+ objenesis
+ 2.6
+ runtime
+
+
diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java
index dcc5b407..6f778852 100644
--- a/racevisionGame/src/main/java/mock/app/Event.java
+++ b/racevisionGame/src/main/java/mock/app/Event.java
@@ -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;
@@ -18,11 +21,8 @@ import shared.model.Constants;
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;
@@ -106,6 +106,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.
@@ -124,14 +125,10 @@ public class Event {
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,
diff --git a/racevisionGame/src/main/java/mock/dataInput/PolarParser.java b/racevisionGame/src/main/java/mock/dataInput/PolarParser.java
index d33c0ac5..9a94d1f0 100644
--- a/racevisionGame/src/main/java/mock/dataInput/PolarParser.java
+++ b/racevisionGame/src/main/java/mock/dataInput/PolarParser.java
@@ -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 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();
+
+ }
+
}
diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java
index e574ae5a..a338cd5d 100644
--- a/racevisionGame/src/main/java/mock/model/MockRace.java
+++ b/racevisionGame/src/main/java/mock/model/MockRace.java
@@ -1,5 +1,6 @@
package mock.model;
+import mock.model.wind.WindGenerator;
import javafx.animation.AnimationTimer;
import mock.model.collider.ColliderRegistry;
import mock.xml.*;
@@ -244,7 +245,9 @@ public class MockRace extends RaceState {
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);
+
}
}
@@ -371,6 +374,7 @@ public class MockRace extends RaceState {
if (boat.getAutoVMG()) {
newOptimalVMG(boat);
+ boat.setAutoVMG(false);
}
} else {
@@ -383,31 +387,38 @@ public class MockRace extends RaceState {
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());
}
@@ -557,7 +568,7 @@ public class MockRace extends RaceState {
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 >= getLegs().size()){
diff --git a/racevisionGame/src/main/java/mock/model/NewPolars.java b/racevisionGame/src/main/java/mock/model/NewPolars.java
new file mode 100644
index 00000000..c525316c
--- /dev/null
+++ b/racevisionGame/src/main/java/mock/model/NewPolars.java
@@ -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,
+ private static Map> 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 prevTWS = null;
+ TreeMap> 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 tws = iterablePolars.get(windSpeed);
+
+ if (prevTWS == null){
+ prevTWS = tws;
+ continue;
+ }
+
+ double previousTWA = -1;
+ TreeMap 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 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 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> 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));
+ }
+ }
+ }
+
+}
diff --git a/racevisionGame/src/main/java/mock/model/Polars.java b/racevisionGame/src/main/java/mock/model/Polars.java
index 32ee8842..943214cf 100644
--- a/racevisionGame/src/main/java/mock/model/Polars.java
+++ b/racevisionGame/src/main/java/mock/model/Polars.java
@@ -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.
*
diff --git a/racevisionGame/src/main/java/mock/model/VMG.java b/racevisionGame/src/main/java/mock/model/VMG.java
index 905fadce..370df835 100644
--- a/racevisionGame/src/main/java/mock/model/VMG.java
+++ b/racevisionGame/src/main/java/mock/model/VMG.java
@@ -45,4 +45,8 @@ public class VMG {
return bearing;
}
+ public String toString(){
+ return String.format("VMG Object: Speed %f, Bearing %f.", speed, bearing.degrees());
+ }
+
}
diff --git a/racevisionGame/src/main/java/mock/model/ConstantWindGenerator.java b/racevisionGame/src/main/java/mock/model/wind/ConstantWindGenerator.java
similarity index 96%
rename from racevisionGame/src/main/java/mock/model/ConstantWindGenerator.java
rename to racevisionGame/src/main/java/mock/model/wind/ConstantWindGenerator.java
index ae14daac..6cdfafe9 100644
--- a/racevisionGame/src/main/java/mock/model/ConstantWindGenerator.java
+++ b/racevisionGame/src/main/java/mock/model/wind/ConstantWindGenerator.java
@@ -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.
diff --git a/racevisionGame/src/main/java/mock/model/RandomWindGenerator.java b/racevisionGame/src/main/java/mock/model/wind/RandomWindGenerator.java
similarity index 99%
rename from racevisionGame/src/main/java/mock/model/RandomWindGenerator.java
rename to racevisionGame/src/main/java/mock/model/wind/RandomWindGenerator.java
index 4f981b8d..bd7b13c3 100644
--- a/racevisionGame/src/main/java/mock/model/RandomWindGenerator.java
+++ b/racevisionGame/src/main/java/mock/model/wind/RandomWindGenerator.java
@@ -1,4 +1,4 @@
-package mock.model;
+package mock.model.wind;
import shared.model.Bearing;
diff --git a/racevisionGame/src/main/java/mock/model/wind/ShiftingWindGenerator.java b/racevisionGame/src/main/java/mock/model/wind/ShiftingWindGenerator.java
new file mode 100644
index 00000000..4ceff627
--- /dev/null
+++ b/racevisionGame/src/main/java/mock/model/wind/ShiftingWindGenerator.java
@@ -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;
+ }
+}
diff --git a/racevisionGame/src/main/java/mock/model/WindGenerator.java b/racevisionGame/src/main/java/mock/model/wind/WindGenerator.java
similarity index 96%
rename from racevisionGame/src/main/java/mock/model/WindGenerator.java
rename to racevisionGame/src/main/java/mock/model/wind/WindGenerator.java
index 8285d5d3..52ee2cd6 100644
--- a/racevisionGame/src/main/java/mock/model/WindGenerator.java
+++ b/racevisionGame/src/main/java/mock/model/wind/WindGenerator.java
@@ -1,4 +1,4 @@
-package mock.model;
+package mock.model.wind;
import shared.model.Wind;
diff --git a/racevisionGame/src/main/java/shared/model/Wind.java b/racevisionGame/src/main/java/shared/model/Wind.java
index 08d391c2..531d8473 100644
--- a/racevisionGame/src/main/java/shared/model/Wind.java
+++ b/racevisionGame/src/main/java/shared/model/Wind.java
@@ -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;
+ }
}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java
index 57d18830..e1aa1ede 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java
@@ -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);
diff --git a/racevisionGame/src/main/resources/visualiser/images/arrow.png b/racevisionGame/src/main/resources/visualiser/images/arrow.png
index fab6e21d..cd7bab10 100644
Binary files a/racevisionGame/src/main/resources/visualiser/images/arrow.png and b/racevisionGame/src/main/resources/visualiser/images/arrow.png differ
diff --git a/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java b/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java
index 6f67dc30..f7c69acf 100644
--- a/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java
+++ b/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java
@@ -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;
diff --git a/racevisionGame/src/test/java/mock/model/MockRaceTest.java b/racevisionGame/src/test/java/mock/model/MockRaceTest.java
index 4f3f7705..f53b2970 100644
--- a/racevisionGame/src/test/java/mock/model/MockRaceTest.java
+++ b/racevisionGame/src/test/java/mock/model/MockRaceTest.java
@@ -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
diff --git a/racevisionGame/src/test/java/mock/model/NewPolarsTest.java b/racevisionGame/src/test/java/mock/model/NewPolarsTest.java
new file mode 100644
index 00000000..dcbcc6cc
--- /dev/null
+++ b/racevisionGame/src/test/java/mock/model/NewPolarsTest.java
@@ -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> polars = (Map>) 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);
+ }
+ }
+ }
+ }
+
+}
diff --git a/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java b/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java
index 76eed977..0f60bcea 100644
--- a/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java
+++ b/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java
@@ -1,5 +1,6 @@
package mock.model;
+import mock.model.wind.RandomWindGenerator;
import org.junit.Before;
import org.junit.Test;
import shared.model.Bearing;
diff --git a/settings/keyBindings.xml b/settings/keyBindings.xml
new file mode 100644
index 00000000..2b807e17
--- /dev/null
+++ b/settings/keyBindings.xml
@@ -0,0 +1,33 @@
+
+
+
+