From a87879eaa9eb775093c8178c7477dd0a06227660 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Wed, 2 Aug 2017 12:41:47 +1200 Subject: [PATCH 01/19] Made new branch for command architecture. --- racevisionGame/src/main/java/mock/model/RaceEvent.java | 4 ++++ racevisionGame/src/main/java/mock/model/RaceLogic.java | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 racevisionGame/src/main/java/mock/model/RaceEvent.java create mode 100644 racevisionGame/src/main/java/mock/model/RaceLogic.java diff --git a/racevisionGame/src/main/java/mock/model/RaceEvent.java b/racevisionGame/src/main/java/mock/model/RaceEvent.java new file mode 100644 index 00000000..cf14a180 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/RaceEvent.java @@ -0,0 +1,4 @@ +package mock.model; + +public class RaceEvent { +} diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java new file mode 100644 index 00000000..b8e904a6 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -0,0 +1,4 @@ +package mock.model; + +public class RaceLogic { +} From 775c32ca92c37d3f862a11b87b951ab0d039e182 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Wed, 2 Aug 2017 12:48:56 +1200 Subject: [PATCH 02/19] Made new branch for command architecture. --- racevisionGame/src/main/java/mock/model/RaceEvent.java | 4 ---- racevisionGame/src/main/java/mock/model/RaceStatus.java | 4 ++++ .../main/java/mock/model/commandFactory/CommandFactory.java | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) delete mode 100644 racevisionGame/src/main/java/mock/model/RaceEvent.java create mode 100644 racevisionGame/src/main/java/mock/model/RaceStatus.java create mode 100644 racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java diff --git a/racevisionGame/src/main/java/mock/model/RaceEvent.java b/racevisionGame/src/main/java/mock/model/RaceEvent.java deleted file mode 100644 index cf14a180..00000000 --- a/racevisionGame/src/main/java/mock/model/RaceEvent.java +++ /dev/null @@ -1,4 +0,0 @@ -package mock.model; - -public class RaceEvent { -} diff --git a/racevisionGame/src/main/java/mock/model/RaceStatus.java b/racevisionGame/src/main/java/mock/model/RaceStatus.java new file mode 100644 index 00000000..74e2823c --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/RaceStatus.java @@ -0,0 +1,4 @@ +package mock.model; + +public class RaceStatus { +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java new file mode 100644 index 00000000..c073fc95 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -0,0 +1,4 @@ +package mock.model.commandFactory; + +public interface CommandFactory { +} From 81eeca3533077da9c80e5518026e5dd657784c74 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Wed, 2 Aug 2017 13:45:10 +1200 Subject: [PATCH 03/19] Split wind off from Race and made it its own class. Deleted dnfChance as it is no longer used. --- .../src/main/java/mock/model/MockRace.java | 108 +----------------- .../src/main/java/mock/model/RaceState.java | 4 + .../src/main/java/mock/model/RaceStatus.java | 4 - .../src/main/java/mock/model/Wind.java | 92 +++++++++++++++ .../src/main/java/shared/model/Race.java | 20 +--- .../java/visualiser/model/VisualiserRace.java | 8 +- 6 files changed, 110 insertions(+), 126 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/model/RaceState.java delete mode 100644 racevisionGame/src/main/java/mock/model/RaceStatus.java create mode 100644 racevisionGame/src/main/java/mock/model/Wind.java diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index ad259bdc..b611b49b 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -48,35 +48,6 @@ public class MockRace extends Race { private int scaleFactor; - /** - * The percent chance that a boat fails the race, and enters a DNF state, at each checkpoint. - * 0 = 0%, 100 = 100%. - */ - private int dnfChance = 0; - - - - /** - * Used to generate random numbers when changing the wind direction. - */ - private int changeWind = 4; - - /** - * The bearing the wind direction starts at. - */ - private static final Bearing windBaselineBearing = Bearing.fromDegrees(225); - - /** - * The lower bearing angle that the wind may have. - */ - private static final Bearing windLowerBound = Bearing.fromDegrees(215); - - /** - * The upper bearing angle that the wind may have. - */ - private static final Bearing windUpperBound = Bearing.fromDegrees(235); - - /** @@ -98,12 +69,7 @@ public class MockRace extends Race { this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary); - - - this.windSpeed = 12; - this.windDirection = Bearing.fromDegrees(180); - - + this.wind = new Wind(); } /** @@ -140,7 +106,6 @@ public class MockRace extends Race { */ public void run() { initialiseBoats(); - initialiseWindDirection(); this.countdownTimer.start(); } @@ -288,8 +253,8 @@ public class MockRace extends Race { //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class. - int windDirectionInt = AC35UnitConverter.encodeHeading(this.windDirection.degrees()); - int windSpeedInt = (int) (this.windSpeed * Constants.KnotsToMMPerSecond); + int windDirectionInt = AC35UnitConverter.encodeHeading(wind.getWindDirection().degrees()); + int windSpeedInt = (int) (wind.getWindSpeed() * Constants.KnotsToMMPerSecond); //Create race status object, and send it. RaceStatus raceStatus = new RaceStatus( @@ -576,7 +541,7 @@ public class MockRace extends Race { //Find the VMG inside these bounds. - VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound); + VMG bestVMG = boat.getPolars().calculateVMG(wind.getWindDirection(), wind.getWindSpeed(), boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound); return bestVMG; @@ -810,44 +775,12 @@ public class MockRace extends Race { boat.setCurrentSpeed(0); boat.setStatus(BoatStatusEnum.FINISHED); - } else if (doNotFinish()) { - //Boat has pulled out of race. - boat.setTimeFinished(timeElapsed); - boat.setCurrentLeg(new Leg("DNF", -1)); - boat.setCurrentSpeed(0); - boat.setStatus(BoatStatusEnum.DNF); - } } } - - - - /** - * Sets the chance each boat has of failing at a gate or marker - * - * @param chance percentage chance a boat has of failing per checkpoint. - */ - protected void setDnfChance(int chance) { - if (chance >= 0 && chance <= 100) { - dnfChance = chance; - } - } - - /** - * Decides if a boat should received a DNF status. - * @return True means it should DNF, false means it shouldn't. - */ - protected boolean doNotFinish() { - Random rand = new Random(); - return rand.nextInt(100) < dnfChance; - } - - - /** * Returns the number of boats that are still active in the race. * They become inactive by either finishing or withdrawing. @@ -878,42 +811,11 @@ public class MockRace extends Race { return boats; } - - /** - * Initialises the wind bearing with the value of the windBaselineBearing. - */ - protected void initialiseWindDirection() { - //Set the starting bearing. - this.windDirection = Bearing.fromDegrees(MockRace.windBaselineBearing.degrees()); - } - - /** * Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound]. */ protected void changeWindDirection() { - - //Randomly add or remove 0.5 degrees. - int r = new Random().nextInt(changeWind) + 1; - - if (r == 1) { - //Add 0.5 degrees to the wind bearing. - this.windDirection.setDegrees(this.windDirection.degrees() + 0.5); - - } else if (r == 2) { - //Minus 0.5 degrees from the wind bearing. - this.windDirection.setDegrees(this.windDirection.degrees() - 0.5); - - } - - //Ensure that the wind is in the correct bounds. - if (this.windDirection.degrees() > MockRace.windUpperBound.degrees()) { - this.windDirection.setBearing(MockRace.windUpperBound); - - } else if (this.windDirection.degrees() < MockRace.windLowerBound.degrees()) { - this.windDirection.setBearing(MockRace.windLowerBound); - - } + this.wind.changeWindDirection(); } diff --git a/racevisionGame/src/main/java/mock/model/RaceState.java b/racevisionGame/src/main/java/mock/model/RaceState.java new file mode 100644 index 00000000..4b13cbb4 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/RaceState.java @@ -0,0 +1,4 @@ +package mock.model; + +public class RaceState { +} diff --git a/racevisionGame/src/main/java/mock/model/RaceStatus.java b/racevisionGame/src/main/java/mock/model/RaceStatus.java deleted file mode 100644 index 74e2823c..00000000 --- a/racevisionGame/src/main/java/mock/model/RaceStatus.java +++ /dev/null @@ -1,4 +0,0 @@ -package mock.model; - -public class RaceStatus { -} diff --git a/racevisionGame/src/main/java/mock/model/Wind.java b/racevisionGame/src/main/java/mock/model/Wind.java new file mode 100644 index 00000000..199cb98b --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/Wind.java @@ -0,0 +1,92 @@ +package mock.model; + +import shared.model.Bearing; + +import java.util.Random; + +public class Wind { + + /** + * Used to generate random numbers when changing the wind direction. + */ + private int changeWind = 4; + + /** + * The bearing the wind direction starts at. + */ + private Bearing windBearing; + + /** + * The lower bearing angle that the wind may have. + */ + private Bearing windLowerBound; + + /** + * The upper bearing angle that the wind may have. + */ + private Bearing windUpperBound; + + double windSpeed; + + public Wind() { + this.windBearing = Bearing.fromDegrees(225); + this.windSpeed = 12; + this.windLowerBound = Bearing.fromDegrees(215); + this.windUpperBound = Bearing.fromDegrees(235); + } + + public Wind(Bearing windBearing, double windSpeed, Bearing windLowerBound, Bearing windUpperBound) { + this.windBearing = windBearing; + this.windSpeed = windSpeed; + this.windLowerBound = windLowerBound; + this.windUpperBound = windUpperBound; + } + + /** + * Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound]. + */ + public void changeWindDirection() { + + //Randomly add or remove 0.5 degrees. + int r = new Random().nextInt(changeWind) + 1; + + if (r == 1) { + //Add 0.5 degrees to the wind bearing. + this.windBearing.setDegrees(this.windBearing.degrees() + 0.5); + + } else if (r == 2) { + //Minus 0.5 degrees from the wind bearing. + this.windBearing.setDegrees(this.windBearing.degrees() - 0.5); + + } + + //Ensure that the wind is in the correct bounds. + if (this.windBearing.degrees() > this.windUpperBound.degrees()) { + this.windBearing.setBearing(this.windUpperBound); + + } else if (this.windBearing.degrees() < this.windLowerBound.degrees()) { + this.windBearing.setBearing(this.windLowerBound); + + } + } + + public Bearing getWindDirection() { + return this.windBearing; + } + + public double getWindSpeed() { + return this.windSpeed; + } + + public void setWindDirection(Bearing windBearing) { + this.windBearing = windBearing; + } + + public void setWindSpeed(double windSpeed) { + this.windSpeed = windSpeed; + } + + public void setDegrees(double degrees) { + this.windBearing.setDegrees(degrees); + } +} diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index 415e9f77..96bdf82a 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -2,6 +2,7 @@ package shared.model; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; +import mock.model.Wind; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import network.Messages.LatestMessages; @@ -100,13 +101,7 @@ public abstract class Race implements Runnable { /** * The current wind direction bearing. */ - protected Bearing windDirection; - - /** - * Wind speed (knots). - * Convert this to millimeters per second before passing to RaceStatus. - */ - protected double windSpeed; + protected Wind wind; /** @@ -169,12 +164,7 @@ public abstract class Race implements Runnable { //Race type. this.raceType = raceDataSource.getRaceType(); - //Wind speed. - this.windSpeed = 0; - //Wind direction. - this.windDirection = Bearing.fromDegrees(0); - - + this.wind = new Wind(); } @@ -259,7 +249,7 @@ public abstract class Race implements Runnable { * @return The wind bearing. */ public Bearing getWindDirection() { - return windDirection; + return wind.getWindDirection(); } /** @@ -268,7 +258,7 @@ public abstract class Race implements Runnable { * @return The wind speed. */ public double getWindSpeed() { - return windSpeed; + return wind.getWindSpeed(); } /** diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index 3a76631d..a98a8345 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -311,11 +311,11 @@ public class VisualiserRace extends Race { //Race status enum. this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus()); - //Wind bearing. - this.windDirection.setDegrees(raceStatus.getScaledWindDirection()); + // Wind direction + wind.setDegrees(raceStatus.getScaledWindDirection()); - //Wind speed. - this.windSpeed = raceStatus.getWindSpeedKnots(); + // Wind speed + wind.setWindSpeed(raceStatus.getWindSpeedKnots()); //Current race time. this.raceClock.setUTCTime(raceStatus.getCurrentTime()); From a4592a10e6248d1764877e83e041e670537cecea Mon Sep 17 00:00:00 2001 From: cbt24 Date: Wed, 2 Aug 2017 13:51:07 +1200 Subject: [PATCH 04/19] Removed boundary checking logic to clean up MockRace before split story[1094] --- .../src/main/java/mock/model/MockRace.java | 115 ++---------------- .../src/main/java/mock/model/RaceState.java | 18 +++ .../src/main/java/mock/model/RaceStatus.java | 4 - 3 files changed, 29 insertions(+), 108 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/model/RaceState.java delete mode 100644 racevisionGame/src/main/java/mock/model/RaceStatus.java diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index ad259bdc..e7ee2dec 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -143,6 +143,13 @@ public class MockRace extends Race { initialiseWindDirection(); this.countdownTimer.start(); } + /** + * Sets the current race status. + * @param raceStatusEnum The new status of the race. + */ + protected void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) { + this.raceStatusEnum = raceStatusEnum; + } /** @@ -565,18 +572,13 @@ public class MockRace extends Race { /** * Calculates a boat's VMG. * @param boat The boat to calculate VMG for. - * @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course. * @return VMG for the specified boat. */ - private VMG calculateVMG(MockBoat boat, Bearing[] bearingBounds) { - - //Get the lower and upper acceptable bounds. - Bearing lowerAcceptableBound = bearingBounds[0]; - Bearing upperAcceptableBound = bearingBounds[1]; + private VMG calculateVMG(MockBoat boat) { //Find the VMG inside these bounds. - VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound); + VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), Bearing.fromDegrees(0d), Bearing.fromDegrees(359.99999d)); return bestVMG; @@ -653,29 +655,16 @@ public class MockRace extends Race { //Move the boat forwards that many meters, and advances its time counters by enough milliseconds. boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor); - - //Only get a new VMG if the boat will go outside the course, or X seconds have elapsed. - boolean willStayInsideCourse = this.checkBearingInsideCourse(boat.getBearing(), boat.getCurrentPosition()); long tackPeriod = 15000; - if (!willStayInsideCourse || (boat.getTimeSinceTackChange() > tackPeriod)) { - - //Calculate the boat's bearing bounds, to ensure that it doesn't go out of the course. - Bearing[] bearingBounds = this.calculateBearingBounds(boat); - - + if (boat.getTimeSinceTackChange() > tackPeriod) { //Calculate the new VMG. - VMG newVMG = this.calculateVMG(boat, bearingBounds); + VMG newVMG = this.calculateVMG(boat); //If the new vmg improves velocity, use it. if (improvesVelocity(boat, newVMG)) { boat.setVMG(newVMG); - } else { - //We also need to use the new VMG if our current bearing will take us out of the course. - if (!willStayInsideCourse) { - boat.setVMG(newVMG); - } } } @@ -689,88 +678,6 @@ public class MockRace extends Race { } - /** - * Calculates the upper and lower bounds that the boat may have in order to not go outside of the course. - * @param boat The boat to check. - * @return An array of bearings. The first is the lower bound, the second is the upper bound. - */ - private Bearing[] calculateBearingBounds(MockBoat boat) { - - Bearing[] bearings = new Bearing[2]; - - Bearing lowerBearing = Bearing.fromDegrees(0.001); - Bearing upperBearing = Bearing.fromDegrees(359.999); - - - - double lastAngle = -1; - boolean lastAngleWasGood = false; - - //Check all bearings between [0, 360). - for (double angle = 0; angle < 360; angle += 1) { - - //Create bearing from angle. - Bearing bearing = Bearing.fromDegrees(angle); - - //Check that if it is acceptable. - boolean bearingIsGood = this.checkBearingInsideCourse(bearing, boat.getCurrentPosition()); - - - if (lastAngle != -1) { - - if (lastAngleWasGood && !bearingIsGood) { - //We have flipped over from good bearings to bad bearings. So the last good bearing is the upper bearing. - upperBearing = Bearing.fromDegrees(lastAngle); - } - - if (!lastAngleWasGood && bearingIsGood) { - //We have flipped over from bad bearings to good bearings. So the current bearing is the lower bearing. - lowerBearing = Bearing.fromDegrees(angle); - } - - } - - lastAngle = angle; - lastAngleWasGood = bearingIsGood; - - } - - - - //TODO BUG if it can't find either upper or lower, it returns (0, 359.999). Should return (boatbearing, boatbearing+0.0001) - bearings[0] = lowerBearing; - bearings[1] = upperBearing; - - return bearings; - } - - - - /** - * Checks if a given bearing, starting at a given position, would put a boat out of the course boundaries. - * @param bearing The bearing to check. - * @param position The position to start from. - * @return True if the bearing would keep the boat in the course, false if it would take it out of the course. - */ - private boolean checkBearingInsideCourse(Bearing bearing, GPSCoordinate position) { - - //Get azimuth from bearing. - Azimuth azimuth = Azimuth.fromBearing(bearing); - - - //Tests to see if a point in front of the boat is out of bounds. - double epsilonMeters = 50d; - GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth); - - //If it isn't inside the boundary, calculate new bearing. - if (GPSCoordinate.isInsideBoundary(testCoord, this.shrinkBoundary)) { - return true; - } else { - return false; - } - - } - /** * Checks if a boat has finished any legs, or has pulled out of race (DNF). diff --git a/racevisionGame/src/main/java/mock/model/RaceState.java b/racevisionGame/src/main/java/mock/model/RaceState.java new file mode 100644 index 00000000..9e01deda --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/RaceState.java @@ -0,0 +1,18 @@ +package mock.model; + +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; +import network.Messages.LatestMessages; +import shared.dataInput.BoatDataSource; +import shared.dataInput.RaceDataSource; +import shared.dataInput.RegattaDataSource; +import shared.model.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class RaceState { +} diff --git a/racevisionGame/src/main/java/mock/model/RaceStatus.java b/racevisionGame/src/main/java/mock/model/RaceStatus.java deleted file mode 100644 index 74e2823c..00000000 --- a/racevisionGame/src/main/java/mock/model/RaceStatus.java +++ /dev/null @@ -1,4 +0,0 @@ -package mock.model; - -public class RaceStatus { -} From fba256113a7acccba09bc808d50a49a89729b068 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Wed, 2 Aug 2017 15:09:59 +1200 Subject: [PATCH 05/19] Merged wind generator into this branch. #story[1094] --- .../src/main/java/mock/model/MockRace.java | 5 +- .../src/main/java/mock/model/Wind.java | 92 ------------------- 2 files changed, 4 insertions(+), 93 deletions(-) delete mode 100644 racevisionGame/src/main/java/mock/model/Wind.java diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 845b00a6..78ce44b2 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -47,7 +47,10 @@ public class MockRace extends Race { */ private int scaleFactor; - + /** + * Object used to generate changes in wind speed/direction. + */ + private WindGenerator windGenerator; /** diff --git a/racevisionGame/src/main/java/mock/model/Wind.java b/racevisionGame/src/main/java/mock/model/Wind.java deleted file mode 100644 index 199cb98b..00000000 --- a/racevisionGame/src/main/java/mock/model/Wind.java +++ /dev/null @@ -1,92 +0,0 @@ -package mock.model; - -import shared.model.Bearing; - -import java.util.Random; - -public class Wind { - - /** - * Used to generate random numbers when changing the wind direction. - */ - private int changeWind = 4; - - /** - * The bearing the wind direction starts at. - */ - private Bearing windBearing; - - /** - * The lower bearing angle that the wind may have. - */ - private Bearing windLowerBound; - - /** - * The upper bearing angle that the wind may have. - */ - private Bearing windUpperBound; - - double windSpeed; - - public Wind() { - this.windBearing = Bearing.fromDegrees(225); - this.windSpeed = 12; - this.windLowerBound = Bearing.fromDegrees(215); - this.windUpperBound = Bearing.fromDegrees(235); - } - - public Wind(Bearing windBearing, double windSpeed, Bearing windLowerBound, Bearing windUpperBound) { - this.windBearing = windBearing; - this.windSpeed = windSpeed; - this.windLowerBound = windLowerBound; - this.windUpperBound = windUpperBound; - } - - /** - * Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound]. - */ - public void changeWindDirection() { - - //Randomly add or remove 0.5 degrees. - int r = new Random().nextInt(changeWind) + 1; - - if (r == 1) { - //Add 0.5 degrees to the wind bearing. - this.windBearing.setDegrees(this.windBearing.degrees() + 0.5); - - } else if (r == 2) { - //Minus 0.5 degrees from the wind bearing. - this.windBearing.setDegrees(this.windBearing.degrees() - 0.5); - - } - - //Ensure that the wind is in the correct bounds. - if (this.windBearing.degrees() > this.windUpperBound.degrees()) { - this.windBearing.setBearing(this.windUpperBound); - - } else if (this.windBearing.degrees() < this.windLowerBound.degrees()) { - this.windBearing.setBearing(this.windLowerBound); - - } - } - - public Bearing getWindDirection() { - return this.windBearing; - } - - public double getWindSpeed() { - return this.windSpeed; - } - - public void setWindDirection(Bearing windBearing) { - this.windBearing = windBearing; - } - - public void setWindSpeed(double windSpeed) { - this.windSpeed = windSpeed; - } - - public void setDegrees(double degrees) { - this.windBearing.setDegrees(degrees); - } -} From b258e94a542e2af487474596efab9a8fdbeaae61 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Wed, 2 Aug 2017 15:19:32 +1200 Subject: [PATCH 06/19] Merged wind generator into this branch. #story[1094] --- .../src/main/java/mock/model/MockRace.java | 120 +----------------- 1 file changed, 5 insertions(+), 115 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 78ce44b2..fec34ad7 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -52,7 +52,6 @@ public class MockRace extends Race { */ private WindGenerator windGenerator; - /** * 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). @@ -72,7 +71,6 @@ public class MockRace extends Race { this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary); - //Set up wind generator. It may be tidier to create this outside the race (with the values sourced from a data file maybe?) and pass it in. this.windGenerator = new WindGenerator( Bearing.fromDegrees(225), @@ -120,7 +118,6 @@ public class MockRace extends Race { */ public void run() { initialiseBoats(); - initialiseWindDirection(); this.countdownTimer.start(); } @@ -545,23 +542,13 @@ public class MockRace extends Race { /** * Calculates a boat's VMG. * @param boat The boat to calculate VMG for. - * @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course. * @return VMG for the specified boat. */ - private VMG calculateVMG(MockBoat boat, Bearing[] bearingBounds) { - - //Get the lower and upper acceptable bounds. - Bearing lowerAcceptableBound = bearingBounds[0]; - Bearing upperAcceptableBound = bearingBounds[1]; + private VMG calculateVMG(MockBoat boat) { //Find the VMG inside these bounds. - VMG bestVMG = boat.getPolars().calculateVMG( - this.getWindDirection(), - this.getWindSpeed(), - boat.calculateBearingToNextMarker(), - lowerAcceptableBound, - upperAcceptableBound); + VMG bestVMG = boat.getPolars().calculateVMG(this.getWindDirection(), this.getWindSpeed(), boat.calculateBearingToNextMarker(), Bearing.fromDegrees(0d), Bearing.fromDegrees(359.99999d)); return bestVMG; @@ -638,29 +625,16 @@ public class MockRace extends Race { //Move the boat forwards that many meters, and advances its time counters by enough milliseconds. boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor); - - //Only get a new VMG if the boat will go outside the course, or X seconds have elapsed. - boolean willStayInsideCourse = this.checkBearingInsideCourse(boat.getBearing(), boat.getCurrentPosition()); long tackPeriod = 15000; - if (!willStayInsideCourse || (boat.getTimeSinceTackChange() > tackPeriod)) { - - //Calculate the boat's bearing bounds, to ensure that it doesn't go out of the course. - Bearing[] bearingBounds = this.calculateBearingBounds(boat); - - + if (boat.getTimeSinceTackChange() > tackPeriod) { //Calculate the new VMG. - VMG newVMG = this.calculateVMG(boat, bearingBounds); + VMG newVMG = this.calculateVMG(boat); //If the new vmg improves velocity, use it. if (improvesVelocity(boat, newVMG)) { boat.setVMG(newVMG); - } else { - //We also need to use the new VMG if our current bearing will take us out of the course. - if (!willStayInsideCourse) { - boat.setVMG(newVMG); - } } } @@ -674,88 +648,6 @@ public class MockRace extends Race { } - /** - * Calculates the upper and lower bounds that the boat may have in order to not go outside of the course. - * @param boat The boat to check. - * @return An array of bearings. The first is the lower bound, the second is the upper bound. - */ - private Bearing[] calculateBearingBounds(MockBoat boat) { - - Bearing[] bearings = new Bearing[2]; - - Bearing lowerBearing = Bearing.fromDegrees(0.001); - Bearing upperBearing = Bearing.fromDegrees(359.999); - - - - double lastAngle = -1; - boolean lastAngleWasGood = false; - - //Check all bearings between [0, 360). - for (double angle = 0; angle < 360; angle += 1) { - - //Create bearing from angle. - Bearing bearing = Bearing.fromDegrees(angle); - - //Check that if it is acceptable. - boolean bearingIsGood = this.checkBearingInsideCourse(bearing, boat.getCurrentPosition()); - - - if (lastAngle != -1) { - - if (lastAngleWasGood && !bearingIsGood) { - //We have flipped over from good bearings to bad bearings. So the last good bearing is the upper bearing. - upperBearing = Bearing.fromDegrees(lastAngle); - } - - if (!lastAngleWasGood && bearingIsGood) { - //We have flipped over from bad bearings to good bearings. So the current bearing is the lower bearing. - lowerBearing = Bearing.fromDegrees(angle); - } - - } - - lastAngle = angle; - lastAngleWasGood = bearingIsGood; - - } - - - - //TODO BUG if it can't find either upper or lower, it returns (0, 359.999). Should return (boatbearing, boatbearing+0.0001) - bearings[0] = lowerBearing; - bearings[1] = upperBearing; - - return bearings; - } - - - - /** - * Checks if a given bearing, starting at a given position, would put a boat out of the course boundaries. - * @param bearing The bearing to check. - * @param position The position to start from. - * @return True if the bearing would keep the boat in the course, false if it would take it out of the course. - */ - private boolean checkBearingInsideCourse(Bearing bearing, GPSCoordinate position) { - - //Get azimuth from bearing. - Azimuth azimuth = Azimuth.fromBearing(bearing); - - - //Tests to see if a point in front of the boat is out of bounds. - double epsilonMeters = 50d; - GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth); - - //If it isn't inside the boundary, calculate new bearing. - if (GPSCoordinate.isInsideBoundary(testCoord, this.shrinkBoundary)) { - return true; - } else { - return false; - } - - } - /** * Checks if a boat has finished any legs, or has pulled out of race (DNF). @@ -831,7 +723,6 @@ public class MockRace extends Race { return boats; } - /** * Initialises the wind bearing with the value of the windBaselineBearing. */ @@ -840,7 +731,6 @@ public class MockRace extends Race { this.setWind(windGenerator.generateBaselineWind()); } - /** * Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound]. */ @@ -872,4 +762,4 @@ public class MockRace extends Race { } } -} +} \ No newline at end of file From 7fc1347377dee56a665411cf94625cc38dac7d48 Mon Sep 17 00:00:00 2001 From: zwu18 Date: Wed, 2 Aug 2017 22:28:47 +1200 Subject: [PATCH 07/19] Created class TackGybeCommand which implements the CommandFactory interface. #story[1097] --- .../src/main/java/mock/model/MockRace.java | 1 - .../mock/model/commandFactory/CommandFactory.java | 3 +++ .../model/commandFactory/TackGybeCommand.java | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index fec34ad7..c14e337f 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -630,7 +630,6 @@ public class MockRace extends Race { //Calculate the new VMG. VMG newVMG = this.calculateVMG(boat); - //If the new vmg improves velocity, use it. if (improvesVelocity(boat, newVMG)) { boat.setVMG(newVMG); diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java index c073fc95..0e21d68c 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -1,4 +1,7 @@ package mock.model.commandFactory; +import mock.model.MockBoat; + public interface CommandFactory { + void runCommand(MockBoat boat); } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java new file mode 100644 index 00000000..80b1fb23 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -0,0 +1,15 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; + +/** + * Created by David on 2/08/2017. + */ +public class TackGybeCommand implements CommandFactory { + + + @Override + public void runCommand(MockBoat boat) { + + } +} From 0466292bd06896d2ed7e550da6aee3ba9f659b76 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Wed, 2 Aug 2017 23:39:13 +1200 Subject: [PATCH 08/19] Partially removed VMG optimisation, separated server-specific functionality from MockRace into RaceServer. #story[1094] --- .../src/main/java/mock/model/MockBoat.java | 15 +- .../src/main/java/mock/model/MockRace.java | 239 ++---------------- .../src/main/java/mock/model/RaceServer.java | 153 +++++++++++ .../src/main/java/shared/model/Race.java | 16 +- 4 files changed, 185 insertions(+), 238 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/model/RaceServer.java diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index c8c6825b..597acae7 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -135,20 +135,9 @@ public class MockBoat extends Boat { /** * Moves the boat meters forward in the direction that it is facing * @param meters The number of meters to move forward. - * @param milliseconds The number of milliseconds to advance the boat's timers by. + * */ - public void moveForwards(double meters, long milliseconds) { - - - //Update the boat's time since last tack. - this.setTimeSinceTackChange(this.getTimeSinceTackChange() + milliseconds); - - //Update the time into the current leg. - this.setTimeElapsedInCurrentLeg(this.getTimeElapsedInCurrentLeg() + milliseconds); - - //Update the distance into the current leg. - this.setDistanceTravelledInLeg(this.getDistanceTravelledInLeg() + meters); - + public void moveForwards(double meters) { //Updates the current position of the boat. GPSCoordinate newPosition = GPSCoordinate.calculateNewPosition(this.getCurrentPosition(), meters, Azimuth.fromBearing(this.getBearing())); this.setCurrentPosition(newPosition); diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index fec34ad7..08c56a2d 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -15,7 +15,6 @@ import shared.model.*; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; import java.util.*; import static java.lang.Math.cos; @@ -27,6 +26,7 @@ import static java.lang.Math.cos; * Is responsible for simulating the race, and sending messages to a MockOutput instance. */ public class MockRace extends Race { + private RaceServer server; /** * An observable list of boats in the race. @@ -82,6 +82,8 @@ public class MockRace extends Race { //Wind. this.setWind(windGenerator.generateBaselineWind()); + + this.server = new RaceServer(this, latestMessages); } /** @@ -122,88 +124,6 @@ public class MockRace extends Race { } - /** - * Parse the compound marker boats through mock output. - */ - private void parseMarks() { - for (CompoundMark compoundMark : this.compoundMarks) { - - //Get the individual marks from the compound mark. - Mark mark1 = compoundMark.getMark1(); - Mark mark2 = compoundMark.getMark2(); - - //If they aren't null, parse them (some compound marks only have one mark). - if (mark1 != null) { - this.parseIndividualMark(mark1); - } - - if (mark2 != null) { - this.parseIndividualMark(mark2); - } - - } - } - - /** - * Parses an individual marker boat, and sends it to mockOutput. - * @param mark The marker boat to parse. - */ - private void parseIndividualMark(Mark mark) { - - //Create message. - BoatLocation boatLocation = new BoatLocation( - mark.getSourceID(), - mark.getPosition().getLatitude(), - mark.getPosition().getLongitude(), - this.boatLocationSequenceNumber, - 0, 0, - this.raceClock.getCurrentTimeMilli()); - - //Iterates the sequence number. - this.boatLocationSequenceNumber++; - - this.latestMessages.setBoatLocation(boatLocation); - - - } - - /** - * Parse the boats in the race, and send it to mockOutput. - */ - private void parseBoatLocations() { - - //Parse each boat. - for (MockBoat boat : this.boats) { - - this.parseIndividualBoatLocation(boat); - - } - - } - - /** - * Parses an individual boat, and sends it to mockOutput. - * @param boat The boat to parse. - */ - private void parseIndividualBoatLocation(MockBoat boat) { - - BoatLocation boatLocation = new BoatLocation( - boat.getSourceID(), - boat.getCurrentPosition().getLatitude(), - boat.getCurrentPosition().getLongitude(), - this.boatLocationSequenceNumber, - boat.getBearing().degrees(), - boat.getCurrentSpeed(), - this.raceClock.getCurrentTimeMilli()); - - //Iterates the sequence number. - this.boatLocationSequenceNumber++; - - this.latestMessages.setBoatLocation(boatLocation); - - } - - /** * Updates the race time to a specified value, in milliseconds since the unix epoch. * @param currentTime Milliseconds since unix epoch. @@ -241,48 +161,6 @@ public class MockRace extends Race { } - } - - /** - * Parses the race status, and sends it to mockOutput. - */ - private void parseRaceStatus() { - - //A race status message contains a list of boat statuses. - List boatStatuses = new ArrayList<>(); - - //Add each boat status to the status list. - for (MockBoat boat : this.boats) { - - BoatStatus boatStatus = new BoatStatus( - boat.getSourceID(), - boat.getStatus(), - boat.getCurrentLeg().getLegNumber(), - boat.getEstimatedTimeAtNextMark().toInstant().toEpochMilli() ); - - boatStatuses.add(boatStatus); - } - - - //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class. - int windDirectionInt = AC35UnitConverter.encodeHeading(this.getWindDirection().degrees()); - int windSpeedInt = (int) (this.getWindSpeed() * Constants.KnotsToMMPerSecond); - - //Create race status object, and send it. - RaceStatus raceStatus = new RaceStatus( - System.currentTimeMillis(), - this.raceId, - this.getRaceStatusEnum().getValue(), - this.raceClock.getStartingTimeMilli(), - windDirectionInt, - windSpeedInt, - this.getRaceType().getValue(), - boatStatuses); - - - this.latestMessages.setRaceStatus(raceStatus); - - } @@ -330,16 +208,16 @@ public class MockRace extends Race { setBoatsTimeNextMark(raceClock.getCurrentTime()); //Parse the boat locations. - parseBoatLocations(); + server.parseBoatLocations(); //Parse the marks. - parseMarks(); + server.parseMarks(); // Change wind direction changeWindDirection(); //Parse the race status. - parseRaceStatus(); + server.parseRaceStatus(); if (getRaceStatusEnum() == RaceStatusEnum.STARTED) { @@ -414,13 +292,13 @@ public class MockRace extends Race { changeWindDirection(); //Parse the boat locations. - parseBoatLocations(); + server.parseBoatLocations(); //Parse the marks. - parseMarks(); + server.parseMarks(); //Parse the race status. - parseRaceStatus(); + server.parseRaceStatus(); //Update the last frame time. @@ -437,7 +315,7 @@ public class MockRace extends Race { @Override public void handle(long now) { - parseRaceStatus(); + server.parseRaceStatus(); if (iters > 500) { stop(); @@ -452,7 +330,7 @@ public class MockRace extends Race { * This sets their starting positions and current legs. */ @Override - protected void initialiseBoats() { + public void initialiseBoats() { //Gets the starting positions of the boats. List startingPositions = getSpreadStartingPositions(); @@ -497,7 +375,7 @@ public class MockRace extends Race { * * @return A list of starting positions. */ - public List getSpreadStartingPositions() { + private List getSpreadStartingPositions() { //The first compound marker of the race - the starting gate. CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark(); @@ -539,23 +417,6 @@ public class MockRace extends Race { } - /** - * Calculates a boat's VMG. - * @param boat The boat to calculate VMG for. - * @return VMG for the specified boat. - */ - private VMG calculateVMG(MockBoat boat) { - - - //Find the VMG inside these bounds. - VMG bestVMG = boat.getPolars().calculateVMG(this.getWindDirection(), this.getWindSpeed(), boat.calculateBearingToNextMarker(), Bearing.fromDegrees(0d), Bearing.fromDegrees(359.99999d)); - - - return bestVMG; - - } - - /** * Determines whether or not a given VMG improves the velocity of a boat, if it were currently using currentVMG. * @param currentVMG The current VMG of the boat. @@ -607,7 +468,7 @@ public class MockRace extends Race { * @param updatePeriodMilliseconds The time, in milliseconds, since the last update. * @param totalElapsedMilliseconds The total number of milliseconds that have elapsed since the start of the race. */ - protected void updatePosition(MockBoat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) { + public void updatePosition(MockBoat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) { //Checks if the current boat has finished the race or not. boolean finish = this.isLastLeg(boat.getCurrentLeg()); @@ -623,12 +484,18 @@ public class MockRace extends Race { //Move the boat forwards that many meters, and advances its time counters by enough milliseconds. - boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor); + boat.moveForwards(distanceTravelledMeters); long tackPeriod = 15000; + if (boat.getTimeSinceTackChange() > tackPeriod) { //Calculate the new VMG. - VMG newVMG = this.calculateVMG(boat); + VMG newVMG = boat.getPolars().calculateVMG( + this.getWindDirection(), + this.getWindSpeed(), + boat.calculateBearingToNextMarker(), + Bearing.fromDegrees(0d), + Bearing.fromDegrees(359.99999d)); //If the new vmg improves velocity, use it. @@ -639,56 +506,6 @@ public class MockRace extends Race { } this.updateEstimatedTime(boat); - - - //Check the boats position (update leg and stuff). - this.checkPosition(boat, totalElapsedMilliseconds); - - } - - } - - - /** - * Checks if a boat has finished any legs, or has pulled out of race (DNF). - * @param boat The boat to check. - * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. - */ - protected void checkPosition(MockBoat boat, long timeElapsed) { - - //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. - double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. - - if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { - //Boat has reached its target marker, and has moved on to a new leg. - - - - //Calculate how much the boat overshot the marker by. - double overshootMeters = boat.calculateDistanceToNextMarker(); - - - //Move boat on to next leg. - Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); - boat.setCurrentLeg(nextLeg); - - //Add overshoot distance into the distance travelled for the next leg. - boat.setDistanceTravelledInLeg(overshootMeters); - - //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark. - boat.setTimeSinceTackChange(999999); - - - //Check if the boat has finished or stopped racing. - - if (this.isLastLeg(boat.getCurrentLeg())) { - //Boat has finished. - boat.setTimeFinished(timeElapsed); - boat.setCurrentSpeed(0); - boat.setStatus(BoatStatusEnum.FINISHED); - - } - } } @@ -723,18 +540,10 @@ public class MockRace extends Race { return boats; } - /** - * Initialises the wind bearing with the value of the windBaselineBearing. - */ - protected void initialiseWindDirection() { - //Set the starting bearing. - this.setWind(windGenerator.generateBaselineWind()); - } - /** * Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound]. */ - protected void changeWindDirection() { + public void changeWindDirection() { Wind nextWind = windGenerator.generateNextWind(raceWind.getValue()); @@ -762,4 +571,8 @@ public class MockRace extends Race { } } + + public List getCompoundMarks() { + return compoundMarks; + } } \ No newline at end of file diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java new file mode 100644 index 00000000..c7e3ab69 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -0,0 +1,153 @@ +package mock.model; + +import network.Messages.BoatLocation; +import network.Messages.BoatStatus; +import network.Messages.LatestMessages; +import network.Messages.RaceStatus; +import network.Utils.AC35UnitConverter; +import shared.model.CompoundMark; +import shared.model.Constants; +import shared.model.Mark; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by connortaylorbrown on 2/08/17. + */ +public class RaceServer { + private MockRace race; + private LatestMessages latestMessages; + + /** + * The sequence number of the latest RaceStatus message sent or received. + */ + private int raceStatusSequenceNumber = 1; + + /** + * The sequence number of the latest BoatLocation message sent or received. + */ + private int boatLocationSequenceNumber = 1; + + + public RaceServer(MockRace race, LatestMessages latestMessages) { + this.race = race; + this.latestMessages = latestMessages; + } + + + /** + * Parses an individual marker boat, and sends it to mockOutput. + * @param mark The marker boat to parse. + */ + private void parseIndividualMark(Mark mark) { + //Create message. + BoatLocation boatLocation = new BoatLocation( + mark.getSourceID(), + mark.getPosition().getLatitude(), + mark.getPosition().getLongitude(), + this.boatLocationSequenceNumber, + 0, 0, + race.getRaceClock().getCurrentTimeMilli()); + + //Iterates the sequence number. + this.boatLocationSequenceNumber++; + + this.latestMessages.setBoatLocation(boatLocation); + } + + /** + * Parse the compound marker boats through mock output. + */ + public void parseMarks() { + for (CompoundMark compoundMark : race.getCompoundMarks()) { + + //Get the individual marks from the compound mark. + Mark mark1 = compoundMark.getMark1(); + Mark mark2 = compoundMark.getMark2(); + + //If they aren't null, parse them (some compound marks only have one mark). + if (mark1 != null) { + this.parseIndividualMark(mark1); + } + + if (mark2 != null) { + this.parseIndividualMark(mark2); + } + } + } + + + /** + * Parse the boats in the race, and send it to mockOutput. + */ + public void parseBoatLocations() { + //Parse each boat. + for (MockBoat boat : race.getBoats()) { + this.parseIndividualBoatLocation(boat); + } + } + + /** + * Parses an individual boat, and sends it to mockOutput. + * @param boat The boat to parse. + */ + private void parseIndividualBoatLocation(MockBoat boat) { + + BoatLocation boatLocation = new BoatLocation( + boat.getSourceID(), + boat.getCurrentPosition().getLatitude(), + boat.getCurrentPosition().getLongitude(), + this.boatLocationSequenceNumber, + boat.getBearing().degrees(), + boat.getCurrentSpeed(), + race.getRaceClock().getCurrentTimeMilli()); + + //Iterates the sequence number. + this.boatLocationSequenceNumber++; + + this.latestMessages.setBoatLocation(boatLocation); + + } + + + + /** + * Parses the race status, and sends it to mockOutput. + */ + public void parseRaceStatus() { + + //A race status message contains a list of boat statuses. + List boatStatuses = new ArrayList<>(); + + //Add each boat status to the status list. + for (MockBoat boat : race.getBoats()) { + + BoatStatus boatStatus = new BoatStatus( + boat.getSourceID(), + boat.getStatus(), + boat.getCurrentLeg().getLegNumber(), + boat.getEstimatedTimeAtNextMark().toInstant().toEpochMilli() ); + + boatStatuses.add(boatStatus); + } + + + //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class. + int windDirectionInt = AC35UnitConverter.encodeHeading(race.getWindDirection().degrees()); + int windSpeedInt = (int) (race.getWindSpeed() * Constants.KnotsToMMPerSecond); + + //Create race status object, and send it. + RaceStatus raceStatus = new RaceStatus( + System.currentTimeMillis(), + race.getRaceId(), + race.getRaceStatusEnum().getValue(), + race.getRaceClock().getStartingTimeMilli(), + windDirectionInt, + windSpeedInt, + race.getRaceType().getValue(), + boatStatuses); + + this.latestMessages.setRaceStatus(raceStatus); + } +} diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index aec57882..040e0d19 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -43,18 +43,6 @@ public abstract class Race implements Runnable { */ protected LatestMessages latestMessages; - /** - * The sequence number of the latest BoatLocation message sent or received. - */ - protected int boatLocationSequenceNumber = 1; - - /** - * The sequence number of the latest RaceStatus message sent or received. - */ - protected int raceStatusSequenceNumber = 1; - - - /** * A list of compound marks in the race. */ @@ -364,4 +352,8 @@ public abstract class Race implements Runnable { this.lastFpsResetTime = 0; } } + + public int getRaceId() { + return raceId; + } } From ad61dc6bcef719028364422465dc779a200d7bd3 Mon Sep 17 00:00:00 2001 From: zwu18 Date: Thu, 3 Aug 2017 00:22:46 +1200 Subject: [PATCH 09/19] Implemented runCommand method in VMGCommand and TackGybeCommand classes. Both classes currently rely on the MockRace class so changes will be required when MockRace is refactored. #story[1097] --- .../src/main/java/mock/model/MockBoat.java | 1 - .../src/main/java/mock/model/MockRace.java | 12 +++++++----- .../model/commandFactory/CommandFactory.java | 4 +++- .../model/commandFactory/TackGybeCommand.java | 12 +++++++++--- .../mock/model/commandFactory/VMGCommand.java | 19 +++++++++++++++++++ .../src/main/java/shared/model/Constants.java | 2 +- 6 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index c8c6825b..dde207e3 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -66,7 +66,6 @@ public class MockBoat extends Boat { //Calculate bearing. Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition); - return bearing; } diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index c14e337f..56fe760a 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -383,7 +383,6 @@ public class MockRace extends Race { //Update race time. updateRaceTime(currentTime); - //As long as there is at least one boat racing, we still simulate the race. if (getNumberOfActiveBoats() != 0) { @@ -392,7 +391,6 @@ public class MockRace extends Race { //For each boat, we update its position, and generate a BoatLocationMessage. for (MockBoat boat : boats) { - //If it is still racing, update its position. if (boat.getStatus() == BoatStatusEnum.RACING) { @@ -544,7 +542,7 @@ public class MockRace extends Race { * @param boat The boat to calculate VMG for. * @return VMG for the specified boat. */ - private VMG calculateVMG(MockBoat boat) { + public VMG calculateVMG(MockBoat boat) { //Find the VMG inside these bounds. @@ -563,14 +561,14 @@ public class MockRace extends Race { * @param bearingToDestination The bearing between the boat and its destination. * @return True if the new VMG is improves velocity, false otherwise. */ - private boolean improvesVelocity(VMG currentVMG, VMG potentialVMG, Bearing bearingToDestination) { + public boolean improvesVelocity(VMG currentVMG, VMG potentialVMG, Bearing bearingToDestination) { //Calculates the angle between the boat and its destination. Angle angleBetweenDestAndHeading = Angle.fromDegrees(currentVMG.getBearing().degrees() - bearingToDestination.degrees()); //Calculates the angle between the new VMG and the boat's destination. Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(potentialVMG.getBearing().degrees() - bearingToDestination.degrees()); - + //System.out.println(angleBetweenDestAndHeading.degrees() + ":" + angleBetweenDestAndNewVMG.degrees()); //Calculate the boat's current velocity. double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * currentVMG.getSpeed(); @@ -581,6 +579,7 @@ public class MockRace extends Race { //Return whether or not the new VMG gives better velocity. return vmgVelocity > currentVelocity; + } /** @@ -625,13 +624,16 @@ public class MockRace extends Race { //Move the boat forwards that many meters, and advances its time counters by enough milliseconds. boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor); + long tackPeriod = 15000; if (boat.getTimeSinceTackChange() > tackPeriod) { + System.out.println("tack loop"); //Calculate the new VMG. VMG newVMG = this.calculateVMG(boat); //If the new vmg improves velocity, use it. if (improvesVelocity(boat, newVMG)) { + System.out.println("NEW VMG"); boat.setVMG(newVMG); } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java index 0e21d68c..ace2f3be 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -1,7 +1,9 @@ package mock.model.commandFactory; import mock.model.MockBoat; +import mock.model.MockRace; public interface CommandFactory { - void runCommand(MockBoat boat); + + void runCommand(MockBoat boat, MockRace race); } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index 80b1fb23..4f1f848e 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -1,15 +1,21 @@ package mock.model.commandFactory; import mock.model.MockBoat; +import mock.model.MockRace; +import mock.model.VMG; /** * Created by David on 2/08/2017. */ public class TackGybeCommand implements CommandFactory { - + //The refactoring of MockRace will require changes to be made @Override - public void runCommand(MockBoat boat) { - + public void runCommand(MockBoat boat, MockRace race) { + VMG newVMG = race.calculateVMG(boat); + VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing()); + if(race.improvesVelocity(boatVMG, newVMG, boat.calculateBearingToNextMarker())){ + boat.setVMG(newVMG); + } } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java new file mode 100644 index 00000000..2f0f527d --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -0,0 +1,19 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; +import mock.model.VMG; +import shared.model.Bearing; + +/** + * Created by David on 2/08/2017. + */ +public class VMGCommand implements CommandFactory { + + //The refactoring of MockRace will require changes to be made + @Override + public void runCommand(MockBoat boat, MockRace race) { + VMG newVMG = race.calculateVMG(boat); + boat.setVMG(newVMG); + } +} diff --git a/racevisionGame/src/main/java/shared/model/Constants.java b/racevisionGame/src/main/java/shared/model/Constants.java index 51666bf2..a6fe3844 100644 --- a/racevisionGame/src/main/java/shared/model/Constants.java +++ b/racevisionGame/src/main/java/shared/model/Constants.java @@ -38,7 +38,7 @@ public class Constants { /** * The race pre-start time, in milliseconds. 3 minutes. */ - public static final long RacePreStartTime = 3 * 60 * 1000; + public static final long RacePreStartTime = 1 * 10 * 1000; /** From 2672c2b13b2a02ed3a0589bd76edbb7a24153c0e Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Thu, 3 Aug 2017 00:32:17 +1200 Subject: [PATCH 10/19] Separated RaceLogic from MockRace - MockRace satisfies refactor requirement for RaceState - VisualiserRace directly implements Runnable to remove method from MockRace #story[1094] --- .../src/main/java/mock/app/Event.java | 3 +- .../src/main/java/mock/model/MockRace.java | 163 +---------------- .../src/main/java/mock/model/RaceLogic.java | 172 +++++++++++++++++- .../src/main/java/shared/model/Race.java | 4 +- .../java/visualiser/model/VisualiserRace.java | 2 +- 5 files changed, 180 insertions(+), 164 deletions(-) diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index c94a691f..b4b0586c 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -3,6 +3,7 @@ package mock.app; import mock.dataInput.PolarParser; import mock.model.MockRace; import mock.model.Polars; +import mock.model.RaceLogic; import network.Messages.LatestMessages; import shared.dataInput.*; import shared.enums.XMLFileType; @@ -91,7 +92,7 @@ public class Event { RegattaDataSource regattaDataSource = new RegattaXMLReader(this.regattaXML, this.xmlFileType); //Create and start race. - MockRace newRace = new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale); + RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); new Thread(newRace).start(); } diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 08c56a2d..a32787d2 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -1,12 +1,7 @@ package mock.model; -import javafx.animation.AnimationTimer; -import network.Messages.BoatLocation; -import network.Messages.BoatStatus; import network.Messages.Enums.BoatStatusEnum; import network.Messages.LatestMessages; -import network.Messages.RaceStatus; -import network.Utils.AC35UnitConverter; import shared.dataInput.BoatDataSource; import shared.dataInput.RaceDataSource; import network.Messages.Enums.RaceStatusEnum; @@ -26,7 +21,6 @@ import static java.lang.Math.cos; * Is responsible for simulating the race, and sending messages to a MockOutput instance. */ public class MockRace extends Race { - private RaceServer server; /** * An observable list of boats in the race. @@ -82,8 +76,6 @@ public class MockRace extends Race { //Wind. this.setWind(windGenerator.generateBaselineWind()); - - this.server = new RaceServer(this, latestMessages); } /** @@ -115,20 +107,11 @@ public class MockRace extends Race { } - /** - * Runnable for the thread. - */ - public void run() { - initialiseBoats(); - this.countdownTimer.start(); - } - - /** * Updates the race time to a specified value, in milliseconds since the unix epoch. * @param currentTime Milliseconds since unix epoch. */ - private void updateRaceTime(long currentTime) { + public void updateRaceTime(long currentTime) { this.raceClock.setUTCTime(currentTime); } @@ -136,7 +119,7 @@ public class MockRace extends Race { /** * Updates the race status enumeration based on the current time. */ - private void updateRaceStatusEnum() { + public void updateRaceStatusEnum() { //The millisecond duration of the race. Negative means it hasn't started, so we flip sign. long timeToStart = - this.raceClock.getDurationMilli(); @@ -167,7 +150,7 @@ public class MockRace extends Race { /** * Sets the status of all boats in the race to RACING. */ - private void setBoatsStatusToRacing() { + public void setBoatsStatusToRacing() { for (MockBoat boat : this.boats) { boat.setStatus(BoatStatusEnum.RACING); @@ -179,7 +162,7 @@ public class MockRace extends Race { * Sets the estimated time at next mark for each boat to a specified time. This is used during the countdown timer to provide this value to boat before the race starts. * @param time The time to provide to each boat. */ - private void setBoatsTimeNextMark(ZonedDateTime time) { + public void setBoatsTimeNextMark(ZonedDateTime time) { for (MockBoat boat : this.boats) { boat.setEstimatedTimeAtNextMark(time); @@ -187,144 +170,6 @@ public class MockRace extends Race { } - /** - * Countdown timer until race starts. - */ - protected AnimationTimer countdownTimer = new AnimationTimer() { - - - long currentTime = System.currentTimeMillis(); - - @Override - public void handle(long arg0) { - - //Update race time. - updateRaceTime(currentTime); - - //Update the race status based on the current time. - updateRaceStatusEnum(); - - //Provide boat's with an estimated time at next mark until the race starts. - setBoatsTimeNextMark(raceClock.getCurrentTime()); - - //Parse the boat locations. - server.parseBoatLocations(); - - //Parse the marks. - server.parseMarks(); - - // Change wind direction - changeWindDirection(); - - //Parse the race status. - server.parseRaceStatus(); - - - if (getRaceStatusEnum() == RaceStatusEnum.STARTED) { - setBoatsStatusToRacing(); - raceTimer.start(); - this.stop(); - } - - //Update the animations timer's time. - currentTime = System.currentTimeMillis(); - } - }; - - - /** - * Timer that runs for the duration of the race, until all boats finish. - */ - private AnimationTimer raceTimer = new AnimationTimer() { - - /** - * Start time of loop, in milliseconds. - */ - long timeRaceStarted = System.currentTimeMillis(); - - /** - * Current time during a loop iteration. - */ - long currentTime = System.currentTimeMillis(); - - /** - * The time of the previous frame, in milliseconds. - */ - long lastFrameTime = timeRaceStarted; - - @Override - public void handle(long arg0) { - - //Get the current time. - currentTime = System.currentTimeMillis(); - - //Update race time. - updateRaceTime(currentTime); - - - //As long as there is at least one boat racing, we still simulate the race. - if (getNumberOfActiveBoats() != 0) { - - //Get the time period of this frame. - long framePeriod = currentTime - lastFrameTime; - - //For each boat, we update its position, and generate a BoatLocationMessage. - for (MockBoat boat : boats) { - - //If it is still racing, update its position. - if (boat.getStatus() == BoatStatusEnum.RACING) { - - updatePosition(boat, framePeriod, raceClock.getDurationMilli()); - - } - - } - - } else { - //Otherwise, the race is over! - raceFinished.start(); - setRaceStatusEnum(RaceStatusEnum.FINISHED); - this.stop(); - } - - if (getNumberOfActiveBoats() != 0) { - // Change wind direction - changeWindDirection(); - - //Parse the boat locations. - server.parseBoatLocations(); - - //Parse the marks. - server.parseMarks(); - - //Parse the race status. - server.parseRaceStatus(); - - - //Update the last frame time. - this.lastFrameTime = currentTime; - } - } - }; - - /** - * Broadcast that the race has finished. - */ - protected AnimationTimer raceFinished = new AnimationTimer(){ - int iters = 0; - @Override - public void handle(long now) { - - server.parseRaceStatus(); - - if (iters > 500) { - stop(); - } - iters++; - } - }; - - /** * Initialise the boats in the race. * This sets their starting positions and current legs. diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index b8e904a6..73b38c0d 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -1,4 +1,174 @@ package mock.model; -public class RaceLogic { +import javafx.animation.AnimationTimer; +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.LatestMessages; + +public class RaceLogic implements Runnable { + /** + * State of current race modified by this object + */ + private MockRace race; + /** + * High-level interface to AC35 protocol server + */ + private RaceServer server; + + /** + * Initialises race loop with state and server message queue + * @param race state of race to modify + * @param messages to send to server + */ + public RaceLogic(MockRace race, LatestMessages messages) { + this.race = race; + this.server = new RaceServer(race, messages); + } + + /** + * Initialise boats and start countdown timer + */ + @Override + public void run() { + race.initialiseBoats(); + this.countdownTimer.start(); + } + + + /** + * Countdown timer until race starts. + */ + protected AnimationTimer countdownTimer = new AnimationTimer() { + + + long currentTime = System.currentTimeMillis(); + + @Override + public void handle(long arg0) { + + //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 boat locations. + server.parseBoatLocations(); + + //Parse the marks. + server.parseMarks(); + + // Change wind direction + race.changeWindDirection(); + + //Parse the race status. + server.parseRaceStatus(); + + + if (race.getRaceStatusEnum() == RaceStatusEnum.STARTED) { + race.setBoatsStatusToRacing(); + raceTimer.start(); + this.stop(); + } + + //Update the animations timer's time. + currentTime = System.currentTimeMillis(); + } + }; + + + /** + * Timer that runs for the duration of the race, until all boats finish. + */ + private AnimationTimer raceTimer = new AnimationTimer() { + + /** + * Start time of loop, in milliseconds. + */ + long timeRaceStarted = System.currentTimeMillis(); + + /** + * Current time during a loop iteration. + */ + long currentTime = System.currentTimeMillis(); + + /** + * The time of the previous frame, in milliseconds. + */ + long lastFrameTime = timeRaceStarted; + + @Override + public void handle(long arg0) { + + //Get the current time. + currentTime = System.currentTimeMillis(); + + //Update race time. + race.updateRaceTime(currentTime); + + + //As long as there is at least one boat racing, we still simulate the race. + if (race.getNumberOfActiveBoats() != 0) { + + //Get the time period of this frame. + long framePeriod = currentTime - lastFrameTime; + + //For each boat, we update its position, and generate a BoatLocationMessage. + for (MockBoat boat : race.getBoats()) { + + //If it is still racing, update its position. + if (boat.getStatus() == BoatStatusEnum.RACING) { + + race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli()); + + } + + } + + } else { + //Otherwise, the race is over! + raceFinished.start(); + race.setRaceStatusEnum(RaceStatusEnum.FINISHED); + this.stop(); + } + + if (race.getNumberOfActiveBoats() != 0) { + // Change wind direction + race.changeWindDirection(); + + //Parse the boat locations. + server.parseBoatLocations(); + + //Parse the marks. + server.parseMarks(); + + //Parse the race status. + server.parseRaceStatus(); + + + //Update the last frame time. + this.lastFrameTime = currentTime; + } + } + }; + + /** + * Broadcast that the race has finished. + */ + protected AnimationTimer raceFinished = new AnimationTimer(){ + int iters = 0; + @Override + public void handle(long now) { + + server.parseRaceStatus(); + + if (iters > 500) { + stop(); + } + iters++; + } + }; } diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index 040e0d19..f9fc984e 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -19,7 +19,7 @@ import java.util.List; * This is a base class inherited by {@link mock.model.MockRace} and {@link visualiser.model.VisualiserRace}. * Has a course, state, wind, boundaries, etc.... Boats are added by inheriting classes (see {@link Boat}, {@link mock.model.MockBoat}, {@link visualiser.model.VisualiserBoat}. */ -public abstract class Race implements Runnable { +public abstract class Race { /** @@ -214,7 +214,7 @@ public abstract class Race implements Runnable { * Sets the current race status. * @param raceStatusEnum The new status of the race. */ - protected void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) { + public void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) { this.raceStatusEnum = raceStatusEnum; } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index 976a4b6e..2969fbb8 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -27,7 +27,7 @@ import java.util.Map; * Has a course, boats, boundaries, etc... * Observes LatestMessages and updates its state based on new messages. */ -public class VisualiserRace extends Race { +public class VisualiserRace extends Race implements Runnable { /** From d9679c1497676c68b89194bb3edcc15fb2fb298f Mon Sep 17 00:00:00 2001 From: zwu18 Date: Thu, 3 Aug 2017 01:31:05 +1200 Subject: [PATCH 11/19] Build failing due to junit even though junit passes all tests. Commented out VMGCommand and TackGybeCommand content. --- .../mock/model/commandFactory/TackGybeCommand.java | 10 ++++++++-- .../java/mock/model/commandFactory/VMGCommand.java | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index 4f1f848e..ef776536 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -3,6 +3,7 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; import mock.model.VMG; +import shared.model.Bearing; /** * Created by David on 2/08/2017. @@ -12,10 +13,15 @@ public class TackGybeCommand implements CommandFactory { //The refactoring of MockRace will require changes to be made @Override public void runCommand(MockBoat boat, MockRace race) { - VMG newVMG = race.calculateVMG(boat); + /*VMG newVMG = boat.getPolars().calculateVMG( + race.getWindDirection(), + race.getWindSpeed(), + boat.calculateBearingToNextMarker(), + Bearing.fromDegrees(0d), + Bearing.fromDegrees(359.99999d)); VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing()); if(race.improvesVelocity(boatVMG, newVMG, boat.calculateBearingToNextMarker())){ boat.setVMG(newVMG); - } + }*/ } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java index 2f0f527d..5d11a27b 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -13,7 +13,12 @@ public class VMGCommand implements CommandFactory { //The refactoring of MockRace will require changes to be made @Override public void runCommand(MockBoat boat, MockRace race) { - VMG newVMG = race.calculateVMG(boat); - boat.setVMG(newVMG); + /*VMG newVMG = boat.getPolars().calculateVMG( + race.getWindDirection(), + race.getWindSpeed(), + boat.calculateBearingToNextMarker(), + Bearing.fromDegrees(0d), + Bearing.fromDegrees(359.99999d)); + boat.setVMG(newVMG);*/ } } From 40a3ed1bb00af265d87fd529e76bbad1bf93952b Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Thu, 3 Aug 2017 16:56:52 +1200 Subject: [PATCH 12/19] Boats now store whether autoVMG is on or off, boat speed scales off their bearing relative to the TWA. #story[1094] --- .../src/main/java/mock/model/MockBoat.java | 12 + .../src/main/java/mock/model/MockRace.java | 60 +++-- .../src/main/java/mock/model/RaceLogic.java | 6 +- .../src/main/java/mock/model/RaceState.java | 226 +++++++++++++++++- 4 files changed, 283 insertions(+), 21 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 3c932126..104fa264 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -22,6 +22,11 @@ public class MockBoat extends Boat { */ private long timeSinceTackChange = 0; + /** + * Stores whether the boat is on autoVMG or not + */ + private boolean autoVMG = true; + /** @@ -191,4 +196,11 @@ public class MockBoat extends Boat { return distanceTravelledMeters; } + public boolean isAutoVMG() { + return autoVMG; + } + + public void setAutoVMG(boolean autoVMG) { + this.autoVMG = autoVMG; + } } diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 5670e989..0ae5cfcc 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -2,11 +2,13 @@ package mock.model; import network.Messages.Enums.BoatStatusEnum; import network.Messages.LatestMessages; +import org.opengis.geometry.primitive.*; import shared.dataInput.BoatDataSource; import shared.dataInput.RaceDataSource; import network.Messages.Enums.RaceStatusEnum; import shared.dataInput.RegattaDataSource; import shared.model.*; +import shared.model.Bearing; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -209,8 +211,7 @@ 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(999999); - + boat.setTimeSinceTackChange(Long.MAX_VALUE); } } @@ -319,8 +320,14 @@ public class MockRace extends Race { //Checks if the current boat has finished the race or not. boolean finish = this.isLastLeg(boat.getCurrentLeg()); - if (!finish) { + if (!finish && totalElapsedMilliseconds >= updatePeriodMilliseconds) { + + if (boat.getCurrentSpeed() == 0) { + newOptimalVMG(boat); + boat.setBearing(boat.calculateBearingToNextMarker()); + } + setBoatSpeed(boat); //Calculates the distance travelled, in meters, in the current timeslice. double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds); @@ -331,30 +338,47 @@ public class MockRace extends Race { //Move the boat forwards that many meters, and advances its time counters by enough milliseconds. boat.moveForwards(distanceTravelledMeters); + boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + updatePeriodMilliseconds); + if (boat.isAutoVMG()) { + newOptimalVMG(boat); + } + + this.updateEstimatedTime(boat); + } - long tackPeriod = 15000; + } - if (boat.getTimeSinceTackChange() > tackPeriod) { - //Calculate the new VMG. - VMG newVMG = boat.getPolars().calculateVMG( - this.getWindDirection(), - this.getWindSpeed(), - boat.calculateBearingToNextMarker(), - Bearing.fromDegrees(0d), - Bearing.fromDegrees(359.99999d)); + private void newOptimalVMG(MockBoat boat) { + long tackPeriod = 15000; + if (boat.getTimeSinceTackChange() > tackPeriod) { + //Calculate the new VMG. + VMG newVMG = boat.getPolars().calculateVMG( + this.getWindDirection(), + this.getWindSpeed(), + boat.calculateBearingToNextMarker(), + Bearing.fromDegrees(0d), + Bearing.fromDegrees(359.99999d)); - //If the new vmg improves velocity, use it. - if (improvesVelocity(boat, newVMG)) { - boat.setVMG(newVMG); - } + //If the new vmg improves velocity, use it. + if (improvesVelocity(boat, newVMG)) { + boat.setVMG(newVMG); } - - this.updateEstimatedTime(boat); } + } + private void setBoatSpeed(MockBoat boat) { + VMG vmg = boat.getPolars().calculateVMG( + this.getWindDirection(), + this.getWindSpeed(), + boat.getBearing(), + boat.getBearing(), + boat.getBearing()); + if (vmg.getSpeed() > 0) { + boat.setCurrentSpeed(vmg.getSpeed()); + } } /** diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index 73b38c0d..9e810761 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -4,6 +4,7 @@ import javafx.animation.AnimationTimer; import network.Messages.Enums.BoatStatusEnum; import network.Messages.Enums.RaceStatusEnum; import network.Messages.LatestMessages; +import shared.model.Race; public class RaceLogic implements Runnable { /** @@ -100,6 +101,8 @@ public class RaceLogic implements Runnable { */ long lastFrameTime = timeRaceStarted; + long framePeriod = currentTime - lastFrameTime; + @Override public void handle(long arg0) { @@ -109,12 +112,11 @@ public class RaceLogic implements Runnable { //Update race time. race.updateRaceTime(currentTime); - //As long as there is at least one boat racing, we still simulate the race. if (race.getNumberOfActiveBoats() != 0) { //Get the time period of this frame. - long framePeriod = currentTime - lastFrameTime; + framePeriod = currentTime - lastFrameTime; //For each boat, we update its position, and generate a BoatLocationMessage. for (MockBoat boat : race.getBoats()) { diff --git a/racevisionGame/src/main/java/mock/model/RaceState.java b/racevisionGame/src/main/java/mock/model/RaceState.java index 4b13cbb4..dd72cc97 100644 --- a/racevisionGame/src/main/java/mock/model/RaceState.java +++ b/racevisionGame/src/main/java/mock/model/RaceState.java @@ -1,4 +1,228 @@ package mock.model; -public class RaceState { +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.LatestMessages; +import shared.dataInput.BoatDataSource; +import shared.dataInput.RaceDataSource; +import shared.dataInput.RegattaDataSource; +import shared.model.*; + +import java.util.*; + +public class RaceState extends Race { + /** + * An observable list of boats in the race. + */ + private List boats; + + private Map boatMap; + + private Wind wind; + + public RaceState(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars) { + super(boatDataSource, raceDataSource, regattaDataSource, latestMessages); + this.boats = this.generateMockBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), polars); + this.boatMap = this.generateMockBoatMap(this.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. + */ + private List generateMockBoats(Map boats, List sourceIDs, Polars polars) { + + List mockBoats = new ArrayList<>(sourceIDs.size()); + + //For each sourceID participating... + for (int sourceID : sourceIDs) { + + //Get the boat associated with the sourceID. + Boat boat = boats.get(sourceID); + + //Construct a MockBoat using the Boat and Polars. + MockBoat mockBoat = new MockBoat(boat, polars); + + mockBoats.add(mockBoat); + + } + + return mockBoats; + + } + + /** + * Generates a map of the sourceID to its MockBoat. + * @param boats The list of MockBoats describing boats that are potentially in the race. + * @return A map of sourceID to MockBoats. + */ + private Map generateMockBoatMap(List boats) { + Map boatMap = new HashMap<>(); + + for (MockBoat boat : boats) { + boatMap.put(boat.getSourceID(), boat); + } + + return boatMap; + } + + /** + * Checks if a boat has finished any legs, or has pulled out of race (DNF). + * @param boat The boat to check. + * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. + */ + public void checkPosition(MockBoat boat, long timeElapsed) { + + //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. + double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. + + if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { + //Boat has reached its target marker, and has moved on to a new leg. + + + + //Calculate how much the boat overshot the marker by. + double overshootMeters = boat.calculateDistanceToNextMarker(); + + + //Move boat on to next leg. + Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + boat.setCurrentLeg(nextLeg); + + //Add overshoot distance into the distance travelled for the next leg. + boat.setDistanceTravelledInLeg(overshootMeters); + + //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark. + boat.setTimeSinceTackChange(999999); + + + //Check if the boat has finished or stopped racing. + + if (this.isLastLeg(boat.getCurrentLeg())) { + //Boat has finished. + boat.setTimeFinished(timeElapsed); + boat.setCurrentSpeed(0); + boat.setStatus(BoatStatusEnum.FINISHED); + + } + + } + + } + + /** + * Initialise the boats in the race. + * This sets their starting positions and current legs. + */ + @Override + protected void initialiseBoats() { + + //Gets the starting positions of the boats. + List startingPositions = getSpreadStartingPositions(); + + //Get iterators for our boat and position lists. + Iterator boatIt = this.boats.iterator(); + Iterator startPositionIt = startingPositions.iterator(); + + //Iterate over the pair of lists. + while (boatIt.hasNext() && startPositionIt.hasNext()) { + + //Get the next boat and position. + MockBoat boat = boatIt.next(); + GPSCoordinate startPosition = startPositionIt.next(); + + + //The boat starts on the first leg of the race. + boat.setCurrentLeg(this.legs.get(0)); + + //Boats start with 0 knots speed. + boat.setCurrentSpeed(0d); + + //Place the boat at its starting position. + boat.setCurrentPosition(startPosition); + + //Boats start facing their next marker. + boat.setBearing(boat.calculateBearingToNextMarker()); + + //Sets the boats status to prestart - it changes to racing when the race starts. + 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(999999); + + } + + } + + /** + * Creates a list of starting positions for the different boats, so they do not appear cramped at the start line. + * + * @return A list of starting positions. + */ + public List getSpreadStartingPositions() { + + //The first compound marker of the race - the starting gate. + CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark(); + + //The position of the two markers from the compound marker. + GPSCoordinate mark1Position = compoundMark.getMark1Position(); + GPSCoordinate mark2Position = compoundMark.getMark2Position(); + + + //Calculates the azimuth between the two points. + Azimuth azimuth = GPSCoordinate.calculateAzimuth(mark1Position, mark2Position); + + //Calculates the distance between the two points. + double distanceMeters = GPSCoordinate.calculateDistanceMeters(mark1Position, mark2Position); + + //The number of boats in the race. + int numberOfBoats = this.boats.size(); + + //Calculates the distance between each boat. We divide by numberOfBoats + 1 to ensure that no boat is placed on one of the starting gate's marks. + double distanceBetweenBoatsMeters = distanceMeters / (numberOfBoats + 1); + + + //List to store coordinates in. + List positions = new ArrayList<>(); + + //We start spacing boats out from mark 1. + GPSCoordinate position = mark1Position; + + //For each boat, displace position, and store it. + for (int i = 0; i < numberOfBoats; i++) { + + position = GPSCoordinate.calculateNewPosition(position, distanceBetweenBoatsMeters, azimuth); + + positions.add(position); + + } + + return positions; + } + + /** + * Updates the race time to a specified value, in milliseconds since the unix epoch. + * @param currentTime Milliseconds since unix epoch. + */ + public void updateRaceTime(long currentTime) { + this.raceClock.setUTCTime(currentTime); + } + + public void run() { + initialiseBoats(); + } + + public Wind getWind() { + return wind; + } + + public List getBoats() { + return boats; + } + + public MockBoat getBoatByid(int id) { + return boatMap.get(id); + } } From c83442761ca6502cedc28feb671705fdf4c0a768 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Thu, 3 Aug 2017 17:54:03 +1200 Subject: [PATCH 13/19] Implemented Factory for Command objects and fixed Command Pattern implementation. - Commands now have standard 0-parameter execute method - Commands are created by CommandFactory #story[1094] --- .../mock/model/commandFactory/Command.java | 14 +++++++++++++ .../model/commandFactory/CommandFactory.java | 21 ++++++++++++++++--- .../model/commandFactory/TackGybeCommand.java | 13 ++++++++---- .../mock/model/commandFactory/VMGCommand.java | 13 ++++++++---- 4 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/model/commandFactory/Command.java diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/Command.java b/racevisionGame/src/main/java/mock/model/commandFactory/Command.java new file mode 100644 index 00000000..e0486114 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/Command.java @@ -0,0 +1,14 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; + +/** + * Allows RaceLogic to control MockRace state according to the Command pattern + */ +public interface Command { + /** + * Execute command - standard method name in pattern + */ + void execute(); +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java index ace2f3be..17834963 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -3,7 +3,22 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; -public interface CommandFactory { - - void runCommand(MockBoat boat, MockRace race); +/** + * Factory class for Command objects + */ +public class CommandFactory { + /** + * Generates a command on a race and boat corresponding to the protocol action number. + * @param race to receive command + * @param boat to receive command in race + * @param action number to select command + * @return + */ + public static Command createCommand(MockRace race, MockBoat boat, int action) { + switch(action) { + case 1: return new VMGCommand(race, boat); + case 4: return new TackGybeCommand(race, boat); + default: return null; // TODO - please please have discussion over what to default to + } + } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index ef776536..150a1da8 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -2,17 +2,22 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; -import mock.model.VMG; -import shared.model.Bearing; /** * Created by David on 2/08/2017. */ -public class TackGybeCommand implements CommandFactory { +public class TackGybeCommand implements Command { + private MockRace race; + private MockBoat boat; + + public TackGybeCommand(MockRace race, MockBoat boat) { + this.race = race; + this.boat = boat; + } //The refactoring of MockRace will require changes to be made @Override - public void runCommand(MockBoat boat, MockRace race) { + public void execute() { /*VMG newVMG = boat.getPolars().calculateVMG( race.getWindDirection(), race.getWindSpeed(), diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java index 5d11a27b..64cc6a9f 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -2,17 +2,22 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; -import mock.model.VMG; -import shared.model.Bearing; /** * Created by David on 2/08/2017. */ -public class VMGCommand implements CommandFactory { +public class VMGCommand implements Command { + private MockRace race; + private MockBoat boat; + + public VMGCommand(MockRace race, MockBoat boat) { + this.race = race; + this.boat = boat; + } //The refactoring of MockRace will require changes to be made @Override - public void runCommand(MockBoat boat, MockRace race) { + public void execute() { /*VMG newVMG = boat.getPolars().calculateVMG( race.getWindDirection(), race.getWindSpeed(), From 148108a6581b16d346c891f4a1165e20ed832a96 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 3 Aug 2017 21:11:25 +1200 Subject: [PATCH 14/19] CommandFactory uses BoatActionEnum instead of raw ints. #story[1094] --- .../java/mock/model/commandFactory/CommandFactory.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java index 17834963..6e87f11b 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -2,6 +2,7 @@ package mock.model.commandFactory; import mock.model.MockBoat; import mock.model.MockRace; +import network.Messages.Enums.BoatActionEnum; /** * Factory class for Command objects @@ -14,10 +15,10 @@ public class CommandFactory { * @param action number to select command * @return */ - public static Command createCommand(MockRace race, MockBoat boat, int action) { + public static Command createCommand(MockRace race, MockBoat boat, BoatActionEnum action) { switch(action) { - case 1: return new VMGCommand(race, boat); - case 4: return new TackGybeCommand(race, boat); + case AUTO_PILOT: return new VMGCommand(race, boat); + case TACK_GYBE: return new TackGybeCommand(race, boat); default: return null; // TODO - please please have discussion over what to default to } } From 851bbb4fde539ed3b3db3bab8bec24c5986657bc Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Thu, 3 Aug 2017 23:03:15 +1200 Subject: [PATCH 15/19] Merged branch "RaceFactory" with master --- .../src/main/java/mock/model/RaceState.java | 228 ------------------ 1 file changed, 228 deletions(-) delete mode 100644 racevisionGame/src/main/java/mock/model/RaceState.java diff --git a/racevisionGame/src/main/java/mock/model/RaceState.java b/racevisionGame/src/main/java/mock/model/RaceState.java deleted file mode 100644 index dd72cc97..00000000 --- a/racevisionGame/src/main/java/mock/model/RaceState.java +++ /dev/null @@ -1,228 +0,0 @@ -package mock.model; - -import network.Messages.Enums.BoatStatusEnum; -import network.Messages.LatestMessages; -import shared.dataInput.BoatDataSource; -import shared.dataInput.RaceDataSource; -import shared.dataInput.RegattaDataSource; -import shared.model.*; - -import java.util.*; - -public class RaceState extends Race { - /** - * An observable list of boats in the race. - */ - private List boats; - - private Map boatMap; - - private Wind wind; - - public RaceState(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars) { - super(boatDataSource, raceDataSource, regattaDataSource, latestMessages); - this.boats = this.generateMockBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), polars); - this.boatMap = this.generateMockBoatMap(this.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. - */ - private List generateMockBoats(Map boats, List sourceIDs, Polars polars) { - - List mockBoats = new ArrayList<>(sourceIDs.size()); - - //For each sourceID participating... - for (int sourceID : sourceIDs) { - - //Get the boat associated with the sourceID. - Boat boat = boats.get(sourceID); - - //Construct a MockBoat using the Boat and Polars. - MockBoat mockBoat = new MockBoat(boat, polars); - - mockBoats.add(mockBoat); - - } - - return mockBoats; - - } - - /** - * Generates a map of the sourceID to its MockBoat. - * @param boats The list of MockBoats describing boats that are potentially in the race. - * @return A map of sourceID to MockBoats. - */ - private Map generateMockBoatMap(List boats) { - Map boatMap = new HashMap<>(); - - for (MockBoat boat : boats) { - boatMap.put(boat.getSourceID(), boat); - } - - return boatMap; - } - - /** - * Checks if a boat has finished any legs, or has pulled out of race (DNF). - * @param boat The boat to check. - * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. - */ - public void checkPosition(MockBoat boat, long timeElapsed) { - - //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. - double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. - - if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { - //Boat has reached its target marker, and has moved on to a new leg. - - - - //Calculate how much the boat overshot the marker by. - double overshootMeters = boat.calculateDistanceToNextMarker(); - - - //Move boat on to next leg. - Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); - boat.setCurrentLeg(nextLeg); - - //Add overshoot distance into the distance travelled for the next leg. - boat.setDistanceTravelledInLeg(overshootMeters); - - //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark. - boat.setTimeSinceTackChange(999999); - - - //Check if the boat has finished or stopped racing. - - if (this.isLastLeg(boat.getCurrentLeg())) { - //Boat has finished. - boat.setTimeFinished(timeElapsed); - boat.setCurrentSpeed(0); - boat.setStatus(BoatStatusEnum.FINISHED); - - } - - } - - } - - /** - * Initialise the boats in the race. - * This sets their starting positions and current legs. - */ - @Override - protected void initialiseBoats() { - - //Gets the starting positions of the boats. - List startingPositions = getSpreadStartingPositions(); - - //Get iterators for our boat and position lists. - Iterator boatIt = this.boats.iterator(); - Iterator startPositionIt = startingPositions.iterator(); - - //Iterate over the pair of lists. - while (boatIt.hasNext() && startPositionIt.hasNext()) { - - //Get the next boat and position. - MockBoat boat = boatIt.next(); - GPSCoordinate startPosition = startPositionIt.next(); - - - //The boat starts on the first leg of the race. - boat.setCurrentLeg(this.legs.get(0)); - - //Boats start with 0 knots speed. - boat.setCurrentSpeed(0d); - - //Place the boat at its starting position. - boat.setCurrentPosition(startPosition); - - //Boats start facing their next marker. - boat.setBearing(boat.calculateBearingToNextMarker()); - - //Sets the boats status to prestart - it changes to racing when the race starts. - 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(999999); - - } - - } - - /** - * Creates a list of starting positions for the different boats, so they do not appear cramped at the start line. - * - * @return A list of starting positions. - */ - public List getSpreadStartingPositions() { - - //The first compound marker of the race - the starting gate. - CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark(); - - //The position of the two markers from the compound marker. - GPSCoordinate mark1Position = compoundMark.getMark1Position(); - GPSCoordinate mark2Position = compoundMark.getMark2Position(); - - - //Calculates the azimuth between the two points. - Azimuth azimuth = GPSCoordinate.calculateAzimuth(mark1Position, mark2Position); - - //Calculates the distance between the two points. - double distanceMeters = GPSCoordinate.calculateDistanceMeters(mark1Position, mark2Position); - - //The number of boats in the race. - int numberOfBoats = this.boats.size(); - - //Calculates the distance between each boat. We divide by numberOfBoats + 1 to ensure that no boat is placed on one of the starting gate's marks. - double distanceBetweenBoatsMeters = distanceMeters / (numberOfBoats + 1); - - - //List to store coordinates in. - List positions = new ArrayList<>(); - - //We start spacing boats out from mark 1. - GPSCoordinate position = mark1Position; - - //For each boat, displace position, and store it. - for (int i = 0; i < numberOfBoats; i++) { - - position = GPSCoordinate.calculateNewPosition(position, distanceBetweenBoatsMeters, azimuth); - - positions.add(position); - - } - - return positions; - } - - /** - * Updates the race time to a specified value, in milliseconds since the unix epoch. - * @param currentTime Milliseconds since unix epoch. - */ - public void updateRaceTime(long currentTime) { - this.raceClock.setUTCTime(currentTime); - } - - public void run() { - initialiseBoats(); - } - - public Wind getWind() { - return wind; - } - - public List getBoats() { - return boats; - } - - public MockBoat getBoatByid(int id) { - return boatMap.get(id); - } -} From 554f8a2a0ffd5525448ede94f4331d546f5ebfb1 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 4 Aug 2017 23:08:07 +1200 Subject: [PATCH 16/19] Added WindCommand and multiple command execution to RaceLogic - Dispatch commands with CompositeCommand - Single WindCommand handles upwind and downwind logic - Changed key bindings as Mac lacks PgUp PgDn - ControllerServer is observable, RaceLogic updates CompositeCommand as observer --- .../src/main/java/mock/model/RaceLogic.java | 26 ++++++++++++++-- .../model/commandFactory/CommandFactory.java | 2 ++ .../commandFactory/CompositeCommand.java | 23 ++++++++++++++ .../model/commandFactory/WindCommand.java | 30 ++++++++++++++++++ .../gameController/ControllerServer.java | 19 ++++++++++-- .../gameController/Keys/KeyFactory.java | 4 +-- .../model/commandFactory/WindCommandTest.java | 31 +++++++++++++++++++ 7 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java create mode 100644 racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java create mode 100644 racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index 9e810761..adc0fe37 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -1,12 +1,20 @@ package mock.model; import javafx.animation.AnimationTimer; +import mock.model.commandFactory.Command; +import mock.model.commandFactory.CommandFactory; +import mock.model.commandFactory.CompositeCommand; +import network.Messages.Enums.BoatActionEnum; import network.Messages.Enums.BoatStatusEnum; import network.Messages.Enums.RaceStatusEnum; import network.Messages.LatestMessages; -import shared.model.Race; +import visualiser.gameController.ControllerServer; -public class RaceLogic implements Runnable { +import java.util.Observable; +import java.util.Observer; +import java.util.Stack; + +public class RaceLogic implements Observer, Runnable { /** * State of current race modified by this object */ @@ -16,6 +24,8 @@ public class RaceLogic implements Runnable { */ private RaceServer server; + private CompositeCommand commands; + /** * Initialises race loop with state and server message queue * @param race state of race to modify @@ -24,6 +34,7 @@ public class RaceLogic implements Runnable { public RaceLogic(MockRace race, LatestMessages messages) { this.race = race; this.server = new RaceServer(race, messages); + this.commands = new CompositeCommand(); } /** @@ -123,7 +134,7 @@ public class RaceLogic implements Runnable { //If it is still racing, update its position. if (boat.getStatus() == BoatStatusEnum.RACING) { - + commands.execute(); race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli()); } @@ -173,4 +184,13 @@ public class RaceLogic implements Runnable { iters++; } }; + + @Override + public void update(Observable o, Object arg) { + ControllerServer server = (ControllerServer)o; + BoatActionEnum action = server.getAction(); + MockBoat boat = race.getBoats().get(0); + + commands.addCommand(CommandFactory.createCommand(race, boat, action)); + } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java index 6e87f11b..fba06cb5 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -19,6 +19,8 @@ public class CommandFactory { switch(action) { case AUTO_PILOT: return new VMGCommand(race, boat); case TACK_GYBE: return new TackGybeCommand(race, boat); + case UPWIND: return new WindCommand(race, boat, true); + case DOWNWIND: return new WindCommand(race, boat, false); default: return null; // TODO - please please have discussion over what to default to } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java new file mode 100644 index 00000000..12690d29 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java @@ -0,0 +1,23 @@ +package mock.model.commandFactory; + +import java.util.Stack; + +/** + * Wraps multiple commands into a composite to execute queued commands during a frame. + */ +public class CompositeCommand implements Command { + private Stack commands; + + public CompositeCommand() { + this.commands = new Stack<>(); + } + + public void addCommand(Command command) { + commands.push(command); + } + + @Override + public void execute() { + while(!commands.isEmpty()) commands.pop().execute(); + } +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java new file mode 100644 index 00000000..b254f087 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java @@ -0,0 +1,30 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; +import shared.model.Bearing; + +/** + * Created by connortaylorbrown on 4/08/17. + */ +public class WindCommand implements Command { + private MockRace race; + private MockBoat boat; + private int direction; + + public WindCommand(MockRace race, MockBoat boat, boolean upwind) { + this.race = race; + this.boat = boat; + this.direction = upwind? 1 : -1; + } + + @Override + public void execute() { + double wind = race.getWindDirection().degrees(); + double heading = boat.getBearing().degrees(); + + double offset = 3; + if(wind - heading < 0) offset *= -1 * direction; + boat.setBearing(Bearing.fromDegrees(heading + offset)); + } +} diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index fb6a257b..a2f8c80e 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -1,5 +1,7 @@ package visualiser.gameController; +import mock.model.commandFactory.Command; +import mock.model.commandFactory.CommandFactory; import network.BinaryMessageDecoder; import network.MessageDecoders.BoatActionDecoder; import network.Messages.Enums.BoatActionEnum; @@ -9,11 +11,12 @@ import visualiser.gameController.Keys.KeyFactory; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; +import java.util.Observable; /** * Service for dispatching key press data to race from client */ -public class ControllerServer implements Runnable { +public class ControllerServer extends Observable implements Runnable { /** * Socket to client */ @@ -22,6 +25,10 @@ public class ControllerServer implements Runnable { * Wrapper for input from client */ private DataInputStream inputStream; + /** + * Last received boat action + */ + private BoatActionEnum action; /** * Initialise server-side controller with live client socket @@ -36,6 +43,10 @@ public class ControllerServer implements Runnable { } } + public BoatActionEnum getAction() { + return action; + } + /** * Wait for controller key input from client and loop. */ @@ -48,8 +59,10 @@ public class ControllerServer implements Runnable { inputStream.read(message); BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message); BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody()); - BoatActionEnum decodedMessage = boatActionDecoder.getBoatAction(); - System.out.println("Received key: " + decodedMessage); + action = boatActionDecoder.getBoatAction(); + + this.notifyObservers(); + this.setChanged(); } } catch (IOException e) { e.printStackTrace(); diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java index ef1368f0..be95abd3 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java @@ -27,8 +27,8 @@ public class KeyFactory { keyState.put("SPACE", new VMGKey("VMG")); keyState.put("SHIFT", new SailsToggleKey("Toggle Sails")); keyState.put("ENTER", new TackGybeKey("Tack/Gybe")); - keyState.put("PAGE_UP", new UpWindKey("Upwind")); - keyState.put("PAGE_DOWN", new DownWindKey("Downwind")); + keyState.put("UP", new UpWindKey("Upwind")); + keyState.put("DOWN", new DownWindKey("Downwind")); } /** diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java new file mode 100644 index 00000000..c3d0df04 --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -0,0 +1,31 @@ +package mock.model.commandFactory; + +import mock.model.MockRace; +import network.Messages.Enums.BoatActionEnum; +import org.junit.Before; +import org.junit.Test; +import shared.model.Boat; +import shared.model.Race; +import visualiser.model.VisualiserRace; + +import static org.testng.Assert.*; + +/** + * Created by connortaylorbrown on 4/08/17. + */ +public class WindCommandTest { + private Race race; + private Boat boat; + private Command upwind; + private Command downwind; + + @Before + public void setUp() { + boat = new Boat(0, "Bob", "NZ"); + } + + @Test + public void upwindCommandDecreasesAngle() { + + } +} \ No newline at end of file From e76de1cbf994dd18241864de90c76f5b52e73145 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Mon, 7 Aug 2017 14:13:14 +1200 Subject: [PATCH 17/19] Added test for WindCommand #story[1096] --- .../model/commandFactory/WindCommand.java | 3 +- .../model/commandFactory/WindCommandTest.java | 33 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java index b254f087..d20c0744 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java @@ -12,6 +12,8 @@ public class WindCommand implements Command { private MockBoat boat; private int direction; + private double offset = 3.0; + public WindCommand(MockRace race, MockBoat boat, boolean upwind) { this.race = race; this.boat = boat; @@ -23,7 +25,6 @@ public class WindCommand implements Command { double wind = race.getWindDirection().degrees(); double heading = boat.getBearing().degrees(); - double offset = 3; if(wind - heading < 0) offset *= -1 * direction; boat.setBearing(Bearing.fromDegrees(heading + offset)); } diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java index c3d0df04..4316d78d 100644 --- a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -1,31 +1,58 @@ package mock.model.commandFactory; +import mock.model.MockBoat; import mock.model.MockRace; import network.Messages.Enums.BoatActionEnum; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import shared.model.Bearing; import shared.model.Boat; import shared.model.Race; import visualiser.model.VisualiserRace; +import static org.mockito.Mockito.when; import static org.testng.Assert.*; +import static org.mockito.Mockito.mock; /** * Created by connortaylorbrown on 4/08/17. */ public class WindCommandTest { - private Race race; - private Boat boat; + private MockRace race; + private MockBoat boat; private Command upwind; private Command downwind; + private double initial; + + private double offset = 3.0; @Before public void setUp() { - boat = new Boat(0, "Bob", "NZ"); + race = mock(MockRace.class); + boat = new MockBoat(0, "Bob", "NZ", null); + + when(race.getWindDirection()).thenReturn(Bearing.fromDegrees(0.0)); + boat.setBearing(Bearing.fromDegrees(45.0)); + + upwind = CommandFactory.createCommand(race, boat, BoatActionEnum.UPWIND); + downwind = CommandFactory.createCommand(race, boat, BoatActionEnum.DOWNWIND); + + initial = boat.getBearing().degrees(); } + /** + * Ensure the difference between initial and final angle is 3 degrees + */ @Test public void upwindCommandDecreasesAngle() { + upwind.execute(); + assertEquals(initial - boat.getBearing().degrees(), offset, 1e-5); + } + @Test + public void downwindCommandIncreasesAngle() { + downwind.execute(); + assertEquals(boat.getBearing().degrees() - initial, offset, 1e-5); } } \ No newline at end of file From 55798447ab4bce81dd418711af7c9bf33f550a8f Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Mon, 7 Aug 2017 14:25:30 +1200 Subject: [PATCH 18/19] Cherry-picked CommandFactory connection between ControllerServer and RaceLogic. #story[1096] --- .../main/java/mock/app/ConnectionAcceptor.java | 16 +++++++++++----- racevisionGame/src/main/java/mock/app/Event.java | 2 ++ .../gameController/ControllerServer.java | 13 ++++++++----- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index 97a974a9..3584fbab 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -1,20 +1,17 @@ package mock.app; +import mock.model.RaceLogic; import network.Messages.Enums.XMLMessageType; import network.Messages.LatestMessages; import network.Messages.XMLMessage; -import org.mockito.Mock; import visualiser.gameController.ControllerServer; import java.io.DataOutputStream; import java.io.IOException; -import java.lang.reflect.Array; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ArrayBlockingQueue; /** @@ -43,6 +40,10 @@ public class ConnectionAcceptor implements Runnable { private short boatXMLSequenceNumber; //regatta xml sequence number private short regattaXMLSequenceNumber; + //controller server + private ControllerServer controllerServer; + // + private RaceLogic rl = null; /** * Connection Acceptor Constructor @@ -65,6 +66,11 @@ public class ConnectionAcceptor implements Runnable { return serverPort; } + + public void setRace(RaceLogic rl){ + this.rl = rl; + } + /** * Run the Acceptor */ @@ -76,7 +82,7 @@ public class ConnectionAcceptor implements Runnable { Socket mockSocket = serverSocket.accept(); DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser); - ControllerServer controllerServer = new ControllerServer(mockSocket); + this.controllerServer = new ControllerServer(mockSocket, rl); new Thread(mockOutput).start(); new Thread(controllerServer).start(); mockOutputList.add(mockOutput); diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index b4b0586c..617ad584 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -94,6 +94,8 @@ public class Event { //Create and start race. RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); + mockOutput.setRace(newRace); + new Thread(newRace).start(); } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index a2f8c80e..c95a0789 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -1,12 +1,9 @@ package visualiser.gameController; -import mock.model.commandFactory.Command; -import mock.model.commandFactory.CommandFactory; +import mock.model.RaceLogic; import network.BinaryMessageDecoder; import network.MessageDecoders.BoatActionDecoder; import network.Messages.Enums.BoatActionEnum; -import visualiser.gameController.Keys.ControlKey; -import visualiser.gameController.Keys.KeyFactory; import java.io.DataInputStream; import java.io.IOException; @@ -29,13 +26,19 @@ public class ControllerServer extends Observable implements Runnable { * Last received boat action */ private BoatActionEnum action; + /** + * + */ + private RaceLogic rc; /** * Initialise server-side controller with live client socket * @param socket to client */ - public ControllerServer(Socket socket) { + public ControllerServer(Socket socket, RaceLogic rc) { this.socket = socket; + this.rc = rc; + this.addObserver(rc); try { this.inputStream = new DataInputStream(this.socket.getInputStream()); } catch (IOException e) { From dad4fa57c69a9b7860cf9381b73ad235dbcb85a0 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Mon, 7 Aug 2017 14:51:32 +1200 Subject: [PATCH 19/19] Fixed WindCommand not updating downwind command when moving upwind. #story[1096] --- .../java/mock/model/commandFactory/WindCommand.java | 10 ++++++---- .../mock/model/commandFactory/WindCommandTest.java | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java index d20c0744..5ba5d5bf 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java @@ -12,12 +12,10 @@ public class WindCommand implements Command { private MockBoat boat; private int direction; - private double offset = 3.0; - public WindCommand(MockRace race, MockBoat boat, boolean upwind) { this.race = race; this.boat = boat; - this.direction = upwind? 1 : -1; + this.direction = upwind? -1 : 1; } @Override @@ -25,7 +23,11 @@ public class WindCommand implements Command { double wind = race.getWindDirection().degrees(); double heading = boat.getBearing().degrees(); - if(wind - heading < 0) offset *= -1 * direction; + double offset = 3.0; + + offset *= direction; + if(wind - heading < 0) offset *= -1; + boat.setBearing(Bearing.fromDegrees(heading + offset)); } } diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java index 4316d78d..e5d147d9 100644 --- a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -47,12 +47,12 @@ public class WindCommandTest { @Test public void upwindCommandDecreasesAngle() { upwind.execute(); - assertEquals(initial - boat.getBearing().degrees(), offset, 1e-5); + assertEquals(initial - boat.getBearing().degrees(), -offset, 1e-5); } @Test public void downwindCommandIncreasesAngle() { downwind.execute(); - assertEquals(boat.getBearing().degrees() - initial, offset, 1e-5); + assertEquals(initial - boat.getBearing().degrees(), offset, 1e-5); } } \ No newline at end of file