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 f606c29d..a9f11770 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); + } } @@ -376,6 +379,7 @@ public class MockRace extends RaceState { if (boat.getAutoVMG()) { newOptimalVMG(boat); + boat.setAutoVMG(false); } } else { @@ -388,31 +392,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()); } @@ -562,7 +573,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 @@ + + + + + SPACE + + + + SHIFT + + + + DOWN + + + + X + + + + ENTER + + + + Z + + + + UP + + + +