From fcea323cfd9ecb805f15833f6a2385e1a745be47 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Fri, 28 Jul 2017 09:30:26 +1200 Subject: [PATCH 01/53] The racevisionGame pom still had "profiles", which aren't needed with a single jar build, and the built jar didn't have a mainifest/main class. --- racevisionGame/pom.xml | 117 ++++++++++++----------------------------- 1 file changed, 34 insertions(+), 83 deletions(-) diff --git a/racevisionGame/pom.xml b/racevisionGame/pom.xml index f77e5ea3..b0f6d203 100644 --- a/racevisionGame/pom.xml +++ b/racevisionGame/pom.xml @@ -83,89 +83,40 @@ - - - - mock - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - - org.apache.maven.plugins - maven-shade-plugin - 2.4.3 - - - - - visualiser.app.App - ${maven.compiler.source} - ${maven.compiler.target} - - - - - - - package - - shade - - - - - - - - - - - visualiser - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - - org.apache.maven.plugins - maven-shade-plugin - 2.4.3 - - - - - visualiser.app.App - ${maven.compiler.source} - ${maven.compiler.target} - - - - - - - package - - shade - - - - - - - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.3 + + + + + visualiser.app.App + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + + package + + shade + + + + + + From f2932e8bae600dd4fa07acbbcba9eb242b593238 Mon Sep 17 00:00:00 2001 From: David Wu Date: Tue, 1 Aug 2017 12:29:05 +1200 Subject: [PATCH 02/53] New branch created for development. --- racevisionGame/src/main/java/visualiser/app/App.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/app/App.java b/racevisionGame/src/main/java/visualiser/app/App.java index 6896556d..8c75ee97 100644 --- a/racevisionGame/src/main/java/visualiser/app/App.java +++ b/racevisionGame/src/main/java/visualiser/app/App.java @@ -25,8 +25,8 @@ public class App extends Application { /** * Method that displays the visualiser, starting with the title screen. - * @param stage the stage to be displayed - * @throws Exception if something wrong with title screen + * @param stage the stage to be displayed. + * @throws Exception if something wrong with title screen. */ public void start(Stage stage) throws Exception { stage.setOnCloseRequest(new EventHandler() { From 5af3053537662af87e9201d2670129f5560fc646 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Tue, 1 Aug 2017 18:15:35 +1200 Subject: [PATCH 03/53] Added shared.model.Wind class. shared.model.Race now uses Wind. It is wrapped in Property<>. #story[1093] --- .../src/main/java/shared/model/Race.java | 32 ++++++------ .../src/main/java/shared/model/Wind.java | 51 +++++++++++++++++++ 2 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 racevisionGame/src/main/java/shared/model/Wind.java diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index 415e9f77..2af7f464 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -1,7 +1,9 @@ package shared.model; import javafx.beans.property.IntegerProperty; +import javafx.beans.property.Property; import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import network.Messages.LatestMessages; @@ -98,15 +100,9 @@ public abstract class Race implements Runnable { /** - * The current wind direction bearing. + * The race's wind. */ - protected Bearing windDirection; - - /** - * Wind speed (knots). - * Convert this to millimeters per second before passing to RaceStatus. - */ - protected double windSpeed; + protected Property raceWind = new SimpleObjectProperty<>(); /** @@ -169,11 +165,9 @@ public abstract class Race implements Runnable { //Race type. this.raceType = raceDataSource.getRaceType(); - //Wind speed. - this.windSpeed = 0; - //Wind direction. - this.windDirection = Bearing.fromDegrees(0); - + //Wind. + Wind wind = new Wind(Bearing.fromDegrees(0), 0); + this.raceWind.setValue(wind); } @@ -259,7 +253,7 @@ public abstract class Race implements Runnable { * @return The wind bearing. */ public Bearing getWindDirection() { - return windDirection; + return raceWind.getValue().getWindDirection(); } /** @@ -268,7 +262,15 @@ public abstract class Race implements Runnable { * @return The wind speed. */ public double getWindSpeed() { - return windSpeed; + return raceWind.getValue().getWindSpeed(); + } + + /** + * Returns the race's wind. + * @return The race's wind. + */ + public Property windProperty() { + return raceWind; } /** diff --git a/racevisionGame/src/main/java/shared/model/Wind.java b/racevisionGame/src/main/java/shared/model/Wind.java new file mode 100644 index 00000000..08d391c2 --- /dev/null +++ b/racevisionGame/src/main/java/shared/model/Wind.java @@ -0,0 +1,51 @@ +package shared.model; + + + +/** + * This class encapsulates the wind during a race. + * It has speed and a bearing. + * This is intended to be immutable. + */ +public class Wind { + + /** + * The current wind direction bearing. + */ + private Bearing windDirection; + + /** + * Wind speed (knots). + * Convert this to millimeters per second before passing to RaceStatus. + */ + private double windSpeed; + + + /** + * Constructs a new wind object, with a given direction and speed, in knots. + * @param windDirection The direction of the wind. + * @param windSpeed The speed of the wind, in knots. + */ + public Wind(Bearing windDirection, double windSpeed) { + this.windDirection = windDirection; + this.windSpeed = windSpeed; + } + + /** + * Returns the race wind's bearing. + * @return The race wind's bearing. + */ + public Bearing getWindDirection() { + return windDirection; + } + + + /** + * Returns the race wind's speed, in knots. + * @return The race wind's speed, in knots. + */ + public double getWindSpeed() { + return windSpeed; + } + +} From 99f3310d8b2d127b219525874cb329a88c59aa3c Mon Sep 17 00:00:00 2001 From: fjc40 Date: Tue, 1 Aug 2017 18:45:17 +1200 Subject: [PATCH 04/53] Created a setWind(bearing, knots) in Race, which updates the Wind with new values. MockRace now uses Wind. VisualiserRace now uses Wind. #story[1093] --- .../src/main/java/mock/model/MockRace.java | 41 ++++++++++++------- .../src/main/java/shared/model/Race.java | 15 ++++++- .../java/visualiser/model/VisualiserRace.java | 9 ++-- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index ad259bdc..64d0f46e 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -99,10 +99,8 @@ public class MockRace extends Race { this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary); - - this.windSpeed = 12; - this.windDirection = Bearing.fromDegrees(180); - + //Wind. + this.setWind(Bearing.fromDegrees(180), 12); } @@ -288,8 +286,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(this.getWindDirection().degrees()); + int windSpeedInt = (int) (this.getWindSpeed() * Constants.KnotsToMMPerSecond); //Create race status object, and send it. RaceStatus raceStatus = new RaceStatus( @@ -576,7 +574,12 @@ 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( + this.getWindDirection(), + this.getWindSpeed(), + boat.calculateBearingToNextMarker(), + lowerAcceptableBound, + upperAcceptableBound); return bestVMG; @@ -884,7 +887,9 @@ public class MockRace extends Race { */ protected void initialiseWindDirection() { //Set the starting bearing. - this.windDirection = Bearing.fromDegrees(MockRace.windBaselineBearing.degrees()); + this.setWind( + Bearing.fromDegrees(MockRace.windBaselineBearing.degrees()), + this.getWindSpeed() ); } @@ -892,28 +897,34 @@ public class MockRace extends Race { * Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound]. */ protected void changeWindDirection() { - + //TODO this wind generation could probably be moved to its own object? //Randomly add or remove 0.5 degrees. int r = new Random().nextInt(changeWind) + 1; + double newWindBearingDegrees = this.getWindDirection().degrees(); + if (r == 1) { //Add 0.5 degrees to the wind bearing. - this.windDirection.setDegrees(this.windDirection.degrees() + 0.5); + newWindBearingDegrees += 0.5; } else if (r == 2) { //Minus 0.5 degrees from the wind bearing. - this.windDirection.setDegrees(this.windDirection.degrees() - 0.5); + newWindBearingDegrees -= 0.5; } //Ensure that the wind is in the correct bounds. - if (this.windDirection.degrees() > MockRace.windUpperBound.degrees()) { - this.windDirection.setBearing(MockRace.windUpperBound); + if (newWindBearingDegrees > MockRace.windUpperBound.degrees()) { + newWindBearingDegrees = MockRace.windUpperBound.degrees(); - } else if (this.windDirection.degrees() < MockRace.windLowerBound.degrees()) { - this.windDirection.setBearing(MockRace.windLowerBound); + } else if (newWindBearingDegrees < MockRace.windLowerBound.degrees()) { + newWindBearingDegrees = MockRace.windLowerBound.degrees(); } + + this.setWind( + Bearing.fromDegrees(newWindBearingDegrees), + this.getWindSpeed() ); } diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index 2af7f464..6240539f 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -166,8 +166,7 @@ public abstract class Race implements Runnable { this.raceType = raceDataSource.getRaceType(); //Wind. - Wind wind = new Wind(Bearing.fromDegrees(0), 0); - this.raceWind.setValue(wind); + this.setWind(Bearing.fromDegrees(0), 0); } @@ -248,6 +247,18 @@ public abstract class Race implements Runnable { return regattaName; } + + /** + * Updates the race to have a specified wind bearing and speed. + * @param windBearing New wind bearing. + * @param windSpeedKnots New wind speed, in knots. + */ + protected void setWind(Bearing windBearing, double windSpeedKnots) { + Wind wind = new Wind(windBearing, windSpeedKnots); + this.raceWind.setValue(wind); + } + + /** * Returns the wind bearing. * @return The wind bearing. diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index 3a76631d..976a4b6e 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -311,11 +311,10 @@ public class VisualiserRace extends Race { //Race status enum. this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus()); - //Wind bearing. - this.windDirection.setDegrees(raceStatus.getScaledWindDirection()); - - //Wind speed. - this.windSpeed = raceStatus.getWindSpeedKnots(); + //Wind. + this.setWind( + Bearing.fromDegrees(raceStatus.getScaledWindDirection()), + raceStatus.getWindSpeedKnots() ); //Current race time. this.raceClock.setUTCTime(raceStatus.getCurrentTime()); From a87879eaa9eb775093c8178c7477dd0a06227660 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Wed, 2 Aug 2017 12:41:47 +1200 Subject: [PATCH 05/53] 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 06/53] 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 07/53] 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 08/53] 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 ed12ff659d336e03eba8abc6116e4cbf217f6dee Mon Sep 17 00:00:00 2001 From: fjc40 Date: Wed, 2 Aug 2017 13:59:19 +1200 Subject: [PATCH 09/53] mock: Moved wind generation logic and state into the WindGenerator class. MockRace initialises this, and calls it to update wind. --- .../src/main/java/mock/model/MockRace.java | 63 ++--- .../main/java/mock/model/WindGenerator.java | 249 ++++++++++++++++++ .../src/main/java/shared/model/Race.java | 8 + 3 files changed, 272 insertions(+), 48 deletions(-) create mode 100644 racevisionGame/src/main/java/mock/model/WindGenerator.java diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 64d0f46e..33884e3d 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -55,26 +55,10 @@ public class MockRace extends Race { 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. + * Object used to generate changes in wind speed/direction. */ - private static final Bearing windUpperBound = Bearing.fromDegrees(235); + private WindGenerator windGenerator; @@ -99,8 +83,17 @@ 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), + Bearing.fromDegrees(215), + Bearing.fromDegrees(235), + 12d, + 8d, + 16d ); + //Wind. - this.setWind(Bearing.fromDegrees(180), 12); + this.setWind(windGenerator.generateBaselineWind()); } @@ -887,9 +880,7 @@ public class MockRace extends Race { */ protected void initialiseWindDirection() { //Set the starting bearing. - this.setWind( - Bearing.fromDegrees(MockRace.windBaselineBearing.degrees()), - this.getWindSpeed() ); + this.setWind(windGenerator.generateBaselineWind()); } @@ -897,34 +888,10 @@ public class MockRace extends Race { * Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound]. */ protected void changeWindDirection() { - //TODO this wind generation could probably be moved to its own object? - //Randomly add or remove 0.5 degrees. - int r = new Random().nextInt(changeWind) + 1; - - double newWindBearingDegrees = this.getWindDirection().degrees(); - - if (r == 1) { - //Add 0.5 degrees to the wind bearing. - newWindBearingDegrees += 0.5; - } else if (r == 2) { - //Minus 0.5 degrees from the wind bearing. - newWindBearingDegrees -= 0.5; - - } - - //Ensure that the wind is in the correct bounds. - if (newWindBearingDegrees > MockRace.windUpperBound.degrees()) { - newWindBearingDegrees = MockRace.windUpperBound.degrees(); - - } else if (newWindBearingDegrees < MockRace.windLowerBound.degrees()) { - newWindBearingDegrees = MockRace.windLowerBound.degrees(); - - } + Wind nextWind = windGenerator.generateNextWind(raceWind.getValue()); - this.setWind( - Bearing.fromDegrees(newWindBearingDegrees), - this.getWindSpeed() ); + setWind(nextWind); } diff --git a/racevisionGame/src/main/java/mock/model/WindGenerator.java b/racevisionGame/src/main/java/mock/model/WindGenerator.java new file mode 100644 index 00000000..30fd24b4 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/WindGenerator.java @@ -0,0 +1,249 @@ +package mock.model; + + +import shared.model.Bearing; +import shared.model.Wind; + +import java.util.Random; + +/** + * This class generates Wind objects for use in a MockRace. + * Bounds on bearing and speed can be specified. + * Wind can be completely random, or random incremental change. + */ +public class WindGenerator { + + /** + * The bearing the wind direction starts at. + */ + private Bearing windBaselineBearing; + + /** + * The lower bearing angle that the wind may have. + */ + private Bearing windBearingLowerBound; + + /** + * The upper bearing angle that the wind may have. + */ + private Bearing windBearingUpperBound; + + + + /** + * The speed the wind starts at, in knots. + */ + private double windBaselineSpeed; + + /** + * The lower speed that the wind may have, in knots. + */ + private double windSpeedLowerBound; + + /** + * The upper speed that the wind may have, in knots. + */ + private double windSpeedUpperBound; + + + /** + * Creates a wind generator, with a baseline, lower bound, and upper bound, for the wind speed and direction. + * @param windBaselineBearing Baseline wind direction. + * @param windBearingLowerBound Lower bound for wind direction. + * @param windBearingUpperBound Upper bound for wind direction. + * @param windBaselineSpeed Baseline wind speed, in knots. + * @param windSpeedLowerBound Lower bound for wind speed, in knots. + * @param windSpeedUpperBound Upper bound for wind speed, in knots. + */ + public WindGenerator(Bearing windBaselineBearing, Bearing windBearingLowerBound, Bearing windBearingUpperBound, double windBaselineSpeed, double windSpeedLowerBound, double windSpeedUpperBound) { + + this.windBaselineBearing = windBaselineBearing; + this.windBearingLowerBound = windBearingLowerBound; + this.windBearingUpperBound = windBearingUpperBound; + this.windBaselineSpeed = windBaselineSpeed; + this.windSpeedLowerBound = windSpeedLowerBound; + this.windSpeedUpperBound = windSpeedUpperBound; + + } + + + /** + * Generates a wind object using the baseline wind speed and bearing. + * @return Baseline wind object. + */ + public Wind generateBaselineWind() { + return new Wind(windBaselineBearing, windBaselineSpeed); + } + + /** + * Generates a random Wind object, that is within the provided bounds. + * @return Generated wind object. + */ + public Wind generateRandomWind() { + + double windSpeed = generateRandomWindSpeed(); + Bearing windBearing = generateRandomWindBearing(); + + return new Wind(windBearing, windSpeed); + + } + + /** + * Generates a random wind speed within the specified bounds. In knots. + * @return Wind speed, in knots. + */ + private double generateRandomWindSpeed() { + + double randomSpeedKnots = generateRandomValueInBounds(windSpeedLowerBound, windSpeedUpperBound); + + return randomSpeedKnots; + } + + + /** + * Generates a random wind bearing within the specified bounds. + * @return Wind bearing. + */ + private Bearing generateRandomWindBearing() { + + double randomBearingDegrees = generateRandomValueInBounds(windBearingLowerBound.degrees(), windBearingUpperBound.degrees()); + + return Bearing.fromDegrees(randomBearingDegrees); + } + + + /** + * Generates a random value within a specified interval. + * @param lowerBound The lower bound of the interval. + * @param upperBound The upper bound of the interval. + * @return A random value within the interval. + */ + private static double generateRandomValueInBounds(double lowerBound, double upperBound) { + + float proportion = new Random().nextFloat(); + + double delta = upperBound - lowerBound; + + double amount = delta * proportion; + + double finalAmount = amount + lowerBound; + + return finalAmount; + + } + + + /** + * Generates a new value within an interval, given a start value, chance to change, and change amount. + * @param lowerBound Lower bound of interval. + * @param upperBound Upper bound of interval. + * @param currentValue The current value to change. + * @param changeAmount The amount to change by. + * @param chanceToChange The change to actually change the value. + * @return The new value. + */ + private static double generateNextValueInBounds(double lowerBound, double upperBound, double currentValue, double changeAmount, double chanceToChange) { + + float chance = new Random().nextFloat(); + + + if (chance <= chanceToChange) { + currentValue += changeAmount; + + } else if (chance <= (2 * chanceToChange)) { + currentValue -= changeAmount; + + } + + currentValue = clamp(lowerBound, upperBound, currentValue); + + return currentValue; + + } + + + /** + * Generates the next Wind object, that is within the provided bounds. This randomly increases or decreases the wind's speed and bearing. + * @param currentWind The current wind to change. This is not modified. + * @return Generated wind object. + */ + public Wind generateNextWind(Wind currentWind) { + + double windSpeed = generateNextWindSpeed(currentWind.getWindSpeed()); + Bearing windBearing = generateNextWindBearing(currentWind.getWindDirection()); + + return new Wind(windBearing, windSpeed); + + } + + + /** + * Generates the next wind speed to use. + * @param windSpeed Current wind speed, in knots. + * @return Next wind speed, in knots. + */ + private double generateNextWindSpeed(double windSpeed) { + + double chanceToChange = 0.2; + double changeAmount = 0.1; + + double nextWindSpeed = generateNextValueInBounds( + windSpeedLowerBound, + windSpeedUpperBound, + windSpeed, + changeAmount, + chanceToChange); + + return nextWindSpeed; + } + + + /** + * Generates the next wind speed to use. + * @param windBearing Current wind bearing. + * @return Next wind speed. + */ + private Bearing generateNextWindBearing(Bearing windBearing) { + + double chanceToChange = 0.2; + double changeAmount = 0.5; + + double nextWindBearingDegrees = generateNextValueInBounds( + windBearingLowerBound.degrees(), + windBearingUpperBound.degrees(), + windBearing.degrees(), + changeAmount, + chanceToChange); + + return Bearing.fromDegrees(nextWindBearingDegrees); + } + + + + + + /** + * Clamps a value to be within an interval. + * @param lower Lower bound of the interval. + * @param upper Upper bound of the interval. + * @param value Value to clamp. + * @return The clamped value. + */ + private static double clamp(double lower, double upper, double value) { + + if (value > upper) { + value = upper; + + } else if (value < lower) { + value = lower; + + } + + return value; + } + + + + + +} diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index 6240539f..aec57882 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -255,6 +255,14 @@ public abstract class Race implements Runnable { */ protected void setWind(Bearing windBearing, double windSpeedKnots) { Wind wind = new Wind(windBearing, windSpeedKnots); + setWind(wind); + } + + /** + * Updates the race to have a specified wind (bearing and speed). + * @param wind New wind. + */ + protected void setWind(Wind wind) { this.raceWind.setValue(wind); } From fba256113a7acccba09bc808d50a49a89729b068 Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Wed, 2 Aug 2017 15:09:59 +1200 Subject: [PATCH 10/53] 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 11/53] 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 9889a474ee59159a2e550f02fce3daab08022b82 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Wed, 2 Aug 2017 18:24:43 +1200 Subject: [PATCH 12/53] Added ArrowController. This is the controller for arrow.fxml. Removed arrow control stuff from ResizableRaceCanvas. Added a wind speed label to arrow.fxml. Also created an outer GridPane to lay things out. #story[1093] --- .../Controllers/ArrowController.java | 152 ++++++++++++++++++ .../Controllers/RaceController.java | 34 ++-- .../visualiser/model/ResizableRaceCanvas.java | 38 +---- .../resources/visualiser/scenes/arrow.fxml | 70 +++++--- .../resources/visualiser/scenes/race.fxml | 7 +- 5 files changed, 228 insertions(+), 73 deletions(-) create mode 100644 racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java new file mode 100644 index 00000000..8ef783a8 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java @@ -0,0 +1,152 @@ +package visualiser.Controllers; + + +import javafx.application.Platform; +import javafx.beans.property.Property; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Circle; +import shared.model.Bearing; +import shared.model.Wind; +import visualiser.model.VisualiserRace; + +/** + * Controller for the arrow.fxml view. + */ +public class ArrowController { + + + @FXML + private Pane compass; + + @FXML + private StackPane arrowStackPane; + + @FXML + private ImageView arrowImage; + + @FXML + private Circle circle; + + @FXML + private Label northLabel; + + @FXML + private Label windLabel; + + @FXML + private Label speedLabel; + + + /** + * This is the property our arrow control binds to. + */ + private Property wind; + + + /** + * Constructor. + */ + public ArrowController() { + } + + + /** + * Sets which wind property the arrow control should bind to. + * @param wind The wind property to bind to. + */ + public void setWindProperty(Property wind) { + this.wind = wind; + + wind.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + Platform.runLater(() -> updateWind(newValue)); + } + }); + } + + + /** + * Updates the control to use the new wind value. + * This updates the arrow direction (due to bearing), arrow length (due to speed), and label (due to speed). + * @param wind The wind value to use. + */ + private void updateWind(Wind wind) { + updateWindBearing(wind.getWindDirection()); + updateWindSpeed(wind.getWindSpeed()); + } + + + /** + * Updates the control to account for the new wind speed. + * This changes the length (height) of the wind arrow, and updates the speed label. + * @param speedKnots The new wind speed, in knots. + */ + private void updateWindSpeed(double speedKnots) { + updateWindArrowLength(speedKnots); + updateWindSpeedLabel(speedKnots); + } + + /** + * Updates the length of the wind arrow according to the specified wind speed. + * @param speedKnots Wind speed, in knots. + */ + private void updateWindArrowLength(double speedKnots) { + + //At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height. + double minKnots = 2; + double maxKnots = 30; + double deltaKnots = maxKnots - minKnots; + + double minHeight = 25; + double maxHeight = 75; + double deltaHeight = maxHeight - minHeight; + + //Clamp speed. + if (speedKnots > maxKnots) { + speedKnots = maxKnots; + } else if (speedKnots < minKnots) { + speedKnots = minKnots; + } + + //How far between the knots bounds is the current speed? + double currentDeltaKnots = speedKnots - minKnots; + double currentKnotsScalar = currentDeltaKnots / deltaKnots; + + //Thus, how far between the pixel height bounds should the arrow height be? + double newHeight = minHeight + (currentKnotsScalar * deltaHeight); + + arrowImage.setFitHeight(newHeight); + } + + /** + * Updates the wind speed label according to the specified wind speed. + * @param speedKnots Wind speed, in knots. + */ + private void updateWindSpeedLabel(double speedKnots) { + speedLabel.setText(String.format("%.1fkn", speedKnots)); + } + + + /** + * Updates the control to account for a new wind bearing. + * This rotates the arrow according to the bearing. + * @param bearing The bearing to use to rotate arrow. + */ + private void updateWindBearing(Bearing bearing) { + + //We need to display wind-from, so add 180 degrees. + Bearing fromBearing = Bearing.fromDegrees(bearing.degrees() + 180d); + + //Rotate the wind arrow. + arrowStackPane.setRotate(fromBearing.degrees()); + } + + + + +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index 5777c06e..7e4cb945 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -5,7 +5,6 @@ import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.chart.LineChart; import javafx.scene.control.*; @@ -24,10 +23,8 @@ import visualiser.gameController.Keys.ControlKey; import visualiser.gameController.Keys.KeyFactory; import visualiser.model.*; -import java.awt.*; import java.io.IOException; import java.net.URL; -import java.text.DecimalFormat; import java.util.ResourceBundle; /** @@ -60,6 +57,11 @@ public class RaceController extends Controller { */ private Sparkline sparkline; + /** + * The arrow controller. + */ + @FXML private ArrowController arrowController; + /** * Service for sending keystrokes to server */ @@ -67,8 +69,18 @@ public class RaceController extends Controller { @FXML private GridPane canvasBase; - @FXML private Pane arrow; + + @FXML private SplitPane race; + + /** + * This is the root node of the arrow control. + */ + @FXML private Pane arrow; + + /** + * This is the pane we place the actual arrow control inside of. + */ @FXML private StackPane arrowPane; @FXML private Label timer; @FXML private Label FPS; @@ -118,15 +130,15 @@ public class RaceController extends Controller { //Fps display. initialiseFps(this.visualiserRace); - //Need to add the included arrow pane to the arrowPane container. - initialiseArrow(); - //Information table. initialiseInfoTable(this.visualiserRace); //Sparkline. initialiseSparkline(this.visualiserRace); + //Arrow. + initialiseArrow(this.visualiserRace); + //Race canvas. initialiseRaceCanvas(this.visualiserRace); @@ -294,7 +306,7 @@ public class RaceController extends Controller { private void initialiseRaceCanvas(VisualiserRace race) { //Create canvas. - raceCanvas = new ResizableRaceCanvas(race, arrow.getChildren().get(0)); + raceCanvas = new ResizableRaceCanvas(race); //Set properties. raceCanvas.setMouseTransparent(true); @@ -367,10 +379,10 @@ public class RaceController extends Controller { /** - * Adds the included arrow pane (see arrow.fxml) to the arrowPane (see race.fxml). + * Initialises the arrow controller with data from the race to observe. */ - private void initialiseArrow() { - arrowPane.getChildren().add(arrow); + private void initialiseArrow(VisualiserRace race) { + arrowController.setWindProperty(race.windProperty()); } diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 7664f854..adbd4840 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -54,22 +54,15 @@ public class ResizableRaceCanvas extends ResizableCanvas { private boolean annoTimeSinceLastMark = true; - /** - * The wind arrow node. - */ - private Node arrow; - /** * Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRace}. * @param visualiserRace The race that data is read from in order to be drawn. - * @param arrow The wind arrow's node. */ - public ResizableRaceCanvas(VisualiserRace visualiserRace, Node arrow) { + public ResizableRaceCanvas(VisualiserRace visualiserRace) { super(); this.visualiserRace = visualiserRace; - this.arrow = arrow; RaceDataSource raceData = visualiserRace.getRaceDataSource(); @@ -375,32 +368,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { - /** - * Displays an arrow representing wind direction on the Canvas. - * This function accepts a wind-to bearing, but displays a wind-from bearing. - * - * @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up). - * @see GraphCoordinate - */ - private void displayWindArrow(double angle) { - - //We need to display wind-from, so add 180 degrees. - angle += 180d; - - //Get it within [0, 360). - while (angle >= 360d) { - angle -= 360d; - } - - //Rotate the wind arrow. - if (arrow != null && arrow.getRotate() != angle) { - arrow.setRotate(angle); - } - } - - - - /** * Draws all of the {@link Mark}s on the canvas. */ @@ -511,9 +478,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { //Marks. drawMarks(); - //Wind arrow. This rotates the wind arrow node. - displayWindArrow(this.visualiserRace.getWindDirection().degrees()); - } diff --git a/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml b/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml index 6e8a88b5..4057753d 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml @@ -1,34 +1,58 @@ - - - - - - - + + + + + + + + + + + - + + + + + + + + + - + - - - - - + + + + + + + + + + + + - - - - + - + diff --git a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml index 76da5379..159d725c 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml @@ -16,7 +16,6 @@ - @@ -76,7 +75,11 @@ - + + + + + From e1905e9e36154f8bdd0cf37942aefe0eb2e4bacc Mon Sep 17 00:00:00 2001 From: fjc40 Date: Wed, 2 Aug 2017 18:27:40 +1200 Subject: [PATCH 13/53] javadoc fix. --- .../src/main/java/visualiser/Controllers/ArrowController.java | 2 +- .../src/main/java/visualiser/Controllers/RaceController.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java index 8ef783a8..3e81cd16 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java @@ -141,7 +141,7 @@ public class ArrowController { //We need to display wind-from, so add 180 degrees. Bearing fromBearing = Bearing.fromDegrees(bearing.degrees() + 180d); - + //Rotate the wind arrow. arrowStackPane.setRotate(fromBearing.degrees()); } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index 7e4cb945..f34c57a8 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -380,6 +380,7 @@ public class RaceController extends Controller { /** * Initialises the arrow controller with data from the race to observe. + * @param race The race to observe. */ private void initialiseArrow(VisualiserRace race) { arrowController.setWindProperty(race.windProperty()); From 7fc1347377dee56a665411cf94625cc38dac7d48 Mon Sep 17 00:00:00 2001 From: zwu18 Date: Wed, 2 Aug 2017 22:28:47 +1200 Subject: [PATCH 14/53] 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 15/53] 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 16/53] 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 17/53] 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 18/53] 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 e4999a3c937d4765362317711e9cc7795f571b9c Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 3 Aug 2017 12:56:15 +1200 Subject: [PATCH 19/53] Added a light variant of the arrow image. nightMode.css loads this instead of the regular dark arrow. ResizableRaceCanvas doesn't cache the race boundary background, as it was unneccessary and stopped the canvas from being transparent. #story[1093] --- .../visualiser/model/ResizableRaceCanvas.java | 29 ++---------------- .../src/main/resources/css/dayMode.css | 4 ++- .../src/main/resources/css/nightMode.css | 7 ++++- .../visualiser/images/arrowLight.png | Bin 0 -> 23441 bytes 4 files changed, 11 insertions(+), 29 deletions(-) create mode 100644 racevisionGame/src/main/resources/visualiser/images/arrowLight.png diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index adbd4840..e9b4c31c 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -12,7 +12,6 @@ import shared.model.GPSCoordinate; import shared.model.Mark; import shared.model.RaceClock; -import java.time.Duration; import java.util.List; /** @@ -39,12 +38,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { */ private VisualiserRace visualiserRace; - /** - * The background of the race. - * We render the background whenever the race boundary changes, or the screen size changes. - */ - private Image background; - private boolean annoName = true; private boolean annoAbbrev = true; @@ -407,13 +400,9 @@ public class ResizableRaceCanvas extends ResizableCanvas { this.map.setWidth((int) getWidth()); this.map.setHeight((int) getHeight()); - //Redraw the boundary. - redrawBoundaryImage(); - //Draw the race. drawRace(); - } @@ -426,15 +415,13 @@ public class ResizableRaceCanvas extends ResizableCanvas { /** - * Draws the race boundary, and saves the image to {@link #background}. - * You should call {@link #clear()} before calling this. + * Draws the race boundary. */ - private void redrawBoundaryImage() { + private void drawBoundary() { //Prepare to draw. gc.setLineWidth(1); gc.setFill(Color.AQUA); - gc.drawImage(new Image(getClass().getClassLoader().getResourceAsStream("images/WaterBackground.png")), 0, 0); //Calculate the screen coordinates of the boundary. @@ -454,9 +441,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { //Draw the boundary. gc.fillPolygon(xpoints, ypoints, xpoints.length); - //Render boundary to image. - this.background = snapshot(null, null); - } /** @@ -481,15 +465,6 @@ public class ResizableRaceCanvas extends ResizableCanvas { } - /** - * Draws the race boundary image onto the canvas. - * See {@link #background}. - */ - private void drawBoundary() { - gc.drawImage(this.background, 0, 0); - } - - diff --git a/racevisionGame/src/main/resources/css/dayMode.css b/racevisionGame/src/main/resources/css/dayMode.css index b15f242b..d0f62fb7 100644 --- a/racevisionGame/src/main/resources/css/dayMode.css +++ b/racevisionGame/src/main/resources/css/dayMode.css @@ -50,4 +50,6 @@ .scroll-bar > .increment-button:pressed > .increment-arrow, .scroll-bar > .decrement-button:pressed > .decrement-arrow { -fx-background-color: -fx-mark-highlight-color, rgb(255, 255, 255); -} \ No newline at end of file +} + + diff --git a/racevisionGame/src/main/resources/css/nightMode.css b/racevisionGame/src/main/resources/css/nightMode.css index 406cc60b..7fe6a67b 100644 --- a/racevisionGame/src/main/resources/css/nightMode.css +++ b/racevisionGame/src/main/resources/css/nightMode.css @@ -51,4 +51,9 @@ .scroll-bar > .increment-button:pressed > .increment-arrow, .scroll-bar > .decrement-button:pressed > .decrement-arrow { -fx-background-color: -fx-mark-highlight-color, rgb(255, 255, 255); -} \ No newline at end of file +} + + +#arrowImage { + -fx-image: url("/visualiser/images/arrowLight.png"); +} diff --git a/racevisionGame/src/main/resources/visualiser/images/arrowLight.png b/racevisionGame/src/main/resources/visualiser/images/arrowLight.png new file mode 100644 index 0000000000000000000000000000000000000000..7a80459d956a2dcb4a2ab5aa4d5704f7f8a23f8f GIT binary patch literal 23441 zcmeHvc~n!^7w!oJG=hq$8nl+df@sAA=b?xJrBzXjLkpEDsA%f|h$y3QMZ{KYQB=TM z0o#iFv>KcWD8m)1D4<|bQJG>9fl@Rm0!o0l&p9_q-~aEgx58RoUAgz%eb3qF+u#27 zzNh`(b;ajHhm0M95E{B<@uF1-8A|a#gAL(|rSXqv;9vYrj>{bpx|m?3@-=|pg22UJ z??7l+5&nlaYT{A`E{+Lyb`M@1urWBqH^>hK`hM#lEcW*cwwNKd6HlEz-9K$TLLS4G zEOJ~E>iut>$Gz#*J1^X?8@p@u{OuvXS-Is}#AkeCmtKsPIn6;Ix?cB|ls$sAKohs$AO*?z5=$#if#Rq+c z&+cz;=-;MlJ(G5;u>Y;RDq3|(74mM!>*f{4pOJA)*zK(G3*+C0&;O`-+F`)V)Zq)F@g}eG^Pl&T51K9N&1|RnQGi^ZCY;2vn`Fh<8yTLqi z=cUcw{jKj-XP8Km28PF3(9tD+kL~6!P&G-S?#U$Ja~r<64gwyP5km zZZRRF746g9b9R3_bxU@gh1x*2U%K*ubF=wOL4|EzTJ_abbgtIVwOqfEeAU9mM;x?+ss_c-iF_OHkrG$+z?%_6Ypgb z|ESrfUSErI(Lm=KRqMHi%fsok!Vf8;jRt0L3&{#ze?Q;6!FSR;@)p~NbACKm{H!u@ z!9Z5Ss~tT<=rc(Z_C%Y;#YPpdtAfcm_mtSa){EVZz5nXdC7d>G?+YE?D`Q zxuzxCcjkJoEov^l*Suqx&mLllJ!k!Rc=4gYijUOpktDe;nBNsuo3@+2OF2Z)neAG! zUVXnJN!l=PenMcrw`NhP*1l2MHP)4Hq&4lc_H&N6UmK(9>}fsMe35gP*(A&U(a*!g z99}|?-mgz^yEO76GLM(rU_YM)elsccOwFtAxse8RueuYJ4#o@h>?IpGx7IF+>Fs>n zJJr04oByUCx}wSttLM71an6MmXI8tsEWgzeTCU#DP&vQrZTQK`d&b*lolpZ$`IVrp zYW=Gc)e{aE0W+WWUbIr%?LOF;bInsc<^1%B<~MWq%cW$RiY>yw_WLe&-Q=itt9-FE z%`9R;QW*!Qo~yBMJfxeRsPFq&{hS?jFVRb0tpO?jO`U&o;EF;20oOlEC#IP#*!|F) z+xN^#dsL%KTaR$Qe0#7fYK@F@!A_Xjym7;(9IiUw7nP=lM5n|t5`eXRNEY=d^U^x@ zi-dghx7@x-3qOlk@CgSErXSllxnjz`)%X76r}u}I`QGvm*hyQpF)Vv0%mY*nkMcFA z0aZ5pY3~hl&S`AW57bRz#dqID-cvg~$!1RkOvb-QpIeXn&#~=*^1ZJ(Ffe|LD+~5* z3pL{IrxfzrA6TG6c zn(tr&7&a0-6}TYj7a~lQ;Q)tW`xm-YE0^^4IjA==^knQmzNW(8pZVK555n83@Dpa@ zHJaVzHZe8eO84FpwX>SXK;+Cjx%QX1L37O#)YxPjtryFy+hV^B&AGerNHL` zw|Q){)r^Li>E+*Z_ai->cC_8KGp}x&saaGZ{{_IR()jY_I{zJ}F$Av4U4pWz#xwGi zPu0^o!Ef?#kF)07spthzxSP4$C)Cq(ZTmR4{l%xO{oKF%53*D%^5ev`!hpyHfvH(vN*#%c{&*%wb@Xnp(aFy;RV6#n(M{)41{FSeG zwC7Z6+uNViaWa2NPhI;i0qgo`*Y&#`YAW~g8(KT6+$uSDX9WxLvlT`vF8tU;;25^r z{lGBn)T~7usPfr>f4h6tQSF`>TL+*D-6Q7$Q06EM##0OZkn??#>`J$OJ;xKw+z%%?*{^)Pdn>T^+M%GT|YHm7&9f%h6|NESG{%s6)%B{WcIRkdA;5gdd2R>tU6-0&taOtX}xdZ(a?u{eiRFFzv_2e}Zza{5Hh4NK09aWQp{n zI7`b>?xvhu3oM=mhyUBh_;EC#5D7A}1BckE0eV>W^xCAXU~LF8PlZcEu6xBys{Fov z*9ndgfXIWxRh^?c?@+*|r^ep*urPScw1qEnjI>$ zV=Dnzi}sA?JbEn&&4UCjUCu3U_t^G6<{xe^-s9B;&%4SWax_@$r!%RF-U zJ+@RY$jks$OfRPwLeF!ka$!Dwtkny7pXoILs*a%$KbkC+LctM?D&=C%Srw4vdG`Cc zx$fXlwOr4>Qkw@A7lq>iRWVlVYAsu_>#d|1RqSMrBs`vt>o>ng~ zy^90K#aTWZp*YRC_2u|e+W}SR#h(-pibkPnz^M^3f9}4+ggqB`e#V65ddt;`Chv=) zzx}gaWc-xVT3m&m;NQfQ#R|FUMEW@WJ!PVv?LI0Ql-quVxlZGPEAj~yqpQZVG5_JL zsL9U)P2>2B7-!C{JwNb^Ax-PI?4b^lP6uywa#=Cc9}v+Ry!nnvx_(LHR4(zPpXC>U zwsIX@c}$xI&n__~&`RY%@DQ>xn#s-dOODxx>IMHN33iGt@mw4kA@u}v&l+KWOP#5Q zr3u1*f1XMDCp0|MXA7dw5AYrYYhe|BSdm@RF6>83Yy7i0Q7mf}zRv)?z*8NeE6Um+ zh|I1W#0HEZ8IoNE)Q%$KPqz?ipTSXkBvRdoDy-n-jQat8K15&_>M^95Qf4W9odML& zWOJeO!XKzz@i8~^+_99}OZ63Ze=J$gQ65`sReQd=jc<<}WEy$JhwWCNwgyv0SeI0)5T&Q7w=gsn z`znB_Iddy304m@Txm|D3FL82GEK1*@#Z`C`ngLX0f{xON zm6iyj2&~T1SOgw-$X4p3V-y^fNBC)vcBAwQ+TfeNxaA25b_e8IuL(Y2aeEScifrm7 z_p`AlJrq3Df17wo_C21NB7%C!J(1(7kB}l>>m^%9r+cu6^AHgX?wJV=N zd1{S5s=cYvpp4t%lreFZdkj&|bP7Ii3&)KZnoJ3Gmf~ySI)6t)6lzcJr&?NOyfsCN z``WVEewAK1h=uQXD0PFTYz7uj#$o{}$I?Bo^@u5(l82CdJ&~b-Uw~LZb|PBe)fRJ| zDg?+ zEtrHmR|Y60SECmc9zs_F3ir`;VFiAWPf-$O9!7%eZ|Gb(@zCfdP1(%Kty7GlbESuD zk5ac5Cb~`oVT`2a6cmdsa`y>QI=2N^tq)yUp@%yE4$tSRE!WjWx4~ByJTy;RGXsq; z!|}%yP((gBY!YNN9!UpC24u!!V5N^GI)Ev_hyb$Mhmf_Ftll40P6dfP7`YxK`A?cG zv?r^}yh|b_U4p$Wr94r&AFz3*>VMY5QTi24hmXBv9SA+r*kOn%)F^hwZ|UO{%z2E2 zR=e&QnDZ1hbhSx-R4aU|j}%5)#a?#k5X>gV5V5oAf^41iZsGKR@~8@CBdtAa_27O4 zy_1ZOEhM!_%AW0h| zH2N-SA1hu72NP(e(;{EGlRzttOpQ6qMIbbp=4lD*>=F}qkPOfu^LnZ-2GbA3G9;8U zS+dB?bvXVqsqk;9gSnJ7{RN*Rr96$N(%o`+< zI)&Ss@FeYDh00r}ZG&iHx$b$uOt0TCjd}eKo;k1KG3ESRDfCD^f8!x{Mnm@n6GY7f zZL%=x0r_$9UA!;0G|H6`Rr|t#@Si>$gP5ChHFZznZRaqURtgxiqn?={dQJ>+9PjEO zzLjk>4WZqHAen1HQ~kTwBNQH_t`eE>Aj|spUAd~kw| zF$|!mZ7kc0;Ny5o3~i`0O)26_I7TmA0XiF{3DT58PuYIo+s>mADxe#;Q6j1)E@Qe$ zbTMJ2``+%_4g{7!r}gg6zN>W}Jf!H;hzdLN+{~A|u6w$})nd&@3vY?&0UARvx6Vz1Ax|{rb*hc%KKRWNrm+7a7Wd4V zI|jJ(22XxX@S*6)C_94}Z%_4;{k`B0{84O|0X$=)Y00yFO-0Z)+0nrV6Pm1t z&=T^d>G$QPYa4J%VQF)nZC)-a$2GRwa)d%JY9xN7r#PwD?s<#=q03adnkN}Gk;KV* z5=LWtpN|i_?y85_pXu+Krtwc+9zn>?N7EGSDe$y_o}f4pu1085r%!TxBcV8yM8857 z*otn|+#G>W649^Bf}h0kI3Tiu33DmGsB+#;C=P~<-mIx0xAj137axFep=6v4!z-Wc zYxvQZOyq=6%cG5#?~s1DtD~5qxc!o_i0h5u zcb75NHj~|Gc@o^=0YZP50r+{mMrd?S6cu3j!?++i&Q%;v&Cmo;8Skm|7tKeAq!`BG zc}(@oF!-fzNif!BH~q6~nqpwmwVIZM%Tc2`>Z%@^R+1)&a%=WMC%$JMmKluoaq)KS zDz=7*_$+>vfj@TDmv~5qCOUT{#Y0mfmxT`<0pwdEd$6f!7Rd%|30bDy=T&}pAse8c zafDo6Y`B(r9%q9~@Qk@8f69ghIeI`g$b_qDSJVNTw@SQ`F1D6(n0$s;DTXlUAKc1O zjLI;2g0sQ7F$e{^X#zBLlIS7^KV)Mw(h+2AN)1SF0x`|>$@@Va*SuJ;L_ee|4hMB? z?v~)-&;+Qxlh_ky1AO(4IzX9-Qs*rs8=$--lA*g3jUKiC0*8k3QnBP<=Pyg1j5%HA zO#-y%VC%Z8I2#;*A>~Et0A&(x@7oNt`#VhGI0;aNe$Zi3d?Q&s+3L`QR-qpTo9$lm zqJXJh>0jY&a2lTZTJsr5IVE=vbih2*;p#rkhwB0>Q3hcoltk(XMsi&2LU!~L2`lB6 zD(7!;HaG^M=>D4~Ko1-J1sggMw!w&ehhnWnzfwEz*WnV})lIx8J01e2l6+KUehRYO z+&vzl^x5j@WYz|acnZnD1Z}w6Os*fr-*0cMWVuuc(Pert8Q+D zZ;Vezffr>Ou(@Y=egnc*JPQxJs0LeEVthFvB4}Jil+~ZpHF2lF$ z_D=v-%AsnoF=1slp@Q?2fw6i5Hrb;f-{T~$bL^jz=DEgS1lfW~AS{xS?78MC`)_NO7Ei&1}oHU|hbSycqJL^~*#* z;$Dv%7-wuWm1eD*cr2zipaCXX4K}9tw!wOmTF?tZLkfqCvVAzUus;(Lh`bkk(H)c9 zO_)~79yQq5lIKpCYQZz;GC`NpGIuelF4XLw=Os4Dj)zcZYYA-gmWxf=&0v~#0UEpo zl-=|NhQnu7vIeWOfzmJ6j#QkO{jvI1Bm}L@@iK*1C6(fCM$h@yM8fQq`dC*m8?RXq z&q1a|wvW}G%M^?D0^%IuVoMAb#;%@XKBc6}k1?U8pKYB+&5-Uw`cJLnvv5XO3Qts? zQcE^hXEDNVgDEl>)U{OQ^}HVZr4|YRYm8p$tBm9*0=NTsOBnCv5*1b--hJ@c5RG8V zzZ2hYXB_qnAU%gi=PWoR|%=s5(S74=W+1fK_n7Wtt9nyH`zA?sR>6#FKgmw!($bsKV`qvJEtas z{MLLEc{xS$6!LI$nnQS>hrZb(j;vv#5P?*Wp>FsPjLWFsGSuEOFT)IG_m z<}>qMuM3~s1uZXx`jq%-yL{8$_b}7+kE8qF z&|-{vnvuNDCmM!ymbh6U^k8Pbi5%!QVkWFJ{ljcY-)=aDBwb!rmL#$e0Rk>XBgCIvA<<@nm zRHG1DCb>mT?#CuAGLjn~+8MXc90OKwUaM=5#C%qm;(Z9?0A)pDqm0i9??kt%H?VFo ztUGH_Zq+-Ct0;-2G+VS0tS8-sel{wwmXz1DcfAUzfJ@~K?ZY{6r7=)UUcCg-v6Ys|H}C&n-vMgHsYebt5iGbInOh1=~HHew6r$3*`W z!pY6Bi?B|ap?}DXV(@9t?|JSX2Z555;lLYq*lNTbqb@i|8SoUI&Rg_yL!3D%&w#M| zsj23Gaqm2^6&eFIdE@@psd)V7r7j`f1p#T^FnJ}){xtapfY;u`yXeidsNU?uU?oT2 zClW|u+4Iw?uAu(niUkAqA7SGq?c%rH5f3{}@!i_z&V6o@_64fWsxKOGu&#&(AWNVB z*x8t{;KlHP?IAyzErc1n|CD!o;N{Kw#*PQO`)ABYUpA%f@p-@L*OJEnb(`)2v(hj8 zMN!PzsGPn=fD?d!$=^O(7-T($!N1^h@W5Jq{=_>7G9*;^M|-NrT1b~*Kmizk{j=~9 z;w_zqV3CM1v+loYIM_82V-Y|&u|qyn$|Pd20Y*OmKKeXQ@8|{q2>i`os@gKtB;5)l z7+$$j_ez~(b@0&ziH-_%9IMMDkP>xNprZmE73ioyM+Le9L05U{>PuY}t?L!&nlQRn zldhqrYa8nNvFfgKe{@91p)C)08u~u?HLR+0nZ4PfLG$90>f%zz+!%T@`pDx|BdvG+ zD}A{3ulr%)`j3B*UwMT))nl#uz4ZAh8>p=K?EC0y#G5ciij`*i%D)?+YrVvfG^W2f z+7mL=M3%@S5={3PI7sIb9ZGb8qay_!PeOA_mpXt9b!kzT7IorDrzdqLhZdBcE1mvYa-iEdkatVqx#-I(dWI- zkLVW5lbc7-*M#?sv6aQwz>z{Y0%Ny)$~gSweC}#c-c#meS;(xA{lo5$xIh z9u`uYY&a7MCmx>^PccbPpl@bI@H5xJ*+uy4pL9c6cRj@CA4@j7B`j_X8O$^+orC{h zn?e&_!K8}IW%&FgLaHr-?+R6~U7W_Vv!8`+`^{@Q;4G48cPxe=GfHd zg&m0=gOAK3CgAgRFXTlTC5;`bzNuuB@nzkg&I39Rs6C(>(XhLFcHZ(6N9|V+&c;`l NIImcA!ATbLe*l%T0dD{R literal 0 HcmV?d00001 From 2e325d5177ec4bb4147086dc6fc6cfc876bb55d5 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 3 Aug 2017 13:21:53 +1200 Subject: [PATCH 20/53] Added WindGeneratorTest. --- .../java/mock/model/WindGeneratorTest.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 racevisionGame/src/test/java/mock/model/WindGeneratorTest.java diff --git a/racevisionGame/src/test/java/mock/model/WindGeneratorTest.java b/racevisionGame/src/test/java/mock/model/WindGeneratorTest.java new file mode 100644 index 00000000..74ad7421 --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/WindGeneratorTest.java @@ -0,0 +1,111 @@ +package mock.model; + +import org.junit.Before; +import org.junit.Test; +import shared.model.Bearing; +import shared.model.Wind; + +import static org.junit.Assert.*; + + +public class WindGeneratorTest { + + + private WindGenerator windGenerator; + + private Bearing windBaselineBearing; + private Bearing windBearingLowerBound; + private Bearing windBearingUpperBound; + private double windBaselineSpeed; + private double windSpeedLowerBound; + private double windSpeedUpperBound; + + private double speedKnotsEpsilon; + private double bearingDegreeEpsilon; + + + @Before + public void setUp() throws Exception { + + + //Bounds. + this.windBaselineBearing = Bearing.fromDegrees(88.3); + this.windBearingLowerBound = Bearing.fromDegrees(66.5); + this.windBearingUpperBound = Bearing.fromDegrees(248.6); + this.windBaselineSpeed = 13; + this.windSpeedLowerBound = 7; + this.windSpeedUpperBound = 20; + + this.windGenerator = new WindGenerator( + windBaselineBearing, + windBearingLowerBound, + windBearingUpperBound, + windBaselineSpeed, + windSpeedLowerBound, + windSpeedUpperBound ); + + this.bearingDegreeEpsilon = 0.001; + this.speedKnotsEpsilon = 0.001; + } + + + /** + * Tests if the baseline wind generated it accurate. + */ + @Test + public void generateBaselineWindTest() { + + Wind wind = windGenerator.generateBaselineWind(); + + assertEquals(windBaselineSpeed, wind.getWindSpeed(), speedKnotsEpsilon); + assertEquals(windBaselineBearing.degrees(), wind.getWindDirection().degrees(), bearingDegreeEpsilon); + + } + + /** + * Tests if the random wind generated is inside the bounds. + */ + @Test + public void generateRandomWindTest() { + + int randomWindCount = 1000; + + for (int i = 0; i < randomWindCount; i++) { + + Wind wind = windGenerator.generateRandomWind(); + + assertTrue(wind.getWindSpeed() >= windSpeedLowerBound); + assertTrue(wind.getWindSpeed() <= windSpeedUpperBound); + + assertTrue(wind.getWindDirection().degrees() >= windBearingLowerBound.degrees()); + assertTrue(wind.getWindDirection().degrees() <= windBearingUpperBound.degrees()); + + } + + } + + + /** + * Tests if the next wind generated is inside the bounds. + */ + @Test + public void generateNextWindTest() { + + Wind wind = windGenerator.generateBaselineWind(); + + int randomWindCount = 1000; + + for (int i = 0; i < randomWindCount; i++) { + + wind = windGenerator.generateNextWind(wind); + + assertTrue(wind.getWindSpeed() >= windSpeedLowerBound); + assertTrue(wind.getWindSpeed() <= windSpeedUpperBound); + + assertTrue(wind.getWindDirection().degrees() >= windBearingLowerBound.degrees()); + assertTrue(wind.getWindDirection().degrees() <= windBearingUpperBound.degrees()); + + } + + } +} From 40a3ed1bb00af265d87fd529e76bbad1bf93952b Mon Sep 17 00:00:00 2001 From: Joseph Gardner Date: Thu, 3 Aug 2017 16:56:52 +1200 Subject: [PATCH 21/53] 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 22/53] 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 23/53] 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 24/53] 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 425cc7f91faf648536ea8a2a0ae318bb9781ba94 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 4 Aug 2017 21:27:53 +1200 Subject: [PATCH 25/53] Changed ControlKey to use BoatActionEnum instead of magic numbers. --- .../visualiser/gameController/ControllerClient.java | 6 +++--- .../visualiser/gameController/Keys/ControlKey.java | 11 ++++++----- .../visualiser/gameController/Keys/DownWindKey.java | 4 +++- .../gameController/Keys/SailsToggleKey.java | 10 ++++++++-- .../visualiser/gameController/Keys/TackGybeKey.java | 4 +++- .../visualiser/gameController/Keys/UpWindKey.java | 4 +++- .../java/visualiser/gameController/Keys/VMGKey.java | 3 ++- 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java index eb46d361..a0f1af75 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java @@ -47,11 +47,11 @@ public class ControllerClient { * @throws IOException if socket write fails */ public void sendKey(ControlKey key) throws IOException { - int protocolCode = key.getProtocolCode(); - if(protocolCode > -1) { + BoatActionEnum protocolCode = key.getProtocolCode(); + if(protocolCode != BoatActionEnum.NOT_A_STATUS) { byte[] bytes = new byte[4]; - ByteBuffer.wrap(bytes).putInt(key.getProtocolCode()); + ByteBuffer.wrap(bytes).putInt(protocolCode.getValue()); BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(bytes[3]); BoatAction boatAction = new BoatAction(boatActionEnum); diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java index ad8a559a..dd489f73 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java @@ -1,6 +1,7 @@ package visualiser.gameController.Keys; import javafx.scene.input.KeyCode; +import network.Messages.Enums.BoatActionEnum; /** * Key for the controller, part of the abstract factory KeyFactory @@ -8,14 +9,14 @@ import javafx.scene.input.KeyCode; public abstract class ControlKey { private String name; - protected int protocolCode; + protected BoatActionEnum protocolCode; /** * Constructor for key state with specified protocol code * @param name of action - * @param protocolCode -1 if not sent + * @param protocolCode NOT_A_STATUS if not sent */ - public ControlKey(String name, int protocolCode) { + public ControlKey(String name, BoatActionEnum protocolCode) { this.name = name; this.protocolCode = protocolCode; } @@ -26,10 +27,10 @@ public abstract class ControlKey { */ public ControlKey(String name){ this.name = name; - this.protocolCode = -1; + this.protocolCode = BoatActionEnum.NOT_A_STATUS; } - public int getProtocolCode() { + public BoatActionEnum getProtocolCode() { return protocolCode; } diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java index 6d929ca1..e4b5455a 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java @@ -1,5 +1,7 @@ package visualiser.gameController.Keys; +import network.Messages.Enums.BoatActionEnum; + /** * Key to send downwind packet to server */ @@ -11,7 +13,7 @@ public class DownWindKey extends ControlKey { * */ public DownWindKey(String name) { - super(name, 6); + super(name, BoatActionEnum.DOWNWIND); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java index bc9b81a6..15b88512 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java @@ -1,5 +1,7 @@ package visualiser.gameController.Keys; +import network.Messages.Enums.BoatActionEnum; + /** * Key to toggle the sails */ @@ -11,7 +13,7 @@ public class SailsToggleKey extends ControlKey { * */ public SailsToggleKey(String name) { - super(name, 2); + super(name, BoatActionEnum.SAILS_IN); } /** @@ -19,7 +21,11 @@ public class SailsToggleKey extends ControlKey { */ @Override public void onAction() { - protocolCode = protocolCode == 2? 3 : 2; + if(protocolCode == BoatActionEnum.SAILS_IN) { + protocolCode = BoatActionEnum.SAILS_OUT; + } else { + protocolCode = BoatActionEnum.SAILS_IN; + } } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java index cf9a0699..80252e73 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java @@ -1,5 +1,7 @@ package visualiser.gameController.Keys; +import network.Messages.Enums.BoatActionEnum; + /** * key to toggle between tacking and gybing */ @@ -11,7 +13,7 @@ public class TackGybeKey extends ControlKey { * */ public TackGybeKey(String name) { - super(name, 4); + super(name, BoatActionEnum.TACK_GYBE); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java index 85f7fc4b..333e5f1f 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java @@ -1,5 +1,7 @@ package visualiser.gameController.Keys; +import network.Messages.Enums.BoatActionEnum; + /** * Key to go upwind */ @@ -11,7 +13,7 @@ public class UpWindKey extends ControlKey { * */ public UpWindKey(String name) { - super(name, 5); + super(name, BoatActionEnum.UPWIND); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java index e6d82ba7..c01658bb 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java @@ -1,6 +1,7 @@ package visualiser.gameController.Keys; import javafx.scene.input.KeyCode; +import network.Messages.Enums.BoatActionEnum; /** * Key to trigger auto VMG @@ -13,7 +14,7 @@ public class VMGKey extends ControlKey{ * @param name name of the key */ public VMGKey(String name) { - super(name, 1); + super(name, BoatActionEnum.AUTO_PILOT); } @Override From 554f8a2a0ffd5525448ede94f4331d546f5ebfb1 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Fri, 4 Aug 2017 23:08:07 +1200 Subject: [PATCH 26/53] 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 1385500e6822d387944b18f7347eea9df8a13641 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 19:10:15 +1200 Subject: [PATCH 27/53] Added JoinAcceptance and RequestToJoin enumerations. Issue #35 #story[1095] --- .../Messages/Enums/JoinAcceptanceEnum.java | 102 ++++++++++++++++++ .../Messages/Enums/RequestToJoinEnum.java | 97 +++++++++++++++++ .../java/network/Messages/JoinAcceptance.java | 4 + .../java/network/Messages/RequestToJoin.java | 13 +++ 4 files changed, 216 insertions(+) create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java create mode 100644 racevisionGame/src/main/java/network/Messages/JoinAcceptance.java create mode 100644 racevisionGame/src/main/java/network/Messages/RequestToJoin.java diff --git a/racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java new file mode 100644 index 00000000..6939d8f3 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java @@ -0,0 +1,102 @@ +package network.Messages.Enums; + + +import java.util.HashMap; +import java.util.Map; + +/** + * This enum encapsulates the different ways in which a server may respond to a client {@link network.Messages.RequestToJoin} message. + */ +public enum JoinAcceptanceEnum { + + + /** + * Client is allowed to join. + */ + JOIN_SUCCESSFUL(1), + + /** + * The race is full - no more participants allowed. + */ + RACE_PARTICIPANTS_FULL(2), + + /** + * The race cannot allow any more ghost participants to join. + */ + GHOST_PARTICIPANTS_FULL(3), + + /** + * The server is completely full, cannot participate or spectate. + */ + SERVER_FULL(4), + + + /** + * Used to indicate that a given byte value is invalid. + */ + NOT_AN_ACCEPTANCE_TYPE(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a JoinAcceptanceEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private JoinAcceptanceEnum(int value) { + this.value = (byte) value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + + + /** + * Stores a mapping between Byte values and JoinAcceptanceEnum values. + */ + private static final Map byteToAcceptanceMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToAcceptanceMap. + */ + static { + for (JoinAcceptanceEnum type : JoinAcceptanceEnum.values()) { + JoinAcceptanceEnum.byteToAcceptanceMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param joinAcceptanceEnum Byte value to convert to a JoinAcceptanceEnum value. + * @return The RequestToJoinEnum value which corresponds to the given byte value. + */ + public static JoinAcceptanceEnum fromByte(byte joinAcceptanceEnum) { + //Gets the corresponding MessageType from the map. + JoinAcceptanceEnum type = JoinAcceptanceEnum.byteToAcceptanceMap.get(joinAcceptanceEnum); + + if (type == null) { + //If the byte value wasn't found, return the NOT_AN_ACCEPTANCE_TYPE JoinAcceptanceEnum. + return JoinAcceptanceEnum.NOT_AN_ACCEPTANCE_TYPE; + } else { + //Otherwise, return the JoinAcceptanceEnum. + return type; + } + + } + + + +} diff --git a/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java new file mode 100644 index 00000000..2aab2925 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java @@ -0,0 +1,97 @@ +package network.Messages.Enums; + + +import java.util.HashMap; +import java.util.Map; + +/** + * This enum encapsulates the different ways in which a client may wish to connect to a server. + */ +public enum RequestToJoinEnum { + + + /** + * Client wants to spectate. + */ + SPECTATOR(0), + + /** + * Client wants to participate. + */ + PARTICIPANT(1), + + /** + * Client wants to particpate as a ghost. + */ + GHOST(5), + + + /** + * Used to indicate that a given byte value is invalid. + */ + NOT_A_REQUEST_TYPE(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a RequestToJoinEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private RequestToJoinEnum(int value) { + this.value = (byte) value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + + + /** + * Stores a mapping between Byte values and RequestToJoinEnum values. + */ + private static final Map byteToRequestMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToRequestMap. + */ + static { + for (RequestToJoinEnum type : RequestToJoinEnum.values()) { + RequestToJoinEnum.byteToRequestMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param requestToJoinEnum Byte value to convert to a RequestToJoinEnum value. + * @return The RequestToJoinEnum value which corresponds to the given byte value. + */ + public static RequestToJoinEnum fromByte(byte requestToJoinEnum) { + //Gets the corresponding MessageType from the map. + RequestToJoinEnum type = RequestToJoinEnum.byteToRequestMap.get(requestToJoinEnum); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_REQUEST_TYPE RequestToJoinEnum. + return RequestToJoinEnum.NOT_A_REQUEST_TYPE; + } else { + //Otherwise, return the RequestToJoinEnum. + return type; + } + + } + + + +} diff --git a/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java new file mode 100644 index 00000000..911efa3f --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java @@ -0,0 +1,4 @@ +package network.Messages; + +public class JoinAcceptance { +} diff --git a/racevisionGame/src/main/java/network/Messages/RequestToJoin.java b/racevisionGame/src/main/java/network/Messages/RequestToJoin.java new file mode 100644 index 00000000..cf3f0ccd --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/RequestToJoin.java @@ -0,0 +1,13 @@ +package network.Messages; + + +/** + * This is the message a client sends to a server to request to join/view a race. + */ +public class RequestToJoin { + + + + + +} From f65ed79619b4d347690b049369a23a25bcfdd55d Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 19:23:32 +1200 Subject: [PATCH 28/53] Implemented RequestToJoin and JoinAcceptance messages. Also added their message types to MessageType. issue #35 #story[1095] --- .../network/Messages/Enums/MessageType.java | 11 ++++ .../java/network/Messages/JoinAcceptance.java | 51 ++++++++++++++++++- .../java/network/Messages/RequestToJoin.java | 25 ++++++++- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java index 086673f5..4072f912 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java @@ -19,6 +19,17 @@ public enum MessageType { MARKROUNDING(38), COURSEWIND(44), AVGWIND(47), + + /** + * This is used for {@link network.Messages.RequestToJoin} messages. + */ + REQUEST_TO_JOIN(55), + + /** + * This is used for {@link network.Messages.JoinAcceptance} messages. + */ + JOIN_ACCEPTANCE(56), + BOATACTION(100), NOTAMESSAGE(0); diff --git a/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java index 911efa3f..d2ff47e2 100644 --- a/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java +++ b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java @@ -1,4 +1,53 @@ package network.Messages; -public class JoinAcceptance { +import network.Messages.Enums.JoinAcceptanceEnum; +import network.Messages.Enums.MessageType; + + +/** + * This is the message a server sends to a client to tell them their boat sourceID, and if they have actually managed to join the server. + */ +public class JoinAcceptance extends AC35Data { + + + /** + * The source ID of the boat assigned to the client. + * 0 indicates they haven't been assigned a boat. + */ + private int sourceID = 0; + + /** + * The type of acceptance response this is. + */ + private JoinAcceptanceEnum acceptanceType; + + + + + /** + * Constructs a JoinAcceptance message of a given acceptance type. + * @param acceptanceType The type of join acceptance this is. + */ + public JoinAcceptance(JoinAcceptanceEnum acceptanceType, int sourceID){ + super(MessageType.JOIN_ACCEPTANCE); + this.acceptanceType = acceptanceType; + this.sourceID = sourceID; + } + + + /** + * The type of acceptance response this is. + * @return The type of acceptance response. + */ + public JoinAcceptanceEnum getAcceptanceType() { + return acceptanceType; + } + + /** + * Returns the source ID of the boat assigned to the client. + * @return The source ID of the boat assigned to the client. + */ + public int getSourceID() { + return sourceID; + } } diff --git a/racevisionGame/src/main/java/network/Messages/RequestToJoin.java b/racevisionGame/src/main/java/network/Messages/RequestToJoin.java index cf3f0ccd..291bb138 100644 --- a/racevisionGame/src/main/java/network/Messages/RequestToJoin.java +++ b/racevisionGame/src/main/java/network/Messages/RequestToJoin.java @@ -1,13 +1,36 @@ package network.Messages; +import network.Messages.Enums.MessageType; +import network.Messages.Enums.RequestToJoinEnum; + /** * This is the message a client sends to a server to request to join/view a race. */ -public class RequestToJoin { +public class RequestToJoin extends AC35Data { + + /** + * The type of join request this is. + */ + private RequestToJoinEnum requestType; + /** + * Constructs a RequestToJoin message of a given request type. + * @param requestType The type of join request this is. + */ + public RequestToJoin(RequestToJoinEnum requestType){ + super(MessageType.REQUEST_TO_JOIN); + this.requestType = requestType; + } + /** + * The type of join request this is. + * @return The type of join request. + */ + public RequestToJoinEnum getRequestType() { + return requestType; + } } From ca2b8a88999e489e5edd16cc951689154c850ba7 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 19:43:58 +1200 Subject: [PATCH 29/53] Added missing javadoc. #story[1095] --- .../src/main/java/network/Messages/JoinAcceptance.java | 1 + 1 file changed, 1 insertion(+) diff --git a/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java index d2ff47e2..378d3285 100644 --- a/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java +++ b/racevisionGame/src/main/java/network/Messages/JoinAcceptance.java @@ -27,6 +27,7 @@ public class JoinAcceptance extends AC35Data { /** * Constructs a JoinAcceptance message of a given acceptance type. * @param acceptanceType The type of join acceptance this is. + * @param sourceID The sourceID to assign to the client. 0 indicates no sourceID. */ public JoinAcceptance(JoinAcceptanceEnum acceptanceType, int sourceID){ super(MessageType.JOIN_ACCEPTANCE); From 7ea5b31fa1999d3b91b219a36eb87d6a36bb513d Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 20:42:25 +1200 Subject: [PATCH 30/53] RequestToJoinEnum contains an int instead of a byte. Added requestToJoin and joinAcceptance encoding functions to RaceVisionByteEncoder. Implemented JoinAcceptanceDecoder. Implemented RequestToJoinDecoder. Added tests for encoding/decoding RequestToJoin and JoinAcceptance messages. issue #35 #story[1095] --- .../JoinAcceptanceDecoder.java | 70 +++++++++++++ .../MessageDecoders/RequestToJoinDecoder.java | 62 ++++++++++++ .../RaceVisionByteEncoder.java | 38 +++++++ .../Messages/Enums/RequestToJoinEnum.java | 30 +++--- .../JoinAcceptanceDecoderTest.java | 99 +++++++++++++++++++ .../RequestToJoinDecoderTest.java | 82 +++++++++++++++ 6 files changed, 366 insertions(+), 15 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java diff --git a/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java new file mode 100644 index 00000000..95c3889e --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java @@ -0,0 +1,70 @@ +package network.MessageDecoders; + + +import network.Messages.Enums.JoinAcceptanceEnum; +import network.Messages.JoinAcceptance; +import network.Utils.ByteConverter; + +import java.util.Arrays; + +/** + * Decoder for {@link JoinAcceptance} messages. + */ +public class JoinAcceptanceDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private JoinAcceptance message; + + + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public JoinAcceptanceDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + //SourceID is first four bytes. + byte[] sourceIdBytes = Arrays.copyOfRange(encodedMessage, 0, 4); + + //Next byte is acceptance type. + byte[] acceptanceBytes = Arrays.copyOfRange(encodedMessage, 4, 5); + + + + //SourceID is an int. + int sourceID = ByteConverter.bytesToInt(sourceIdBytes); + + //Acceptance enum is a byte. + JoinAcceptanceEnum acceptanceType = JoinAcceptanceEnum.fromByte(acceptanceBytes[0]); + + + message = new JoinAcceptance(acceptanceType, sourceID); + } + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public JoinAcceptance getMessage() { + return message; + } + + +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java new file mode 100644 index 00000000..2d272f05 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java @@ -0,0 +1,62 @@ +package network.MessageDecoders; + + +import network.Messages.Enums.RequestToJoinEnum; +import network.Messages.RequestToJoin; +import network.Utils.ByteConverter; + +import java.util.Arrays; + +/** + * Decoder for {@link network.Messages.RequestToJoin} messages. + */ +public class RequestToJoinDecoder { + + /** + * The encoded message. + */ + private byte[] encodedRequest; + + /** + * The decoded message. + */ + private RequestToJoin message; + + /** + * Constructs a decoder to decode a given message. + * @param encodedRequest The message to decode. + */ + public RequestToJoinDecoder(byte[] encodedRequest) { + this.encodedRequest = encodedRequest; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + //Request type is first four bytes. + byte[] requestTypeBytes = Arrays.copyOfRange(encodedRequest, 0, 4); + + //Request type is an integral type. + int requestTypeInt = ByteConverter.bytesToInt(requestTypeBytes); + RequestToJoinEnum requestType = RequestToJoinEnum.fromInt(requestTypeInt); + + + message = new RequestToJoin(requestType); + } + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public RequestToJoin getMessage() { + return message; + } + + +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 4c57cf0c..88dcb024 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -346,4 +346,42 @@ public class RaceVisionByteEncoder { return result; } + + /** + * Encodes a {@link RequestToJoin} message. + * @param requestToJoin Message to encode. + * @return Encoded message. + */ + public static byte[] requestToJoin(RequestToJoin requestToJoin) { + + ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4); + + requestToJoinBuffer.put(intToBytes(requestToJoin.getRequestType().getValue(), 4)); + + byte [] result = requestToJoinBuffer.array(); + + return result; + + } + + /** + * Encodes a {@link JoinAcceptance} message. + * @param joinAcceptance Message to encode. + * @return Encoded message. + */ + public static byte[] joinAcceptance(JoinAcceptance joinAcceptance) { + + //Message is 5 bytes. + ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5); + + //Source ID is first four bytes. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4)); + //Acceptance type is next byte. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1)); + + byte [] result = joinAcceptanceBuffer.array(); + + return result; + + } } diff --git a/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java index 2aab2925..36bb5955 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java @@ -35,22 +35,22 @@ public enum RequestToJoinEnum { /** * Primitive value of the enum. */ - private byte value; + private int value; /** - * Ctor. Creates a RequestToJoinEnum from a given primitive integer value, cast to a byte. - * @param value Integer, which is cast to byte, to construct from. + * Ctor. Creates a RequestToJoinEnum from a given int value. + * @param value Integer to construct from. */ private RequestToJoinEnum(int value) { - this.value = (byte) value; + this.value = value; } /** * Returns the primitive value of the enum. * @return Primitive value of the enum. */ - public byte getValue() { + public int getValue() { return value; } @@ -58,32 +58,32 @@ public enum RequestToJoinEnum { /** - * Stores a mapping between Byte values and RequestToJoinEnum values. + * Stores a mapping between Integer values and RequestToJoinEnum values. */ - private static final Map byteToRequestMap = new HashMap<>(); + private static final Map intToRequestMap = new HashMap<>(); /* - Static initialization block. Initializes the byteToRequestMap. + Static initialization block. Initializes the intToRequestMap. */ static { for (RequestToJoinEnum type : RequestToJoinEnum.values()) { - RequestToJoinEnum.byteToRequestMap.put(type.value, type); + RequestToJoinEnum.intToRequestMap.put(type.value, type); } } /** - * Returns the enumeration value which corresponds to a given byte value. - * @param requestToJoinEnum Byte value to convert to a RequestToJoinEnum value. - * @return The RequestToJoinEnum value which corresponds to the given byte value. + * Returns the enumeration value which corresponds to a given int value. + * @param requestToJoinEnum int value to convert to a RequestToJoinEnum value. + * @return The RequestToJoinEnum value which corresponds to the given int value. */ - public static RequestToJoinEnum fromByte(byte requestToJoinEnum) { + public static RequestToJoinEnum fromInt(int requestToJoinEnum) { //Gets the corresponding MessageType from the map. - RequestToJoinEnum type = RequestToJoinEnum.byteToRequestMap.get(requestToJoinEnum); + RequestToJoinEnum type = RequestToJoinEnum.intToRequestMap.get(requestToJoinEnum); if (type == null) { - //If the byte value wasn't found, return the NOT_A_REQUEST_TYPE RequestToJoinEnum. + //If the int value wasn't found, return the NOT_A_REQUEST_TYPE RequestToJoinEnum. return RequestToJoinEnum.NOT_A_REQUEST_TYPE; } else { //Otherwise, return the RequestToJoinEnum. diff --git a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java new file mode 100644 index 00000000..cca27fb2 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java @@ -0,0 +1,99 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.Enums.JoinAcceptanceEnum; +import network.Messages.JoinAcceptance; +import org.junit.Test; + +import static org.junit.Assert.*; + + +/** + * Test for the {@link network.Messages.JoinAcceptance} encoder and decoder + */ +public class JoinAcceptanceDecoderTest { + + + /** + * Encodes and decodes a given message. + * @param message Message to encode/decode. + * @return The decoded message. + */ + private JoinAcceptance encodeDecodeMessage(JoinAcceptance message) { + + //Encode. + byte [] testEncodedMessage = RaceVisionByteEncoder.joinAcceptance(message); + + //Decode. + JoinAcceptanceDecoder testDecoder = new JoinAcceptanceDecoder(testEncodedMessage); + JoinAcceptance decodedMessage = testDecoder.getMessage(); + + return decodedMessage; + } + + + /** + * Tests if a specific acceptance type message can be encoded and decoded correctly. + * @param type The type of acceptance response. + * @param sourceID The source ID to assign. + */ + private void responseTypeTest(JoinAcceptanceEnum type, int sourceID) throws Exception { + + //Prepare message. + JoinAcceptance beforeMessage = new JoinAcceptance(type, sourceID); + + + //Encode/decode it. + JoinAcceptance afterMessage = encodeDecodeMessage(beforeMessage); + + + //Compare. + assertEquals(beforeMessage.getAcceptanceType().getValue(), afterMessage.getAcceptanceType().getValue()); + assertEquals(beforeMessage.getSourceID(), afterMessage.getSourceID()); + + } + + + /** + * Tests if a join success message, with a source ID, can be encoded and decoded correctly. + */ + @Test + public void joinSuccessSourceIDTest() throws Exception { + responseTypeTest(JoinAcceptanceEnum.JOIN_SUCCESSFUL, 12345); + } + + /** + * Tests if a join success message, with no source ID, can be encoded and decoded correctly. + */ + @Test + public void joinSuccessNoSourceIDTest() throws Exception { + responseTypeTest(JoinAcceptanceEnum.JOIN_SUCCESSFUL, 0); + } + + /** + * Tests if a participants full message can be encoded and decoded correctly. + */ + @Test + public void participantFullTest() throws Exception { + responseTypeTest(JoinAcceptanceEnum.RACE_PARTICIPANTS_FULL, 0); + } + + /** + * Tests if a ghosts full message can be encoded and decoded correctly. + */ + @Test + public void ghostFullTest() throws Exception { + responseTypeTest(JoinAcceptanceEnum.GHOST_PARTICIPANTS_FULL, 0); + } + + /** + * Tests if a server full message can be encoded and decoded correctly. + */ + @Test + public void serverFullTest() throws Exception { + responseTypeTest(JoinAcceptanceEnum.SERVER_FULL, 0); + } + + + +} diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java new file mode 100644 index 00000000..5ea9f5ee --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java @@ -0,0 +1,82 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.Enums.RequestToJoinEnum; +import network.Messages.RequestToJoin; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + + +/** + * Test for the RequestToJoin encoder and decoder + */ +public class RequestToJoinDecoderTest { + + + /** + * Encodes and decodes a given message. + * @param message Message to encode/decode. + * @return The decoded message. + */ + private RequestToJoin encodeDecodeMessage(RequestToJoin message) { + + //Encode. + byte [] testEncodedMessage = RaceVisionByteEncoder.requestToJoin(message); + + //Decode. + RequestToJoinDecoder testDecoder = new RequestToJoinDecoder(testEncodedMessage); + RequestToJoin decodedMessage = testDecoder.getMessage(); + + return decodedMessage; + } + + + /** + * Tests if a specific request type message can be encoded and decoded correctly. + * @param type The type of join request. + */ + private void requestTypeTest(RequestToJoinEnum type) throws Exception { + + //Prepare message. + RequestToJoin beforeMessage = new RequestToJoin(type); + + + //Encode/decode it. + RequestToJoin afterMessage = encodeDecodeMessage(beforeMessage); + + + //Compare. + assertEquals(beforeMessage.getRequestType().getValue(), afterMessage.getRequestType().getValue()); + + } + + + /** + * Tests if a spectator request message can be encoded and decoded correctly. + */ + @Test + public void spectatorTest() throws Exception { + requestTypeTest(RequestToJoinEnum.SPECTATOR); + } + + /** + * Tests if a participant request message can be encoded and decoded correctly. + */ + @Test + public void participantTest() throws Exception { + requestTypeTest(RequestToJoinEnum.PARTICIPANT); + } + + /** + * Tests if a ghost request message can be encoded and decoded correctly. + */ + @Test + public void ghostTest() throws Exception { + requestTypeTest(RequestToJoinEnum.GHOST); + } + + + +} From 16686678a76ed99e65517d29ea3af9cd57214e08 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 21:31:47 +1200 Subject: [PATCH 31/53] Added an InvalidMessageTypeException - thrown whenever we encounter a MessageType that isn't recognised or isn't supported. Added EncoderFactory. This creates specific MessageEncoders. Supports JoinAcceptance and RequestToJoin. Added MessageEncoder interface. Added JoinAcceptanceEncoder. Added encode(AC35Data) function to RaceVisionByteEncoder. Added RequestToJonEncoder. Updated RequestToJoin and JoinAcceptance decode/encode tests to use the above. issue #35 #36 #story[1095] --- .../InvalidMessageTypeException.java | 17 +++++++ .../MessageEncoders/EncoderFactory.java | 44 +++++++++++++++++++ .../JoinAcceptanceEncoder.java | 44 +++++++++++++++++++ .../MessageEncoders/MessageEncoder.java | 21 +++++++++ .../RaceVisionByteEncoder.java | 42 ++++++------------ .../MessageEncoders/RequestToJoinEncoder.java | 39 ++++++++++++++++ .../JoinAcceptanceDecoderTest.java | 5 ++- .../RequestToJoinDecoderTest.java | 5 ++- 8 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 racevisionGame/src/main/java/network/Exceptions/InvalidMessageTypeException.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java diff --git a/racevisionGame/src/main/java/network/Exceptions/InvalidMessageTypeException.java b/racevisionGame/src/main/java/network/Exceptions/InvalidMessageTypeException.java new file mode 100644 index 00000000..02867eaa --- /dev/null +++ b/racevisionGame/src/main/java/network/Exceptions/InvalidMessageTypeException.java @@ -0,0 +1,17 @@ +package network.Exceptions; + + +/** + * An exception thrown when we encounter a message type that isn't recognised. + */ +public class InvalidMessageTypeException extends Exception { + + + public InvalidMessageTypeException(String message) { + super(message); + } + + public InvalidMessageTypeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java new file mode 100644 index 00000000..620107fa --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -0,0 +1,44 @@ +package network.MessageEncoders; + + +import network.Exceptions.InvalidMessageTypeException; +import network.Messages.Enums.MessageType; + +/** + * Factory to create the appropriate encoder for a given message. + */ +public class EncoderFactory { + + + /** + * Private constructor. Currently doesn't need to be constructed. + */ + private EncoderFactory(){ + + } + + + /** + * Creates the correct type of encoder for a given message type. + * @param type Type of message you want an encoder for. + * @return The encoder. + * @throws InvalidMessageTypeException If you pass in a {@link MessageType} that isn't recognised. + */ + public static MessageEncoder create(MessageType type) throws InvalidMessageTypeException { + + + switch (type) { + + case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); + + case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder(); + + + default: throw new InvalidMessageTypeException("Unrecognised message type: " + type); + } + + + + } + +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java new file mode 100644 index 00000000..2b23c0dd --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java @@ -0,0 +1,44 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.JoinAcceptance; +import network.Utils.ByteConverter; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; + +/** + * This encoder can encode a {@link JoinAcceptance} message. + */ +public class JoinAcceptanceEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public JoinAcceptanceEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + JoinAcceptance joinAcceptance = (JoinAcceptance) message; + + //Message is 5 bytes. + ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5); + + //Source ID is first four bytes. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4)); + //Acceptance type is next byte. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1)); + + byte [] result = joinAcceptanceBuffer.array(); + + return result; + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java new file mode 100644 index 00000000..d5ec4cd8 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java @@ -0,0 +1,21 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; + + +/** + * This is the interface that all message encoders must implement. + * It allows for {@link #encode(AC35Data)}ing messages. + */ +public interface MessageEncoder { + + + /** + * Encodes a given message. + * @param message The message to encode. + * @return Message in byte encoded form. + */ + public byte[] encode(AC35Data message); + +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 88dcb024..78498457 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -1,6 +1,8 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; +import network.Exceptions.InvalidMessageTypeException; import network.Messages.*; import static network.Utils.ByteConverter.*; @@ -348,40 +350,24 @@ public class RaceVisionByteEncoder { /** - * Encodes a {@link RequestToJoin} message. - * @param requestToJoin Message to encode. + * Encodes a given message. + * @param message Message to encode. * @return Encoded message. + * @throws InvalidMessageException If the message cannot be encoded. */ - public static byte[] requestToJoin(RequestToJoin requestToJoin) { + public static byte[] encode(AC35Data message) throws InvalidMessageException { - ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4); - - requestToJoinBuffer.put(intToBytes(requestToJoin.getRequestType().getValue(), 4)); - - byte [] result = requestToJoinBuffer.array(); + MessageEncoder encoder = null; + try { + encoder = EncoderFactory.create(message.getType()); + } catch (InvalidMessageTypeException e) { + throw new InvalidMessageException("Could not create encoder for MessageType: " + message.getType(), e); + } - return result; + byte[] encodedMessage = encoder.encode(message); + return encodedMessage; } - /** - * Encodes a {@link JoinAcceptance} message. - * @param joinAcceptance Message to encode. - * @return Encoded message. - */ - public static byte[] joinAcceptance(JoinAcceptance joinAcceptance) { - //Message is 5 bytes. - ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5); - - //Source ID is first four bytes. - joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4)); - //Acceptance type is next byte. - joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1)); - - byte [] result = joinAcceptanceBuffer.array(); - - return result; - - } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java new file mode 100644 index 00000000..b01e92de --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java @@ -0,0 +1,39 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.RequestToJoin; +import network.Utils.ByteConverter; + +import java.nio.ByteBuffer; + +/** + * This encoder can encode a {@link network.Messages.RequestToJoin} message. + */ +public class RequestToJoinEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public RequestToJoinEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + RequestToJoin requestToJoin = (RequestToJoin) message; + + + ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4); + + requestToJoinBuffer.put(ByteConverter.intToBytes(requestToJoin.getRequestType().getValue(), 4)); + + byte [] result = requestToJoinBuffer.array(); + + return result; + + } +} diff --git a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java index cca27fb2..5cefe5ea 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.Enums.JoinAcceptanceEnum; import network.Messages.JoinAcceptance; @@ -19,10 +20,10 @@ public class JoinAcceptanceDecoderTest { * @param message Message to encode/decode. * @return The decoded message. */ - private JoinAcceptance encodeDecodeMessage(JoinAcceptance message) { + private JoinAcceptance encodeDecodeMessage(JoinAcceptance message) throws InvalidMessageException { //Encode. - byte [] testEncodedMessage = RaceVisionByteEncoder.joinAcceptance(message); + byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. JoinAcceptanceDecoder testDecoder = new JoinAcceptanceDecoder(testEncodedMessage); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java index 5ea9f5ee..ddfd11bd 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.Enums.RequestToJoinEnum; import network.Messages.RequestToJoin; @@ -20,10 +21,10 @@ public class RequestToJoinDecoderTest { * @param message Message to encode/decode. * @return The decoded message. */ - private RequestToJoin encodeDecodeMessage(RequestToJoin message) { + private RequestToJoin encodeDecodeMessage(RequestToJoin message) throws InvalidMessageException { //Encode. - byte [] testEncodedMessage = RaceVisionByteEncoder.requestToJoin(message); + byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. RequestToJoinDecoder testDecoder = new RequestToJoinDecoder(testEncodedMessage); From 31ce9fff94638209dd6a88b3e301ffdee7f7a20d Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 21:53:54 +1200 Subject: [PATCH 32/53] Added BoatActonEncoder. Updated ControllerClient to use RaceVisionByteEncoder.encode(message). It also logs a warning if the encoding fails. Also removed pointless and out of place encode/decode of the action enum. Added a BoatActionDecoderTest which tests encoding/decoding a BoatAction Message. issue #35 #36 #story[1095] --- .../MessageEncoders/BoatActionEncoder.java | 40 +++++++ .../MessageEncoders/EncoderFactory.java | 2 + .../RaceVisionByteEncoder.java | 6 - .../Messages/Enums/BoatActionEnum.java | 6 +- .../gameController/ControllerClient.java | 19 +-- .../BoatActionDecoderTest.java | 109 ++++++++++++++++++ 6 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java new file mode 100644 index 00000000..ba5ce901 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java @@ -0,0 +1,40 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.BoatAction; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; + +/** + * This encoder can encode a {@link BoatAction} message. + */ +public class BoatActionEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public BoatActionEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + BoatAction boatAction = (BoatAction) message; + + //Message is 1 byte. + ByteBuffer boatActionMessage = ByteBuffer.allocate(1); + + boatActionMessage.put(intToBytes(boatAction.getBoatAction(), 1)); + + byte [] result = boatActionMessage.array(); + + return result; + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index 620107fa..ebb7814a 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -33,6 +33,8 @@ public class EncoderFactory { case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder(); + case BOATACTION: return new BoatActionEncoder(); + default: throw new InvalidMessageTypeException("Unrecognised message type: " + type); } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 78498457..e3269fba 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -341,12 +341,6 @@ public class RaceVisionByteEncoder { return result.array(); } - public static byte[] boatActionMessage(BoatAction boatAction){ - ByteBuffer boatActionMessage = ByteBuffer.allocate(1); - boatActionMessage.put(intToBytes(boatAction.getBoatAction(), 1)); - byte [] result = boatActionMessage.array(); - return result; - } /** diff --git a/racevisionGame/src/main/java/network/Messages/Enums/BoatActionEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/BoatActionEnum.java index 84f6e0fd..372349f7 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/BoatActionEnum.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/BoatActionEnum.java @@ -8,6 +8,10 @@ import java.util.Map; */ public enum BoatActionEnum { NOT_A_STATUS(-1), + + /** + * Autopilot = auto VMG. + */ AUTO_PILOT(1), SAILS_IN(2), SAILS_OUT(3), @@ -68,4 +72,4 @@ public enum BoatActionEnum { } } -} \ No newline at end of file +} diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java index a0f1af75..6c49cd69 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java @@ -1,6 +1,7 @@ package visualiser.gameController; import network.BinaryMessageEncoder; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; @@ -12,6 +13,8 @@ import java.io.IOException; import java.net.Socket; import java.net.SocketException; import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Basic service for sending key presses to game server @@ -50,18 +53,20 @@ public class ControllerClient { BoatActionEnum protocolCode = key.getProtocolCode(); if(protocolCode != BoatActionEnum.NOT_A_STATUS) { - byte[] bytes = new byte[4]; - ByteBuffer.wrap(bytes).putInt(protocolCode.getValue()); - BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(bytes[3]); + BoatAction boatAction = new BoatAction(protocolCode); - BoatAction boatAction = new BoatAction(boatActionEnum); - - byte[] encodedBoatAction = RaceVisionByteEncoder.boatActionMessage(boatAction); + //Encode BoatAction. + byte[] encodedBoatAction = new byte[0]; + try { + encodedBoatAction = RaceVisionByteEncoder.encode(boatAction); + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode BoatAction: " + boatAction, e); + } BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0, (short) encodedBoatAction.length, encodedBoatAction); - System.out.println("Sending out key: " + boatActionEnum); + System.out.println("Sending out key: " + protocolCode); outputStream.write(binaryMessage.getFullMessage()); } } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java new file mode 100644 index 00000000..1a16f669 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java @@ -0,0 +1,109 @@ +package network.MessageDecoders; + +import network.Exceptions.InvalidMessageException; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.BoatAction; +import network.Messages.Enums.BoatActionEnum; +import network.Messages.Enums.RequestToJoinEnum; +import network.Messages.RequestToJoin; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +/** + * Test for the BoatAction encoder and decoder + */ +public class BoatActionDecoderTest { + + + /** + * Encodes and decodes a given message. + * @param message Message to encode/decode. + * @return The decoded message. + */ + private BoatAction encodeDecodeMessage(BoatAction message) throws InvalidMessageException { + + //Encode. + byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); + + //Decode. + BoatActionDecoder testDecoder = new BoatActionDecoder(testEncodedMessage); + BoatActionEnum decodedBoatAction = testDecoder.getBoatAction(); + + BoatAction decodedMessage = new BoatAction(decodedBoatAction); + + return decodedMessage; + } + + + /** + * Tests if a specific boat action type message can be encoded and decoded correctly. + * @param type The type of boat action. + */ + private void boatActionTypeTest(BoatActionEnum type) throws Exception { + + //Prepare message. + BoatAction beforeMessage = new BoatAction(type); + + + //Encode/decode it. + BoatAction afterMessage = encodeDecodeMessage(beforeMessage); + + + //Compare. + assertEquals(beforeMessage.getBoatAction(), afterMessage.getBoatAction()); + + } + + + /** + * Tests if an autopilot message can be encoded and decoded correctly. + */ + @Test + public void autoPilotTest() throws Exception { + boatActionTypeTest(BoatActionEnum.AUTO_PILOT); + } + + /** + * Tests if a sails in message can be encoded and decoded correctly. + */ + @Test + public void sailsInTest() throws Exception { + boatActionTypeTest(BoatActionEnum.SAILS_IN); + } + + /** + * Tests if a sails out message can be encoded and decoded correctly. + */ + @Test + public void sailsOutTest() throws Exception { + boatActionTypeTest(BoatActionEnum.SAILS_OUT); + } + + /** + * Tests if a tack/gybe message can be encoded and decoded correctly. + */ + @Test + public void tackGybeTest() throws Exception { + boatActionTypeTest(BoatActionEnum.TACK_GYBE); + } + + /** + * Tests if an upwind message can be encoded and decoded correctly. + */ + @Test + public void upwindTest() throws Exception { + boatActionTypeTest(BoatActionEnum.UPWIND); + } + + /** + * Tests if a downwind message can be encoded and decoded correctly. + */ + @Test + public void downwindTest() throws Exception { + boatActionTypeTest(BoatActionEnum.DOWNWIND); + } + + +} From c3ed30019cab1b8826d8b8a52f04c0b5a2091b36 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 22:11:54 +1200 Subject: [PATCH 33/53] Added BoatLocationEncoder. Updated BoatLocationDecoder test to use new encoder. Updated MockOutput to use new encoder. It logs a warning if encoding fails. #story[1095] --- .../src/main/java/mock/app/MockOutput.java | 24 ++++-- .../MessageEncoders/BoatLocationEncoder.java | 83 +++++++++++++++++++ .../MessageEncoders/EncoderFactory.java | 2 + .../RaceVisionByteEncoder.java | 50 ----------- .../BoatLocationDecoderTest.java | 45 ++++++++-- 5 files changed, 140 insertions(+), 64 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index aff0947e..b773a06a 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -3,6 +3,7 @@ package mock.app; import network.BinaryMessageEncoder; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.*; import network.Messages.Enums.MessageType; @@ -13,6 +14,8 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; +import java.util.logging.Level; +import java.util.logging.Logger; /** * TCP server to send race information to connected clients. @@ -158,12 +161,13 @@ public class MockOutput implements Runnable * Encodes/serialises a BoatLocation message, and returns it. * @param boatLocation The BoatLocation message to serialise. * @return The BoatLocation message in a serialised form. + * @throws InvalidMessageException If the message cannot be encoded. */ - private synchronized byte[] parseBoatLocation(BoatLocation boatLocation){ + private synchronized byte[] parseBoatLocation(BoatLocation boatLocation) throws InvalidMessageException { //Encodes the message. - byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation); + byte[] encodedBoatLoc = RaceVisionByteEncoder.encode(boatLocation); //Encodes the full message with header. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( @@ -277,11 +281,19 @@ public class MockOutput implements Runnable BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID); if (boatLocation != null) { - //Encode. - byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); - //Write it. - this.outToVisualiser.write(boatLocationBlob); + try { + //Encode. + byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); + + //Write it. + this.outToVisualiser.write(boatLocationBlob); + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode BoatLocation: " + boatLocation, e); + } + + } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java new file mode 100644 index 00000000..9ff3e197 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java @@ -0,0 +1,83 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.BoatLocation; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link BoatLocation} message. + */ +public class BoatLocationEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public BoatLocationEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + BoatLocation boatLocation = (BoatLocation) message; + + + int messageVersionNumber = 0b1; + byte[] messageVersionBytes = intToBytes(messageVersionNumber, 1); + byte[] time = longToBytes(boatLocation.getTime(), 6); + byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4); + byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4); + byte[] deviceType = intToBytes(boatLocation.getDeviceType(), 1); + byte[] latitude = intToBytes(boatLocation.getLatitude(), 4); + byte[] longitude = intToBytes(boatLocation.getLongitude(), 4); + byte[] altitude = intToBytes(boatLocation.getAltitude(), 4); + byte[] heading = intToBytes(boatLocation.getHeading(), 2); + byte[] pitch = intToBytes(boatLocation.getPitch(), 2); + byte[] roll = intToBytes(boatLocation.getRoll(), 2); + byte[] boatSpeed = intToBytes(boatLocation.getBoatSpeed(), 2); + byte[] cog = intToBytes(boatLocation.getBoatCOG(), 2); + byte[] sog = intToBytes(boatLocation.getBoatSOG(), 2); + byte[] apparentWindSpeed = intToBytes(boatLocation.getApparentWindSpeed(), 2); + byte[] apparentWindAngle = intToBytes(boatLocation.getApparentWindAngle(), 2); + byte[] trueWindSpeed = intToBytes(boatLocation.getTrueWindSpeed(), 2); + byte[] trueWindDirection = intToBytes(boatLocation.getTrueWindDirection(), 2); + byte[] trueWindAngle = intToBytes(boatLocation.getTrueWindAngle(), 2); + byte[] currentDrift = intToBytes(boatLocation.getCurrentDrift(), 2); + byte[] currentSet = intToBytes(boatLocation.getCurrentSet(), 2); + byte[] rudderAngle = intToBytes(boatLocation.getRudderAngle(), 2); + + ByteBuffer result = ByteBuffer.allocate(56); + result.put(messageVersionBytes); + result.put(time); + result.put(sourceID); + result.put(seqNum); + result.put(deviceType); + result.put(latitude); + result.put(longitude); + result.put(altitude); + result.put(heading); + result.put(pitch); + result.put(roll); + result.put(boatSpeed); + result.put(cog); + result.put(sog); + result.put(apparentWindSpeed); + result.put(apparentWindAngle); + result.put(trueWindSpeed); + result.put(trueWindDirection); + result.put(trueWindAngle); + result.put(currentDrift); + result.put(currentSet); + result.put(rudderAngle); + + return result.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index ebb7814a..e23541db 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -29,6 +29,8 @@ public class EncoderFactory { switch (type) { + case BOATLOCATION: return new BoatLocationEncoder(); + case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index e3269fba..1bd0d1f9 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -220,56 +220,6 @@ public class RaceVisionByteEncoder { } - public static byte[] boatLocation(BoatLocation boatLocation){ - int messageVersionNumber = 0b1; - byte[] time = longToBytes(boatLocation.getTime(), 6); - byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4); - byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4); - byte[] deviceType = intToBytes(boatLocation.getDeviceType(), 1); - byte[] latitude = intToBytes(boatLocation.getLatitude(), 4); - byte[] longitude = intToBytes(boatLocation.getLongitude(), 4); - byte[] altitude = intToBytes(boatLocation.getAltitude(), 4); - byte[] heading = intToBytes(boatLocation.getHeading(), 2); - byte[] pitch = intToBytes(boatLocation.getPitch(), 2); - byte[] roll = intToBytes(boatLocation.getRoll(), 2); - byte[] boatSpeed = intToBytes(boatLocation.getBoatSpeed(), 2); - byte[] cog = intToBytes(boatLocation.getBoatCOG(), 2); - byte[] sog = intToBytes(boatLocation.getBoatSOG(), 2); - byte[] apparentWindSpeed = intToBytes(boatLocation.getApparentWindSpeed(), 2); - byte[] apparentWindAngle = intToBytes(boatLocation.getApparentWindAngle(), 2); - byte[] trueWindSpeed = intToBytes(boatLocation.getTrueWindSpeed(), 2); - byte[] trueWindDirection = intToBytes(boatLocation.getTrueWindDirection(), 2); - byte[] trueWindAngle = intToBytes(boatLocation.getTrueWindAngle(), 2); - byte[] currentDrift = intToBytes(boatLocation.getCurrentDrift(), 2); - byte[] currentSet = intToBytes(boatLocation.getCurrentSet(), 2); - byte[] rudderAngle = intToBytes(boatLocation.getRudderAngle(), 2); - - ByteBuffer result = ByteBuffer.allocate(56); - result.put(intToBytes(messageVersionNumber, 1)); - result.put(time); - result.put(sourceID); - result.put(seqNum); - result.put(deviceType); - result.put(latitude); - result.put(longitude); - result.put(altitude); - result.put(heading); - result.put(pitch); - result.put(roll); - result.put(boatSpeed); - result.put(cog); - result.put(sog); - result.put(apparentWindSpeed); - result.put(apparentWindAngle); - result.put(trueWindSpeed); - result.put(trueWindDirection); - result.put(trueWindAngle); - result.put(currentDrift); - result.put(currentSet); - result.put(rudderAngle); - return result.array(); - } - public static byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){ int messageVersionNumber = 0b1; byte[] byteTime = longToBytes(time, 6); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index 2f269257..18893609 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -7,23 +7,52 @@ import org.junit.Test; /** - * Created by hba56 on 23/04/17. + * Test for the BoatLocation encoder and decoder */ public class BoatLocationDecoderTest { + + + /** + * Creates a BoatLocation message, encodes it, decodes it, and checks that the result matches the starting message. + */ @Test - public void getByteArrayTest(){ + public void getByteArrayTest() throws Exception { + + //Create message. long time = System.currentTimeMillis(); - BoatLocation testMessage = new BoatLocation((byte) 1, time, 2, - 3, (byte) 1, 180, -180, 4, 5, - (short) 6, (short) 7, 8, 9, 10, 11, - (short) 12, 13, 14 , (short) 15, - 16, 17, (short) 18); - byte [] testEncodedMessage = RaceVisionByteEncoder.boatLocation(testMessage); + BoatLocation testMessage = new BoatLocation( + (byte) 1, + time, + 2, + 3, + (byte) 1, + 180, + -180, + 4, + 5, + (short) 6, + (short) 7, + 8, + 9, + 10, + 11, + (short) 12, + 13, + 14, + (short) 15, + 16, + 17, + (short) 18 ); + + //Encode. + byte [] testEncodedMessage = RaceVisionByteEncoder.encode(testMessage); + //Decode. BoatLocationDecoder testDecoder = new BoatLocationDecoder(testEncodedMessage); BoatLocation decodedTest = testDecoder.getMessage(); + //Check if valid. Assert.assertEquals(testMessage.getMessageVersionNumber(), decodedTest.getMessageVersionNumber()); Assert.assertEquals(testMessage.getTime(), decodedTest.getTime()); Assert.assertEquals(testMessage.getSequenceNumber(), decodedTest.getSequenceNumber()); From 8ef906472bd13d98d8a559c8cbfd89d6186871f2 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sat, 5 Aug 2017 23:27:28 +1200 Subject: [PATCH 34/53] Renamed Heartbeat to HeartBeat. Added HeartBeatDecoder. Added HeartBeatEncoder. BinaryMessageDecoder now uses HeartBeatDecoder. MockOutput now logs a warning if a heartBeat cannot be encoded. Added HeartBeatDecoderTest. issue #35 #36 #story[1095] --- .../src/main/java/mock/app/MockOutput.java | 26 ++++--- .../java/network/BinaryMessageDecoder.java | 5 +- .../MessageDecoders/HeartBeatDecoder.java | 53 +++++++++++++ .../MessageEncoders/EncoderFactory.java | 2 + .../MessageEncoders/HeartBeatEncoder.java | 40 ++++++++++ .../RaceVisionByteEncoder.java | 13 ---- .../{Heartbeat.java => HeartBeat.java} | 4 +- .../java/visualiser/app/VisualiserInput.java | 21 +----- .../MessageDecoders/HeartBeatDecoderTest.java | 74 +++++++++++++++++++ 9 files changed, 193 insertions(+), 45 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java rename racevisionGame/src/main/java/network/Messages/{Heartbeat.java => HeartBeat.java} (87%) create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index b773a06a..23e7b738 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -7,12 +7,9 @@ import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.*; import network.Messages.Enums.MessageType; -import network.Messages.Enums.XMLMessageType; import java.io.DataOutputStream; import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; import java.net.SocketException; import java.util.logging.Level; import java.util.logging.Logger; @@ -101,24 +98,25 @@ public class MockOutput implements Runnable * Generates the next heartbeat message and returns it. Increments the heartbeat sequence number. * @return The next heartbeat message. */ - private Heartbeat createHeartbeatMessage() { + private HeartBeat createHeartbeatMessage() { //Create the heartbeat message. - Heartbeat heartbeat = new Heartbeat(this.heartbeatSequenceNum); + HeartBeat heartBeat = new HeartBeat(this.heartbeatSequenceNum); heartbeatSequenceNum++; - return heartbeat; + return heartBeat; } /** * Serializes a heartbeat message into a packet to be sent, and returns the byte array. - * @param heartbeat The heartbeat message to serialize. + * @param heartBeat The heartbeat message to serialize. * @return Byte array containing the next heartbeat message. + * @throws InvalidMessageException Thrown if the message cannot be encoded. */ - private byte[] parseHeartbeat(Heartbeat heartbeat) { + private byte[] parseHeartbeat(HeartBeat heartBeat) throws InvalidMessageException { //Serializes the heartbeat message. - byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeat); + byte[] heartbeatMessage = RaceVisionByteEncoder.encode(heartBeat); //Places the serialized message in a packet. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( @@ -213,7 +211,15 @@ public class MockOutput implements Runnable public void sendHeartBeat() throws IOException { //Sends a heartbeat every so often. if (timeSinceHeartbeat() >= heartbeatPeriod) { - outToVisualiser.write(parseHeartbeat(createHeartbeatMessage())); + + HeartBeat heartBeat = createHeartbeatMessage(); + + try { + outToVisualiser.write(parseHeartbeat(heartBeat)); + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode HeartBeat: " + heartBeat, e); + } + lastHeartbeatTime = System.currentTimeMillis(); } } diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index ecc6b2f3..69ecc1f0 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -133,9 +133,8 @@ public class BinaryMessageDecoder { switch(mType) { case HEARTBEAT: //System.out.println("Decoding HeartBeat Message!"); - //TODO maybe use HeartbeatDecoder.decode(message). - //TODO also, decoders for each message type should encapsulate the constructing of the object. E.g., return HeartbeatDecoder.decode(message);. - return new Heartbeat(bytesToLong(messageBody)); + HeartBeatDecoder heartBeatDecoder = new HeartBeatDecoder(messageBody); + return heartBeatDecoder.getMessage(); case RACESTATUS: //System.out.println("Race Status Message"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java new file mode 100644 index 00000000..0984a9ea --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java @@ -0,0 +1,53 @@ +package network.MessageDecoders; + +import network.Messages.Enums.BoatActionEnum; +import network.Messages.HeartBeat; + +import static network.Utils.ByteConverter.bytesToLong; + +/** + * Decodes {@link network.Messages.HeartBeat} messages. + */ +public class HeartBeatDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private HeartBeat message; + + + + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public HeartBeatDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + message = new HeartBeat(bytesToLong(encodedMessage)); + } + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public HeartBeat getMessage() { + return message; + } + +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index e23541db..8eff9cae 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -29,6 +29,8 @@ public class EncoderFactory { switch (type) { + case HEARTBEAT: return new HeartBeatEncoder(); + case BOATLOCATION: return new BoatLocationEncoder(); case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java new file mode 100644 index 00000000..934d1217 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java @@ -0,0 +1,40 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.HeartBeat; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link HeartBeat} message. + */ +public class HeartBeatEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public HeartBeatEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + HeartBeat heartbeat = (HeartBeat) message; + + //Message is 4 bytes. + ByteBuffer heartBeat = ByteBuffer.allocate(4); + heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4)); + + byte[] result = heartBeat.array(); + + return result; + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 1bd0d1f9..51e232d7 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -19,20 +19,7 @@ import java.util.List; */ public class RaceVisionByteEncoder { - /** - * Serializes a heartbeat message. - * @param heartbeat Heartbeat message. - * @return Serialized message. - */ - public static byte[] heartBeat(Heartbeat heartbeat) { - - ByteBuffer heartBeat = ByteBuffer.allocate(4); - heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4)); - byte[] result = heartBeat.array(); - - return result; - } /** * Serializes a RaceStatus message. diff --git a/racevisionGame/src/main/java/network/Messages/Heartbeat.java b/racevisionGame/src/main/java/network/Messages/HeartBeat.java similarity index 87% rename from racevisionGame/src/main/java/network/Messages/Heartbeat.java rename to racevisionGame/src/main/java/network/Messages/HeartBeat.java index fb1dd23f..35f378ac 100644 --- a/racevisionGame/src/main/java/network/Messages/Heartbeat.java +++ b/racevisionGame/src/main/java/network/Messages/HeartBeat.java @@ -6,7 +6,7 @@ import network.Messages.Enums.MessageType; /** * Represents a Heartbeat message. */ -public class Heartbeat extends AC35Data { +public class HeartBeat extends AC35Data { /** * Sequence number of the heartbeat. @@ -17,7 +17,7 @@ public class Heartbeat extends AC35Data { * Ctor. * @param sequenceNumber Sequence number of the heartbeat. */ - public Heartbeat(long sequenceNumber) { + public HeartBeat(long sequenceNumber) { super(MessageType.HEARTBEAT); this.sequenceNumber = sequenceNumber; } diff --git a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java index 0b8102b5..e77a2fef 100644 --- a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java +++ b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java @@ -1,26 +1,13 @@ package visualiser.app; -import javafx.application.Platform; import network.BinaryMessageDecoder; import network.Exceptions.InvalidMessageException; import network.Messages.*; -import org.xml.sax.SAXException; -import shared.dataInput.BoatXMLReader; -import shared.dataInput.RaceXMLReader; -import shared.dataInput.RegattaXMLReader; -import shared.exceptions.InvalidBoatDataException; -import shared.exceptions.InvalidRaceDataException; -import shared.exceptions.InvalidRegattaDataException; -import shared.exceptions.XMLReaderException; - -import javax.xml.parsers.ParserConfigurationException; + import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ArrayBlockingQueue; import static network.Utils.ByteConverter.bytesToShort; @@ -213,12 +200,12 @@ public class VisualiserInput implements Runnable { //Heartbeat. case HEARTBEAT: { - Heartbeat heartbeat = (Heartbeat) message; + HeartBeat heartBeat = (HeartBeat) message; //Check that the heartbeat number is greater than the previous value, and then set the last heartbeat time. - if (heartbeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) { + if (heartBeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) { lastHeartbeatTime = System.currentTimeMillis(); - lastHeartbeatSequenceNum = heartbeat.getSequenceNumber(); + lastHeartbeatSequenceNum = heartBeat.getSequenceNumber(); //System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum); } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java new file mode 100644 index 00000000..ca1086e7 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java @@ -0,0 +1,74 @@ +package network.MessageDecoders; + +import network.Exceptions.InvalidMessageException; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.BoatAction; +import network.Messages.Enums.BoatActionEnum; +import network.Messages.HeartBeat; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +/** + * Test for the HeartBeat encoder and decoder + */ +public class HeartBeatDecoderTest { + + + /** + * Encodes and decodes a given message. + * @param message Message to encode/decode. + * @return The decoded message. + */ + private HeartBeat encodeDecodeMessage(HeartBeat message) throws InvalidMessageException { + + //Encode. + byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); + + //Decode. + HeartBeatDecoder testDecoder = new HeartBeatDecoder(testEncodedMessage); + HeartBeat decodedMessage = testDecoder.getMessage(); + + return decodedMessage; + } + + + /** + * Tests if a heartbeat message with a given sequence number can be encoded and decoded correctly. + * @param sequenceNumber The sequenceNumber to use. + */ + private void heartBeatTest(long sequenceNumber) throws Exception { + + //Prepare message. + HeartBeat beforeMessage = new HeartBeat(sequenceNumber); + + + //Encode/decode it. + HeartBeat afterMessage = encodeDecodeMessage(beforeMessage); + + + //Compare. + assertEquals(beforeMessage.getSequenceNumber(), afterMessage.getSequenceNumber()); + + } + + + /** + * Tests if a heartbeat message with a sequence number of zero can be encoded and decoded correctly. + */ + @Test + public void heartBeatZeroTest() throws Exception { + heartBeatTest(0); + } + + /** + * Tests if a heartbeat message with a sequence number of 1234512 can be encoded and decoded correctly. + */ + @Test + public void heartBeatNonZeroTest() throws Exception { + heartBeatTest(1234512); + } + + +} From b486f99dbef9e18d2c5b4b89d24d52b6bf24313b Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 01:01:05 +1200 Subject: [PATCH 35/53] Added XMLMessageEncoder. Refactored XMLMessageDecoder to be consistent with other decoders - only needs to expose the XMLMessage. Refactored BoatLocationDecoder to be consistent with other decoders - only needs to expose the BoatLocation. Updated XMLMessageDecoderTest to use new encoder/decoder. Also tests all three message types. Removed XMLMessageEncoderTest as it was redundant. Updated BinaryMessageDecoderTest.xmlMessageTest() to use updated XMLMessage encoder/decoder. issue #35 #36 #story[1095] --- .../src/main/java/mock/app/MockOutput.java | 31 ++- .../java/network/BinaryMessageDecoder.java | 3 +- .../MessageDecoders/BoatLocationDecoder.java | 242 +++++++++--------- .../MessageDecoders/XMLMessageDecoder.java | 107 ++++---- .../MessageEncoders/EncoderFactory.java | 2 + .../RaceVisionByteEncoder.java | 37 --- .../MessageEncoders/XMLMessageEncoder.java | 62 +++++ .../network/BinaryMessageDecoderTest.java | 68 ++--- .../BoatLocationDecoderTest.java | 2 +- .../XMLMessageDecoderTest.java | 77 +++++- .../java/network/XMLMessageEncoderTest.java | 50 ---- .../test/resources/network/raceXML/Boats.xml | 119 +++++++++ .../test/resources/network/raceXML/Race.xml | 58 +++++ 13 files changed, 538 insertions(+), 320 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java delete mode 100644 racevisionGame/src/test/java/network/XMLMessageEncoderTest.java create mode 100644 racevisionGame/src/test/resources/network/raceXML/Boats.xml create mode 100644 racevisionGame/src/test/resources/network/raceXML/Race.xml diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index 23e7b738..2119fa06 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -134,11 +134,12 @@ public class MockOutput implements Runnable * Encodes/serialises a XMLMessage message, and returns it. * @param xmlMessage The XMLMessage message to serialise. * @return The XMLMessage message in a serialised form. + * @throws InvalidMessageException Thrown if the message cannot be encoded. */ - private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) { + private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) throws InvalidMessageException { //Serialize the xml message. - byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(xmlMessage); + byte[] encodedXML = RaceVisionByteEncoder.encode(xmlMessage); //Place the message in a packet. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( @@ -262,15 +263,23 @@ public class MockOutput implements Runnable //Send XML messages. if (!sentXMLs) { //Serialise them. - byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage()); - byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage()); - byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage()); - - //Send them. - outToVisualiser.write(raceXMLBlob); - outToVisualiser.write(regattaXMLBlob); - outToVisualiser.write(boatsXMLBlob); - sentXMLs = true; + + try { + byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage()); + byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage()); + byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage()); + + //Send them. + outToVisualiser.write(raceXMLBlob); + outToVisualiser.write(regattaXMLBlob); + outToVisualiser.write(boatsXMLBlob); + sentXMLs = true; + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode XMLMessage: " + latestMessages.getRaceXMLMessage(), e); + continue; //Go to next iteration. + } + } //Sends the RaceStatus message. diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 69ecc1f0..34a5d88d 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -149,8 +149,7 @@ public class BinaryMessageDecoder { case XMLMESSAGE: //System.out.println("XML Message!"); XMLMessageDecoder xmdecoder = new XMLMessageDecoder(messageBody); - xmdecoder.decode(); - return new XMLMessage(XMLMessage.currentVersionNumber, xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMessageContents()); + return xmdecoder.getMessage(); case RACESTARTSTATUS: //System.out.println("Race Start Status Message"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java index db90a343..c0f4a652 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java @@ -11,132 +11,138 @@ import static network.Utils.ByteConverter.bytesToShort; /** - * Created by hba56 on 21/04/17. + * Decodes {@link BoatLocation} messages. */ public class BoatLocationDecoder { - private byte messageVersionNumber; - private byte[] time; - private byte[] sourceID; - private byte[] seqNum; - private byte deviceType; - private byte[] latitude; - private byte[] longitude; - private byte[] altitude; - private byte[] heading; - private byte[] pitch; - private byte[] roll; - private byte[] boatSpeed; - private byte[] cog; - private byte[] sog; - private byte[] apparentWindSpeed; - private byte[] apparentWindAngle; - private byte[] trueWindSpeed; - private byte[] trueWindDirection; - private byte[] trueWindAngle; - private byte[] currentDrift; - private byte[] currentSet; - private byte[] rudderAngle; + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ private BoatLocation message; - public BoatLocationDecoder(byte[] encodedBoatLocation) { - byte numMessageVersionNumber = 0; - long numTime = 0; - int numSourceID = 0; - int numSeqNum = 0; - byte numDeviceType = 0; - int numLatitude = 0; - int numLongitude = 0; - int numAltitude = 0; - int numHeading = 0; - short numPitch = 0; - short numRoll = 0; - int numBoatSpeed = 0; - int numCog = 0; - int numSog = 0; - int numApparentWindSpeed = 0; - short numApparentWindAngle = 0; - int numTrueWindSpeed = 0; - short numTrueWindDirection = 0; - short numTrueWindAngle = 0; - int numCurrentDrift = 0; - int numCurrentSet = 0; - short numRudderAngle = 0; - - try { - messageVersionNumber = encodedBoatLocation[0]; - numMessageVersionNumber = messageVersionNumber; - time = Arrays.copyOfRange(encodedBoatLocation, 1, 7); - numTime = bytesToLong(time); - sourceID = Arrays.copyOfRange(encodedBoatLocation, 7, 11); - numSourceID = bytesToInt(sourceID); - seqNum = Arrays.copyOfRange(encodedBoatLocation, 11, 15); - numSeqNum = bytesToInt(seqNum); - deviceType = encodedBoatLocation[15]; - numDeviceType = deviceType; - latitude = Arrays.copyOfRange(encodedBoatLocation, 16, 20); - numLatitude = bytesToInt(latitude); - longitude = Arrays.copyOfRange(encodedBoatLocation, 20, 24); - numLongitude = bytesToInt(longitude); - altitude = Arrays.copyOfRange(encodedBoatLocation, 24, 28); - numAltitude = bytesToInt(altitude); - heading = Arrays.copyOfRange(encodedBoatLocation, 28, 30); - numHeading = bytesToInt(heading); - pitch = Arrays.copyOfRange(encodedBoatLocation, 30, 32); - numPitch = bytesToShort(pitch); - roll = Arrays.copyOfRange(encodedBoatLocation, 32, 34); - numRoll = bytesToShort(roll); - boatSpeed = Arrays.copyOfRange(encodedBoatLocation, 34, 36); - numBoatSpeed = bytesToInt(boatSpeed); - cog = Arrays.copyOfRange(encodedBoatLocation, 36, 38); - numCog = bytesToInt(cog); - sog = Arrays.copyOfRange(encodedBoatLocation, 38, 40); - numSog = bytesToInt(sog); - apparentWindSpeed = Arrays.copyOfRange(encodedBoatLocation, 40, 42); - numApparentWindSpeed = bytesToInt(apparentWindSpeed); - apparentWindAngle = Arrays.copyOfRange(encodedBoatLocation, 42, 44); - numApparentWindAngle = bytesToShort(apparentWindAngle); - trueWindSpeed = Arrays.copyOfRange(encodedBoatLocation, 44, 46); - numTrueWindSpeed = bytesToInt(trueWindSpeed); - trueWindDirection = Arrays.copyOfRange(encodedBoatLocation, 46, 48); - numTrueWindDirection = bytesToShort(trueWindDirection); - trueWindAngle = Arrays.copyOfRange(encodedBoatLocation, 48, 50); - numTrueWindAngle = bytesToShort(trueWindAngle); - currentDrift = Arrays.copyOfRange(encodedBoatLocation, 50, 52); - numCurrentDrift = bytesToInt(currentDrift); - currentSet = Arrays.copyOfRange(encodedBoatLocation, 52, 54); - numCurrentSet = bytesToShort(currentSet); - rudderAngle = Arrays.copyOfRange(encodedBoatLocation, 54, 56); - numRudderAngle = bytesToShort(rudderAngle); - } catch(ArrayIndexOutOfBoundsException e){ - - } - - message = new BoatLocation(numMessageVersionNumber, numTime, - numSourceID, numSeqNum, numDeviceType, numLatitude, - numLongitude, numAltitude, numHeading, numPitch, - numRoll, numBoatSpeed, numCog, numSog, numApparentWindSpeed, - numApparentWindAngle, numTrueWindSpeed, numTrueWindDirection, - numTrueWindAngle, numCurrentDrift, numCurrentSet, numRudderAngle - );/* - message = new BoatLocation(messageVersionNumber, bytesToLong(time), - bytesToInt(sourceID), bytesToInt(seqNum), - deviceType, bytesToInt(latitude), - bytesToInt(longitude), bytesToInt(altitude), - bytesToInt(heading), bytesToShort(pitch), - bytesToShort(roll), bytesToInt(boatSpeed), - bytesToInt(cog), bytesToInt(sog), - bytesToInt(apparentWindSpeed), bytesToShort(apparentWindAngle), - bytesToInt(trueWindSpeed), bytesToShort(trueWindDirection), - bytesToShort(trueWindAngle), bytesToInt(currentDrift), - bytesToInt(currentSet), bytesToShort(rudderAngle) - );*/ - -// System.out.println(bytesToInt(sourceID)); -// System.out.println(bytesToInt(boatSpeed)); + + + + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public BoatLocationDecoder(byte[] encodedMessage) { + + this.encodedMessage = encodedMessage; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + byte[] messageVersionNumber = Arrays.copyOfRange(encodedMessage, 0, 1); + byte numMessageVersionNumber = messageVersionNumber[0]; + + byte[] time = Arrays.copyOfRange(encodedMessage, 1, 7); + long numTime = bytesToLong(time); + + byte[] sourceID = Arrays.copyOfRange(encodedMessage, 7, 11); + int numSourceID = bytesToInt(sourceID); + + byte[] seqNum = Arrays.copyOfRange(encodedMessage, 11, 15); + int numSeqNum = bytesToInt(seqNum); + + byte[] deviceType = Arrays.copyOfRange(encodedMessage, 15, 16); + byte numDeviceType = deviceType[0]; + + byte[] latitude = Arrays.copyOfRange(encodedMessage, 16, 20); + int numLatitude = bytesToInt(latitude); + + byte[] longitude = Arrays.copyOfRange(encodedMessage, 20, 24); + int numLongitude = bytesToInt(longitude); + + byte[] altitude = Arrays.copyOfRange(encodedMessage, 24, 28); + int numAltitude = bytesToInt(altitude); + + byte[] heading = Arrays.copyOfRange(encodedMessage, 28, 30); + int numHeading = bytesToInt(heading); + + byte[] pitch = Arrays.copyOfRange(encodedMessage, 30, 32); + short numPitch = bytesToShort(pitch); + + byte[] roll = Arrays.copyOfRange(encodedMessage, 32, 34); + short numRoll = bytesToShort(roll); + + byte[] boatSpeed = Arrays.copyOfRange(encodedMessage, 34, 36); + int numBoatSpeed = bytesToInt(boatSpeed); + + byte[] cog = Arrays.copyOfRange(encodedMessage, 36, 38); + int numCog = bytesToInt(cog); + + byte[] sog = Arrays.copyOfRange(encodedMessage, 38, 40); + int numSog = bytesToInt(sog); + + byte[] apparentWindSpeed = Arrays.copyOfRange(encodedMessage, 40, 42); + int numApparentWindSpeed = bytesToInt(apparentWindSpeed); + + byte[] apparentWindAngle = Arrays.copyOfRange(encodedMessage, 42, 44); + short numApparentWindAngle = bytesToShort(apparentWindAngle); + + byte[] trueWindSpeed = Arrays.copyOfRange(encodedMessage, 44, 46); + int numTrueWindSpeed = bytesToInt(trueWindSpeed); + + byte[] trueWindDirection = Arrays.copyOfRange(encodedMessage, 46, 48); + short numTrueWindDirection = bytesToShort(trueWindDirection); + + byte[] trueWindAngle = Arrays.copyOfRange(encodedMessage, 48, 50); + short numTrueWindAngle = bytesToShort(trueWindAngle); + + byte[] currentDrift = Arrays.copyOfRange(encodedMessage, 50, 52); + int numCurrentDrift = bytesToInt(currentDrift); + + byte[] currentSet = Arrays.copyOfRange(encodedMessage, 52, 54); + int numCurrentSet = bytesToShort(currentSet); + + byte[] rudderAngle = Arrays.copyOfRange(encodedMessage, 54, 56); + short numRudderAngle = bytesToShort(rudderAngle); + + + message = new BoatLocation( + numMessageVersionNumber, + numTime, + numSourceID, + numSeqNum, + numDeviceType, + numLatitude, + numLongitude, + numAltitude, + numHeading, + numPitch, + numRoll, + numBoatSpeed, + numCog, + numSog, + numApparentWindSpeed, + numApparentWindAngle, + numTrueWindSpeed, + numTrueWindDirection, + numTrueWindAngle, + numCurrentDrift, + numCurrentSet, + numRudderAngle ); + } + /** + * Returns the decoded message. + * @return The decoded message. + */ public BoatLocation getMessage() { return message; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java index 18ead92e..4fb985a1 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java @@ -1,10 +1,8 @@ package network.MessageDecoders; import network.Messages.Enums.XMLMessageType; +import network.Messages.XMLMessage; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import static network.Utils.ByteConverter.bytesToLong; @@ -12,73 +10,70 @@ import static network.Utils.ByteConverter.bytesToShort; /** - * Created by hba56 on 20/04/17. + * Decodes {@link network.Messages.XMLMessage} messages. */ public class XMLMessageDecoder { - private byte messageVersionNumber; - private short ackNumber; - private long timeStamp; - private byte xmlMsgSubType; - private short sequenceNumber; - private short xmlMsgLength; - private String xmlMessage; - - private byte[] bytes; - - public XMLMessageDecoder(byte[] bytes) { - this.bytes = bytes; - } - - public void decode(){ - byte[] ackNumberBytes = Arrays.copyOfRange(bytes, 1, 3); - byte[] timeStampBytes = Arrays.copyOfRange(bytes, 3, 9); - byte[] sequenceNumberBytes = Arrays.copyOfRange(bytes, 10, 12); - byte[] xmlMsgLengthBytes = Arrays.copyOfRange(bytes, 12, 14); - byte[] xmlMessagebytes = Arrays.copyOfRange(bytes, 14, bytes.length); - - this.xmlMsgSubType = bytes[9]; - this.messageVersionNumber = bytes[0]; - this.ackNumber = bytesToShort(ackNumberBytes); - this.timeStamp = bytesToLong(timeStampBytes); + /** + * The encoded message. + */ + private byte[] encodedMessage; - this.sequenceNumber = bytesToShort(sequenceNumberBytes); - this.xmlMsgLength = bytesToShort(xmlMsgLengthBytes); - this.xmlMessage = new String(xmlMessagebytes).trim(); - } + /** + * The decoded message. + */ + private XMLMessage message; - public byte getMessageVersionNumber() { - return messageVersionNumber; - } - public short getAckNumber() { - return ackNumber; - } - public long getTimeStamp() { - return timeStamp; - } - - public XMLMessageType getXmlMsgSubType() { - return XMLMessageType.fromByte(xmlMsgSubType); - } + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public XMLMessageDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; - public short getSequenceNumber() { - return sequenceNumber; + decode(); } - public short getXmlMsgLength() { - return xmlMsgLength; + /** + * Decodes the contained message. + */ + private void decode() { + + byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte[] ackNumberBytes = Arrays.copyOfRange(encodedMessage, 1, 3); + byte[] timeStampBytes = Arrays.copyOfRange(encodedMessage, 3, 9); + byte[] xmlMsgSubTypeBytes = Arrays.copyOfRange(encodedMessage, 9, 10); + byte[] sequenceNumberBytes = Arrays.copyOfRange(encodedMessage, 10, 12); + byte[] xmlMsgLengthBytes = Arrays.copyOfRange(encodedMessage, 12, 14); + byte[] xmlMessagebytes = Arrays.copyOfRange(encodedMessage, 14, encodedMessage.length); + + + byte messageVersionNumber = messageVersionNumberBytes[0]; + short ackNumber = bytesToShort(ackNumberBytes); + long timeStamp = bytesToLong(timeStampBytes); + XMLMessageType xmlMsgSubType = XMLMessageType.fromByte(xmlMsgSubTypeBytes[0]); + short sequenceNumber = bytesToShort(sequenceNumberBytes); + short xmlMsgLength = bytesToShort(xmlMsgLengthBytes); + String xmlMessage = new String(xmlMessagebytes); + + + message = new XMLMessage( + messageVersionNumber, + ackNumber, + timeStamp, + xmlMsgSubType, + sequenceNumber, + xmlMessage ); } - /** - * Returns the contents of the XML message (e.g., the contents of a race.xml file). - * @return The contents of the XML message. + * Returns the decoded message. + * @return The decoded message. */ - public String getXmlMessageContents() { - return xmlMessage; + public XMLMessage getMessage() { + return message; } - } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index 8eff9cae..52bff99a 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -31,6 +31,8 @@ public class EncoderFactory { case HEARTBEAT: return new HeartBeatEncoder(); + case XMLMESSAGE: return new XMLMessageEncoder(); + case BOATLOCATION: return new BoatLocationEncoder(); case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 51e232d7..b6b7c774 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -170,43 +170,6 @@ public class RaceVisionByteEncoder { } - /** - * Serializes an xml message into a byte array. - * @param xmlMessage The message to serialize. - * @return byte array contaning serialized message. - */ - public static byte[] xmlMessage(XMLMessage xmlMessage) { - - - byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8); - - ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length); - - //ackNumber converted to bytes - byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2); - - //Timestamp converted to bytes. - byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6); - - //sequenceNumber converted to bytes - byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2); - - //xmlMsgLength converted to bytes - byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2); - - - tempOutputByteBuffer.put(xmlMessage.getVersionNumber()); - tempOutputByteBuffer.put(ackNumberBytes); - tempOutputByteBuffer.put(timestampBytes); - tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue()); - tempOutputByteBuffer.put(sequenceNumberBytes); - tempOutputByteBuffer.put(xmlMsgLengthBytes); - tempOutputByteBuffer.put(messageBytes); - - return tempOutputByteBuffer.array(); - - } - public static byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){ int messageVersionNumber = 0b1; byte[] byteTime = longToBytes(time, 6); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java new file mode 100644 index 00000000..92a75ca7 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java @@ -0,0 +1,62 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.XMLMessage; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link XMLMessage} message. + */ +public class XMLMessageEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public XMLMessageEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + XMLMessage xmlMessage = (XMLMessage) message; + + + byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8); + + //Message is 14 + xmlMessage.length bytes. + ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length); + + //ackNumber converted to bytes + byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2); + + //Timestamp converted to bytes. + byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6); + + //sequenceNumber converted to bytes + byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2); + + //xmlMsgLength converted to bytes + byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2); + + + tempOutputByteBuffer.put(xmlMessage.getVersionNumber()); + tempOutputByteBuffer.put(ackNumberBytes); + tempOutputByteBuffer.put(timestampBytes); + tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue()); + tempOutputByteBuffer.put(sequenceNumberBytes); + tempOutputByteBuffer.put(xmlMsgLengthBytes); + tempOutputByteBuffer.put(messageBytes); + + return tempOutputByteBuffer.array(); + + } +} diff --git a/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java b/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java index 7414d709..c7c843e5 100644 --- a/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java @@ -2,6 +2,7 @@ package network; import network.Exceptions.InvalidMessageException; import network.MessageDecoders.XMLMessageDecoder; +import network.MessageDecoders.XMLMessageDecoderTest; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.AC35Data; import network.Messages.Enums.MessageType; @@ -21,29 +22,46 @@ import java.nio.charset.StandardCharsets; import static org.junit.Assert.fail; /** - * Created by hba56 on 21/04/17. + * Tests the binary message decoder and encoder for a variety of messages. */ public class BinaryMessageDecoderTest { + + + /** + * Tests if an XMLMessage can be encoded and decoded correctly. + */ @Test - public void decodeTest(){ - try{ - String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8); + public void xmlMessageTest() throws Exception { + + try { + + String filePath = "network/raceXML/Regatta.xml"; + XMLMessageType messageType = XMLMessageType.REGATTA; + + String xmlString = XMLReader.readXMLFileToString(filePath, StandardCharsets.UTF_8); long time = System.currentTimeMillis(); - XMLMessage xmlMessagePre = new XMLMessage( + XMLMessage xmlMessage = new XMLMessage( (byte)1, 1, time, - XMLMessageType.REGATTA, + messageType, (short)1, - xmlString ); + xmlString ); + + byte[] encodedMessage = RaceVisionByteEncoder.encode(xmlMessage); + - byte[] encodedMessage = RaceVisionByteEncoder.xmlMessage(xmlMessagePre); + BinaryMessageEncoder encoder = new BinaryMessageEncoder( + xmlMessage.getType(), + time, + 1, + (short)encodedMessage.length, + encodedMessage ); - BinaryMessageEncoder testMessage = new BinaryMessageEncoder(MessageType.XMLMESSAGE, time, 1, (short)encodedMessage.length, encodedMessage); + BinaryMessageDecoder decoder = new BinaryMessageDecoder(encoder.getFullMessage()); - BinaryMessageDecoder decoder = new BinaryMessageDecoder(testMessage.getFullMessage()); AC35Data message = null; try { @@ -56,7 +74,7 @@ public class BinaryMessageDecoderTest { if (!(message instanceof XMLMessage)){ Assert.assertFalse(true); } - XMLMessage xmlMessage = (XMLMessage) message; + XMLMessage xmlMessageDecoded = (XMLMessage) message; //message length @@ -66,29 +84,17 @@ public class BinaryMessageDecoderTest { //source ID Assert.assertEquals((short) 1, decoder.getHeaderSourceID()); //message type - Assert.assertEquals(26, decoder.getHeaderMessageType()); - - XMLMessageDecoder decoderXML = new XMLMessageDecoder(decoder.getMessageBody()); - decoderXML.decode(); - - //tests from seng302.Networking.MessageDecoders.XMLMessageDecoderTest to make sure the file is still good - Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber()); - Assert.assertEquals((short)1, decoderXML.getAckNumber()); - Assert.assertEquals(time, decoderXML.getTimeStamp()); - Assert.assertEquals(XMLMessageType.REGATTA, decoderXML.getXmlMsgSubType()); - Assert.assertEquals((short)1, decoderXML.getSequenceNumber()); - Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength()); - -// Reader reader = decoderXML.getXmlMessageInputStream().getCharacterStream(); -// int c; -// String contents = ""; -// while((c = reader.read()) != -1) { -// contents += (char)c; -// } -// Assert.assertEquals(xmlString.toString(), contents); + Assert.assertEquals(MessageType.XMLMESSAGE.getValue(), decoder.getHeaderMessageType()); + + + XMLMessageDecoderTest.compareXMLMessages(xmlMessage, xmlMessageDecoded); + } catch (XMLReaderException | TransformerException e){ fail("couldn't read file" + e.getMessage()); } } + + //TODO add some tests for more messages types. + } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index 18893609..22831eaa 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -16,7 +16,7 @@ public class BoatLocationDecoderTest { * Creates a BoatLocation message, encodes it, decodes it, and checks that the result matches the starting message. */ @Test - public void getByteArrayTest() throws Exception { + public void boatLocationEncodeDecodeTest() throws Exception { //Create message. long time = System.currentTimeMillis(); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java index b0d0eee3..fb0683a3 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.Enums.XMLMessageType; import network.Messages.XMLMessage; @@ -14,13 +15,21 @@ import java.nio.charset.StandardCharsets; import static org.junit.Assert.fail; /** - * Created by hba56 on 20/04/17. + * Test for the XMLMessage encoder and decoder */ public class XMLMessageDecoderTest { - @Test - public void getByteArrayTest(){ - try{ - String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8); + + + /** + * Creates an XML message of the given type, using the specified filePath, encodes it, decodes it, and checks that the result matches the starting message. + * @param filePath The file path for xml file. + * @param type The type of xml file. + * @throws InvalidMessageException Thrown if message cannot be encoded. + */ + private void xmlMessageTest(String filePath, XMLMessageType type) throws InvalidMessageException { + + try { + String xmlString = XMLReader.readXMLFileToString(filePath, StandardCharsets.UTF_8); long time = System.currentTimeMillis(); @@ -28,27 +37,67 @@ public class XMLMessageDecoderTest { (byte)1, 1, time, - XMLMessageType.REGATTA, + type, (short)1, xmlString ); - byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(message); + byte[] encodedXML = RaceVisionByteEncoder.encode(message); XMLMessageDecoder decoderXML = new XMLMessageDecoder(encodedXML); - decoderXML.decode(); + XMLMessage decodedMessage = decoderXML.getMessage(); + + compareXMLMessages(message, decodedMessage); - Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber()); - Assert.assertEquals((short)1, decoderXML.getAckNumber()); - Assert.assertEquals(time, decoderXML.getTimeStamp()); - Assert.assertEquals(XMLMessageType.REGATTA, decoderXML.getXmlMsgSubType()); - Assert.assertEquals((short)1, decoderXML.getSequenceNumber()); - Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength()); } catch (XMLReaderException | TransformerException e){ fail("couldn't read file" + e.getMessage()); } + + } + + /** + * Compares two XML messages to check that they are the same. + * @param originalMessage The first message to test. + * @param decodedMessage The second message to test. + */ + public static void compareXMLMessages(XMLMessage originalMessage, XMLMessage decodedMessage) { + + Assert.assertEquals(originalMessage.getVersionNumber(), decodedMessage.getVersionNumber()); + Assert.assertEquals(originalMessage.getAckNumber(), decodedMessage.getAckNumber()); + Assert.assertEquals(originalMessage.getTimeStamp(), decodedMessage.getTimeStamp()); + Assert.assertEquals(originalMessage.getXmlMsgSubType(), decodedMessage.getXmlMsgSubType()); + Assert.assertEquals(originalMessage.getSequenceNumber(), decodedMessage.getSequenceNumber()); + Assert.assertEquals(originalMessage.getXmlMsgLength(), decodedMessage.getXmlMsgLength()); + Assert.assertEquals(originalMessage.getXmlMessage(), decodedMessage.getXmlMessage()); + + } + + /** + * Tests if a regatta.xml message can be encoded and decoded. + */ + @Test + public void regattaXMLMessageTest() throws Exception { + xmlMessageTest("network/raceXML/Regatta.xml", XMLMessageType.REGATTA); + } + + + /** + * Tests if a race.xml message can be encoded and decoded. + */ + @Test + public void raceXMLMessageTest() throws Exception { + xmlMessageTest("network/raceXML/Race.xml", XMLMessageType.RACE); + } + + + /** + * Tests if a boat.xml message can be encoded and decoded. + */ + @Test + public void boatXMLMessageTest() throws Exception { + xmlMessageTest("network/raceXML/Boats.xml", XMLMessageType.BOAT); } } diff --git a/racevisionGame/src/test/java/network/XMLMessageEncoderTest.java b/racevisionGame/src/test/java/network/XMLMessageEncoderTest.java deleted file mode 100644 index 44a99997..00000000 --- a/racevisionGame/src/test/java/network/XMLMessageEncoderTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package network; - -import network.MessageEncoders.RaceVisionByteEncoder; -import network.Messages.Enums.XMLMessageType; -import network.Messages.XMLMessage; -import org.junit.Assert; -import org.junit.Test; -import shared.dataInput.XMLReader; -import shared.exceptions.XMLReaderException; - -import javax.xml.transform.TransformerException; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - -import static org.junit.Assert.fail; - -/** - * Created by hba56 on 19/04/17. - */ -public class XMLMessageEncoderTest { - @Test - public void getByteArrayTest() { - try { - - String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8); - - - XMLMessage message = new XMLMessage( - (byte)1, - 1, - System.currentTimeMillis(), - XMLMessageType.REGATTA, - (short)1, - xmlString ); - - int xmlMessageLength = xmlString.getBytes().length; - - byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(message); - - //1 + 2 + 6 + 1 + 2 + 2 + xml.byteLength - Assert.assertEquals(14 + xmlMessageLength, encodedXML.length); - - } catch (XMLReaderException | TransformerException e){ - fail("couldn't read file" + e.getMessage()); - } - } - -} diff --git a/racevisionGame/src/test/resources/network/raceXML/Boats.xml b/racevisionGame/src/test/resources/network/raceXML/Boats.xml new file mode 100644 index 00000000..ed4b6ded --- /dev/null +++ b/racevisionGame/src/test/resources/network/raceXML/Boats.xml @@ -0,0 +1,119 @@ + + + + 2017-04-19T15:49:40+1200 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/test/resources/network/raceXML/Race.xml b/racevisionGame/src/test/resources/network/raceXML/Race.xml new file mode 100644 index 00000000..29478c60 --- /dev/null +++ b/racevisionGame/src/test/resources/network/raceXML/Race.xml @@ -0,0 +1,58 @@ + + + 17041901 + Fleet + 2017-04-19T15:30:00+1200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From e464ee298e3e56360085eacf8a09d78889992e58 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 01:08:47 +1200 Subject: [PATCH 36/53] javadoc fixes for some warnings. --- .../src/test/java/network/BinaryMessageDecoderTest.java | 1 + .../network/MessageDecoders/BoatActionDecoderTest.java | 8 ++++++++ .../network/MessageDecoders/BoatLocationDecoderTest.java | 1 + .../network/MessageDecoders/HeartBeatDecoderTest.java | 4 ++++ .../MessageDecoders/JoinAcceptanceDecoderTest.java | 7 +++++++ .../network/MessageDecoders/RequestToJoinDecoderTest.java | 5 +++++ .../network/MessageDecoders/XMLMessageDecoderTest.java | 3 +++ 7 files changed, 29 insertions(+) diff --git a/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java b/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java index c7c843e5..55ac00be 100644 --- a/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java @@ -29,6 +29,7 @@ public class BinaryMessageDecoderTest { /** * Tests if an XMLMessage can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void xmlMessageTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java index 1a16f669..d7e13b97 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java @@ -21,6 +21,7 @@ public class BoatActionDecoderTest { * Encodes and decodes a given message. * @param message Message to encode/decode. * @return The decoded message. + * @throws InvalidMessageException If the message cannot be encoded. */ private BoatAction encodeDecodeMessage(BoatAction message) throws InvalidMessageException { @@ -40,6 +41,7 @@ public class BoatActionDecoderTest { /** * Tests if a specific boat action type message can be encoded and decoded correctly. * @param type The type of boat action. + * @throws Exception if test fails. */ private void boatActionTypeTest(BoatActionEnum type) throws Exception { @@ -59,6 +61,7 @@ public class BoatActionDecoderTest { /** * Tests if an autopilot message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void autoPilotTest() throws Exception { @@ -67,6 +70,7 @@ public class BoatActionDecoderTest { /** * Tests if a sails in message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void sailsInTest() throws Exception { @@ -75,6 +79,7 @@ public class BoatActionDecoderTest { /** * Tests if a sails out message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void sailsOutTest() throws Exception { @@ -83,6 +88,7 @@ public class BoatActionDecoderTest { /** * Tests if a tack/gybe message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void tackGybeTest() throws Exception { @@ -91,6 +97,7 @@ public class BoatActionDecoderTest { /** * Tests if an upwind message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void upwindTest() throws Exception { @@ -99,6 +106,7 @@ public class BoatActionDecoderTest { /** * Tests if a downwind message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void downwindTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index 22831eaa..05964aaf 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -14,6 +14,7 @@ public class BoatLocationDecoderTest { /** * Creates a BoatLocation message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. */ @Test public void boatLocationEncodeDecodeTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java index ca1086e7..778a667a 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java @@ -20,6 +20,7 @@ public class HeartBeatDecoderTest { * Encodes and decodes a given message. * @param message Message to encode/decode. * @return The decoded message. + * @throws InvalidMessageException if the message cannot be encoded. */ private HeartBeat encodeDecodeMessage(HeartBeat message) throws InvalidMessageException { @@ -37,6 +38,7 @@ public class HeartBeatDecoderTest { /** * Tests if a heartbeat message with a given sequence number can be encoded and decoded correctly. * @param sequenceNumber The sequenceNumber to use. + * @throws Exception if test fails. */ private void heartBeatTest(long sequenceNumber) throws Exception { @@ -56,6 +58,7 @@ public class HeartBeatDecoderTest { /** * Tests if a heartbeat message with a sequence number of zero can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void heartBeatZeroTest() throws Exception { @@ -64,6 +67,7 @@ public class HeartBeatDecoderTest { /** * Tests if a heartbeat message with a sequence number of 1234512 can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void heartBeatNonZeroTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java index 5cefe5ea..cd31ad67 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java @@ -19,6 +19,7 @@ public class JoinAcceptanceDecoderTest { * Encodes and decodes a given message. * @param message Message to encode/decode. * @return The decoded message. + * @throws InvalidMessageException If the message cannot be encoded. */ private JoinAcceptance encodeDecodeMessage(JoinAcceptance message) throws InvalidMessageException { @@ -37,6 +38,7 @@ public class JoinAcceptanceDecoderTest { * Tests if a specific acceptance type message can be encoded and decoded correctly. * @param type The type of acceptance response. * @param sourceID The source ID to assign. + * @throws Exception if test fails. */ private void responseTypeTest(JoinAcceptanceEnum type, int sourceID) throws Exception { @@ -57,6 +59,7 @@ public class JoinAcceptanceDecoderTest { /** * Tests if a join success message, with a source ID, can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void joinSuccessSourceIDTest() throws Exception { @@ -65,6 +68,7 @@ public class JoinAcceptanceDecoderTest { /** * Tests if a join success message, with no source ID, can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void joinSuccessNoSourceIDTest() throws Exception { @@ -73,6 +77,7 @@ public class JoinAcceptanceDecoderTest { /** * Tests if a participants full message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void participantFullTest() throws Exception { @@ -81,6 +86,7 @@ public class JoinAcceptanceDecoderTest { /** * Tests if a ghosts full message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void ghostFullTest() throws Exception { @@ -89,6 +95,7 @@ public class JoinAcceptanceDecoderTest { /** * Tests if a server full message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void serverFullTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java index ddfd11bd..bde4ae75 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java @@ -20,6 +20,7 @@ public class RequestToJoinDecoderTest { * Encodes and decodes a given message. * @param message Message to encode/decode. * @return The decoded message. + * @throws InvalidMessageException If the message cannot be encoded. */ private RequestToJoin encodeDecodeMessage(RequestToJoin message) throws InvalidMessageException { @@ -37,6 +38,7 @@ public class RequestToJoinDecoderTest { /** * Tests if a specific request type message can be encoded and decoded correctly. * @param type The type of join request. + * @throws Exception if test fails. */ private void requestTypeTest(RequestToJoinEnum type) throws Exception { @@ -56,6 +58,7 @@ public class RequestToJoinDecoderTest { /** * Tests if a spectator request message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void spectatorTest() throws Exception { @@ -64,6 +67,7 @@ public class RequestToJoinDecoderTest { /** * Tests if a participant request message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void participantTest() throws Exception { @@ -72,6 +76,7 @@ public class RequestToJoinDecoderTest { /** * Tests if a ghost request message can be encoded and decoded correctly. + * @throws Exception if test fails. */ @Test public void ghostTest() throws Exception { diff --git a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java index fb0683a3..32d1a61e 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java @@ -77,6 +77,7 @@ public class XMLMessageDecoderTest { /** * Tests if a regatta.xml message can be encoded and decoded. + * @throws Exception if test fails. */ @Test public void regattaXMLMessageTest() throws Exception { @@ -86,6 +87,7 @@ public class XMLMessageDecoderTest { /** * Tests if a race.xml message can be encoded and decoded. + * @throws Exception if test fails. */ @Test public void raceXMLMessageTest() throws Exception { @@ -95,6 +97,7 @@ public class XMLMessageDecoderTest { /** * Tests if a boat.xml message can be encoded and decoded. + * @throws Exception if test fails. */ @Test public void boatXMLMessageTest() throws Exception { From b1922fc3fc7be6fb6d208ff5d96d745d97ef43ab Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 13:38:27 +1200 Subject: [PATCH 37/53] Added RaceStatusEncoder. Refactored RaceStatusDecoder to be more consistent with other decoders - it now has a getMessage() function. Added BoatStatus encoder and decoder - the RaceStatus encoder and decoder uses this for BoatStatuses. The BoatStatus encoder doesn't implement the MessageEncoder interface as BoatStatus is not a proper message type (doesn't inherit from AC35Data). Added remaining cases to EncoderFactory, but commented them out. BoatStatus now uses BoatStatusEnum instead of a byte. Added some comments to RaceStatus, and it uses enums instead of bytes. MockOutput logs a warning if a RaceStatus cannot be encoded. Added a BoatStatusDecoderTest. Updated RaceStatusDecoder to use new encoders/decoders. issue #35 #36 #story[1095] --- .../src/main/java/mock/app/MockOutput.java | 15 +- .../src/main/java/mock/model/RaceServer.java | 5 +- .../java/network/BinaryMessageDecoder.java | 2 +- .../MessageDecoders/BoatStatusDecoder.java | 90 +++++++++ .../MessageDecoders/RaceStatusDecoder.java | 177 +++++++++--------- .../MessageEncoders/BoatStatusEncoder.java | 56 ++++++ .../MessageEncoders/EncoderFactory.java | 18 ++ .../MessageEncoders/HeartBeatEncoder.java | 1 - .../MessageEncoders/RaceStatusEncoder.java | 90 +++++++++ .../RaceVisionByteEncoder.java | 51 ----- .../java/network/Messages/BoatLocation.java | 18 +- .../java/network/Messages/BoatStatus.java | 8 +- .../network/Messages/Enums/MessageType.java | 8 +- .../java/network/Messages/RaceStatus.java | 137 +++++++++++--- .../java/visualiser/model/VisualiserRace.java | 4 +- .../BoatStatusDecoderTest.java | 90 +++++++++ .../RaceStatusDecoderTest.java | 103 +++++++--- 17 files changed, 659 insertions(+), 214 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index 2119fa06..328c5e79 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -185,11 +185,12 @@ public class MockOutput implements Runnable * Encodes/serialises a RaceStatus message, and returns it. * @param raceStatus The RaceStatus message to serialise. * @return The RaceStatus message in a serialised form. + * @throws InvalidMessageException Thrown if the message cannot be encoded. */ - private synchronized byte[] parseRaceStatus(RaceStatus raceStatus){ + private synchronized byte[] parseRaceStatus(RaceStatus raceStatus) throws InvalidMessageException { //Encodes the messages. - byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus); + byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatus); //Encodes the full message with header. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( @@ -284,9 +285,15 @@ public class MockOutput implements Runnable //Sends the RaceStatus message. if (this.latestMessages.getRaceStatus() != null) { - byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); - this.outToVisualiser.write(raceStatusBlob); + try { + byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); + + this.outToVisualiser.write(raceStatusBlob); + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode RaceStatus: " + latestMessages.getRaceStatus(), e); + } } //Send all of the BoatLocation messages. diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index c7e3ab69..b68ada32 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -139,13 +139,14 @@ public class RaceServer { //Create race status object, and send it. RaceStatus raceStatus = new RaceStatus( + RaceStatus.currentMessageVersionNumber, System.currentTimeMillis(), race.getRaceId(), - race.getRaceStatusEnum().getValue(), + race.getRaceStatusEnum(), race.getRaceClock().getStartingTimeMilli(), windDirectionInt, windSpeedInt, - race.getRaceType().getValue(), + race.getRaceType(), boatStatuses); this.latestMessages.setRaceStatus(raceStatus); diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 34a5d88d..eb9c6997 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -139,7 +139,7 @@ public class BinaryMessageDecoder { case RACESTATUS: //System.out.println("Race Status Message"); RaceStatusDecoder rsdecoder = new RaceStatusDecoder(messageBody); - return new RaceStatus(rsdecoder.getTime(), rsdecoder.getRace(), rsdecoder.getRaceState(), rsdecoder.getStartTime(), rsdecoder.getRaceWindDir(), rsdecoder.getRaceWindSpeed(), rsdecoder.getRaceType(), rsdecoder.getBoats()); + return rsdecoder.getMessage(); case DISPLAYTEXTMESSAGE: //System.out.println("Display Text Message"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java new file mode 100644 index 00000000..dd482486 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java @@ -0,0 +1,90 @@ +package network.MessageDecoders; + + +import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static network.Utils.ByteConverter.*; + + +/** + * Decodes {@link BoatStatus} messages. + */ +public class BoatStatusDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private BoatStatus message; + + + + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public BoatStatusDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + + byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 0, 4); + int sourceID = bytesToInt(sourceIDBytes); + + byte[] boatStatusBytes = Arrays.copyOfRange(encodedMessage, 4, 5); + BoatStatusEnum boatStatus = BoatStatusEnum.fromByte(boatStatusBytes[0]); + + byte[] legNumberBytes = Arrays.copyOfRange(encodedMessage, 5, 6); + byte legNumber = legNumberBytes[0]; + + byte[] numPenaltiesAwardedBytes = Arrays.copyOfRange(encodedMessage, 6, 7); + byte numPenaltiesAwarded = numPenaltiesAwardedBytes[0]; + + byte[] numPenaltiesServedBytes = Arrays.copyOfRange(encodedMessage, 7, 8); + byte numPenaltiesServed = numPenaltiesServedBytes[0]; + + byte[] estTimeAtNextMarkBytes = Arrays.copyOfRange(encodedMessage, 8, 14); + long estTimeAtNextMark = bytesToLong(estTimeAtNextMarkBytes); + + byte[] estTimeAtFinishBytes = Arrays.copyOfRange(encodedMessage, 14, 20); + long estTimeAtFinish = bytesToLong(estTimeAtFinishBytes); + + message = new BoatStatus( + sourceID, + boatStatus, + legNumber, + numPenaltiesAwarded, + numPenaltiesServed, + estTimeAtNextMark, + estTimeAtFinish ); + + + } + + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public BoatStatus getMessage() { + return message; + } +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index e4d147df..4b67daa5 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -2,9 +2,13 @@ package network.MessageDecoders; import network.Messages.BoatStatus; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; +import network.Messages.RaceStatus; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import static network.Utils.ByteConverter.bytesToInt; import static network.Utils.ByteConverter.bytesToLong; @@ -12,110 +16,101 @@ import static network.Utils.ByteConverter.bytesToShort; /** - * Created by hba56 on 21/04/17. + * Decodes {@link RaceStatus} messages. */ public class RaceStatusDecoder { - private byte versionNum; - private byte[] timeBytes; - private byte[] raceID; - private byte raceStatus; - private byte[] expectedStart; - private byte[] raceWind; - private byte[] windSpeed; - private byte numBoats; - private byte bytesRaceType; - private byte[] boatsBytes; - - private long time; - private int race; - private byte raceState; - private long startTime; - private int raceWindDir; - private short raceWindSpeed; - private int numberOfBoats; - private int raceType; - private ArrayList boats = new ArrayList<>(); - - - public RaceStatusDecoder(byte[] encodedRaceStatus){ - versionNum = encodedRaceStatus[0]; - timeBytes = Arrays.copyOfRange(encodedRaceStatus, 1, 7); - raceID = Arrays.copyOfRange(encodedRaceStatus, 7, 11); - raceStatus = encodedRaceStatus[11]; - expectedStart = Arrays.copyOfRange(encodedRaceStatus, 12, 18); - raceWind = Arrays.copyOfRange(encodedRaceStatus, 18, 20); - windSpeed = Arrays.copyOfRange(encodedRaceStatus, 20, 22); - numBoats = encodedRaceStatus[22]; - bytesRaceType = encodedRaceStatus[23]; - boatsBytes = Arrays.copyOfRange(encodedRaceStatus, 24, 25+20*this.numBoats); - - time = bytesToLong(timeBytes); - race = bytesToInt(raceID); - raceState = raceStatus; - startTime = bytesToLong(expectedStart); - raceWindDir = bytesToInt(raceWind); - raceWindSpeed = bytesToShort(windSpeed); - numberOfBoats = bytesToInt(numBoats); - - int boatLoopIndex = 0; - - for (int i=0; i < numberOfBoats; i++) { - byte[] boatBytes = Arrays.copyOfRange(boatsBytes, boatLoopIndex, boatLoopIndex+20); - - byte[] sourceID = Arrays.copyOfRange(boatBytes, 0, 3); - byte boatStatus = boatBytes[4]; - byte legNumber = boatBytes[5]; - byte numPenaltiesAwarded = boatBytes[6]; - byte numPenaltiesServed = boatBytes[7]; - byte[] estTimeAtNextMark = Arrays.copyOfRange(boatBytes, 8, 14); - byte[] estTimeAtFinish = Arrays.copyOfRange(boatBytes, 14, 20); - - BoatStatus boat = new BoatStatus(bytesToInt(sourceID),boatStatus, - legNumber, numPenaltiesAwarded, numPenaltiesServed, - bytesToLong(estTimeAtNextMark), bytesToLong(estTimeAtFinish)); - - boats.add(boat); - boatLoopIndex += 20; - } - } - public byte getVersionNum() { - return versionNum; - } + /** + * The encoded message. + */ + private byte[] encodedMessage; - public long getTime() { - return time; - } + /** + * The decoded message. + */ + private RaceStatus message; - public int getRace() { - return race; - } - public byte getRaceState() { - return raceState; - } - public long getStartTime() { - return startTime; - } + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public RaceStatusDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; - public int getRaceWindDir() { - return raceWindDir; + decode(); } - public short getRaceWindSpeed() { - return raceWindSpeed; - } - public int getNumberOfBoats() { - return numberOfBoats; - } + /** + * Decodes the contained message. + */ + private void decode() { + + + byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte versionNum = versionNumBytes[0]; + + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); + + byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int raceID = bytesToInt(raceIDBytes); + + byte[] raceStatusBytes = Arrays.copyOfRange(encodedMessage, 11, 12); + RaceStatusEnum raceStatus = RaceStatusEnum.fromByte(raceStatusBytes[0]); + + byte[] expectedStartBytes = Arrays.copyOfRange(encodedMessage, 12, 18); + long expectedStart = bytesToLong(expectedStartBytes); - public int getRaceType() { - return raceType; + byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20); + int windDirection = bytesToInt(windDirectionBytes); + + byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22); + short windSpeed = bytesToShort(windSpeedBytes); + + byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23); + int numberOfBoats = bytesToInt(numberOfBoatsBytes); + + byte[] raceTypeBytes = Arrays.copyOfRange(encodedMessage, 23, 24); + RaceTypeEnum raceType = RaceTypeEnum.fromByte(raceTypeBytes[0]); + + byte[] boatStatusesBytes = Arrays.copyOfRange(encodedMessage, 24, 25 + 20 * numberOfBoats); + List boatStatuses = new ArrayList<>(); + + + + //Decode each BoatStatus. + for (int boatLoopIndex=0; boatLoopIndex < (numberOfBoats * 20); boatLoopIndex += 20) { + + byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + 20); + + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(boatStatusBytes); + + boatStatuses.add(boatStatusDecoder.getMessage()); + } + + + message = new RaceStatus( + versionNum, + time, + raceID, + raceStatus, + expectedStart, + windDirection, + windSpeed, + raceType, + boatStatuses ); } - public ArrayList getBoats() { - return boats; + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public RaceStatus getMessage() { + return message; } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java new file mode 100644 index 00000000..3a40cbba --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java @@ -0,0 +1,56 @@ +package network.MessageEncoders; + + +import network.Messages.BoatStatus; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link BoatStatus} message. + */ +public class BoatStatusEncoder { + + + /** + * Constructor. + */ + public BoatStatusEncoder() { + } + + + /** + * Encodes a given BoatStatus message. + * @param message The message to encode. + * @return The encoded message. + */ + public byte[] encode(BoatStatus message) { + + //Downcast. + BoatStatus boatStatus = (BoatStatus) message; + + //BoatStatus is 20 bytes. + ByteBuffer boatStatusBuffer = ByteBuffer.allocate(20); + + byte[] sourceID = intToBytes(boatStatus.getSourceID()); + byte[] boatStatusBytes = intToBytes(boatStatus.getBoatStatus().getValue(), 1); + byte[] legNum = intToBytes(boatStatus.getLegNumber(), 1); + byte[] numPenalties = intToBytes(boatStatus.getNumPenaltiesAwarded(), 1); + byte[] numPenaltiesServed = intToBytes(boatStatus.getNumPenaltiesServed(), 1); + byte[] estNextMarkTime = longToBytes(boatStatus.getEstTimeAtNextMark(), 6); + byte[] estFinishTime = longToBytes(boatStatus.getEstTimeAtFinish(), 6); + + boatStatusBuffer.put(sourceID); + boatStatusBuffer.put(boatStatusBytes); + boatStatusBuffer.put(legNum); + boatStatusBuffer.put(numPenalties); + boatStatusBuffer.put(numPenaltiesServed); + boatStatusBuffer.put(estNextMarkTime); + boatStatusBuffer.put(estFinishTime); + + return boatStatusBuffer.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index 52bff99a..c026efd7 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -31,10 +31,28 @@ public class EncoderFactory { case HEARTBEAT: return new HeartBeatEncoder(); + case RACESTATUS: return new RaceStatusEncoder(); + + //case DISPLAYTEXTMESSAGE: return new DisplayTextMessageEncoder();//TODO + case XMLMESSAGE: return new XMLMessageEncoder(); + //case RACESTARTSTATUS: return new RaceStartStatusEncoder();//TODO + + //case YACHTEVENTCODE: return new YachtEventCodeEncoder();//TODO + + //case YACHTACTIONCODE: return new YachtActionCodeEncoder();//TODO + + //case CHATTERTEXT: return new ChatterTextEncoder();//TODO + case BOATLOCATION: return new BoatLocationEncoder(); + //case MARKROUNDING: return new MarkRoundingEncoder();//TODO + + //case COURSEWIND: return new CourseWindEncoder();//TODO + + //case AVGWIND: return new AverageWindEncoder();//TODO + case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java index 934d1217..dcf64b61 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java @@ -6,7 +6,6 @@ import network.Messages.HeartBeat; import java.nio.ByteBuffer; -import static network.Utils.ByteConverter.intToBytes; import static network.Utils.ByteConverter.longToBytes; /** diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java new file mode 100644 index 00000000..7a0be153 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java @@ -0,0 +1,90 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.BoatStatus; +import network.Messages.RaceStatus; + +import java.nio.ByteBuffer; +import java.util.List; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link RaceStatus} message. + */ +public class RaceStatusEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public RaceStatusEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + RaceStatus raceStatus = (RaceStatus) message; + + + List boatStatuses = raceStatus.getBoatStatuses(); + + //24 byte header, plus 20 bytes per boat status. + ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20 * boatStatuses.size()); + + //Version Number 1 bytes. this changes with the pdf. (2) + byte versionNum = 0b10; + + //time (6 bytes) + byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6); + + //race identifier in case multiple races are going at once. + byte[] raceID = intToBytes(raceStatus.getRaceID()); + + //race status 0 - 10 + byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus().getValue(), 1); + + //number of milliseconds from Jan 1, 1970 for when the data is valid + byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6); + + //North = 0x0000 East = 0x4000 South = 0x8000. + byte[] raceWind = intToBytes(raceStatus.getWindDirection(), 2); + + //mm/sec + byte[] windSpeed = intToBytes(raceStatus.getWindSpeed(), 2); + + + byte[] numBoats = intToBytes(boatStatuses.size(), 1); + + //1 match race, 2 fleet race + byte[] bytesRaceType = intToBytes(raceStatus.getRaceType().getValue(), 1); + + + raceStatusMessage.put(versionNum); + raceStatusMessage.put(timeBytes); + raceStatusMessage.put(raceID); + raceStatusMessage.put(raceStatusByte); + raceStatusMessage.put(expectedStart); + raceStatusMessage.put(raceWind); + raceStatusMessage.put(windSpeed); + raceStatusMessage.put(numBoats); + raceStatusMessage.put(bytesRaceType); + + //Encode each BoatStatus. + for (BoatStatus boatStatus : boatStatuses) { + + BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); + + byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); + + raceStatusMessage.put(boatStatusEncoded); + } + + return raceStatusMessage.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index b6b7c774..ebd67819 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -21,57 +21,6 @@ public class RaceVisionByteEncoder { - /** - * Serializes a RaceStatus message. - * @param raceStatus Message to serialize. - * @return Serialized (byte array) message, ready to be written to a socket. - */ - public static byte[] raceStatus(RaceStatus raceStatus){ - - List boatStatuses = raceStatus.getBoatStatuses(); - - ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20* boatStatuses.size()); - //Version Number 1 bytes - byte versionNum = 0b10; //this changes with the pdf. (2) - byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6);//time (6 bytes) - byte[] raceID = ByteBuffer.allocate(4).put(intToBytes(raceStatus.getRaceID())).array();//race identifier incase multiple races are going at once. - byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus(), 1);//race status 0 - 10 - byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6);//number of milliseconds from Jan 1, 1970 for when the data is valid - byte[] raceWind = ByteBuffer.allocate(2).put(intToBytes(raceStatus.getWindDirection(), 2)).array();//North = 0x0000 East = 0x4000 South = 0x8000. - byte[] windSpeed = ByteBuffer.allocate(2).put(intToBytes(raceStatus.getWindSpeed(), 2)).array();//mm/sec - byte[] numBoats = intToBytes(boatStatuses.size(), 1); - byte[] bytesRaceType = intToBytes(raceStatus.getRaceType(), 1);//1 match race, 2 fleet race - - raceStatusMessage.put(versionNum); - raceStatusMessage.put(timeBytes); - raceStatusMessage.put(raceID); - raceStatusMessage.put(raceStatusByte); - raceStatusMessage.put(expectedStart); - raceStatusMessage.put(raceWind); - raceStatusMessage.put(windSpeed); - raceStatusMessage.put(numBoats); - raceStatusMessage.put(bytesRaceType); - - for (int i = 0; i < boatStatuses.size(); i++){ - byte[] sourceID = intToBytes(boatStatuses.get(i).getSourceID()); - byte[] boatStatus = intToBytes(boatStatuses.get(i).getBoatStatus(), 1); - byte[] legNum = intToBytes(boatStatuses.get(i).getLegNumber(), 1); - byte[] numPenalties = intToBytes(boatStatuses.get(i).getNumPenaltiesAwarded(), 1); - byte[] numPenaltiesServed = intToBytes(boatStatuses.get(i).getNumPenaltiesServed(), 1); - byte[] estNextMarkTime = longToBytes(boatStatuses.get(i).getEstTimeAtNextMark(), 6); - byte[] estFinishTime = longToBytes( boatStatuses.get(i).getEstTimeAtFinish(), 6); - - raceStatusMessage.put(sourceID); - raceStatusMessage.put(boatStatus); - raceStatusMessage.put(legNum); - raceStatusMessage.put(numPenalties); - raceStatusMessage.put(numPenaltiesServed); - raceStatusMessage.put(estNextMarkTime); - raceStatusMessage.put(estFinishTime); - } - - return raceStatusMessage.array(); - } public static byte[] displayTextMessage(RaceMessage[] message){ //ByteBuffer result = ByteBuffer.allocate(4 + numLines * 32); diff --git a/racevisionGame/src/main/java/network/Messages/BoatLocation.java b/racevisionGame/src/main/java/network/Messages/BoatLocation.java index 301584b4..1f2bfc49 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatLocation.java +++ b/racevisionGame/src/main/java/network/Messages/BoatLocation.java @@ -29,8 +29,14 @@ public class BoatLocation extends AC35Data { public static final byte Helicopter = 12; public static final byte DataProcessingApplication = 13; - ///Version number of the message - is always 1. - private byte messageVersionNumber = 1; + + /** + * The current messageVersionNumber according to the API spec. + */ + public static final byte currentMessageVersionNumber = 1; + + ///Version number of the message. + private byte messageVersionNumber; ///Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int. private long time; ///Source ID of the boat. @@ -91,12 +97,6 @@ public class BoatLocation extends AC35Data { private short rudderAngle; - /** - * Ctor. Default. - */ - public BoatLocation() { - super(MessageType.BOATLOCATION); - } /** * Ctor, with all parameters. @@ -154,7 +154,7 @@ public class BoatLocation extends AC35Data { public BoatLocation(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed, long time) { super(MessageType.BOATLOCATION); - this.messageVersionNumber = (byte) 1; + this.messageVersionNumber = BoatLocation.currentMessageVersionNumber; this.time = time; this.sourceID = sourceID; this.sequenceNumber = sequenceNumber; diff --git a/racevisionGame/src/main/java/network/Messages/BoatStatus.java b/racevisionGame/src/main/java/network/Messages/BoatStatus.java index 54996726..277ae9ca 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatStatus.java +++ b/racevisionGame/src/main/java/network/Messages/BoatStatus.java @@ -10,14 +10,14 @@ import network.Utils.ByteConverter; public class BoatStatus { private int sourceID; - private byte boatStatus; + private BoatStatusEnum boatStatus; private byte legNumber; private byte numPenaltiesAwarded; private byte numPenaltiesServed; private long estTimeAtNextMark; private long estTimeAtFinish; - public BoatStatus(int sourceID, byte boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) { + public BoatStatus(int sourceID, BoatStatusEnum boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) { this.sourceID = sourceID; this.boatStatus = boatStatus; this.legNumber = legNumber; @@ -30,7 +30,7 @@ public class BoatStatus { public BoatStatus(int sourceID, BoatStatusEnum boatStatusEnum, int legNum, long estTimeAtNextMark) { this.sourceID = sourceID; - this.boatStatus = boatStatusEnum.getValue(); + this.boatStatus = boatStatusEnum; this.legNumber = ByteConverter.intToBytes(legNum)[0]; this.numPenaltiesAwarded = 0; this.numPenaltiesServed = 0; @@ -43,7 +43,7 @@ public class BoatStatus { return sourceID; } - public byte getBoatStatus() { + public BoatStatusEnum getBoatStatus() { return boatStatus; } diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java index 4072f912..15f70f40 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java @@ -33,11 +33,15 @@ public enum MessageType { BOATACTION(100), NOTAMESSAGE(0); - ///Primitive value of the enum. + + /** + * Primitive value of the enum. + */ private byte value; + /** - * Ctor. Creates a MessageType enum from a given primitive integer value, cast to a byte. + * Creates a MessageType enum from a given primitive integer value, cast to a byte. * @param value Integer, which is cast to byte, to construct from. */ private MessageType(int value) { diff --git a/racevisionGame/src/main/java/network/Messages/RaceStatus.java b/racevisionGame/src/main/java/network/Messages/RaceStatus.java index 42bb8208..4d98700d 100644 --- a/racevisionGame/src/main/java/network/Messages/RaceStatus.java +++ b/racevisionGame/src/main/java/network/Messages/RaceStatus.java @@ -2,27 +2,89 @@ package network.Messages; import network.Messages.Enums.MessageType; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; import network.Utils.AC35UnitConverter; import shared.model.Constants; import java.util.List; /** - * Created by fwy13 on 25/04/17. + * Represents the information in a RaceStatus message (AC streaming spec: 4.2). */ public class RaceStatus extends AC35Data { + + /** + * The current messageVersionNumber according to the API spec. + */ + public static final byte currentMessageVersionNumber = 2; + + + /** + * Version number of the message. + */ + private byte messageVersionNumber; + + /** + * Time the message was generated at. + * Milliseconds since unix epoch. + */ private long currentTime; + + /** + * ID number of the race. + */ private int raceID; - private byte raceStatus; + + /** + * The status of the race. + */ + private RaceStatusEnum raceStatus; + + /** + * The expected race start time. + * Milliseconds since unix epoch. + */ private long expectedStartTime; + + /** + * The wind direction of the course. + */ private int windDirection; + + /** + * The wind speed of the course. + */ private int windSpeed; - private int raceType; + + /** + * The type of race this is. + */ + private RaceTypeEnum raceType; + + /** + * A list of boat statuses. + * One for each boat. + */ private List boatStatuses; - public RaceStatus(long currentTime, int raceID, byte raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List boatStatuses){ + + /** + * Constructs a RaceStatus message with the given parameters. + * @param messageVersionNumber The version number of the message. + * @param currentTime Time at which the message was generated. + * @param raceID The ID of the race. + * @param raceStatus The status of the race. + * @param expectedStartTime The expected start time of the race. + * @param windDirection The current wind direction in the race. + * @param windSpeed The current wind speed in the race. + * @param raceType The type of race this is. + * @param boatStatuses A list of BoatStatuses. One for each boat. + */ + public RaceStatus(byte messageVersionNumber, long currentTime, int raceID, RaceStatusEnum raceStatus, long expectedStartTime, int windDirection, int windSpeed, RaceTypeEnum raceType, List boatStatuses) { super(MessageType.RACESTATUS); + this.messageVersionNumber = messageVersionNumber; this.currentTime = currentTime; this.raceID = raceID; this.raceStatus = raceStatus; @@ -30,37 +92,58 @@ public class RaceStatus extends AC35Data { this.windDirection = windDirection; this.windSpeed = windSpeed; this.raceType = raceType; - this.boatStatuses = boatStatuses;//note this is not a copy so any alterations to the parent will affect this. + this.boatStatuses = boatStatuses; } + /** + * Returns the version number of this message. + * @return The version number of the message. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } - ///Getters. - + /** + * Returns the current time at which this message was generated. Milliseconds since unix epoch. + * @return Time this message was generated at. + */ public long getCurrentTime() { return currentTime; } + /** + * Returns the RaceID. + * @return The raceID. + */ public int getRaceID() { return raceID; } /** - * - * @return race status number + * Returns the race status. + * @return The current race status. */ - public byte getRaceStatus() + public RaceStatusEnum getRaceStatus() { return raceStatus; } + /** + * Returns the expected start time for the race. Milliseconds since unix epoch. + * @return Expected start time for the race. + */ public long getExpectedStartTime() { return expectedStartTime; } + /** + * Returns the current direction of the wind in the race. + * @return Current wind direction. + */ public int getWindDirection() { return windDirection; @@ -75,60 +158,70 @@ public class RaceStatus extends AC35Data { return windSpeed; } - public int getRaceType() + /** + * Retrusn the type of race this is. + * @return The type of race this is. + */ + public RaceTypeEnum getRaceType() { return raceType; } + /** + * Returns the list of BoatStatuses. One for each boat. + * @return List of BoatStatuses. + */ public List getBoatStatuses() { return boatStatuses; } + public boolean isNotActive() { - return raceStatus == 0; + return raceStatus == RaceStatusEnum.NOT_ACTIVE; } public boolean isWarning() { - return raceStatus == 1; + return raceStatus == RaceStatusEnum.WARNING; } public boolean isPreparatory() { - return raceStatus == 2; + return raceStatus == RaceStatusEnum.PREPARATORY; } public boolean isStarted() { - return raceStatus == 3; + return raceStatus == RaceStatusEnum.STARTED; } public boolean isFinished() { - return raceStatus == 4; + return raceStatus == RaceStatusEnum.FINISHED; } public boolean isRetired() { - return raceStatus == 5; + return raceStatus == RaceStatusEnum.RETIRED; } public boolean isAbandoned() { - return raceStatus == 6; + return raceStatus == RaceStatusEnum.ABANDONED; } public boolean isPostponed() { - return raceStatus == 7; + return raceStatus == RaceStatusEnum.POSTPONED; } public boolean isTerminated() { - return raceStatus == 8; + return raceStatus == RaceStatusEnum.TERMINATED; } public boolean isStartTimeSet() { - return raceStatus != 9; + return raceStatus != RaceStatusEnum.RACE_START_TIME_NOT_SET; } public boolean isPrestart() { - return raceStatus == 10; + return raceStatus == RaceStatusEnum.PRESTART; } + public double getScaledWindDirection() { return AC35UnitConverter.convertHeading(windDirection); } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index 2969fbb8..fdc2802d 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -215,7 +215,7 @@ public class VisualiserRace extends Race implements Runnable { //Boat status. - BoatStatusEnum newBoatStatusEnum = BoatStatusEnum.fromByte(boatStatus.getBoatStatus()); + BoatStatusEnum newBoatStatusEnum = boatStatus.getBoatStatus(); //If we are changing from non-racing to racing, we need to initialise boat with their time at last mark. if ((boat.getStatus() != BoatStatusEnum.RACING) && (newBoatStatusEnum == BoatStatusEnum.RACING)) { @@ -309,7 +309,7 @@ public class VisualiserRace extends Race implements Runnable { private void updateRaceStatus(RaceStatus raceStatus) { //Race status enum. - this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus()); + this.raceStatusEnum = raceStatus.getRaceStatus(); //Wind. this.setWind( diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java new file mode 100644 index 00000000..635e581c --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java @@ -0,0 +1,90 @@ +package network.MessageDecoders; + +import network.MessageEncoders.BoatStatusEncoder; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Test for the BoatStatus encoder and decoder + */ +public class BoatStatusDecoderTest { + + + /** + * Creates a BoatStatus message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ + @Test + public void boatStatusEncodeDecodeTest() throws Exception { + + long time = System.currentTimeMillis(); + + //Create data to serialize. + int boatSourceID = 5; + BoatStatusEnum boatStatusEnum = BoatStatusEnum.RACING; + byte boatLegNumber = 5; + byte boatPenaltiesAwarded = 4; + byte boatPenaltiesServed = 2; + long boatTimeAtNextMark = time + (1000 * 3); + long boatTimeAtFinish = boatTimeAtNextMark + (1000 * 15); + + BoatStatus boatStatus = new BoatStatus( + boatSourceID, + boatStatusEnum, + boatLegNumber, + boatPenaltiesAwarded, + boatPenaltiesServed, + boatTimeAtNextMark, + boatTimeAtFinish ); + + + BoatStatus boatStatusDecoded = encodeDecodeBoatStatus(boatStatus); + + compareBoatStatusMessages(boatStatus, boatStatusDecoded); + + } + + /** + * Encodes and decodes a BoatStatus, and returns it. + * @param boatStatus The BoatStatus to encode and decode. + * @return The decoded BoatStatus. + */ + private static BoatStatus encodeDecodeBoatStatus(BoatStatus boatStatus) { + + BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); + + byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); + + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(boatStatusEncoded); + + BoatStatus boatStatusDecoded = boatStatusDecoder.getMessage(); + + return boatStatusDecoded; + } + + + /** + * Compares two BoatStatus messages to check that they are equal. + * @param original The original BoatStatus message. + * @param decoded The decoded BoatStatus message. + */ + public static void compareBoatStatusMessages(BoatStatus original, BoatStatus decoded) { + + Assert.assertEquals(original.getSourceID(), decoded.getSourceID()); + Assert.assertEquals(original.getBoatStatus(), decoded.getBoatStatus()); + Assert.assertEquals(original.getLegNumber(), decoded.getLegNumber()); + Assert.assertEquals(original.getNumPenaltiesAwarded(), decoded.getNumPenaltiesAwarded()); + Assert.assertEquals(original.getNumPenaltiesServed(), decoded.getNumPenaltiesServed()); + Assert.assertEquals(original.getEstTimeAtNextMark(), decoded.getEstTimeAtNextMark()); + Assert.assertEquals(original.getEstTimeAtFinish(), decoded.getEstTimeAtFinish()); + + } + +} diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java index a5600446..1adb69b4 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java @@ -1,27 +1,39 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; import network.Messages.RaceStatus; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** - * Created by hba56 on 23/04/17. + * Test for the RaceStatus encoder and decoder */ public class RaceStatusDecoderTest { + + + /** + * Creates a RaceStatus message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ @Test - public void getByteArrayTest(){ + public void raceStatusEncodeDecodeTest() throws Exception { + long time = System.currentTimeMillis(); //Create data to serialize. int boat1SourceID = 5; int boat2SourceID = 8; - byte boat1Status = 2; - byte boat2Status = 2; + BoatStatusEnum boat1Status = BoatStatusEnum.RACING; + BoatStatusEnum boat2Status = BoatStatusEnum.RACING; byte boat1LegNumber = 5; byte boat2LegNumber = 3; byte boat1PenaltiesAwarded = 4; @@ -33,44 +45,85 @@ public class RaceStatusDecoderTest { long boat1TimeAtFinish = boat1TimeAtNextMark + (1000 * 15); long boat2TimeAtFinish = boat2TimeAtNextMark + (1000 * 7); - BoatStatus boatStatus1 = new BoatStatus(boat1SourceID, boat1Status, boat1LegNumber, boat1PenaltiesAwarded, boat1PenaltiesServed, boat1TimeAtNextMark, boat1TimeAtFinish); - BoatStatus boatStatus2 = new BoatStatus(boat2SourceID, boat2Status, boat2LegNumber, boat2PenaltiesAwarded, boat2PenaltiesServed, boat2TimeAtNextMark, boat2TimeAtFinish); + BoatStatus boatStatus1 = new BoatStatus( + boat1SourceID, + boat1Status, + boat1LegNumber, + boat1PenaltiesAwarded, + boat1PenaltiesServed, + boat1TimeAtNextMark, + boat1TimeAtFinish ); + + BoatStatus boatStatus2 = new BoatStatus( + boat2SourceID, + boat2Status, + boat2LegNumber, + boat2PenaltiesAwarded, + boat2PenaltiesServed, + boat2TimeAtNextMark, + boat2TimeAtFinish ); + int raceID = 585; - byte raceStatus = 3; + RaceStatusEnum raceStatus = RaceStatusEnum.STARTED; long raceStartTime = time - (1000 * 31); int windDirection = 2341; int windSpeed = 10201; - int raceType = 1; + RaceTypeEnum raceType = RaceTypeEnum.MATCH_RACE; List boatStatuses = new ArrayList<>(2); boatStatuses.add(boatStatus1); boatStatuses.add(boatStatus2); - RaceStatus raceStatusObject = new RaceStatus(time, raceID, raceStatus, raceStartTime, windDirection, windSpeed, raceType, boatStatuses); + RaceStatus raceStatusOriginal = new RaceStatus( + RaceStatus.currentMessageVersionNumber, + time, + raceID, + raceStatus, + raceStartTime, + windDirection, + windSpeed, + raceType, + boatStatuses ); - byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatusObject); + byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatusOriginal); RaceStatusDecoder decoderTest = new RaceStatusDecoder(encodedRaceStatus); - Assert.assertEquals(0b10, decoderTest.getVersionNum()); - Assert.assertEquals(time, decoderTest.getTime()); - Assert.assertEquals(raceID, decoderTest.getRace()); - Assert.assertEquals(raceStatus, decoderTest.getRaceState()); - Assert.assertEquals(raceStartTime, decoderTest.getStartTime()); - Assert.assertEquals(windDirection, decoderTest.getRaceWindDir()); - Assert.assertEquals(windSpeed, decoderTest.getRaceWindSpeed()); + RaceStatus decodedMessage = decoderTest.getMessage(); + + compareRaceStatusMessages(raceStatusOriginal, decodedMessage); + + } + + + /** + * Compares two RaceStatus messages to check that they are equal. + * @param original The original RaceStatus message. + * @param decoded The decoded RaceStatus message. + */ + public static void compareRaceStatusMessages(RaceStatus original, RaceStatus decoded) { + + //Compare RaceStatus body. + Assert.assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + Assert.assertEquals(original.getCurrentTime(), decoded.getCurrentTime()); + Assert.assertEquals(original.getRaceID(), decoded.getRaceID()); + Assert.assertEquals(original.getRaceStatus(), decoded.getRaceStatus()); + Assert.assertEquals(original.getExpectedStartTime(), decoded.getExpectedStartTime()); + Assert.assertEquals(original.getWindDirection(), decoded.getWindDirection()); + Assert.assertEquals(original.getWindSpeed(), decoded.getWindSpeed()); + //Compare all BoatStatuses + Iterator originalIterator = original.getBoatStatuses().iterator(); + Iterator decodedIterator = decoded.getBoatStatuses().iterator(); - BoatStatus boat1 = decoderTest.getBoats().get(0); + while (originalIterator.hasNext() && decodedIterator.hasNext()) { - Assert.assertEquals(boat1SourceID, boat1.getSourceID()); - Assert.assertEquals(boat1Status, boat1.getBoatStatus()); - Assert.assertEquals(boat1LegNumber, boat1.getLegNumber()); - Assert.assertEquals(boat1PenaltiesAwarded, boat1.getNumPenaltiesAwarded()); - Assert.assertEquals(boat1PenaltiesServed, boat1.getNumPenaltiesServed()); - Assert.assertEquals(boat1TimeAtNextMark, boat1.getEstTimeAtNextMark()); - Assert.assertEquals(boat1TimeAtFinish, boat1.getEstTimeAtFinish()); + BoatStatusDecoderTest.compareBoatStatusMessages(originalIterator.next(), decodedIterator.next()); + + } } + + } From a0f98eadaa18186a9979e82311097b324d1925ae Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 13:57:09 +1200 Subject: [PATCH 38/53] Added some documentation to BoatStatus. issue #35 #36 #story[1095] --- .../java/network/Messages/BoatStatus.java | 98 ++++++++++++++++--- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/racevisionGame/src/main/java/network/Messages/BoatStatus.java b/racevisionGame/src/main/java/network/Messages/BoatStatus.java index 277ae9ca..b62c4469 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatStatus.java +++ b/racevisionGame/src/main/java/network/Messages/BoatStatus.java @@ -5,22 +5,62 @@ import network.Messages.Enums.BoatStatusEnum; import network.Utils.ByteConverter; /** - * Created by hba56 on 23/04/17. + * Represents the information in a BoatStatus message, which is contained inside a RaceStatus message (AC streaming spec: 4.2). */ public class BoatStatus { + /** + * The sourceID of the boat. + */ private int sourceID; + + /** + * The status of the boat. + */ private BoatStatusEnum boatStatus; + + /** + * The leg number that the boat is on. + */ private byte legNumber; + + /** + * The number of penalties awarded to the boat. + */ private byte numPenaltiesAwarded; + + /** + * The number of penalties served by the boat. + */ private byte numPenaltiesServed; + + /** + * The time at which it is estimated the boat will reach the next mark. + * Milliseconds since unix epoch. + */ private long estTimeAtNextMark; + + /** + * The time at which it is estimated the boat will finish the race. + * Milliseconds since unix epoch. + */ private long estTimeAtFinish; - public BoatStatus(int sourceID, BoatStatusEnum boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) { + + /** + * Constructs a BoatStatus message with the given parameters. + * @param sourceID The sourceID of the boat. + * @param boatStatus The status of the boat. + * @param legNumber The leg number the boat is on. + * @param numPenaltiesAwarded The number of penalties awarded to the boat. + * @param numPenaltiesServed The number of penalties served by the boat. + * @param estTimeAtNextMark The estimated time at which the boat will reach the next mark. + * @param estTimeAtFinish The estimated time at which the boat will finish the race. + */ + public BoatStatus(int sourceID, BoatStatusEnum boatStatus, int legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) { this.sourceID = sourceID; this.boatStatus = boatStatus; - this.legNumber = legNumber; + this.legNumber = ByteConverter.intToBytes(legNumber, 1)[0]; this.numPenaltiesAwarded = numPenaltiesAwarded; this.numPenaltiesServed = numPenaltiesServed; this.estTimeAtNextMark = estTimeAtNextMark; @@ -28,41 +68,77 @@ public class BoatStatus { } - public BoatStatus(int sourceID, BoatStatusEnum boatStatusEnum, int legNum, long estTimeAtNextMark) { - this.sourceID = sourceID; - this.boatStatus = boatStatusEnum; - this.legNumber = ByteConverter.intToBytes(legNum)[0]; - this.numPenaltiesAwarded = 0; - this.numPenaltiesServed = 0; - this.estTimeAtFinish = 0; - this.estTimeAtNextMark = estTimeAtNextMark; + /** + * Constructs a BoatStatus message with the given parameters. Sets penalties to zero, and time at finish to zero. + * @param sourceID The sourceID of the boat. + * @param boatStatus The status of the boat. + * @param legNumber The leg number the boat is on. + * @param estTimeAtNextMark The estimated time at which the boat will reach the next mark. + */ + public BoatStatus(int sourceID, BoatStatusEnum boatStatus, int legNumber, long estTimeAtNextMark) { + this( + sourceID, + boatStatus, + legNumber, + (byte) 0, + (byte) 0, + estTimeAtNextMark, + 0 ); } + /** + * Returns the sourceID of the boat. + * @return The sourceID of the boat. + */ public int getSourceID() { return sourceID; } + /** + * Returns the status of the boat. + * @return The status of the boat. + */ public BoatStatusEnum getBoatStatus() { return boatStatus; } + /** + * Returns the leg number of boat is on. + * @return The leg number of boat is on. + */ public byte getLegNumber() { return legNumber; } + /** + * Returns the number of penalties awarded to the boat. + * @return Number of penalties awarded to boat. + */ public byte getNumPenaltiesAwarded() { return numPenaltiesAwarded; } + /** + * Returns the number of penalties served by the boat. + * @return The number of penalties served by the boat. + */ public byte getNumPenaltiesServed() { return numPenaltiesServed; } + /** + * Returns he time at which it is estimated the boat will reach the next mark. Milliseconds since unix epoch. + * @return Time at which boat will reach next mark. + */ public long getEstTimeAtNextMark() { return estTimeAtNextMark; } + /** + * Returns he time at which it is estimated the boat will finish the race. Milliseconds since unix epoch. + * @return Time at which boat will finish the race. + */ public long getEstTimeAtFinish() { return estTimeAtFinish; } From ff262a62272050cee68d0897212078dddfcdcae9 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 14:57:51 +1200 Subject: [PATCH 39/53] Added Knots <-> MMperSec conversions to AC35UnitConverter. Removed redundant/unused conversions. Documented all of the conversions, and renamed them to pack/unpackX, to match the API spec. Updated/added some tests in AC35UnitConverterTest. RaceStatus now contains a Bearing instead of a packed int bearing. RaceStatus now contains wind speed in knots, instead of MMperSec packed. This means that only RaceStatus decoder/encoder need to care about the bits-over-wire packed values. issue #35 #36 #story[1095] --- .../src/main/java/mock/model/RaceServer.java | 7 +- .../MessageDecoders/RaceStatusDecoder.java | 10 ++- .../MessageEncoders/RaceStatusEncoder.java | 9 +- .../java/network/Messages/BoatLocation.java | 16 ++-- .../java/network/Messages/RaceStatus.java | 32 +++---- .../java/network/Utils/AC35UnitConverter.java | 79 +++++++++++++---- .../java/visualiser/model/VisualiserRace.java | 4 +- .../RaceStatusDecoderTest.java | 11 +-- .../network/Utils/AC35UnitConverterTest.java | 84 +++++++++++++++---- 9 files changed, 173 insertions(+), 79 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index b68ada32..8629056d 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -133,9 +133,6 @@ public class RaceServer { } - //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( @@ -144,8 +141,8 @@ public class RaceServer { race.getRaceId(), race.getRaceStatusEnum(), race.getRaceClock().getStartingTimeMilli(), - windDirectionInt, - windSpeedInt, + race.getWindDirection(), + race.getWindSpeed(), race.getRaceType(), boatStatuses); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index 4b67daa5..307c2815 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -5,6 +5,8 @@ import network.Messages.BoatStatus; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import network.Messages.RaceStatus; +import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import java.util.ArrayList; import java.util.Arrays; @@ -65,10 +67,12 @@ public class RaceStatusDecoder { long expectedStart = bytesToLong(expectedStartBytes); byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20); - int windDirection = bytesToInt(windDirectionBytes); + int windDirectionInt = bytesToInt(windDirectionBytes); + Bearing windDirection = Bearing.fromDegrees(AC35UnitConverter.unpackHeading(windDirectionInt)); byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22); - short windSpeed = bytesToShort(windSpeedBytes); + int windSpeedInt = bytesToInt(windSpeedBytes); + double windSpeedKnots = AC35UnitConverter.unpackMMperSecToKnots(windSpeedInt); byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23); int numberOfBoats = bytesToInt(numberOfBoatsBytes); @@ -99,7 +103,7 @@ public class RaceStatusDecoder { raceStatus, expectedStart, windDirection, - windSpeed, + windSpeedKnots, raceType, boatStatuses ); } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java index 7a0be153..fcb06d82 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java @@ -4,10 +4,13 @@ package network.MessageEncoders; import network.Messages.AC35Data; import network.Messages.BoatStatus; import network.Messages.RaceStatus; +import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import java.nio.ByteBuffer; import java.util.List; +import static network.Utils.ByteConverter.bytesToInt; import static network.Utils.ByteConverter.intToBytes; import static network.Utils.ByteConverter.longToBytes; @@ -52,10 +55,12 @@ public class RaceStatusEncoder implements MessageEncoder { byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6); //North = 0x0000 East = 0x4000 South = 0x8000. - byte[] raceWind = intToBytes(raceStatus.getWindDirection(), 2); + int windDirectionInt = AC35UnitConverter.packHeading(raceStatus.getWindDirection().degrees()); + byte[] raceWind = intToBytes(windDirectionInt, 2); //mm/sec - byte[] windSpeed = intToBytes(raceStatus.getWindSpeed(), 2); + int windSpeedInt = AC35UnitConverter.packKnotsToMMperSec(raceStatus.getWindSpeed()); + byte[] windSpeed = intToBytes(windSpeedInt, 2); byte[] numBoats = intToBytes(boatStatuses.size(), 1); diff --git a/racevisionGame/src/main/java/network/Messages/BoatLocation.java b/racevisionGame/src/main/java/network/Messages/BoatLocation.java index 1f2bfc49..e870a802 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatLocation.java +++ b/racevisionGame/src/main/java/network/Messages/BoatLocation.java @@ -5,8 +5,8 @@ import network.Messages.Enums.MessageType; import network.Utils.AC35UnitConverter; import shared.model.Constants; -import static network.Utils.AC35UnitConverter.convertGPS; -import static network.Utils.AC35UnitConverter.convertGPSToInt; +import static network.Utils.AC35UnitConverter.unpackGPS; +import static network.Utils.AC35UnitConverter.packGPS; /** * Represents the information in a boat location message (AC streaming spec: 4.9). @@ -159,8 +159,8 @@ public class BoatLocation extends AC35Data { this.sourceID = sourceID; this.sequenceNumber = sequenceNumber; this.deviceType = 1; - this.latitude = convertGPSToInt(lat); - this.longitude = convertGPSToInt(lon); + this.latitude = packGPS(lat); + this.longitude = packGPS(lon); this.altitude = 0; this.heading = convertHeadingDoubleToInt(heading); this.pitch = 0; @@ -340,11 +340,11 @@ public class BoatLocation extends AC35Data { } public double getLatitudeDouble(){ - return convertGPS(this.latitude); + return unpackGPS(this.latitude); } public double getLongitudeDouble(){ - return convertGPS(this.longitude); + return unpackGPS(this.longitude); } public void setLongitude(int longitude) { @@ -474,11 +474,11 @@ public class BoatLocation extends AC35Data { } public double getHeadingDegrees(){ - return AC35UnitConverter.convertHeading(getHeading()); + return AC35UnitConverter.unpackHeading(getHeading()); } public double getTrueWindAngleDegrees(){ - return AC35UnitConverter.convertTrueWindAngle(getTrueWindAngle()); + return AC35UnitConverter.unpackTrueWindAngle(getTrueWindAngle()); } @Override diff --git a/racevisionGame/src/main/java/network/Messages/RaceStatus.java b/racevisionGame/src/main/java/network/Messages/RaceStatus.java index 4d98700d..574cfce4 100644 --- a/racevisionGame/src/main/java/network/Messages/RaceStatus.java +++ b/racevisionGame/src/main/java/network/Messages/RaceStatus.java @@ -5,6 +5,7 @@ import network.Messages.Enums.MessageType; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import shared.model.Constants; import java.util.List; @@ -51,12 +52,13 @@ public class RaceStatus extends AC35Data { /** * The wind direction of the course. */ - private int windDirection; + private Bearing windDirection; /** * The wind speed of the course. + * Knots. */ - private int windSpeed; + private double windSpeed; /** * The type of race this is. @@ -78,11 +80,12 @@ public class RaceStatus extends AC35Data { * @param raceStatus The status of the race. * @param expectedStartTime The expected start time of the race. * @param windDirection The current wind direction in the race. - * @param windSpeed The current wind speed in the race. + * @param windSpeed The current wind speed in the race, in knots. * @param raceType The type of race this is. * @param boatStatuses A list of BoatStatuses. One for each boat. */ - public RaceStatus(byte messageVersionNumber, long currentTime, int raceID, RaceStatusEnum raceStatus, long expectedStartTime, int windDirection, int windSpeed, RaceTypeEnum raceType, List boatStatuses) { + public RaceStatus(byte messageVersionNumber, long currentTime, int raceID, RaceStatusEnum raceStatus, long expectedStartTime, Bearing windDirection, double windSpeed, RaceTypeEnum raceType, List boatStatuses) { + super(MessageType.RACESTATUS); this.messageVersionNumber = messageVersionNumber; this.currentTime = currentTime; @@ -96,6 +99,7 @@ public class RaceStatus extends AC35Data { } + /** * Returns the version number of this message. * @return The version number of the message. @@ -144,16 +148,16 @@ public class RaceStatus extends AC35Data { * Returns the current direction of the wind in the race. * @return Current wind direction. */ - public int getWindDirection() + public Bearing getWindDirection() { return windDirection; } /** - * Returns the wind speed for this race status, in millimeters per second. - * @return Wind speed in millimeters per second. + * Returns the wind speed for this race status, in knots. + * @return Wind speed in knots. */ - public int getWindSpeed() + public double getWindSpeed() { return windSpeed; } @@ -221,16 +225,4 @@ public class RaceStatus extends AC35Data { return raceStatus == RaceStatusEnum.PRESTART; } - - public double getScaledWindDirection() { - return AC35UnitConverter.convertHeading(windDirection); - } - - /** - * Returns the wind speed for this race status, in knots. - * @return Wind speed in knots. - */ - public double getWindSpeedKnots() { - return (windSpeed / Constants.KnotsToMMPerSecond); - } } diff --git a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java index 73f6d0e9..4838e42c 100644 --- a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java +++ b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java @@ -1,43 +1,88 @@ package network.Utils; +import shared.model.Constants; + /** - * Created by fwy13 on 28/04/17. + * Contains various unit conversion for encoding/decoding messages. + * Our program uses the "unpacked" units, and the over-the-wire format uses "packed" units (e.g., degrees stored as ints). */ public class AC35UnitConverter { - public static double convertGPS(int value){ - //converts latitude or longitue to angle + + /** + * Converts a packed GPSCoordinate (latitude or longitude) into the unpacked unit. + * @param value Packed lat/long value. + * @return Unpacked lat/long angle, in degrees. + */ + public static double unpackGPS(int value) { return (double) value * 180.0 / 2147483648.0;//2^31 = 2147483648 } - public static int convertGPSToInt(double value){ - //converts latitude or longitue to angle + /** + * Converts a latitude or longitude angle into a packed unit. + * @param value The lat/long angle, in degrees, to convert. + * @return The packed value. + */ + public static int packGPS(double value) { return (int) (value * 2147483648.0/180.0);//2^31 = 2147483648 } - public static double convertHeading(long value){ - return (double) value * 360.0/65536.0;//2^15 + + /** + * Unpacks a heading from an int to an angle in degrees (this is a bearing). + * @param value The packed value to unpack. + * @return The unpacked value in degrees. + */ + public static double unpackHeading(int value) { + return (value * 360.0 / 65536.0);//2^15 } - public static double convertHeading(int value){ - return (double) value * 360.0/65536.0;//2^15 + /** + * Packs a heading (this is a bearing), in degrees, to a packed int value. + * @param value The heading in degrees. + * @return The packed value. + */ + public static int packHeading(double value) { + return (int) (value / 360.0 * 65536.0);//2^15 } - public static double convertHeading(double value){ - return value * 360.0/65536.0;//2^15 + /** + * Unpacks a true wind angle from a short to an angle in degrees (this is an azimuth). + * @param value The packed value to unpack. + * @return The unpacked value in degrees. + */ + public static double unpackTrueWindAngle(short value) { + return (value * 180.0 / 32768.0);//-2^15 to 2^15 } - public static int encodeHeading(int value){ - return (int) (value / 360.0 * 65536.0);//2^15 + /** + * Packs a true wind angle (this is an azimuth) from an angle in degrees to a packed short value. + * @param value The unpacked value in degrees. + * @return The packed value. + */ + public static short packTrueWindAngle(double value) { + return (short) (value / 180.0 * 32768.0);//-2^15 to 2^15 } - public static int encodeHeading(double value){ - return (int) (value / 360.0 * 65536.0);//2^15 + + /** + * Unpacks a speed, in millimeters per second, to a double, in knots. + * @param millimetersPerSec Speed in millimeters per second. + * @return Speed in knots. + */ + public static double unpackMMperSecToKnots(int millimetersPerSec) { + return (millimetersPerSec / Constants.KnotsToMMPerSecond); } - public static double convertTrueWindAngle(long value){ - return (double) value * 180.0/32768.0;//-2^15 to 2^15 + /** + * Packs a speed, in knots, into an int, in millimeters per second. + * @param speedKnots Speed in knots. + * @return Speed in millimeters per second. + */ + public static int packKnotsToMMperSec(double speedKnots) { + return (int) (speedKnots * Constants.KnotsToMMPerSecond); } + } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index fdc2802d..faa47688 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -313,8 +313,8 @@ public class VisualiserRace extends Race implements Runnable { //Wind. this.setWind( - Bearing.fromDegrees(raceStatus.getScaledWindDirection()), - raceStatus.getWindSpeedKnots() ); + raceStatus.getWindDirection(), + raceStatus.getWindSpeed() ); //Current race time. this.raceClock.setUTCTime(raceStatus.getCurrentTime()); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java index 1adb69b4..4f1e2721 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java @@ -9,6 +9,7 @@ import network.Messages.Enums.RaceTypeEnum; import network.Messages.RaceStatus; import org.junit.Assert; import org.junit.Test; +import shared.model.Bearing; import java.util.ArrayList; import java.util.Iterator; @@ -67,8 +68,8 @@ public class RaceStatusDecoderTest { int raceID = 585; RaceStatusEnum raceStatus = RaceStatusEnum.STARTED; long raceStartTime = time - (1000 * 31); - int windDirection = 2341; - int windSpeed = 10201; + Bearing windDirection = Bearing.fromDegrees(185.34); + double windSpeedKnots = 14.52; RaceTypeEnum raceType = RaceTypeEnum.MATCH_RACE; List boatStatuses = new ArrayList<>(2); boatStatuses.add(boatStatus1); @@ -81,7 +82,7 @@ public class RaceStatusDecoderTest { raceStatus, raceStartTime, windDirection, - windSpeed, + windSpeedKnots, raceType, boatStatuses ); @@ -110,8 +111,8 @@ public class RaceStatusDecoderTest { Assert.assertEquals(original.getRaceID(), decoded.getRaceID()); Assert.assertEquals(original.getRaceStatus(), decoded.getRaceStatus()); Assert.assertEquals(original.getExpectedStartTime(), decoded.getExpectedStartTime()); - Assert.assertEquals(original.getWindDirection(), decoded.getWindDirection()); - Assert.assertEquals(original.getWindSpeed(), decoded.getWindSpeed()); + Assert.assertEquals(original.getWindDirection().degrees(), decoded.getWindDirection().degrees(), 0.01); + Assert.assertEquals(original.getWindSpeed(), decoded.getWindSpeed(), 0.01); //Compare all BoatStatuses Iterator originalIterator = original.getBoatStatuses().iterator(); diff --git a/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java b/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java index 24fc2be2..17369098 100644 --- a/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java +++ b/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java @@ -2,42 +2,92 @@ package network.Utils; import org.junit.Test; -import static network.Utils.AC35UnitConverter.convertGPS; -import static network.Utils.AC35UnitConverter.convertGPSToInt; -import static network.Utils.AC35UnitConverter.convertHeading; -import static network.Utils.AC35UnitConverter.convertTrueWindAngle; +import static network.Utils.AC35UnitConverter.*; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** - * Created by fwy13 on 4/05/17. + * Tests the pack/unpack and conversion functions in {@link AC35UnitConverter}. */ public class AC35UnitConverterTest { + + /** + * Tests if gps coordinates can be unpacked. + */ + @Test + public void testUnpackGPS(){ + assertTrue(unpackGPS(0) == 0); + assertTrue(unpackGPS(Integer.MAX_VALUE) == (double)Integer.MAX_VALUE * 180.0 / Math.pow(2, 31)); + + } + + + /** + * Tests if gps coodinates can be packed. + */ @Test - public void testConvertGPS(){ - assertTrue(convertGPS(0) == 0); - assertTrue(convertGPS(Integer.MAX_VALUE) == (double)Integer.MAX_VALUE * 180.0 / Math.pow(2, 31)); + public void testPackGPS(){ + assertTrue(packGPS(0) == 0); + assertTrue(packGPS(180) == (int)2147483648.0); } + /** + * Tests if headings/bearings can be unpacked. + */ + @Test + public void testUnpackHeading(){ + assertTrue(unpackHeading(0) == 0); + assertTrue(unpackHeading(65536) == 360.0); + } + + /** + * Tests if headings/bearings can be packed. + */ @Test - public void testConvertGPSToInt(){ - assertTrue(convertGPSToInt(0) == 0); - assertTrue(convertGPSToInt(180) == (int)2147483648.0); + public void testPackHeading(){ + assertTrue(packHeading(0) == 0); + assertTrue(packHeading(360) == 65536); + } + + /** + * Tests if true wind angles (azimuths) can be unpacked. + */ + @Test + public void testUnpackTrueWindAngle(){ + assertEquals(unpackTrueWindAngle((short)0), 0, 0.001); + assertEquals(unpackTrueWindAngle((short)32767), 180.0, 0.01); } + /** + * Tests if true wind angles (azimuths) can be packed. + */ @Test - public void testConvertHeading(){ - assertTrue(convertHeading(0) == 0); - assertTrue(convertHeading(65536) == 360.0); + public void testPackTrueWindAngle(){ + assertTrue(packTrueWindAngle(0) == (short)0); + assertTrue(packTrueWindAngle(180.0) == (short)32768); } + /** + * Tests if millimeters per second can be unpacked to knots. + */ @Test - public void testConvertTrueWindAngle(){ - assertTrue(convertTrueWindAngle(0) == 0); - assertTrue(convertTrueWindAngle(32768) == 180.0); + public void testUnpackMMperSecToKnots(){ + assertEquals(unpackMMperSecToKnots(0), 0d, 0.001); + assertEquals(unpackMMperSecToKnots(7331), 14.25, 0.01); } + + /** + * Tests if knots can be packed into millimeters per second. + */ + @Test + public void testPackKnotsToMMperSec(){ + assertEquals(packKnotsToMMperSec(0), 0, 1); + assertEquals(packKnotsToMMperSec(7.44), 3828, 1); + } + } From 1fbdd09d70b91988897e8d6658c7012646ad73a1 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 16:13:25 +1200 Subject: [PATCH 40/53] Refactored BoatLocation message, encoders, decoders. It now exposes datatypes that we actually use in the program (double knots, bearings, etc..), instead of the bits-on-the-wire packed units (like int mmPerSec). Also documented it, and updated test. issue #35 #36 #story[1095] --- .../src/main/java/mock/model/MockRace.java | 2 +- .../src/main/java/mock/model/RaceServer.java | 9 +- .../MessageDecoders/BoatLocationDecoder.java | 148 +++-- .../MessageEncoders/BoatLocationEncoder.java | 31 +- .../java/network/Messages/BoatLocation.java | 579 ++++++++---------- .../Enums/BoatLocationDeviceEnum.java | 107 ++++ .../java/network/Utils/AC35UnitConverter.java | 2 +- .../java/visualiser/model/VisualiserRace.java | 13 +- .../BoatLocationDecoderTest.java | 47 +- 9 files changed, 515 insertions(+), 423 deletions(-) create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/BoatLocationDeviceEnum.java diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 0ae5cfcc..9755099d 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -446,4 +446,4 @@ 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 index 8629056d..d776b693 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -2,9 +2,11 @@ package mock.model; import network.Messages.BoatLocation; import network.Messages.BoatStatus; +import network.Messages.Enums.BoatLocationDeviceEnum; import network.Messages.LatestMessages; import network.Messages.RaceStatus; import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import shared.model.CompoundMark; import shared.model.Constants; import shared.model.Mark; @@ -47,7 +49,9 @@ public class RaceServer { mark.getPosition().getLatitude(), mark.getPosition().getLongitude(), this.boatLocationSequenceNumber, - 0, 0, + BoatLocationDeviceEnum.Mark, + Bearing.fromDegrees(0), + 0, race.getRaceClock().getCurrentTimeMilli()); //Iterates the sequence number. @@ -99,7 +103,8 @@ public class RaceServer { boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), this.boatLocationSequenceNumber, - boat.getBearing().degrees(), + BoatLocationDeviceEnum.RacingYacht, + boat.getBearing(), boat.getCurrentSpeed(), race.getRaceClock().getCurrentTimeMilli()); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java index c0f4a652..46a4dccd 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java @@ -2,12 +2,14 @@ package network.MessageDecoders; import network.Messages.BoatLocation; +import network.Messages.Enums.BoatLocationDeviceEnum; +import shared.model.Azimuth; +import shared.model.Bearing; import java.util.Arrays; -import static network.Utils.ByteConverter.bytesToInt; -import static network.Utils.ByteConverter.bytesToLong; -import static network.Utils.ByteConverter.bytesToShort; +import static network.Utils.AC35UnitConverter.*; +import static network.Utils.ByteConverter.*; /** @@ -45,96 +47,110 @@ public class BoatLocationDecoder { */ private void decode() { - byte[] messageVersionNumber = Arrays.copyOfRange(encodedMessage, 0, 1); - byte numMessageVersionNumber = messageVersionNumber[0]; + byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte messageVersionNumber = messageVersionNumberBytes[0]; - byte[] time = Arrays.copyOfRange(encodedMessage, 1, 7); - long numTime = bytesToLong(time); + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); - byte[] sourceID = Arrays.copyOfRange(encodedMessage, 7, 11); - int numSourceID = bytesToInt(sourceID); + byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int sourceID = bytesToInt(sourceIDBytes); - byte[] seqNum = Arrays.copyOfRange(encodedMessage, 11, 15); - int numSeqNum = bytesToInt(seqNum); + byte[] seqNumBytes = Arrays.copyOfRange(encodedMessage, 11, 15); + int seqNum = bytesToInt(seqNumBytes); - byte[] deviceType = Arrays.copyOfRange(encodedMessage, 15, 16); - byte numDeviceType = deviceType[0]; + byte[] deviceTypeBytes = Arrays.copyOfRange(encodedMessage, 15, 16); + BoatLocationDeviceEnum deviceType = BoatLocationDeviceEnum.fromByte(deviceTypeBytes[0]); - byte[] latitude = Arrays.copyOfRange(encodedMessage, 16, 20); - int numLatitude = bytesToInt(latitude); + byte[] latitudeBytes = Arrays.copyOfRange(encodedMessage, 16, 20); + int numLatitude = bytesToInt(latitudeBytes); + double latitude = unpackGPS(numLatitude); - byte[] longitude = Arrays.copyOfRange(encodedMessage, 20, 24); - int numLongitude = bytesToInt(longitude); + byte[] longitudeBytes = Arrays.copyOfRange(encodedMessage, 20, 24); + int numLongitude = bytesToInt(longitudeBytes); + double longitude = unpackGPS(numLongitude); - byte[] altitude = Arrays.copyOfRange(encodedMessage, 24, 28); - int numAltitude = bytesToInt(altitude); + byte[] altitudeBytes = Arrays.copyOfRange(encodedMessage, 24, 28); + int numAltitude = bytesToInt(altitudeBytes); - byte[] heading = Arrays.copyOfRange(encodedMessage, 28, 30); - int numHeading = bytesToInt(heading); + byte[] headingBytes = Arrays.copyOfRange(encodedMessage, 28, 30); + int numHeading = bytesToInt(headingBytes); + Bearing heading = Bearing.fromDegrees(unpackHeading(numHeading)); - byte[] pitch = Arrays.copyOfRange(encodedMessage, 30, 32); - short numPitch = bytesToShort(pitch); + byte[] pitchBytes = Arrays.copyOfRange(encodedMessage, 30, 32); + short numPitch = bytesToShort(pitchBytes); - byte[] roll = Arrays.copyOfRange(encodedMessage, 32, 34); - short numRoll = bytesToShort(roll); + byte[] rollBytes = Arrays.copyOfRange(encodedMessage, 32, 34); + short numRoll = bytesToShort(rollBytes); - byte[] boatSpeed = Arrays.copyOfRange(encodedMessage, 34, 36); - int numBoatSpeed = bytesToInt(boatSpeed); + byte[] boatSpeedBytes = Arrays.copyOfRange(encodedMessage, 34, 36); + int numBoatSpeed = bytesToInt(boatSpeedBytes); + double boatSpeedKnots = unpackMMperSecToKnots(numBoatSpeed); - byte[] cog = Arrays.copyOfRange(encodedMessage, 36, 38); - int numCog = bytesToInt(cog); + byte[] cogBytes = Arrays.copyOfRange(encodedMessage, 36, 38); + int numCog = bytesToInt(cogBytes); + Bearing cog = Bearing.fromDegrees(unpackHeading(numCog)); - byte[] sog = Arrays.copyOfRange(encodedMessage, 38, 40); - int numSog = bytesToInt(sog); + byte[] sogBytes = Arrays.copyOfRange(encodedMessage, 38, 40); + int numSog = bytesToInt(sogBytes); + double sogKnots = unpackMMperSecToKnots(numSog); - byte[] apparentWindSpeed = Arrays.copyOfRange(encodedMessage, 40, 42); - int numApparentWindSpeed = bytesToInt(apparentWindSpeed); + byte[] apparentWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 40, 42); + int numApparentWindSpeed = bytesToInt(apparentWindSpeedBytes); + double apparentWindSpeedKnots = unpackMMperSecToKnots(numApparentWindSpeed); - byte[] apparentWindAngle = Arrays.copyOfRange(encodedMessage, 42, 44); - short numApparentWindAngle = bytesToShort(apparentWindAngle); + byte[] apparentWindAngleBytes = Arrays.copyOfRange(encodedMessage, 42, 44); + short numApparentWindAngle = bytesToShort(apparentWindAngleBytes); + Azimuth apparentWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numApparentWindAngle)); - byte[] trueWindSpeed = Arrays.copyOfRange(encodedMessage, 44, 46); - int numTrueWindSpeed = bytesToInt(trueWindSpeed); + byte[] trueWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 44, 46); + int numTrueWindSpeed = bytesToInt(trueWindSpeedBytes); + double trueWindSpeedKnots = unpackMMperSecToKnots(numTrueWindSpeed); - byte[] trueWindDirection = Arrays.copyOfRange(encodedMessage, 46, 48); - short numTrueWindDirection = bytesToShort(trueWindDirection); + byte[] trueWindDirectionBytes = Arrays.copyOfRange(encodedMessage, 46, 48); + short numTrueWindDirection = bytesToShort(trueWindDirectionBytes); + Bearing trueWindDirection = Bearing.fromDegrees(unpackHeading(numTrueWindDirection)); - byte[] trueWindAngle = Arrays.copyOfRange(encodedMessage, 48, 50); - short numTrueWindAngle = bytesToShort(trueWindAngle); + byte[] trueWindAngleBytes = Arrays.copyOfRange(encodedMessage, 48, 50); + short numTrueWindAngle = bytesToShort(trueWindAngleBytes); + Azimuth trueWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numTrueWindAngle)); - byte[] currentDrift = Arrays.copyOfRange(encodedMessage, 50, 52); - int numCurrentDrift = bytesToInt(currentDrift); + byte[] currentDriftBytes = Arrays.copyOfRange(encodedMessage, 50, 52); + int numCurrentDrift = bytesToInt(currentDriftBytes); + double currentDriftKnots = unpackMMperSecToKnots(numCurrentDrift); - byte[] currentSet = Arrays.copyOfRange(encodedMessage, 52, 54); - int numCurrentSet = bytesToShort(currentSet); + byte[] currentSetBytes = Arrays.copyOfRange(encodedMessage, 52, 54); + int numCurrentSet = bytesToShort(currentSetBytes); + Bearing currentSet = Bearing.fromDegrees(unpackHeading(numCurrentSet)); - byte[] rudderAngle = Arrays.copyOfRange(encodedMessage, 54, 56); - short numRudderAngle = bytesToShort(rudderAngle); + byte[] rudderAngleBytes = Arrays.copyOfRange(encodedMessage, 54, 56); + short numRudderAngle = bytesToShort(rudderAngleBytes); + Azimuth rudderAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numRudderAngle)); message = new BoatLocation( - numMessageVersionNumber, - numTime, - numSourceID, - numSeqNum, - numDeviceType, - numLatitude, - numLongitude, + messageVersionNumber, + time, + sourceID, + seqNum, + deviceType, + latitude, + longitude, numAltitude, - numHeading, + heading, numPitch, numRoll, - numBoatSpeed, - numCog, - numSog, - numApparentWindSpeed, - numApparentWindAngle, - numTrueWindSpeed, - numTrueWindDirection, - numTrueWindAngle, - numCurrentDrift, - numCurrentSet, - numRudderAngle ); + boatSpeedKnots, + cog, + sogKnots, + apparentWindSpeedKnots, + apparentWindAngle, + trueWindSpeedKnots, + trueWindDirection, + trueWindAngle, + currentDriftKnots, + currentSet, + rudderAngle ); } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java index 9ff3e197..9c3833ac 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java @@ -6,6 +6,7 @@ import network.Messages.BoatLocation; import java.nio.ByteBuffer; +import static network.Utils.AC35UnitConverter.*; import static network.Utils.ByteConverter.intToBytes; import static network.Utils.ByteConverter.longToBytes; @@ -34,24 +35,24 @@ public class BoatLocationEncoder implements MessageEncoder { byte[] time = longToBytes(boatLocation.getTime(), 6); byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4); byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4); - byte[] deviceType = intToBytes(boatLocation.getDeviceType(), 1); - byte[] latitude = intToBytes(boatLocation.getLatitude(), 4); - byte[] longitude = intToBytes(boatLocation.getLongitude(), 4); + byte[] deviceType = intToBytes(boatLocation.getDeviceType().getValue(), 1); + byte[] latitude = intToBytes(packGPS(boatLocation.getLatitude()), 4); + byte[] longitude = intToBytes(packGPS(boatLocation.getLongitude()), 4); byte[] altitude = intToBytes(boatLocation.getAltitude(), 4); - byte[] heading = intToBytes(boatLocation.getHeading(), 2); + byte[] heading = intToBytes(packHeading(boatLocation.getHeading().degrees()), 2); byte[] pitch = intToBytes(boatLocation.getPitch(), 2); byte[] roll = intToBytes(boatLocation.getRoll(), 2); - byte[] boatSpeed = intToBytes(boatLocation.getBoatSpeed(), 2); - byte[] cog = intToBytes(boatLocation.getBoatCOG(), 2); - byte[] sog = intToBytes(boatLocation.getBoatSOG(), 2); - byte[] apparentWindSpeed = intToBytes(boatLocation.getApparentWindSpeed(), 2); - byte[] apparentWindAngle = intToBytes(boatLocation.getApparentWindAngle(), 2); - byte[] trueWindSpeed = intToBytes(boatLocation.getTrueWindSpeed(), 2); - byte[] trueWindDirection = intToBytes(boatLocation.getTrueWindDirection(), 2); - byte[] trueWindAngle = intToBytes(boatLocation.getTrueWindAngle(), 2); - byte[] currentDrift = intToBytes(boatLocation.getCurrentDrift(), 2); - byte[] currentSet = intToBytes(boatLocation.getCurrentSet(), 2); - byte[] rudderAngle = intToBytes(boatLocation.getRudderAngle(), 2); + byte[] boatSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSpeedKnots()), 2); + byte[] cog = intToBytes(packHeading(boatLocation.getBoatCOG().degrees()), 2); + byte[] sog = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSOGKnots()), 2); + byte[] apparentWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getApparentWindSpeedKnots()), 2); + byte[] apparentWindAngle = intToBytes(packTrueWindAngle(boatLocation.getApparentWindAngle().degrees()), 2); + byte[] trueWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getTrueWindSpeedKnots()), 2); + byte[] trueWindDirection = intToBytes(packHeading(boatLocation.getTrueWindDirection().degrees()), 2); + byte[] trueWindAngle = intToBytes(packTrueWindAngle(boatLocation.getTrueWindAngle().degrees()), 2); + byte[] currentDrift = intToBytes(packKnotsToMMperSec(boatLocation.getCurrentDriftKnots()), 2); + byte[] currentSet = intToBytes(packHeading(boatLocation.getCurrentSet().degrees()), 2); + byte[] rudderAngle = intToBytes(packTrueWindAngle(boatLocation.getRudderAngle().degrees()), 2); ByteBuffer result = ByteBuffer.allocate(56); result.put(messageVersionBytes); diff --git a/racevisionGame/src/main/java/network/Messages/BoatLocation.java b/racevisionGame/src/main/java/network/Messages/BoatLocation.java index e870a802..90529d96 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatLocation.java +++ b/racevisionGame/src/main/java/network/Messages/BoatLocation.java @@ -1,130 +1,188 @@ package network.Messages; +import network.Messages.Enums.BoatLocationDeviceEnum; import network.Messages.Enums.MessageType; import network.Utils.AC35UnitConverter; -import shared.model.Constants; +import shared.model.Azimuth; +import shared.model.Bearing; import static network.Utils.AC35UnitConverter.unpackGPS; -import static network.Utils.AC35UnitConverter.packGPS; /** * Represents the information in a boat location message (AC streaming spec: 4.9). */ public class BoatLocation extends AC35Data { - //TODO move these to an enum. - public static final byte Unknown = 0; - public static final byte RacingYacht = 1; - public static final byte CommitteeBoat = 2; - public static final byte Mark = 3; - public static final byte Pin = 4; - public static final byte ChaseBoat = 5; - public static final byte MedicalBoat = 6; - public static final byte MarshallBoat = 7; - public static final byte UmpireBoat = 8; - public static final byte UmpireSoftwareApplication = 9; - public static final byte PrincipalRaceOfficerApplication = 10; - public static final byte WeatherStation = 11; - public static final byte Helicopter = 12; - public static final byte DataProcessingApplication = 13; - /** * The current messageVersionNumber according to the API spec. */ public static final byte currentMessageVersionNumber = 1; - ///Version number of the message. + + /** + * Version number of the message. + */ private byte messageVersionNumber; - ///Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int. + + /** + * Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int. + */ private long time; - ///Source ID of the boat. + + /** + * Source ID of the boat. + */ private int sourceID; - ///Sequence number of the message. + + /** + * Sequence number of the message. + */ private long sequenceNumber; - ///Device type of the message (physical source of the message). - private byte deviceType; - ///Latitude of the boat. - private int latitude; - ///Longitude of the boat. - private int longitude; + /** + * Device type of the message (physical source of the message). + */ + private BoatLocationDeviceEnum deviceType; - ///Altitude of the boat. + /** + * Latitude of the boat. + */ + private double latitude; + + /** + * Longitude of the boat. + */ + private double longitude; + + /** + * Altitude of the boat. + */ private int altitude; - ///Heading of the boat. Clockwise, 0 = north. Proper type is unsigned 2 byte int. - private int heading; + /** + * Heading of the boat. Clockwise, 0 = north. Proper type is unsigned 2 byte int. + */ + private Bearing heading; - ///Pitch of the boat. + /** + * Pitch of the boat. + */ private short pitch; - ///Roll of the boat. + /** + * Roll of the boat. + */ private short roll; - ///Speed of the boat. Proper type is unsigned 2 byte int. millimeters per second. - private int boatSpeed; + /** + * Speed of the boat, in knots. + */ + private double boatSpeedKnots; - ///Course over ground (COG) of the boat. Proper type is unsigned 2 byte int. - private int boatCOG; + /** + * Course over ground (COG) of the boat. + */ + private Bearing boatCOG; - ///Speed over ground (SOG) of the boat. Proper type is unsigned 2 byte int. millimeters per second. - private int boatSOG; + /** + * Speed over ground (SOG) of the boat, in knots. + */ + private double boatSOGKnots; - ///Apparent wind speed at time of event. Proper type is unsigned 2 byte int. millimeters per second. - private int apparentWindSpeed; + /** + * Apparent wind speed at time of event. Proper type is unsigned 2 byte int. millimeters per second. + */ + private double apparentWindSpeedKnots; - ///Apparent wind angle at time of the event. Wind over starboard = positive. - private short apparentWindAngle; + /** + * Apparent wind angle at time of the event. Wind over starboard = positive. + */ + private Azimuth apparentWindAngle; - ///True wind speed. Proper type is unsigned 2 byte int. millimeters per second. - private int trueWindSpeed; + /** + * True wind speed, in knots. + */ + private double trueWindSpeedKnots; - ///True wind direction. Proper type is unsigned 2 byte int. 0x0000 = North, etc.. - private int trueWindDirection; + /** + * True wind direction. + */ + private Bearing trueWindDirection; - ///True wind angle. Clockwise compass direction, 0 = north. - private short trueWindAngle; + /** + * True wind angle. Clockwise compass direction, 0 = north. + */ + private Azimuth trueWindAngle; - ///Current drift. Proper type is unsigned 2 byte int. millimeters per second. - private int currentDrift; + /** + * Current drift, in knots. + */ + private double currentDriftKnots; - ///Current set. Proper type is unsigned 2 byte int. Clockwise compass direction, 0 = north. - private int currentSet; + /** + * Current set. + */ + private Bearing currentSet; - ///Rudder angle. Positive is rudder set to turn yacht to port. - private short rudderAngle; + /** + * Rudder angle. Positive is rudder set to turn yacht to port. + */ + private Azimuth rudderAngle; /** - * Ctor, with all parameters. + * Constructs a BoatLocation message with the given parameters. * * @param messageVersionNumber message number * @param time time of message * @param sourceID id of boat * @param sequenceNumber number of boat message - * @param deviceType type of boat + * @param deviceType The source of the BoatLocation message. * @param latitude lat of boat * @param longitude lon of boat * @param altitude altitude of boat * @param heading heading of boat * @param pitch pitch of boat * @param roll roll of boat - * @param boatSpeed boats speed + * @param boatSpeedKnots boats speed * @param boatCOG boat cog - * @param boatSOG boat sog - * @param apparentWindSpeed wind speed + * @param boatSOGKnots boat sog + * @param apparentWindSpeedKnots wind speed * @param apparentWindAngle wind angle - * @param trueWindSpeed true wind speed + * @param trueWindSpeedKnots true wind speed * @param trueWindDirection true wind direction * @param trueWindAngle true wind angle - * @param currentDrift current drift + * @param currentDriftKnots current drift * @param currentSet current set * @param rudderAngle rudder angle */ - public BoatLocation(byte messageVersionNumber, long time, int sourceID, long sequenceNumber, byte deviceType, int latitude, int longitude, int altitude, int heading, short pitch, short roll, int boatSpeed, int boatCOG, int boatSOG, int apparentWindSpeed, short apparentWindAngle, int trueWindSpeed, int trueWindDirection, short trueWindAngle, int currentDrift, int currentSet, short rudderAngle) { + public BoatLocation( + byte messageVersionNumber, + long time, + int sourceID, + long sequenceNumber, + BoatLocationDeviceEnum deviceType, + double latitude, + double longitude, + int altitude, + Bearing heading, + short pitch, + short roll, + double boatSpeedKnots, + Bearing boatCOG, + double boatSOGKnots, + double apparentWindSpeedKnots, + Azimuth apparentWindAngle, + double trueWindSpeedKnots, + Bearing trueWindDirection, + Azimuth trueWindAngle, + double currentDriftKnots, + Bearing currentSet, + Azimuth rudderAngle ) { + super(MessageType.BOATLOCATION); this.messageVersionNumber = messageVersionNumber; @@ -138,348 +196,251 @@ public class BoatLocation extends AC35Data { this.heading = heading; this.pitch = pitch; this.roll = roll; - this.boatSpeed = boatSpeed; + this.boatSpeedKnots = boatSpeedKnots; this.boatCOG = boatCOG; - this.boatSOG = boatSOG; - this.apparentWindSpeed = apparentWindSpeed; + this.boatSOGKnots = boatSOGKnots; + this.apparentWindSpeedKnots = apparentWindSpeedKnots; this.apparentWindAngle = apparentWindAngle; - this.trueWindSpeed = trueWindSpeed; + this.trueWindSpeedKnots = trueWindSpeedKnots; this.trueWindDirection = trueWindDirection; this.trueWindAngle = trueWindAngle; - this.currentDrift = currentDrift; + this.currentDriftKnots = currentDriftKnots; this.currentSet = currentSet; this.rudderAngle = rudderAngle; } - public BoatLocation(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed, long time) { - super(MessageType.BOATLOCATION); - - this.messageVersionNumber = BoatLocation.currentMessageVersionNumber; - this.time = time; - this.sourceID = sourceID; - this.sequenceNumber = sequenceNumber; - this.deviceType = 1; - this.latitude = packGPS(lat); - this.longitude = packGPS(lon); - this.altitude = 0; - this.heading = convertHeadingDoubleToInt(heading); - this.pitch = 0; - this.roll = 0; - this.boatSpeed = convertBoatSpeedDoubleToInt(boatSpeed); - this.boatCOG = 0; - this.boatSOG = convertBoatSpeedDoubleToInt(boatSpeed); - this.apparentWindSpeed = 0; - this.apparentWindAngle = 0; - this.trueWindSpeed = 0; - this.trueWindDirection = 0; - this.trueWindAngle = 0; - this.currentDrift = 0; - this.currentSet = 0; - this.rudderAngle = 0; - } - - - //Getters and setters for message properties. - - /** - * Converts a double representing a latitude or longitude coordinate to an int, as required by the streaming spec format. - * - * @param coordinate Latitude or longitude to convert. Double. - * @return int representation of coordinate. - */ - public static int convertCoordinateDoubleToInt(double coordinate) { - int coordinateInt = (int) ((coordinate / 180.0) * 2147483648.0); - - return coordinateInt; - } - - /** - * Converts an int representing a latitude or longitude coordinate to a double, as required by the streaming spec format. - * - * @param coordinate Latitude or longitude to convert. int. - * @return double representation of coordinate. - */ - public static double convertCoordinateIntToDouble(int coordinate) { - double coordinateDouble = (double) ((coordinate * 180.0) / 2147483648.0); - - return coordinateDouble; - } - - /** - * Converts an int representing a heading to a double, as required by the streaming spec format. - * - * @param heading Heading to convert. int. - * @return double representation of heading. - */ - public static double convertHeadingIntToDouble(int heading) { - - double headingDouble = (double) ((heading * 360.0) / 65536.0); - - return headingDouble; - } - - /** - * Converts a double representing a heading to an int, as required by the streaming spec format. - * - * @param heading Heading to convert. double. - * @return int representation of heading. - */ - public static int convertHeadingDoubleToInt(double heading) { - int headingInt = (int) ((heading * 65536.0) / 360.0); - - return headingInt; + public BoatLocation( + int sourceID, + double lat, + double lon, + long sequenceNumber, + BoatLocationDeviceEnum deviceType, + Bearing heading, + double boatSpeedKnots, + long time ) { + + this( + BoatLocation.currentMessageVersionNumber, + time, + sourceID, + sequenceNumber, + deviceType, + lat, + lon, + 0, + heading, + (short) 0, + (short) 0, + boatSpeedKnots, + heading, + boatSpeedKnots, + 0, + Azimuth.fromDegrees(0), + 0, + Bearing.fromDegrees(0), + Azimuth.fromDegrees(0), + 0, + Bearing.fromDegrees(0), + Azimuth.fromDegrees(0) ); } - /** - * Converts a short representing the wind's true angle to a double, as required by the streaming spec format. - * - * @param angle Angle to convert. short. - * @return double representation of heading. - */ - public static double convertTrueWindAngleShortToDouble(short angle) { - - double angleDouble = (double) ((angle * 180.0) / 32768.0); - - return angleDouble; - } - - /** - * Converts a double representing the wind's true angle to a short, as required by the streaming spec format. - * - * @param angle Angle to convert. double. - * @return short representation of heading. - */ - public static short convertTrueWindAngleDoubleToShort(double angle) { - - short angleShort = (short) ((angle / 180.0) * 32768.0); - - return angleShort; - } - - /** - * Converts a double representing the speed of a boat in knots to an int in millimeters per second, as required by the streaming spec format. - * - * @param speed Speed in knots, stored as a double. - * @return Speed in millimeters per second, stored as an int (using only the two least significant bytes). - */ - public static int convertBoatSpeedDoubleToInt(double speed) { - //Calculate millimeters per second. - double millimetersPerSecond = speed * Constants.KnotsToMMPerSecond; - - //Convert to an int. - int millimetersPerSecondInt = (int) Math.round(millimetersPerSecond); - - return millimetersPerSecondInt; - } /** - * Converts an int representing the speed of a boat in millimeters per second to a double in knots, as required by the streaming spec format. - * - * @param speed Speed in millimeters per second, stored as an int. - * @return Speed in knots, stored as a double. + * Returns the version number of the message. + * @return The version number of the message. */ - public static double convertBoatSpeedIntToDouble(int speed) { - - //Calculate knots. - double knots = speed / Constants.KnotsToMMPerSecond; - - return knots; - } - public byte getMessageVersionNumber() { return messageVersionNumber; } - public void setMessageVersionNumber(byte messageVersionNumber) { - this.messageVersionNumber = messageVersionNumber; - } + /** + * Returns the time that this message was generated at. + * @return Time message was generated at, in milliseconds since unix epoch. + */ public long getTime() { return time; } - public void setTime(long time) { - this.time = time; - } - + /** + * Returns the sourceID of the boat this message relates to. + * @return SourceID of the boat this message relates to. + */ public int getSourceID() { return sourceID; } - public void setSourceID(int sourceID) { - this.sourceID = sourceID; - } - + /** + * Returns the sequence number of this message. + * @return The sequence number of the message. + */ public long getSequenceNumber() { return sequenceNumber; } - public void setSequenceNumber(long sequenceNumber) { - this.sequenceNumber = sequenceNumber; - } - public byte getDeviceType() { + /** + * Returns the device source of this message. + * @return The device source of this message. + */ + public BoatLocationDeviceEnum getDeviceType() { return deviceType; } - public void setDeviceType(byte deviceType) { - this.deviceType = deviceType; - } - public int getLatitude() { + /** + * Returns the latitude, in degrees, that the boat is located at. + * @return Latitude, in degrees, of boat. + */ + public double getLatitude() { return latitude; } - public void setLatitude(int latitude) { - this.latitude = latitude; - } - public int getLongitude() { + /** + * Returns the longitude, in degrees, that the boat is located at. + * @return Longitude, in degrees, of boat. + */ + public double getLongitude() { return longitude; } - public double getLatitudeDouble(){ - return unpackGPS(this.latitude); - } - - public double getLongitudeDouble(){ - return unpackGPS(this.longitude); - } - - public void setLongitude(int longitude) { - this.longitude = longitude; - } - + /** + * Returns the altitude of the boat. + * @return The altitude of the boat. + */ public int getAltitude() { return altitude; } - public void setAltitude(int altitude) { - this.altitude = altitude; - } - public int getHeading() { + /** + * Returns the current heading/bearing of the boat. + * @return Heading of the boat. + */ + public Bearing getHeading() { return heading; } - public void setHeading(int heading) { - this.heading = heading; - } + /** + * Returns the current pitch of the boat. + * @return Pitch of the boat. + */ public short getPitch() { return pitch; } - public void setPitch(short pitch) { - this.pitch = pitch; - } + /** + * Returns the current roll of the boat. + * @return Roll of the boat. + */ public short getRoll() { return roll; } - public void setRoll(short roll) { - this.roll = roll; - } - public int getBoatSpeed() { - return boatSpeed; + /** + * Returns the current boat speed, in knots. + * @return Current boat speed, in knots. + */ + public double getBoatSpeedKnots() { + return boatSpeedKnots; } - public void setBoatSpeed(int boatSpeed) { - this.boatSpeed = boatSpeed; - } - public int getBoatCOG() { + /** + * Returns the boat's Course Over Ground. + * @return Boat's COG. + */ + public Bearing getBoatCOG() { return boatCOG; } - public void setBoatCOG(int boatCOG) { - this.boatCOG = boatCOG; - } - public int getBoatSOG() { - return boatSOG; - } - - public void setBoatSOG(int boatSOG) { - this.boatSOG = boatSOG; + /** + * Returns the boats Speed Over Ground, in knots. + * @return Boat's SOG. + */ + public double getBoatSOGKnots() { + return boatSOGKnots; } - public int getApparentWindSpeed() { - return apparentWindSpeed; - } - public void setApparentWindSpeed(int apparentWindSpeed) { - this.apparentWindSpeed = apparentWindSpeed; + /** + * Returns the apparent wind speed, in knots, at the boat. + * @return Wind speed, in knots, at the boat. + */ + public double getApparentWindSpeedKnots() { + return apparentWindSpeedKnots; } - public short getApparentWindAngle() { + /** + * Returns the apparent wind angle at the boat. + * @return Wind angle at the boat. + */ + public Azimuth getApparentWindAngle() { return apparentWindAngle; } - public void setApparentWindAngle(short apparentWindAngle) { - this.apparentWindAngle = apparentWindAngle; - } - public int getTrueWindSpeed() { - return trueWindSpeed; + /** + * Returns the true wind speed, in knots. + * @return True wind speed, in knots. + */ + public double getTrueWindSpeedKnots() { + return trueWindSpeedKnots; } - public void setTrueWindSpeed(int trueWindSpeed) { - this.trueWindSpeed = trueWindSpeed; - } - public int getTrueWindDirection() + /** + * Returns the true wind direction. + * @return True wind direction. + */ + public Bearing getTrueWindDirection() { return trueWindDirection; } - public void setTrueWindDirection(int trueWindDirection) - { - this.trueWindDirection = trueWindDirection; - } - public short getTrueWindAngle() { + /** + * Returns the true wind angle. + * @return True wind angle. + */ + public Azimuth getTrueWindAngle() { return trueWindAngle; } - public void setTrueWindAngle(short trueWindAngle) { - this.trueWindAngle = trueWindAngle; - } - public int getCurrentDrift() { - return currentDrift; + /** + * Returns the current drift of the boat, in knots. + * @return Current drift, in knots. + */ + public double getCurrentDriftKnots() { + return currentDriftKnots; } - public void setCurrentDrift(int currentDrift) { - this.currentDrift = currentDrift; - } - public int getCurrentSet() { + /** + * Returns the current set of the boat. + * @return Current set of the boat. + */ + public Bearing getCurrentSet() { return currentSet; } - public void setCurrentSet(int currentSet) { - this.currentSet = currentSet; - } - public short getRudderAngle() { + /** + * Returns the current rudder angle of the boat. + * @return Current rudder angle of the boat. + */ + public Azimuth getRudderAngle() { return rudderAngle; } - public void setRudderAngle(short rudderAngle) { - this.rudderAngle = rudderAngle; - } - - public double getHeadingDegrees(){ - return AC35UnitConverter.unpackHeading(getHeading()); - } - public double getTrueWindAngleDegrees(){ - return AC35UnitConverter.unpackTrueWindAngle(getTrueWindAngle()); - } @Override public String toString() { @@ -519,28 +480,28 @@ public class BoatLocation extends AC35Data { builder.append(this.getRoll()); builder.append("\nBoat speed (mm/sec): "); - builder.append(this.getBoatSpeed()); + builder.append(this.getBoatSpeedKnots()); builder.append("\nBoat COG: "); builder.append(this.getBoatCOG()); builder.append("\nBoat SOG: "); - builder.append(this.getBoatSOG()); + builder.append(this.getBoatSOGKnots()); builder.append("\nApparent wind speed: "); - builder.append(this.getApparentWindSpeed()); + builder.append(this.getApparentWindSpeedKnots()); builder.append("\nApparent wind angle: "); builder.append(this.getApparentWindAngle()); builder.append("\nTrue wind speed: "); - builder.append(this.getTrueWindSpeed()); + builder.append(this.getTrueWindSpeedKnots()); builder.append("\nTrue wind angle: "); builder.append(this.getTrueWindAngle()); builder.append("\nCurrent drift: "); - builder.append(this.getCurrentDrift()); + builder.append(this.getCurrentDriftKnots()); builder.append("\nCurrent set: "); builder.append(this.getCurrentSet()); diff --git a/racevisionGame/src/main/java/network/Messages/Enums/BoatLocationDeviceEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/BoatLocationDeviceEnum.java new file mode 100644 index 00000000..42e8c733 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/BoatLocationDeviceEnum.java @@ -0,0 +1,107 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Various device sources for a BoatLocation message. + */ +public enum BoatLocationDeviceEnum { + + + NOT_A_DEVICE(-1), + + + Unknown(0), + + /** + * A yacht particpating in the race. + */ + RacingYacht(1), + + CommitteeBoat(2), + + /** + * A marker boat. + */ + Mark(3), + + Pin(4), + + ChaseBoat(5), + + MedicalBoat(6), + + MarshallBoat(7), + + UmpireBoat(8), + + UmpireSoftwareApplication(9), + + PrincipalRaceOfficerApplication(10), + + WeatherStation(11), + + Helicopter(12), + + DataProcessingApplication(13); + + + /** + * Value of the enum. + */ + private byte value; + + /** + * Creates a BoatLocationDeviceEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private BoatLocationDeviceEnum(int value) { + this.value = (byte) value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + /** + * Stores a mapping between Byte values and BoatLocationDeviceEnum values. + */ + private static final Map byteToDeviceMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToDeviceMap. + */ + static { + for (BoatLocationDeviceEnum type : BoatLocationDeviceEnum.values()) { + BoatLocationDeviceEnum.byteToDeviceMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param deviceValue Byte value to convert to a BoatLocationDeviceEnum value. + * @return The BoatLocationDeviceEnum value which corresponds to the given byte value. + */ + public static BoatLocationDeviceEnum fromByte(byte deviceValue) { + //Gets the corresponding BoatLocationDeviceEnum from the map. + BoatLocationDeviceEnum type = BoatLocationDeviceEnum.byteToDeviceMap.get(deviceValue); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_DEVICE BoatLocationDeviceEnum. + return BoatLocationDeviceEnum.NOT_A_DEVICE; + } else { + //Otherwise, return the BoatLocationDeviceEnum. + return type; + } + + } + +} diff --git a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java index 4838e42c..6d894ab6 100644 --- a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java +++ b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java @@ -24,7 +24,7 @@ public class AC35UnitConverter { * @return The packed value. */ public static int packGPS(double value) { - return (int) (value * 2147483648.0/180.0);//2^31 = 2147483648 + return (int) (value * 2147483648.0 / 180.0);//2^31 = 2147483648 } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index faa47688..15067515 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -16,7 +16,6 @@ import shared.dataInput.RegattaDataSource; import shared.model.*; import java.time.Duration; -import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -198,20 +197,20 @@ public class VisualiserRace extends Race implements Runnable { if (boatLocation != null && boatStatus != null) { //Get the new position. - double latitude = boatLocation.getLatitudeDouble(); - double longitude = boatLocation.getLongitudeDouble(); + double latitude = boatLocation.getLatitude(); + double longitude = boatLocation.getLongitude(); GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude); boat.setCurrentPosition(gpsCoordinate); //Bearing. - boat.setBearing(Bearing.fromDegrees(boatLocation.getHeadingDegrees())); + boat.setBearing(boatLocation.getHeading()); //Time until next mark. boat.setEstimatedTimeAtNextMark(raceClock.getLocalTime(boatStatus.getEstTimeAtNextMark())); //Speed. - boat.setCurrentSpeed(boatLocation.getBoatSOG() / Constants.KnotsToMMPerSecond); + boat.setCurrentSpeed(boatLocation.getBoatSpeedKnots()); //Boat status. @@ -292,8 +291,8 @@ public class VisualiserRace extends Race implements Runnable { if (boatLocation != null) { //We only update the boat's position. - double latitude = boatLocation.getLatitudeDouble(); - double longitude = boatLocation.getLongitudeDouble(); + double latitude = boatLocation.getLatitude(); + double longitude = boatLocation.getLongitude(); GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude); mark.setPosition(gpsCoordinate); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index 05964aaf..dcc69abe 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -2,8 +2,11 @@ package network.MessageDecoders; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.BoatLocation; +import network.Messages.Enums.BoatLocationDeviceEnum; import org.junit.Assert; import org.junit.Test; +import shared.model.Azimuth; +import shared.model.Bearing; /** @@ -23,28 +26,28 @@ public class BoatLocationDecoderTest { long time = System.currentTimeMillis(); BoatLocation testMessage = new BoatLocation( - (byte) 1, + BoatLocation.currentMessageVersionNumber, time, 2, 3, - (byte) 1, + BoatLocationDeviceEnum.RacingYacht, 180, -180, 4, - 5, + Bearing.fromDegrees(45), (short) 6, (short) 7, 8, - 9, + Bearing.fromDegrees(40), 10, 11, - (short) 12, + Azimuth.fromDegrees(35), 13, - 14, - (short) 15, + Bearing.fromDegrees(80), + Azimuth.fromDegrees(80), 16, - 17, - (short) 18 ); + Bearing.fromDegrees(80), + Azimuth.fromDegrees(22) ); //Encode. byte [] testEncodedMessage = RaceVisionByteEncoder.encode(testMessage); @@ -58,22 +61,22 @@ public class BoatLocationDecoderTest { Assert.assertEquals(testMessage.getTime(), decodedTest.getTime()); Assert.assertEquals(testMessage.getSequenceNumber(), decodedTest.getSequenceNumber()); Assert.assertEquals(testMessage.getDeviceType(), decodedTest.getDeviceType()); - Assert.assertEquals(testMessage.getLatitude(), decodedTest.getLatitude()); - Assert.assertEquals(testMessage.getLongitude(), decodedTest.getLongitude()); + Assert.assertEquals(testMessage.getLatitude(), decodedTest.getLatitude(), 0.01); + Assert.assertEquals(testMessage.getLongitude(), decodedTest.getLongitude(), 0.01); Assert.assertEquals(testMessage.getAltitude(), decodedTest.getAltitude()); - Assert.assertEquals(testMessage.getHeading(), decodedTest.getHeading()); + Assert.assertEquals(testMessage.getHeading().degrees(), decodedTest.getHeading().degrees(), 0.01); Assert.assertEquals(testMessage.getPitch(), decodedTest.getPitch()); Assert.assertEquals(testMessage.getRoll(), decodedTest.getRoll()); - Assert.assertEquals(testMessage.getBoatSpeed(), decodedTest.getBoatSpeed()); + Assert.assertEquals(testMessage.getBoatSpeedKnots(), decodedTest.getBoatSpeedKnots(), 0.01); - Assert.assertEquals(testMessage.getBoatCOG(), decodedTest.getBoatCOG()); - Assert.assertEquals(testMessage.getBoatSOG(), decodedTest.getBoatSOG()); - Assert.assertEquals(testMessage.getApparentWindSpeed(), decodedTest.getApparentWindSpeed()); - Assert.assertEquals(testMessage.getTrueWindSpeed(), decodedTest.getTrueWindSpeed()); - Assert.assertEquals(testMessage.getTrueWindDirection(), decodedTest.getTrueWindDirection()); - Assert.assertEquals(testMessage.getTrueWindAngle(), decodedTest.getTrueWindAngle()); - Assert.assertEquals(testMessage.getCurrentDrift(), decodedTest.getCurrentDrift()); - Assert.assertEquals(testMessage.getCurrentSet(), decodedTest.getCurrentSet()); - Assert.assertEquals(testMessage.getRudderAngle(), decodedTest.getRudderAngle()); + Assert.assertEquals(testMessage.getBoatCOG().degrees(), decodedTest.getBoatCOG().degrees(), 0.01); + Assert.assertEquals(testMessage.getBoatSOGKnots(), decodedTest.getBoatSOGKnots(), 0.01); + Assert.assertEquals(testMessage.getApparentWindSpeedKnots(), decodedTest.getApparentWindSpeedKnots(), 0.01); + Assert.assertEquals(testMessage.getTrueWindSpeedKnots(), decodedTest.getTrueWindSpeedKnots(), 0.01); + Assert.assertEquals(testMessage.getTrueWindDirection().degrees(), decodedTest.getTrueWindDirection().degrees(), 0.01); + Assert.assertEquals(testMessage.getTrueWindAngle().degrees(), decodedTest.getTrueWindAngle().degrees(), 0.01); + Assert.assertEquals(testMessage.getCurrentDriftKnots(), decodedTest.getCurrentDriftKnots(), 0.01); + Assert.assertEquals(testMessage.getCurrentSet().degrees(), decodedTest.getCurrentSet().degrees(), 0.01); + Assert.assertEquals(testMessage.getRudderAngle().degrees(), decodedTest.getRudderAngle().degrees(), 0.01); } } From 750ea5c141c81432104043a5c8960f196a16f8b8 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 18:51:13 +1200 Subject: [PATCH 41/53] Added MessageDecoder interface. All decoder implement this. Added DecoderFactory. This creates an appropriate decoder based on a MessageType. BoatActionDecoder implements MessageDecoder. BoatLocationDecoder implements MessageDecoder. HeartBeatDecoder implements MessageDecoder. JoinAcceptance implements MessageDecoder. RaceStatusDecoder implements MessageDecoder. RequestToJoinDecoder implements MessageDecoder. XMLMessageDecoder implements MessageDecoder. Refactored CourseWind decoder/encoder. CourseWind decoder/encoder is for an individual CourseWind. CourseWinds decoder/encoder is for the combined message from the API. Documented BoatAction, and it now contains a BoatActionEnum instead of a byte. Refactored CourseWind and CourseWinds classes. They now expose correct units, instead of packed units. Added CourseWindDecoderTest, and updated CourseWindsDecoderTest. issue #35 #36 #story[1095] --- .../java/network/BinaryMessageDecoder.java | 35 +++-- .../MessageDecoders/BoatActionDecoder.java | 48 +++++-- .../MessageDecoders/BoatLocationDecoder.java | 19 ++- .../MessageDecoders/BoatStatusDecoder.java | 12 +- .../MessageDecoders/CourseWindDecoder.java | 120 +++++++++++------- .../MessageDecoders/CourseWindsDecoder.java | 87 +++++++++++++ .../MessageDecoders/DecoderFactory.java | 70 ++++++++++ .../MessageDecoders/HeartBeatDecoder.java | 19 ++- .../JoinAcceptanceDecoder.java | 18 ++- .../MessageDecoders/MessageDecoder.java | 21 +++ .../MessageDecoders/RaceStatusDecoder.java | 29 +++-- .../MessageDecoders/RequestToJoinDecoder.java | 18 ++- .../MessageDecoders/XMLMessageDecoder.java | 19 ++- .../MessageEncoders/BoatActionEncoder.java | 2 +- .../MessageEncoders/CourseWindEncoder.java | 73 +++++++++++ .../MessageEncoders/CourseWindsEncoder.java | 57 +++++++++ .../MessageEncoders/EncoderFactory.java | 2 +- .../RaceVisionByteEncoder.java | 21 +-- .../java/network/Messages/BoatAction.java | 21 ++- .../java/network/Messages/CourseWind.java | 101 +++++++++++++-- .../java/network/Messages/CourseWinds.java | 58 ++++++++- .../gameController/ControllerServer.java | 8 +- .../BoatActionDecoderTest.java | 6 +- .../BoatLocationDecoderTest.java | 3 +- .../BoatStatusDecoderTest.java | 6 +- .../CourseWindDecoderTest.java | 90 ++++++++----- .../CourseWindsDecoderTest.java | 96 ++++++++++++++ .../MessageDecoders/HeartBeatDecoderTest.java | 3 +- .../JoinAcceptanceDecoderTest.java | 3 +- .../RaceStatusDecoderTest.java | 3 +- .../RequestToJoinDecoderTest.java | 3 +- .../XMLMessageDecoderTest.java | 3 +- 32 files changed, 840 insertions(+), 234 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/CourseWindsDecoderTest.java diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index eb9c6997..0ee0d541 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -2,6 +2,7 @@ package network; import network.Exceptions.InvalidMessageException; +import network.Exceptions.InvalidMessageTypeException; import network.MessageDecoders.*; import network.Messages.*; import network.Messages.Enums.MessageType; @@ -130,16 +131,26 @@ public class BinaryMessageDecoder { //Now we create the message object based on what is actually in the message body. MessageType mType = MessageType.fromByte(headerMessageType); + /*MessageDecoder decoder = null; + try { + decoder = DecoderFactory.create(mType); + } catch (InvalidMessageTypeException e) { + throw new InvalidMessageException("Could not create decoder for MessageType: " + mType, e); + } + + return decoder.decode(messageBody);*/ + + switch(mType) { case HEARTBEAT: //System.out.println("Decoding HeartBeat Message!"); - HeartBeatDecoder heartBeatDecoder = new HeartBeatDecoder(messageBody); - return heartBeatDecoder.getMessage(); + HeartBeatDecoder heartBeatDecoder = new HeartBeatDecoder(); + return heartBeatDecoder.decode(messageBody); case RACESTATUS: //System.out.println("Race Status Message"); - RaceStatusDecoder rsdecoder = new RaceStatusDecoder(messageBody); - return rsdecoder.getMessage(); + RaceStatusDecoder rsdecoder = new RaceStatusDecoder(); + return rsdecoder.decode(messageBody); case DISPLAYTEXTMESSAGE: //System.out.println("Display Text Message"); @@ -148,8 +159,8 @@ public class BinaryMessageDecoder { case XMLMESSAGE: //System.out.println("XML Message!"); - XMLMessageDecoder xmdecoder = new XMLMessageDecoder(messageBody); - return xmdecoder.getMessage(); + XMLMessageDecoder xmdecoder = new XMLMessageDecoder(); + return xmdecoder.decode(messageBody); case RACESTARTSTATUS: //System.out.println("Race Start Status Message"); @@ -173,8 +184,8 @@ public class BinaryMessageDecoder { case BOATLOCATION: //System.out.println("Boat Location Message!"); - BoatLocationDecoder blDecoder = new BoatLocationDecoder(messageBody); - return blDecoder.getMessage(); + BoatLocationDecoder blDecoder = new BoatLocationDecoder(); + return blDecoder.decode(messageBody); case MARKROUNDING: //System.out.println("Mark Rounding Message!"); @@ -183,8 +194,8 @@ public class BinaryMessageDecoder { case COURSEWIND: //System.out.println("Course Wind Message!"); - CourseWindDecoder cwDecoder = new CourseWindDecoder(messageBody); - return new CourseWinds(cwDecoder.getMessageVersionNumber(), cwDecoder.getByteWindID(), cwDecoder.getLoopMessages()); + CourseWindsDecoder cwDecoder = new CourseWindsDecoder(); + return cwDecoder.decode(messageBody); case AVGWIND: //System.out.println("Average Wind Message!"); @@ -192,8 +203,8 @@ public class BinaryMessageDecoder { return awDecoder.getAverageWind(); case BOATACTION: - BoatActionDecoder baDecoder = new BoatActionDecoder(messageBody); - return new BoatAction(baDecoder.getBoatAction()); + BoatActionDecoder baDecoder = new BoatActionDecoder(); + return baDecoder.decode(messageBody); default: //System.out.println("Broken Message!"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java index bf2076b5..a3a5d38a 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java @@ -1,20 +1,50 @@ package network.MessageDecoders; +import network.Messages.AC35Data; +import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; import java.util.Arrays; -public class BoatActionDecoder { - byte byteBoatAction; - BoatActionEnum boatAction; +/** + * Decodes {@link BoatAction} messages. + */ +public class BoatActionDecoder implements MessageDecoder { - public BoatActionDecoder(byte[] encodedBoatAction) { - byteBoatAction = encodedBoatAction[0]; + /** + * The encoded message. + */ + private byte[] encodedMessage; - boatAction = BoatActionEnum.fromByte(byteBoatAction); + /** + * The decoded message. + */ + private BoatAction message; + + + /** + * Constructs a decoder to decode a given message. + */ + public BoatActionDecoder() { + } + + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(encodedMessage[0]); + + message = new BoatAction(boatActionEnum); + + return message; } - public BoatActionEnum getBoatAction() { - return boatAction; + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public BoatAction getMessage() { + return message; } -} \ No newline at end of file +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java index 46a4dccd..1e175588 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.BoatLocation; import network.Messages.Enums.BoatLocationDeviceEnum; import shared.model.Azimuth; @@ -15,7 +16,7 @@ import static network.Utils.ByteConverter.*; /** * Decodes {@link BoatLocation} messages. */ -public class BoatLocationDecoder { +public class BoatLocationDecoder implements MessageDecoder { /** * The encoded message. @@ -32,20 +33,14 @@ public class BoatLocationDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public BoatLocationDecoder(byte[] encodedMessage) { - - this.encodedMessage = encodedMessage; - - decode(); + public BoatLocationDecoder() { } - /** - * Decodes the contained message. - */ - private void decode() { + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); byte messageVersionNumber = messageVersionNumberBytes[0]; @@ -152,6 +147,8 @@ public class BoatLocationDecoder { currentSet, rudderAngle ); + return message; + } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java index dd482486..972c1eae 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java @@ -30,19 +30,17 @@ public class BoatStatusDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public BoatStatusDecoder(byte[] encodedMessage) { - this.encodedMessage = encodedMessage; - - decode(); + public BoatStatusDecoder() { } /** * Decodes the contained message. + * @param encodedMessage The message to decode. */ - private void decode() { + public BoatStatus decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 0, 4); @@ -75,6 +73,8 @@ public class BoatStatusDecoder { estTimeAtNextMark, estTimeAtFinish ); + return message; + } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java index 038a79d2..78bc6f1c 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java @@ -2,63 +2,93 @@ package network.MessageDecoders; import network.Messages.CourseWind; +import shared.model.Bearing; -import java.util.ArrayList; import java.util.Arrays; -import static network.Utils.ByteConverter.bytesToInt; -import static network.Utils.ByteConverter.bytesToLong; +import static network.Utils.AC35UnitConverter.*; +import static network.Utils.ByteConverter.*; /** - * Created by hba56 on 23/04/17. + * Decodes {@link CourseWind} messages. */ public class CourseWindDecoder { - byte messageVersionNumber; - byte byteWindID; - byte loopCount; - ArrayList loopMessages = new ArrayList(); - - public CourseWindDecoder(byte[] encodedCourseWind) { - final int lengthInBytesOfMessages = 20; - - messageVersionNumber = encodedCourseWind[0]; - byteWindID = encodedCourseWind[1]; - loopCount = encodedCourseWind[2]; - byte[] loopMessagesBytes = Arrays.copyOfRange(encodedCourseWind, 3, lengthInBytesOfMessages*loopCount+3); - int messageLoopIndex = 0; - - for (int i=0; i < loopCount; i++) { - byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex+20); - ArrayList test = new ArrayList(); - byte[] windId = Arrays.copyOfRange(messageBytes, 0, 1); - byte[] time = Arrays.copyOfRange(messageBytes, 1, 7); - byte[] raceID = Arrays.copyOfRange(messageBytes, 7, 11); - byte[] windDirection = Arrays.copyOfRange(messageBytes, 11, 13); - byte[] windSpeed = Arrays.copyOfRange(messageBytes, 13, 15); - byte[] bestUpwindAngle = Arrays.copyOfRange(messageBytes, 15, 17); - byte[] bestDownwindAngle = Arrays.copyOfRange(messageBytes, 17, 19); - byte[] flags = Arrays.copyOfRange(messageBytes, 19, 20); - - CourseWind message = new CourseWind(windId[0], bytesToLong(time), - bytesToInt(raceID), bytesToInt(windDirection), - bytesToInt(windSpeed), bytesToInt(bestUpwindAngle), - bytesToInt(bestDownwindAngle), flags[0]); - - loopMessages.add(message); - messageLoopIndex += 20; - } - } - public ArrayList getLoopMessages() { - return loopMessages; + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private CourseWind message; + + + + /** + * Constructs a decoder to decode a given message. + */ + public CourseWindDecoder() { } - public byte getMessageVersionNumber() { - return messageVersionNumber; + + /** + * Decodes the contained message. + * @param encodedMessage The message to decode. + */ + public CourseWind decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + byte[] windId = Arrays.copyOfRange(encodedMessage, 0, 1); + + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); + + byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int raceIDInt = bytesToInt(raceIDBytes); + + byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 11, 13); + int windDirectionInt = bytesToInt(windDirectionBytes); + Bearing windDirection = Bearing.fromDegrees(unpackHeading(windDirectionInt)); + + byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 13, 15); + int windSpeedInt = bytesToInt(windSpeedBytes); + double windSpeedKnots = unpackMMperSecToKnots(windSpeedInt); + + byte[] bestUpwindAngleBytes = Arrays.copyOfRange(encodedMessage, 15, 17); + int bestUpwindAngleInt = bytesToInt(bestUpwindAngleBytes); + Bearing bestUpwindAngle = Bearing.fromDegrees(unpackHeading(bestUpwindAngleInt)); + + byte[] bestDownwindAngleBytes = Arrays.copyOfRange(encodedMessage, 17, 19); + int bestDownwindAngleInt = bytesToInt(bestDownwindAngleBytes); + Bearing bestDownwindAngle = Bearing.fromDegrees(unpackHeading(bestDownwindAngleInt)); + + byte[] flags = Arrays.copyOfRange(encodedMessage, 19, 20); + + + + message = new CourseWind( + windId[0], + time, + raceIDInt, + windDirection, + windSpeedKnots, + bestUpwindAngle, + bestDownwindAngle, + flags[0] ); + + return message; } - public byte getByteWindID() { - return byteWindID; + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public CourseWind getMessage() { + return message; } + } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java new file mode 100644 index 00000000..f478ac6b --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java @@ -0,0 +1,87 @@ +package network.MessageDecoders; + + +import network.Messages.AC35Data; +import network.Messages.CourseWind; +import network.Messages.CourseWinds; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static network.Utils.ByteConverter.bytesToInt; +import static network.Utils.ByteConverter.bytesToLong; + + +/** + * Decodes {@link CourseWinds} messages. + */ +public class CourseWindsDecoder implements MessageDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private CourseWinds message; + + + + /** + * Constructs a decoder to decode a given message. + */ + public CourseWindsDecoder() { + } + + + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + //The header is three bytes. + byte messageVersionNumber = encodedMessage[0]; + byte byteWindID = encodedMessage[1]; + byte loopCount = encodedMessage[2]; + + + //A CourseWind object is 20 bytes. + final int courseWindByteLength = 20; + + List loopMessages = new ArrayList(); + + //The header is 3 bytes, so we need the remaining bytes. + byte[] loopMessagesBytes = Arrays.copyOfRange(encodedMessage, 3, courseWindByteLength * loopCount + 3); + + for (int messageLoopIndex = 0; messageLoopIndex < (loopCount * courseWindByteLength); messageLoopIndex += courseWindByteLength) { + + byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex + courseWindByteLength); + + CourseWindDecoder courseWindDecoder = new CourseWindDecoder(); + CourseWind courseWind = courseWindDecoder.decode(messageBytes); + + loopMessages.add(courseWind); + } + + + + message = new CourseWinds( + messageVersionNumber, + byteWindID, + loopMessages ); + + return message; + } + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public CourseWinds getMessage() { + return message; + } + +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java new file mode 100644 index 00000000..500d20f6 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java @@ -0,0 +1,70 @@ +package network.MessageDecoders; + + +import network.Exceptions.InvalidMessageTypeException; +import network.Messages.Enums.MessageType; + +/** + * Factory to create the appropriate decoder for a given message. + */ +public class DecoderFactory { + + + /** + * Private constructor. Currently doesn't need to be constructed. + */ + private DecoderFactory(){ + + } + + + /** + * Creates the correct type of decoder for a given message type. + * @param type Type of message you want a decoder for. + * @return The decoder. + * @throws InvalidMessageTypeException If you pass in a {@link MessageType} that isn't recognised. + */ + public static MessageDecoder create(MessageType type) throws InvalidMessageTypeException { + + + switch (type) { + + case HEARTBEAT: return new HeartBeatDecoder(); + + case RACESTATUS: return new RaceStatusDecoder(); + + //case DISPLAYTEXTMESSAGE: return new DisplayTextMessageDecoder();//TODO + + case XMLMESSAGE: return new XMLMessageDecoder(); + + //case RACESTARTSTATUS: return new RaceStartStatusDecoder();//TODO + + //case YACHTEVENTCODE: return new YachtEventCodeDecoder();//TODO + + //case YACHTACTIONCODE: return new YachtActionCodeDecoder();//TODO + + //case CHATTERTEXT: return new ChatterTextDecoder();//TODO + + case BOATLOCATION: return new BoatLocationDecoder(); + + //case MARKROUNDING: return new MarkRoundingDecoder()//TODO; + + case COURSEWIND: return new CourseWindsDecoder(); + + //case AVGWIND: return new AverageWindDecoder()//TODO; + + case REQUEST_TO_JOIN: return new RequestToJoinDecoder(); + + case JOIN_ACCEPTANCE: return new JoinAcceptanceDecoder(); + + case BOATACTION: return new BoatActionDecoder(); + + + default: throw new InvalidMessageTypeException("Unrecognised message type: " + type); + } + + + + } + +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java index 0984a9ea..49cdcc5f 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.Enums.BoatActionEnum; import network.Messages.HeartBeat; @@ -8,7 +9,7 @@ import static network.Utils.ByteConverter.bytesToLong; /** * Decodes {@link network.Messages.HeartBeat} messages. */ -public class HeartBeatDecoder { +public class HeartBeatDecoder implements MessageDecoder { /** * The encoded message. @@ -24,21 +25,17 @@ public class HeartBeatDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public HeartBeatDecoder(byte[] encodedMessage) { - this.encodedMessage = encodedMessage; - - decode(); + public HeartBeatDecoder() { } - - /** - * Decodes the contained message. - */ - private void decode() { + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; message = new HeartBeat(bytesToLong(encodedMessage)); + + return message; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java index 95c3889e..eaa82c18 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.Enums.JoinAcceptanceEnum; import network.Messages.JoinAcceptance; import network.Utils.ByteConverter; @@ -10,7 +11,7 @@ import java.util.Arrays; /** * Decoder for {@link JoinAcceptance} messages. */ -public class JoinAcceptanceDecoder { +public class JoinAcceptanceDecoder implements MessageDecoder { /** * The encoded message. @@ -25,19 +26,14 @@ public class JoinAcceptanceDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public JoinAcceptanceDecoder(byte[] encodedMessage) { - this.encodedMessage = encodedMessage; - - decode(); + public JoinAcceptanceDecoder() { } - /** - * Decodes the contained message. - */ - private void decode() { + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; //SourceID is first four bytes. byte[] sourceIdBytes = Arrays.copyOfRange(encodedMessage, 0, 4); @@ -55,6 +51,8 @@ public class JoinAcceptanceDecoder { message = new JoinAcceptance(acceptanceType, sourceID); + + return message; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java new file mode 100644 index 00000000..39b4370f --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java @@ -0,0 +1,21 @@ +package network.MessageDecoders; + + +import network.Messages.AC35Data; + + +/** + * This is the interface that all message decoders must implement. + * It allows for {@link #decode(byte[])}ing messages. + */ +public interface MessageDecoder { + + + /** + * Decodes a given message. + * @param encodedMessage The message to decode. + * @return The decoded message. + */ + public AC35Data decode(byte[] encodedMessage); + +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index 307c2815..15700f49 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.BoatStatus; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; @@ -20,7 +21,7 @@ import static network.Utils.ByteConverter.bytesToShort; /** * Decodes {@link RaceStatus} messages. */ -public class RaceStatusDecoder { +public class RaceStatusDecoder implements MessageDecoder { /** * The encoded message. @@ -36,19 +37,14 @@ public class RaceStatusDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public RaceStatusDecoder(byte[] encodedMessage) { - this.encodedMessage = encodedMessage; - - decode(); + public RaceStatusDecoder() { } - /** - * Decodes the contained message. - */ - private void decode() { + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1); @@ -84,15 +80,18 @@ public class RaceStatusDecoder { List boatStatuses = new ArrayList<>(); + //BoatStatus is 20 bytes. + int boatStatusByteLength = 20; + //Decode each BoatStatus. - for (int boatLoopIndex=0; boatLoopIndex < (numberOfBoats * 20); boatLoopIndex += 20) { + for (int boatLoopIndex = 0; boatLoopIndex < (numberOfBoats * boatStatusByteLength); boatLoopIndex += boatStatusByteLength) { - byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + 20); + byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + boatStatusByteLength); - BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(boatStatusBytes); + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(); - boatStatuses.add(boatStatusDecoder.getMessage()); + boatStatuses.add(boatStatusDecoder.decode(boatStatusBytes)); } @@ -106,6 +105,8 @@ public class RaceStatusDecoder { windSpeedKnots, raceType, boatStatuses ); + + return message; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java index 2d272f05..1abf61a6 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.Enums.RequestToJoinEnum; import network.Messages.RequestToJoin; import network.Utils.ByteConverter; @@ -10,7 +11,7 @@ import java.util.Arrays; /** * Decoder for {@link network.Messages.RequestToJoin} messages. */ -public class RequestToJoinDecoder { +public class RequestToJoinDecoder implements MessageDecoder{ /** * The encoded message. @@ -24,19 +25,14 @@ public class RequestToJoinDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedRequest The message to decode. */ - public RequestToJoinDecoder(byte[] encodedRequest) { - this.encodedRequest = encodedRequest; - - decode(); + public RequestToJoinDecoder() { } - /** - * Decodes the contained message. - */ - private void decode() { + @Override + public AC35Data decode(byte[] encodedRequest) { + this.encodedRequest = encodedRequest; //Request type is first four bytes. byte[] requestTypeBytes = Arrays.copyOfRange(encodedRequest, 0, 4); @@ -47,6 +43,8 @@ public class RequestToJoinDecoder { message = new RequestToJoin(requestType); + + return message; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java index 4fb985a1..99c56e28 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.Enums.XMLMessageType; import network.Messages.XMLMessage; @@ -12,7 +13,7 @@ import static network.Utils.ByteConverter.bytesToShort; /** * Decodes {@link network.Messages.XMLMessage} messages. */ -public class XMLMessageDecoder { +public class XMLMessageDecoder implements MessageDecoder { /** * The encoded message. @@ -28,18 +29,14 @@ public class XMLMessageDecoder { /** * Constructs a decoder to decode a given message. - * @param encodedMessage The message to decode. */ - public XMLMessageDecoder(byte[] encodedMessage) { - this.encodedMessage = encodedMessage; - - decode(); + public XMLMessageDecoder() { } - /** - * Decodes the contained message. - */ - private void decode() { + + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); byte[] ackNumberBytes = Arrays.copyOfRange(encodedMessage, 1, 3); @@ -66,6 +63,8 @@ public class XMLMessageDecoder { xmlMsgSubType, sequenceNumber, xmlMessage ); + + return message; } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java index ba5ce901..8083487f 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java @@ -30,7 +30,7 @@ public class BoatActionEncoder implements MessageEncoder { //Message is 1 byte. ByteBuffer boatActionMessage = ByteBuffer.allocate(1); - boatActionMessage.put(intToBytes(boatAction.getBoatAction(), 1)); + boatActionMessage.put(intToBytes(boatAction.getBoatAction().getValue(), 1)); byte [] result = boatActionMessage.array(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java new file mode 100644 index 00000000..b6e407c0 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java @@ -0,0 +1,73 @@ +package network.MessageEncoders; + + +import network.Messages.CourseWind; +import shared.model.Bearing; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static network.Utils.AC35UnitConverter.*; +import static network.Utils.ByteConverter.*; +import static network.Utils.ByteConverter.bytesToInt; + +/** + * This encoder can encode a {@link CourseWind} message. + */ +public class CourseWindEncoder { + + + /** + * Constructor. + */ + public CourseWindEncoder() { + } + + + /** + * Encodes a given CourseWind message. + * @param message The message to encode. + * @return The encoded message. + */ + public byte[] encode(CourseWind message) { + + CourseWind courseWind = message; + + + //CourseWind is 20 bytes. + ByteBuffer courseWindBuffer = ByteBuffer.allocate(20); + + + byte[] windId = intToBytes(courseWind.getID(), 1); + + byte[] timeBytes = longToBytes(courseWind.getTime(), 6); + + byte[] raceIDBytes = intToBytes(courseWind.getRaceID(), 4); + + int windDirectionInt = packHeading(courseWind.getWindDirection().degrees()); + byte[] windDirectionBytes = intToBytes(windDirectionInt, 2); + + int windSpeedInt = packKnotsToMMperSec(courseWind.getWindSpeedKnots()); + byte[] windSpeedBytes = intToBytes(windSpeedInt, 2); + + int bestUpwindAngleInt = packHeading(courseWind.getBestUpwindAngle().degrees()); + byte[] bestUpwindAngleBytes = intToBytes(bestUpwindAngleInt, 2); + + int bestDownwindAngleInt = packHeading(courseWind.getBestDownwindAngle().degrees()); + byte[] bestDownwindAngleBytes = intToBytes(bestDownwindAngleInt, 2); + + byte[] flags = intToBytes(courseWind.getFlags(), 1); + + courseWindBuffer.put(windId); + courseWindBuffer.put(timeBytes); + courseWindBuffer.put(raceIDBytes); + courseWindBuffer.put(windDirectionBytes); + courseWindBuffer.put(windSpeedBytes); + courseWindBuffer.put(bestUpwindAngleBytes); + courseWindBuffer.put(bestDownwindAngleBytes); + courseWindBuffer.put(flags); + + return courseWindBuffer.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java new file mode 100644 index 00000000..193144c3 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java @@ -0,0 +1,57 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.BoatAction; +import network.Messages.CourseWind; +import network.Messages.CourseWinds; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; + +/** + * This encoder can encode a {@link CourseWinds} message. + */ +public class CourseWindsEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public CourseWindsEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + CourseWinds courseWinds = (CourseWinds) message; + + + byte messageVersionNumber = CourseWinds.currentMessageVersionNumber; + + byte byteWindID = courseWinds.getSelectedWindID(); + + byte[] loopcount = intToBytes(courseWinds.getCourseWinds().size(), 1); + + ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.getCourseWinds().size()); + + result.put(messageVersionNumber); + result.put(byteWindID); + result.put(loopcount); + + //Encode each CourseWind. + for (CourseWind wind: courseWinds.getCourseWinds()){ + + CourseWindEncoder courseWindEncoder = new CourseWindEncoder(); + byte[] encodedCourseWind = courseWindEncoder.encode(wind); + + result.put(encodedCourseWind); + } + return result.array(); + + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index c026efd7..6ac7ec5b 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -49,7 +49,7 @@ public class EncoderFactory { //case MARKROUNDING: return new MarkRoundingEncoder();//TODO - //case COURSEWIND: return new CourseWindEncoder();//TODO + case COURSEWIND: return new CourseWindsEncoder(); //case AVGWIND: return new AverageWindEncoder();//TODO diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index ebd67819..a933e179 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -143,26 +143,7 @@ public class RaceVisionByteEncoder { return result.array(); } - public static byte[] courseWind(byte windID, ArrayList courseWinds){ - int messageVersionNumber = 0b1; - byte byteWindID = windID; - byte[] loopcount = intToBytes(courseWinds.size(), 1); - ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.size()); - result.put(intToBytes(messageVersionNumber, 1)); - result.put(byteWindID); - result.put(loopcount); - for (CourseWind wind: courseWinds){ - result.put(intToBytes(wind.getID(), 1)); - result.put(longToBytes(wind.getTime(), 6)); - result.put(intToBytes(wind.getRaceID(), 4)); - result.put(intToBytes(wind.getWindDirection(), 2)); - result.put(intToBytes(wind.getWindSpeed(), 2)); - result.put(intToBytes(wind.getBestUpwindAngle(), 2)); - result.put(intToBytes(wind.getBestDownwindAngle(), 2)); - result.put(intToBytes(wind.getFlags(), 1)); - } - return result.array(); - } + public static byte[] averageWind(int time, int rawPeriod, int rawSampleSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){ int messageVersionNumber = 0b1; diff --git a/racevisionGame/src/main/java/network/Messages/BoatAction.java b/racevisionGame/src/main/java/network/Messages/BoatAction.java index d20943a5..fcc96aa8 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatAction.java +++ b/racevisionGame/src/main/java/network/Messages/BoatAction.java @@ -4,19 +4,30 @@ import network.Messages.Enums.BoatActionEnum; import network.Messages.Enums.MessageType; /** - * Created by David on 10/07/2017. + * Represents a BoatAction message. */ public class BoatAction extends AC35Data { - private byte boatAction; + /** + * The action for this message. + */ + private BoatActionEnum boatAction; + /** + * Constructs a BoatActon message with a given action. + * @param boatAction Action to use. + */ public BoatAction(BoatActionEnum boatAction){ super(MessageType.BOATACTION); - this.boatAction = boatAction.getValue(); + this.boatAction = boatAction; } - public byte getBoatAction() { + /** + * Returns the action for this message. + * @return The action for this message. + */ + public BoatActionEnum getBoatAction() { return boatAction; } -} \ No newline at end of file +} diff --git a/racevisionGame/src/main/java/network/Messages/CourseWind.java b/racevisionGame/src/main/java/network/Messages/CourseWind.java index 727d5fcc..60575edb 100644 --- a/racevisionGame/src/main/java/network/Messages/CourseWind.java +++ b/racevisionGame/src/main/java/network/Messages/CourseWind.java @@ -2,57 +2,132 @@ package network.Messages; import network.Messages.Enums.MessageType; +import shared.model.Bearing; /** - * Created by fwy13 on 21/04/17. + * Contains a single CourseWind record. + * A CourseWinds message contains one or more CourseWind messages. */ public class CourseWind extends AC35Data { - private int ID, raceID, windDirection, windSpeed, bestUpwindAngle, bestDownwindAngle, flags; + /** + * The ID for this wind source. + */ + private int ID; + + /** + * The time the wind was captured at. Milliseconds since unix epoch. + */ private long time; - public CourseWind(int ID, long time, int raceID, int windDirection, int windSpeed, int bestUpwindAngle, int bestDownwindAngle, - int flags){ + /** + * The ID of the race this applies to. + * 0 means it isn't race specific. + */ + private int raceID; + + /** + * Direction of the wind. + */ + private Bearing windDirection; + + /** + * The speed of the wind, in knots. + */ + private double windSpeedKnots; + + /** + * Optimum upwind sailing angle. + */ + private Bearing bestUpwindAngle; + + /** + * Optimum downwind sailing angle. + */ + private Bearing bestDownwindAngle; + + /** + * Various flags which determine which values are valid. + */ + private short flags; + + + + public CourseWind(int ID, long time, int raceID, Bearing windDirection, double windSpeedKnots, Bearing bestUpwindAngle, Bearing bestDownwindAngle, short flags) { super(MessageType.COURSEWIND); this.ID = ID; this.time = time; this.raceID = raceID; this.windDirection = windDirection; - this.windSpeed = windSpeed; + this.windSpeedKnots = windSpeedKnots; this.bestUpwindAngle = bestUpwindAngle; this.bestDownwindAngle = bestDownwindAngle; this.flags = flags; } + + /** + * Returns the ID of the wind source. + * @return ID of the wind source. + */ public int getID() { return ID; } + /** + * Returns the time that this was captured at. Milliseconds since unix epoch. + * @return Time this wind was captured at. + */ + public long getTime() { + return time; + } + + /** + * Returns the ID of the race this wind source belongs to. 0 means any race. + * @return ID of the race this belongs to. + */ public int getRaceID() { return raceID; } - public int getWindDirection() { + /** + * Returns the direction of the wind. + * @return The direction of the wind. + */ + public Bearing getWindDirection() { return windDirection; } - public int getWindSpeed() { - return windSpeed; + /** + * Returns the wind speed, in knots. + * @return Wind speed, in knots. + */ + public double getWindSpeedKnots() { + return windSpeedKnots; } - public int getBestUpwindAngle() { + /** + * Returns the best upwind sailing angle. + * @return Best upwind sailing angle. + */ + public Bearing getBestUpwindAngle() { return bestUpwindAngle; } - public int getBestDownwindAngle() { + /** + * Returns the best downwind sailing angle. + * @return The best downwind sailing angle. + */ + public Bearing getBestDownwindAngle() { return bestDownwindAngle; } + /** + * Returns various flags which determine which values are valid. + * @return Flag which determines which values are valid. + */ public int getFlags() { return flags; } - public long getTime() { - return time; - } } diff --git a/racevisionGame/src/main/java/network/Messages/CourseWinds.java b/racevisionGame/src/main/java/network/Messages/CourseWinds.java index fc575867..d62f3135 100644 --- a/racevisionGame/src/main/java/network/Messages/CourseWinds.java +++ b/racevisionGame/src/main/java/network/Messages/CourseWinds.java @@ -6,19 +6,67 @@ import network.Messages.Enums.MessageType; import java.util.List; /** - * Created by fwy13 on 25/04/17. + * Represents the information in a CourseWind message (AC streaming spec: 4.11). */ public class CourseWinds extends AC35Data { - private int msgVerNum; - private int selectedWindID; + /** + * The current version number for this message type. + */ + public static final byte currentMessageVersionNumber = 1; + + + /** + * The version number of this message. + */ + private byte messageVersionNumber; + + /** + * The ID of the wind source currently selected. + */ + private byte selectedWindID; + + /** + * A list of wind sources. + */ private List courseWinds; - public CourseWinds(int msgVerNum, int selectedWindID, List courseWinds){ + + /** + * Constructs a CourseWinds with given parameters. + * @param messageVersionNumber The version number of the message. + * @param selectedWindID The selected wind ID. + * @param courseWinds A list of wind sources. + */ + public CourseWinds(byte messageVersionNumber, byte selectedWindID, List courseWinds) { super(MessageType.COURSEWIND); - this.msgVerNum = msgVerNum; + this.messageVersionNumber = messageVersionNumber; this.selectedWindID = selectedWindID; this.courseWinds = courseWinds; } + + /** + * Returns the version number of this message. + * @return Version number of this message. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } + + /** + * Returns the ID of the selected wind source. + * @return ID of the selected wind source. + */ + public byte getSelectedWindID() { + return selectedWindID; + } + + /** + * Returns the list of wind sources. + * @return List of wind sources. + */ + public List getCourseWinds() { + return courseWinds; + } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index fb6a257b..df54d16d 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -2,6 +2,7 @@ package visualiser.gameController; import network.BinaryMessageDecoder; import network.MessageDecoders.BoatActionDecoder; +import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; import visualiser.gameController.Keys.ControlKey; import visualiser.gameController.Keys.KeyFactory; @@ -47,9 +48,10 @@ public class ControllerServer implements Runnable { if (inputStream.available() > 0) { inputStream.read(message); BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message); - BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody()); - BoatActionEnum decodedMessage = boatActionDecoder.getBoatAction(); - System.out.println("Received key: " + decodedMessage); + BoatActionDecoder boatActionDecoder = new BoatActionDecoder(); + boatActionDecoder.decode(encodedMessage.getMessageBody()); + BoatAction boatAction = boatActionDecoder.getMessage(); + System.out.println("Received key: " + boatAction.getBoatAction()); } } catch (IOException e) { e.printStackTrace(); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java index d7e13b97..80dacbe6 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatActionDecoderTest.java @@ -29,10 +29,10 @@ public class BoatActionDecoderTest { byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. - BoatActionDecoder testDecoder = new BoatActionDecoder(testEncodedMessage); - BoatActionEnum decodedBoatAction = testDecoder.getBoatAction(); + BoatActionDecoder testDecoder = new BoatActionDecoder(); + testDecoder.decode(testEncodedMessage); - BoatAction decodedMessage = new BoatAction(decodedBoatAction); + BoatAction decodedMessage = testDecoder.getMessage(); return decodedMessage; } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index dcc69abe..7fcdeb4a 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -53,7 +53,8 @@ public class BoatLocationDecoderTest { byte [] testEncodedMessage = RaceVisionByteEncoder.encode(testMessage); //Decode. - BoatLocationDecoder testDecoder = new BoatLocationDecoder(testEncodedMessage); + BoatLocationDecoder testDecoder = new BoatLocationDecoder(); + testDecoder.decode(testEncodedMessage); BoatLocation decodedTest = testDecoder.getMessage(); //Check if valid. diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java index 635e581c..088a85cb 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java @@ -59,12 +59,10 @@ public class BoatStatusDecoderTest { private static BoatStatus encodeDecodeBoatStatus(BoatStatus boatStatus) { BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); - byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); - BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(boatStatusEncoded); - - BoatStatus boatStatusDecoded = boatStatusDecoder.getMessage(); + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(); + BoatStatus boatStatusDecoded = boatStatusDecoder.decode(boatStatusEncoded); return boatStatusDecoded; } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java index 53793b81..0a8753db 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java @@ -1,57 +1,77 @@ package network.MessageDecoders; -import network.MessageEncoders.RaceVisionByteEncoder; +import network.MessageEncoders.CourseWindEncoder; +import network.Messages.BoatStatus; import network.Messages.CourseWind; +import network.Messages.Enums.BoatStatusEnum; import org.junit.Assert; import org.junit.Test; - -import java.util.ArrayList; - +import shared.model.Bearing; /** - * Created by hba56 on 23/04/17. + * Test for the CourseWind encoder and decoder */ public class CourseWindDecoderTest { + + + /** + * Creates a CourseWind message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ @Test - public void getByteArrayTest(){ + public void courseWindEncodeDecodeTest() throws Exception { + long time = System.currentTimeMillis(); - CourseWind testCourseWind1 = new CourseWind(1, time, 2, - 3, 4, 5, - 7, 6); + CourseWind courseWind = new CourseWind( + 1, + time, + 2, + Bearing.fromDegrees(45), + 4, + Bearing.fromDegrees(70), + Bearing.fromDegrees(160), + (byte) 0x13 ); - long time2 = System.currentTimeMillis(); - CourseWind testCourseWind2 = new CourseWind(2, time2, 2, - 3, 4, 5, - 7, 6); - ArrayList testCourseWinds = new ArrayList(); - testCourseWinds.add(testCourseWind1); - testCourseWinds.add(testCourseWind2); + CourseWind courseWindDecoded = encodeDecodeCourseWind(courseWind); + compareCourseWindMessages(courseWind, courseWindDecoded); - byte[] testEncodedCourseWind = RaceVisionByteEncoder.courseWind((byte) 1, testCourseWinds); + } - CourseWindDecoder testDecoder = new CourseWindDecoder(testEncodedCourseWind); + /** + * Encodes and decodes a CourseWind, and returns it. + * @param courseWind The CourseWind to encode and decode. + * @return The decoded CourseWind. + */ + private static CourseWind encodeDecodeCourseWind(CourseWind courseWind) { - ArrayList testDecodedCourseWinds = testDecoder.getLoopMessages(); + CourseWindEncoder courseWindEncoder = new CourseWindEncoder(); + byte[] courseWindEncoded = courseWindEncoder.encode(courseWind); - Assert.assertEquals(testCourseWinds.get(0).getID(), testDecodedCourseWinds.get(0).getID()); - Assert.assertEquals(testCourseWinds.get(0).getTime(), testDecodedCourseWinds.get(0).getTime()); - Assert.assertEquals(testCourseWinds.get(0).getRaceID(), testDecodedCourseWinds.get(0).getRaceID()); - Assert.assertEquals(testCourseWinds.get(0).getWindDirection(), testDecodedCourseWinds.get(0).getWindDirection()); - Assert.assertEquals(testCourseWinds.get(0).getWindSpeed(), testDecodedCourseWinds.get(0).getWindSpeed()); - Assert.assertEquals(testCourseWinds.get(0).getBestUpwindAngle(), testDecodedCourseWinds.get(0).getBestUpwindAngle()); - Assert.assertEquals(testCourseWinds.get(0).getBestDownwindAngle(), testDecodedCourseWinds.get(0).getBestDownwindAngle()); - Assert.assertEquals(testCourseWinds.get(0).getFlags(), testDecodedCourseWinds.get(0).getFlags()); + CourseWindDecoder courseWindDecoder = new CourseWindDecoder(); + CourseWind courseWindDecoded = courseWindDecoder.decode(courseWindEncoded); - Assert.assertEquals(testCourseWinds.get(1).getID(), testDecodedCourseWinds.get(1).getID()); - Assert.assertEquals(testCourseWinds.get(1).getTime(), testDecodedCourseWinds.get(1).getTime()); - Assert.assertEquals(testCourseWinds.get(1).getRaceID(), testDecodedCourseWinds.get(1).getRaceID()); - Assert.assertEquals(testCourseWinds.get(1).getWindDirection(), testDecodedCourseWinds.get(1).getWindDirection()); - Assert.assertEquals(testCourseWinds.get(1).getWindSpeed(), testDecodedCourseWinds.get(1).getWindSpeed()); - Assert.assertEquals(testCourseWinds.get(1).getBestUpwindAngle(), testDecodedCourseWinds.get(1).getBestUpwindAngle()); - Assert.assertEquals(testCourseWinds.get(1).getBestDownwindAngle(), testDecodedCourseWinds.get(1).getBestDownwindAngle()); - Assert.assertEquals(testCourseWinds.get(1).getFlags(), testDecodedCourseWinds.get(1).getFlags()); + return courseWindDecoded; + } + + + /** + * Compares two CourseWind messages to check that they are equal. + * @param original The original CourseWind message. + * @param decoded The decoded CourseWind message. + */ + public static void compareCourseWindMessages(CourseWind original, CourseWind decoded) { + + Assert.assertEquals(original.getID(), decoded.getID()); + Assert.assertEquals(original.getTime(), decoded.getTime()); + Assert.assertEquals(original.getRaceID(), decoded.getRaceID()); + Assert.assertEquals(original.getWindDirection().degrees(), decoded.getWindDirection().degrees(), 0.01); + Assert.assertEquals(original.getWindSpeedKnots(), decoded.getWindSpeedKnots(), 0.01); + Assert.assertEquals(original.getBestUpwindAngle().degrees(), decoded.getBestUpwindAngle().degrees(), 0.01); + Assert.assertEquals(original.getBestDownwindAngle().degrees(), decoded.getBestDownwindAngle().degrees(), 0.01); + Assert.assertEquals(original.getFlags(), decoded.getFlags()); } + } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/CourseWindsDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindsDecoderTest.java new file mode 100644 index 00000000..45c87f55 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindsDecoderTest.java @@ -0,0 +1,96 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.CourseWind; +import network.Messages.CourseWinds; +import org.junit.Test; +import shared.model.Bearing; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + + +import static org.junit.Assert.*; + + +/** + * Tests for CourseWinds encoder and decoder. + */ +public class CourseWindsDecoderTest { + + /** + * Tests if a CourseWinds message can be encoded and decoded correctly. + * @throws Exception Thrown if an error occurs. + */ + @Test + public void courseWindsEncodeDecodeTest() throws Exception { + + long time1 = System.currentTimeMillis(); + CourseWind testCourseWind1 = new CourseWind( + 1, + time1, + 2, + Bearing.fromDegrees(45), + 4, + Bearing.fromDegrees(70), + Bearing.fromDegrees(160), + (byte) 0xCE ); + + long time2 = System.currentTimeMillis(); + CourseWind testCourseWind2 = new CourseWind( + 2, + time2, + 2, + Bearing.fromDegrees(55), + 4, + Bearing.fromDegrees(80), + Bearing.fromDegrees(180), + (byte) 0x0D ); + + List testCourseWinds = new ArrayList<>(); + testCourseWinds.add(testCourseWind1); + testCourseWinds.add(testCourseWind2); + + CourseWinds courseWinds = new CourseWinds(CourseWinds.currentMessageVersionNumber, (byte) 2, testCourseWinds); + + + byte[] testEncodedCourseWind = RaceVisionByteEncoder.encode(courseWinds); + + CourseWindsDecoder courseWindsDecoder = new CourseWindsDecoder(); + courseWindsDecoder.decode(testEncodedCourseWind); + CourseWinds courseWindsDecoded = courseWindsDecoder.getMessage(); + + compareCourseWindsMessages(courseWinds, courseWindsDecoded); + + + } + + + /** + * Compares two course winds messages to ensure they are the same. + * @param original The original message. + * @param decoded The decoded message. + */ + public static void compareCourseWindsMessages(CourseWinds original, CourseWinds decoded) { + + //Compare header. + assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + assertEquals(original.getSelectedWindID(), decoded.getSelectedWindID()); + assertEquals(original.getCourseWinds().size(), decoded.getCourseWinds().size()); + + //Compare each CourseWind. + List originalWinds = original.getCourseWinds(); + List decodedWinds = decoded.getCourseWinds(); + + Iterator originalIterator = originalWinds.iterator(); + Iterator decodedIterator = decodedWinds.iterator(); + + while (originalIterator.hasNext() && decodedIterator.hasNext()) { + + CourseWindDecoderTest.compareCourseWindMessages(originalIterator.next(), decodedIterator.next()); + + } + + } +} diff --git a/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java index 778a667a..ea1de885 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/HeartBeatDecoderTest.java @@ -28,7 +28,8 @@ public class HeartBeatDecoderTest { byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. - HeartBeatDecoder testDecoder = new HeartBeatDecoder(testEncodedMessage); + HeartBeatDecoder testDecoder = new HeartBeatDecoder(); + testDecoder.decode(testEncodedMessage); HeartBeat decodedMessage = testDecoder.getMessage(); return decodedMessage; diff --git a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java index cd31ad67..1e3a5c3d 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java @@ -27,7 +27,8 @@ public class JoinAcceptanceDecoderTest { byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. - JoinAcceptanceDecoder testDecoder = new JoinAcceptanceDecoder(testEncodedMessage); + JoinAcceptanceDecoder testDecoder = new JoinAcceptanceDecoder(); + testDecoder.decode(testEncodedMessage); JoinAcceptance decodedMessage = testDecoder.getMessage(); return decodedMessage; diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java index 4f1e2721..d9a47b81 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java @@ -89,7 +89,8 @@ public class RaceStatusDecoderTest { byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatusOriginal); - RaceStatusDecoder decoderTest = new RaceStatusDecoder(encodedRaceStatus); + RaceStatusDecoder decoderTest = new RaceStatusDecoder(); + decoderTest.decode(encodedRaceStatus); RaceStatus decodedMessage = decoderTest.getMessage(); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java index bde4ae75..fe70a709 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RequestToJoinDecoderTest.java @@ -28,7 +28,8 @@ public class RequestToJoinDecoderTest { byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message); //Decode. - RequestToJoinDecoder testDecoder = new RequestToJoinDecoder(testEncodedMessage); + RequestToJoinDecoder testDecoder = new RequestToJoinDecoder(); + testDecoder.decode(testEncodedMessage); RequestToJoin decodedMessage = testDecoder.getMessage(); return decodedMessage; diff --git a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java index 32d1a61e..f0c0ba0b 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java @@ -45,7 +45,8 @@ public class XMLMessageDecoderTest { - XMLMessageDecoder decoderXML = new XMLMessageDecoder(encodedXML); + XMLMessageDecoder decoderXML = new XMLMessageDecoder(); + decoderXML.decode(encodedXML); XMLMessage decodedMessage = decoderXML.getMessage(); From e99ad00294f54da2b5ffe034960f08d589e8e54a Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 18:54:45 +1200 Subject: [PATCH 42/53] Javadoc fixes. --- .../src/main/java/network/MessageDecoders/BoatStatusDecoder.java | 1 + .../src/main/java/network/MessageDecoders/CourseWindDecoder.java | 1 + 2 files changed, 2 insertions(+) diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java index 972c1eae..ed2c6cf0 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java @@ -38,6 +38,7 @@ public class BoatStatusDecoder { /** * Decodes the contained message. * @param encodedMessage The message to decode. + * @return The decoded message. */ public BoatStatus decode(byte[] encodedMessage) { this.encodedMessage = encodedMessage; diff --git a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java index 78bc6f1c..6a26e4ae 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java @@ -37,6 +37,7 @@ public class CourseWindDecoder { /** * Decodes the contained message. * @param encodedMessage The message to decode. + * @return The decoded message. */ public CourseWind decode(byte[] encodedMessage) { this.encodedMessage = encodedMessage; From ce63f58429f779433b71f59bff9a56fe7d6b6944 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 19:36:21 +1200 Subject: [PATCH 43/53] Added RaceStartStatusEncoder. Added RaceStartTypeEnum. Refactored RaceStartStatusDecoder to implement the MessageDecoder interface. Documented RaceStartStatus, and it actually exposes its properties now. Updated RaceStartStatusDecoderTest. issue #35 #36 #story[1095] --- .../java/network/BinaryMessageDecoder.java | 4 +- .../MessageDecoders/DecoderFactory.java | 2 +- .../RaceStartStatusDecoder.java | 105 ++++++++++-------- .../MessageEncoders/EncoderFactory.java | 2 +- .../RaceStartStatusEncoder.java | 51 +++++++++ .../RaceVisionByteEncoder.java | 19 ---- .../Messages/Enums/RaceStartTypeEnum.java | 97 ++++++++++++++++ .../network/Messages/RaceStartStatus.java | 97 +++++++++++++++- .../RaceStartStatusDecoderTest.java | 59 +++++++--- 9 files changed, 352 insertions(+), 84 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/RaceStartTypeEnum.java diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 0ee0d541..b0d5b2a6 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -164,8 +164,8 @@ public class BinaryMessageDecoder { case RACESTARTSTATUS: //System.out.println("Race Start Status Message"); - RaceStartStatusDecoder rssDecoder = new RaceStartStatusDecoder(messageBody); - return new RaceStartStatus(rssDecoder.getTime(), rssDecoder.getAck(), rssDecoder.getStartTime(), rssDecoder.getRaceID(), rssDecoder. getNotification()); + RaceStartStatusDecoder rssDecoder = new RaceStartStatusDecoder(); + return rssDecoder.decode(messageBody); case YACHTEVENTCODE: //System.out.println("Yacht Action Code!"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java index 500d20f6..3121f7f7 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java @@ -37,7 +37,7 @@ public class DecoderFactory { case XMLMESSAGE: return new XMLMessageDecoder(); - //case RACESTARTSTATUS: return new RaceStartStatusDecoder();//TODO + case RACESTARTSTATUS: return new RaceStartStatusDecoder(); //case YACHTEVENTCODE: return new YachtEventCodeDecoder();//TODO diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java index 236c5d27..062f1212 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java @@ -1,66 +1,81 @@ package network.MessageDecoders; +import network.Messages.AC35Data; +import network.Messages.Enums.RaceStartTypeEnum; +import network.Messages.RaceStartStatus; + import java.util.Arrays; import static network.Utils.ByteConverter.*; /** - * Created by hba56 on 21/04/17. + * Decodes {@link RaceStartStatus} messages. */ -public class RaceStartStatusDecoder { - private byte messageVersion; - private byte[] timestamp; - private byte[] ackNumber; - private byte[] raceStartTime; - private byte[] raceIdentifier; - private byte notificationType; - - private long time; - private short ack; - private long startTime; - private int raceID; - private char notification; - - - public RaceStartStatusDecoder(byte[] encodedRaceStartStatus) { - messageVersion = encodedRaceStartStatus[0]; - timestamp = Arrays.copyOfRange(encodedRaceStartStatus, 1, 7); - ackNumber = Arrays.copyOfRange(encodedRaceStartStatus, 7, 9); - raceStartTime = Arrays.copyOfRange(encodedRaceStartStatus, 9, 15); - raceIdentifier = Arrays.copyOfRange(encodedRaceStartStatus, 15, 19); - notificationType = encodedRaceStartStatus[19]; - - time = bytesToLong(timestamp); - ack = bytesToShort(ackNumber); - startTime = bytesToLong(raceStartTime); - raceID = bytesToInt(raceIdentifier); - notification = bytesToChar(notificationType); - } +public class RaceStartStatusDecoder implements MessageDecoder { + /** + * The encoded message. + */ + private byte[] encodedMessage; - public byte getMessageVersion() { - return messageVersion; - } + /** + * The decoded message. + */ + private RaceStartStatus message; - public long getTime() { - return time; - } - public short getAck() { - return ack; - } - public long getStartTime() { - return startTime; + /** + * Constructs a decoder to decode a given message. + */ + public RaceStartStatusDecoder() { } - public int getRaceID() { - return raceID; + + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + + byte messageVersion = encodedMessage[0]; + + byte[] timestamp = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timestamp); + + byte[] ackNumber = Arrays.copyOfRange(encodedMessage, 7, 9); + short ack = bytesToShort(ackNumber); + + byte[] raceStartTime = Arrays.copyOfRange(encodedMessage, 9, 15); + long startTime = bytesToLong(raceStartTime); + + byte[] raceIdentifier = Arrays.copyOfRange(encodedMessage, 15, 19); + int raceID = bytesToInt(raceIdentifier); + + byte notificationType = encodedMessage[19]; + + + + message = new RaceStartStatus( + messageVersion, + time, + ack, + startTime, + raceID, + RaceStartTypeEnum.fromByte(notificationType) + ); + + return message; + } - public char getNotification() { - return notification; + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public RaceStartStatus getMessage() { + return message; } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index 6ac7ec5b..fadf6dea 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -37,7 +37,7 @@ public class EncoderFactory { case XMLMESSAGE: return new XMLMessageEncoder(); - //case RACESTARTSTATUS: return new RaceStartStatusEncoder();//TODO + case RACESTARTSTATUS: return new RaceStartStatusEncoder(); //case YACHTEVENTCODE: return new YachtEventCodeEncoder();//TODO diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java new file mode 100644 index 00000000..f2d41d20 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java @@ -0,0 +1,51 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.RaceStartStatus; +import network.Utils.ByteConverter; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link RaceStartStatus} message. + */ +public class RaceStartStatusEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public RaceStartStatusEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + RaceStartStatus raceStartStatus = (RaceStartStatus) message; + + + byte messageVersion = raceStartStatus.getMessageVersionNumber(); + byte[] timestamp = longToBytes(raceStartStatus.getTimestamp(), 6); + byte[] ackNumber = intToBytes(raceStartStatus.getAckNum(), 2); + byte[] raceStartTime = longToBytes(raceStartStatus.getRaceStartTime(), 6); + byte[] raceIdentifier = intToBytes(raceStartStatus.getRaceID()); + byte[] notificationType = intToBytes(raceStartStatus.getNotificationType().getValue(), 1); + + ByteBuffer result = ByteBuffer.allocate(20); + result.put(messageVersion); + result.put(timestamp); + result.put(ackNumber); + result.put(raceStartTime); + result.put(raceIdentifier); + result.put(notificationType); + + return result.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index a933e179..45690bca 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -63,25 +63,6 @@ public class RaceVisionByteEncoder { return result.array(); } - public static byte[] raceStartStatus(long time, short ack, long startTime, int raceID, char notification){ - int messageVersion = 0b1; - byte[] timestamp = longToBytes(time, 6); - byte[] ackNumber = intToBytes(ack, 2); - byte[] raceStartTime = longToBytes(startTime, 6); - int raceIdentifier = raceID; - byte[] notificationType = intToBytes(notification, 1); - - ByteBuffer result = ByteBuffer.allocate(20); - result.put(intToBytes(messageVersion, 1)); - result.put(timestamp); - result.put(ackNumber); - result.put(raceStartTime); - result.put(intToBytes(raceIdentifier)); - result.put(notificationType); - - return result.array(); - } - public static byte[] yachtEventCode(long time, short acknowledgeNumber, int raceID, int destSourceID, int incidentID, int eventID){ int messageVersion = 0b10; diff --git a/racevisionGame/src/main/java/network/Messages/Enums/RaceStartTypeEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/RaceStartTypeEnum.java new file mode 100644 index 00000000..d9b8d69b --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/RaceStartTypeEnum.java @@ -0,0 +1,97 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various types race start status notifications. See AC35 streaming spec, 4.5. + */ +public enum RaceStartTypeEnum { + + + /** + * The race start time is being set. + */ + SET_RACE_START(1), + + /** + * The race has been postponed. + */ + RACE_POSTPONED(2), + + /** + * The race has been abandoned. + */ + RACE_ABANDONED(3), + + /** + * The race has been terminated. + */ + RACE_TERMINATED(4), + + /** + * Used to indicate that a given byte value is invalid. + */ + NOT_A_TYPE(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a RaceStartTypeEnum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private RaceStartTypeEnum(int value) { + this.value = (byte) value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + /** + * Stores a mapping between Byte values and RaceStartTypeEnum values. + */ + private static final Map byteToTypeMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToTypeMap. + */ + static { + for (RaceStartTypeEnum type : RaceStartTypeEnum.values()) { + RaceStartTypeEnum.byteToTypeMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param startTypeEnum Byte value to convert to a RaceStartTypeEnum value. + * @return The RaceStartTypeEnum value which corresponds to the given byte value. + */ + public static RaceStartTypeEnum fromByte(byte startTypeEnum) { + //Gets the corresponding MessageType from the map. + RaceStartTypeEnum type = RaceStartTypeEnum.byteToTypeMap.get(startTypeEnum); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_TYPE RaceStartTypeEnum. + return RaceStartTypeEnum.NOT_A_TYPE; + } else { + //Otherwise, return the RaceStartTypeEnum. + return type; + } + + } + + +} diff --git a/racevisionGame/src/main/java/network/Messages/RaceStartStatus.java b/racevisionGame/src/main/java/network/Messages/RaceStartStatus.java index 8dc442ab..6ad514d5 100644 --- a/racevisionGame/src/main/java/network/Messages/RaceStartStatus.java +++ b/racevisionGame/src/main/java/network/Messages/RaceStartStatus.java @@ -2,20 +2,63 @@ package network.Messages; import network.Messages.Enums.MessageType; +import network.Messages.Enums.RaceStartTypeEnum; + /** - * Created by fwy13 on 25/04/17. + * Represents a RaceStartStatus message from the API, section 4.5. */ public class RaceStartStatus extends AC35Data { + /** + * The current version number of this message type. + */ + public static final byte currentMessageVersionNumber = 1; + + + /** + * The version number of this message. + */ + private byte messageVersionNumber; + + /** + * The time at which this message was created. Milliseconds since unix epoch. + */ private long timestamp; + + /** + * Sequence number of message. + */ private int ackNum; + + /** + * The time the race is expected to start at. Milliseconds since unix epoch. + */ private long raceStartTime; + + /** + * The ID of the race this message relates to. + */ private int raceID; - private int notificationType; - public RaceStartStatus(long timestamp, int ackNum, long raceStartTime, int raceID, int notificationType){ + /** + * The type of notification this is. + */ + private RaceStartTypeEnum notificationType; + + + /** + * Constructs a RaceStartStatus message with the given parameters. + * @param messageVersionNumber Version number of the message. + * @param timestamp The timestamp at which this message was generated. + * @param ackNum The sequence number of this message. + * @param raceStartTime The expected race start time. + * @param raceID The ID of the race this message relates to. + * @param notificationType The type of notification this is. + */ + public RaceStartStatus(byte messageVersionNumber, long timestamp, int ackNum, long raceStartTime, int raceID, RaceStartTypeEnum notificationType) { super(MessageType.RACESTARTSTATUS); + this.messageVersionNumber = messageVersionNumber; this.timestamp = timestamp; this.ackNum = ackNum; this.raceStartTime = raceStartTime; @@ -23,4 +66,52 @@ public class RaceStartStatus extends AC35Data { this.notificationType = notificationType; } + + /** + * Returns the version number of this message. + * @return Version number of this message. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } + + /** + * Return the time at which this message was generated. Milliseconds since unix epoch. + * @return Time at which this message was generated. + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Returns the sequence number of this message. + * @return Sequence number of this message. + */ + public int getAckNum() { + return ackNum; + } + + /** + * Returns the expected race start time. Milliseconds since unix epoch. + * @return Expected race start time. + */ + public long getRaceStartTime() { + return raceStartTime; + } + + /** + * Returns the race ID this message relates to. + * @return Race ID this message relates to. + */ + public int getRaceID() { + return raceID; + } + + /** + * Returns the type of start status notification this message is. + * @return The type of notification this is. + */ + public RaceStartTypeEnum getNotificationType() { + return notificationType; + } } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java index ce2fe475..b0029321 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java @@ -1,29 +1,62 @@ package network.MessageDecoders; import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.Enums.RaceStartTypeEnum; +import network.Messages.RaceStartStatus; import org.junit.Assert; import org.junit.Test; /** - * Created by hba56 on 23/04/17. + * Tests for the RaceStartStatus encoder and decoder. */ public class RaceStartStatusDecoderTest { + + + /** + * Tests if a RaceStartStatus message can be encoded and decoded correctly. + * @throws Exception Thrown when an error occurs. + */ @Test - public void getByteArrayTest(){ - long time = System.currentTimeMillis(); + public void raceStartStatusEncodeDecodeTest() throws Exception { + + long timestamp = System.currentTimeMillis(); - long time2 = System.currentTimeMillis(); - byte[] encodedRaceStartStatus = RaceVisionByteEncoder.raceStartStatus(time, (short)1, - time2, 2, (char)3); + long startTime = System.currentTimeMillis() + 10 * 1000; - RaceStartStatusDecoder testDecoder = new RaceStartStatusDecoder(encodedRaceStartStatus); + RaceStartStatus raceStartStatus = new RaceStartStatus( + RaceStartStatus.currentMessageVersionNumber, + timestamp, + 55, + startTime, + 35, + RaceStartTypeEnum.SET_RACE_START + ); - Assert.assertEquals(0b1, testDecoder.getMessageVersion()); - Assert.assertEquals(time, testDecoder.getTime()); - Assert.assertEquals(1, testDecoder.getAck()); - Assert.assertEquals(time2, testDecoder.getStartTime()); - Assert.assertEquals(2, testDecoder.getRaceID()); - Assert.assertEquals((char)3, testDecoder.getNotification()); + byte[] encodedRaceStartStatus = RaceVisionByteEncoder.encode(raceStartStatus); + + RaceStartStatusDecoder testDecoder = new RaceStartStatusDecoder(); + testDecoder.decode(encodedRaceStartStatus); + RaceStartStatus raceStartStatusDecoded = testDecoder.getMessage(); + + compareRaceStartStatusMessages(raceStartStatus, raceStartStatusDecoded); } + + /** + * Compares two RaceStartStatus messages to check that they are the same. + * @param original The original message. + * @param decoded The decoded message. + */ + public static void compareRaceStartStatusMessages(RaceStartStatus original, RaceStartStatus decoded) { + + Assert.assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + Assert.assertEquals(original.getTimestamp(), decoded.getTimestamp()); + Assert.assertEquals(original.getAckNum(), decoded.getAckNum()); + Assert.assertEquals(original.getRaceStartTime(), decoded.getRaceStartTime()); + Assert.assertEquals(original.getRaceID(), decoded.getRaceID()); + Assert.assertEquals(original.getNotificationType(), decoded.getNotificationType()); + + } + + } From da800e659a4d40b98a9c2b43029fc1f4d67b6c27 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 20:31:21 +1200 Subject: [PATCH 44/53] Added AverageWindEncoder. Refactored AverageWindDecoder - it now implements MessageDecoder. Refactored AverageWind - it now uses milliseconds and knots, instead of packed MMperSec and tenths of a second. It also exposes its attributes now. Added (un)packAverageWindPeriod to AC35UnitConverter. Added AverageWindDecoderTest. issue #35 #36 #story[1095] --- .../java/network/BinaryMessageDecoder.java | 4 +- .../MessageDecoders/AverageWindDecoder.java | 111 +++++++--- .../MessageDecoders/DecoderFactory.java | 2 +- .../MessageEncoders/AverageWindEncoder.java | 86 ++++++++ .../MessageEncoders/EncoderFactory.java | 2 +- .../RaceVisionByteEncoder.java | 28 --- .../java/network/Messages/AverageWind.java | 198 ++++++++++++++++-- .../java/network/Utils/AC35UnitConverter.java | 19 ++ .../AverageWindDecoderTest.java | 75 +++++++ 9 files changed, 439 insertions(+), 86 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/AverageWindDecoderTest.java diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index b0d5b2a6..71765e12 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -199,8 +199,8 @@ public class BinaryMessageDecoder { case AVGWIND: //System.out.println("Average Wind Message!"); - AverageWindDecoder awDecoder = new AverageWindDecoder(messageBody); - return awDecoder.getAverageWind(); + AverageWindDecoder awDecoder = new AverageWindDecoder(); + return awDecoder.decode(messageBody); case BOATACTION: BoatActionDecoder baDecoder = new BoatActionDecoder(); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java index 13de3c80..1b0f12d5 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java @@ -1,56 +1,103 @@ package network.MessageDecoders; +import network.Messages.AC35Data; import network.Messages.AverageWind; import network.Utils.ByteConverter; +import static network.Utils.AC35UnitConverter.*; + import java.util.Arrays; /** - * Created by hba56 on 23/04/17. + * Decodes {@link AverageWind} messages. */ -public class AverageWindDecoder { - byte messageVersionNumber; - byte[] byteTime; - byte[] byteRawPeriod; - byte[] byteRawSpeed; - byte[] bytePeriod2; - byte[] byteSpeed2; - byte[] bytePeriod3; - byte[] byteSpeed3; - byte[] bytePeriod4; - byte[] byteSpeed4; - - AverageWind averageWind; - - public AverageWindDecoder(byte[] encodedAverageWind) { - messageVersionNumber = encodedAverageWind[0]; - byteTime = Arrays.copyOfRange(encodedAverageWind, 1, 7); - byteRawPeriod = Arrays.copyOfRange(encodedAverageWind, 7, 9); - byteRawSpeed = Arrays.copyOfRange(encodedAverageWind, 9, 11); - bytePeriod2 = Arrays.copyOfRange(encodedAverageWind, 11, 13); - byteSpeed2 = Arrays.copyOfRange(encodedAverageWind, 13, 15); - bytePeriod3 = Arrays.copyOfRange(encodedAverageWind, 15, 17); - byteSpeed3 = Arrays.copyOfRange(encodedAverageWind, 17, 19); - bytePeriod4 = Arrays.copyOfRange(encodedAverageWind, 19, 21); - byteSpeed4 = Arrays.copyOfRange(encodedAverageWind, 21, 23); - - int msgNum = ByteConverter.bytesToInt(messageVersionNumber); - long lngTime = ByteConverter.bytesToLong(byteTime); +public class AverageWindDecoder implements MessageDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private AverageWind message; + + + + public AverageWindDecoder() { + } + + + + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + + byte messageVersionNumber = encodedMessage[0]; + + + byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = ByteConverter.bytesToLong(byteTime); + + byte[] byteRawPeriod = Arrays.copyOfRange(encodedMessage, 7, 9); int intRawPeriod = ByteConverter.bytesToInt(byteRawPeriod); + long rawPeriod = unpackAverageWindPeriod(intRawPeriod); + + byte[] byteRawSpeed = Arrays.copyOfRange(encodedMessage, 9, 11); int intRawSpeed = ByteConverter.bytesToInt(byteRawSpeed); + double rawSpeedKnots = unpackMMperSecToKnots(intRawSpeed); + + byte[] bytePeriod2 = Arrays.copyOfRange(encodedMessage, 11, 13); int intPeriod2 = ByteConverter.bytesToInt(bytePeriod2); + long period2 = unpackAverageWindPeriod(intPeriod2); + + byte[] byteSpeed2 = Arrays.copyOfRange(encodedMessage, 13, 15); int intSpeed2 = ByteConverter.bytesToInt(byteSpeed2); + double speed2Knots = unpackMMperSecToKnots(intSpeed2); + + byte[] bytePeriod3 = Arrays.copyOfRange(encodedMessage, 15, 17); int intPeriod3 = ByteConverter.bytesToInt(bytePeriod3); + long period3 = unpackAverageWindPeriod(intPeriod3); + + byte[] byteSpeed3 = Arrays.copyOfRange(encodedMessage, 17, 19); int intSpeed3 = ByteConverter.bytesToInt(byteSpeed3); + double speed3Knots = unpackMMperSecToKnots(intSpeed3); + + byte[] bytePeriod4 = Arrays.copyOfRange(encodedMessage, 19, 21); int intPeriod4 = ByteConverter.bytesToInt(bytePeriod4); + long period4 = unpackAverageWindPeriod(intPeriod4); + + byte[] byteSpeed4 = Arrays.copyOfRange(encodedMessage, 21, 23); int intSpeed4 = ByteConverter.bytesToInt(byteSpeed4); + double speed4Knots = unpackMMperSecToKnots(intSpeed4); + + + + + message = new AverageWind( + messageVersionNumber, + time, + rawPeriod, + rawSpeedKnots, + period2, + speed2Knots, + period3, + speed3Knots, + period4, + speed4Knots ); - this.averageWind = new AverageWind(msgNum, lngTime, intRawPeriod, intRawSpeed, intPeriod2, intSpeed2, intPeriod3, intSpeed3, intPeriod4, intSpeed4); + return message; } - public AverageWind getAverageWind() { - return averageWind; + /** + * Returns the decoded message. + * @return The decoded message. + */ + public AverageWind getMessage() { + return message; } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java index 3121f7f7..9b04803c 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java @@ -51,7 +51,7 @@ public class DecoderFactory { case COURSEWIND: return new CourseWindsDecoder(); - //case AVGWIND: return new AverageWindDecoder()//TODO; + case AVGWIND: return new AverageWindDecoder(); case REQUEST_TO_JOIN: return new RequestToJoinDecoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java new file mode 100644 index 00000000..dc1966fc --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java @@ -0,0 +1,86 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.AverageWind; + +import java.nio.ByteBuffer; + +import static network.Utils.AC35UnitConverter.*; +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link AverageWind} message. + */ +public class AverageWindEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public AverageWindEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + AverageWind averageWind = (AverageWind) message; + + + byte messageVersionNumber = averageWind.getMessageVersionNumber(); + + long time = averageWind.getTime(); + byte[] byteTime = longToBytes(time,6); + + long rawPeriod = averageWind.getRawPeriod(); + int rawPeriodInt = packAverageWindPeriod(rawPeriod); + byte[] byteRawPeriod = intToBytes(rawPeriodInt, 2); + + double rawSampleSpeed = averageWind.getRawSpeedKnots(); + int rawSampleSpeedInt = packKnotsToMMperSec(rawSampleSpeed); + byte[] byteRawSpeed = intToBytes(rawSampleSpeedInt, 2); + + long period2 = averageWind.getSampleTwoPeriod(); + int period2Int = packAverageWindPeriod(period2); + byte[] bytePeriod2 = intToBytes(period2Int, 2); + + double speed2 = averageWind.getSampleTwoSpeedKnots(); + int speed2Int = packKnotsToMMperSec(speed2); + byte[] byteSpeed2 = intToBytes(speed2Int, 2); + + long period3 = averageWind.getSampleThreePeriod(); + int period3Int = packAverageWindPeriod(period3); + byte[] bytePeriod3 = intToBytes(period3Int, 2); + + double speed3 = averageWind.getSampleThreeSpeedKnots(); + int speed3Int = packKnotsToMMperSec(speed3); + byte[] byteSpeed3 = intToBytes(speed3Int, 2); + + long period4 = averageWind.getSampleFourPeriod(); + int period4Int = packAverageWindPeriod(period4); + byte[] bytePeriod4 = intToBytes(period4Int, 2); + + double speed4 = averageWind.getSampleFourSpeedKnots(); + int speed4Int = packKnotsToMMperSec(speed4); + byte[] byteSpeed4 = intToBytes(speed4Int, 2); + + + + ByteBuffer result = ByteBuffer.allocate(23); + result.put(messageVersionNumber); + result.put(byteTime); + result.put(byteRawPeriod); + result.put(byteRawSpeed); + result.put(bytePeriod2); + result.put(byteSpeed2); + result.put(bytePeriod3); + result.put(byteSpeed3); + result.put(bytePeriod4); + result.put(byteSpeed4); + return result.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index fadf6dea..e023ffb7 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -51,7 +51,7 @@ public class EncoderFactory { case COURSEWIND: return new CourseWindsEncoder(); - //case AVGWIND: return new AverageWindEncoder();//TODO + case AVGWIND: return new AverageWindEncoder(); case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 45690bca..154e25e4 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -126,34 +126,6 @@ public class RaceVisionByteEncoder { - public static byte[] averageWind(int time, int rawPeriod, int rawSampleSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){ - int messageVersionNumber = 0b1; - byte[] byteTime = longToBytes(time,6); - byte[] byteRawPeriod = intToBytes(rawPeriod, 2); - byte[] byteRawSpeed = intToBytes(rawSampleSpeed, 2); - byte[] bytePeriod2 = intToBytes(period2, 2); - byte[] byteSpeed2 = intToBytes(speed2, 2); - byte[] bytePeriod3 = intToBytes(period3, 2); - byte[] byteSpeed3 = intToBytes(speed3, 2); - byte[] bytePeriod4 = intToBytes(period4, 2); - byte[] byteSpeed4 = intToBytes(speed4, 2); - - ByteBuffer result = ByteBuffer.allocate(23); - result.put(intToBytes(messageVersionNumber, 1)); - result.put(byteTime); - result.put(byteRawPeriod); - result.put(byteRawSpeed); - result.put(bytePeriod2); - result.put(byteSpeed2); - result.put(bytePeriod3); - result.put(byteSpeed3); - result.put(bytePeriod4); - result.put(byteSpeed4); - return result.array(); - } - - - /** * Encodes a given message. * @param message Message to encode. diff --git a/racevisionGame/src/main/java/network/Messages/AverageWind.java b/racevisionGame/src/main/java/network/Messages/AverageWind.java index 1ba17bd5..1b59f30f 100644 --- a/racevisionGame/src/main/java/network/Messages/AverageWind.java +++ b/racevisionGame/src/main/java/network/Messages/AverageWind.java @@ -4,33 +4,187 @@ package network.Messages; import network.Messages.Enums.MessageType; /** - * Created by fwy13 on 25/04/17. + * Represents an AverageWind message in the streaming API (see section 4.12). */ public class AverageWind extends AC35Data { - private int msgNum; - private long lngTime; - private int rawPeriod; - private int rawSpeed; - private int period2; - private int speed2; - private int period3; - private int speed3; - private int period4; - private int speed4; - - public AverageWind(int msgNum, long lngTime, int rawPeriod, int rawSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){ + /** + * The current version number for this message type. + */ + public static final byte currentMessageVersionNumber = 1; + + + /** + * The version number for this message. + */ + private byte messageVersionNumber; + + /** + * Timestamp for when the measurement was taken. Milliseconds since unix epoch. + */ + private long time; + + /** + * Raw sample rate period. Milliseconds. + */ + private long rawPeriod; + + /** + * Raw wind speed, in knots. + */ + private double rawSpeedKnots; + + /** + * Wind speed average period for second sample. Milliseconds. + */ + private long sampleTwoPeriod; + + /** + * Wind speed of second sample. Knots. + */ + private double sampleTwoSpeedKnots; + + /** + * Wind speed average period for third sample. Milliseconds. + */ + private long sampleThreePeriod; + + /** + * Wind speed of third sample. Knots. + */ + private double sampleThreeSpeedKnots; + + /** + * Wind speed average period for fourth sample. Milliseconds. + */ + private long sampleFourPeriod; + + /** + * Wind speed of fourth sample. Knots. + */ + private double sampleFourSpeedKnots; + + + /** + * Creates an AverageWind message, with the given parameters. + * @param messageVersionNumber The version number of message. + * @param time The timestamp of the message. + * @param rawPeriod The period of the raw measurement. Milliseconds. + * @param rawSpeedKnots The speed of the raw measurement. Knots. + * @param sampleTwoPeriod The period of the second measurement. Milliseconds. + * @param sampleTwoSpeedKnots The speed of the second measurement. Knots. + * @param sampleThreePeriod The period of the third measurement. Milliseconds. + * @param sampleThreeSpeedKnots The speed of the third measurement. Knots. + * @param sampleFourPeriod The period of the fourth measurement. Milliseconds. + * @param sampleFourSpeedKnots The speed of the fourth measurement. Knots. + */ + public AverageWind( + byte messageVersionNumber, + long time, + long rawPeriod, + double rawSpeedKnots, + long sampleTwoPeriod, + double sampleTwoSpeedKnots, + long sampleThreePeriod, + double sampleThreeSpeedKnots, + long sampleFourPeriod, + double sampleFourSpeedKnots ) { + super(MessageType.AVGWIND); - this.msgNum = msgNum; - this.lngTime = lngTime; + + this.messageVersionNumber = messageVersionNumber; + this.time = time; this.rawPeriod = rawPeriod; - this.rawSpeed = rawSpeed; - this.period2 = period2; - this.speed2 = speed2; - this.period3 = period3; - this.speed3 = speed3; - this.period4 = period4; - this.speed4 = speed4; + this.rawSpeedKnots = rawSpeedKnots; + this.sampleTwoPeriod = sampleTwoPeriod; + this.sampleTwoSpeedKnots = sampleTwoSpeedKnots; + this.sampleThreePeriod = sampleThreePeriod; + this.sampleThreeSpeedKnots = sampleThreeSpeedKnots; + this.sampleFourPeriod = sampleFourPeriod; + this.sampleFourSpeedKnots = sampleFourSpeedKnots; + } + + + /** + * Returns the version number of this message. + * @return Message version number. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } + + /** + * Returns the timestamp of this message. + * @return Timestamp of this message. Milliseconds since unix epoch. + */ + public long getTime() { + return time; + } + + /** + * Returns the period of time over which the raw sample was done. Milliseconds. + * @return Raw sample's period. + */ + public long getRawPeriod() { + return rawPeriod; + } + + /** + * Returns the wind speed of the raw sample. Knots. + * @return Wind speed of raw sample. Knots + */ + public double getRawSpeedKnots() { + return rawSpeedKnots; } + /** + * Returns the period of time over which the second sample was done. Milliseconds. + * @return Second sample's period. + */ + public long getSampleTwoPeriod() { + return sampleTwoPeriod; + } + + /** + * Returns the wind speed of the second sample. Knots. + * @return Wind speed of second sample. Knots + */ + public double getSampleTwoSpeedKnots() { + return sampleTwoSpeedKnots; + } + + /** + * Returns the period of time over which the third sample was done. Milliseconds. + * @return Third sample's period. + */ + public long getSampleThreePeriod() { + return sampleThreePeriod; + } + + /** + * Returns the wind speed of the third sample. Knots. + * @return Wind speed of third sample. Knots + */ + public double getSampleThreeSpeedKnots() { + return sampleThreeSpeedKnots; + } + + /** + * Returns the period of time over which the fourth sample was done. Milliseconds. + * @return Fourth sample's period. + */ + public long getSampleFourPeriod() { + return sampleFourPeriod; + } + + /** + * Returns the wind speed of the fourth sample. Knots. + * @return Wind speed of fourth sample. Knots + */ + public double getSampleFourSpeedKnots() { + return sampleFourSpeedKnots; + } + + + } diff --git a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java index 6d894ab6..40b87628 100644 --- a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java +++ b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java @@ -85,4 +85,23 @@ public class AC35UnitConverter { } + /** + * Packs an average wind period, in milliseconds, into an int, in tenths of a second. + * @param periodMilliseconds Period in milliseconds. + * @return Period in tenths of a second. + */ + public static int packAverageWindPeriod(long periodMilliseconds) { + return (int) (periodMilliseconds / 100); + } + + /** + * Unpacks an average wind period, in tenths of a second, into an long, in milliseconds. + * @param periodTenthsOfSecond Period in tenths of a second. + * @return Period in milliseconds + */ + public static long unpackAverageWindPeriod(int periodTenthsOfSecond) { + return (periodTenthsOfSecond * 100); + } + + } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/AverageWindDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/AverageWindDecoderTest.java new file mode 100644 index 00000000..001c54eb --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/AverageWindDecoderTest.java @@ -0,0 +1,75 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.AverageWind; +import static org.junit.Assert.*; +import org.junit.Test; +import shared.model.Bearing; + +import java.util.ArrayList; + +/** + * Test for the AverageWind encoder and decoder + */ +public class AverageWindDecoderTest { + + + /** + * Creates a AverageWind message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ + @Test + public void averageWindEncodeDecodeTest() throws Exception { + + AverageWind averageWind = new AverageWind( + AverageWind.currentMessageVersionNumber, + System.currentTimeMillis(), + 3000, + 12.5, + 4050, + 12.6, + 3055, + 12.7, + 6051, + 13.37 + ); + + byte[] encodedMessage = RaceVisionByteEncoder.encode(averageWind); + + AverageWindDecoder averageWindDecoder = new AverageWindDecoder(); + averageWindDecoder.decode(encodedMessage); + AverageWind averageWindDecoded = averageWindDecoder.getMessage(); + + compareAverageWindMessages(averageWind, averageWindDecoded); + + } + + + /** + * Compares two AverageWind messages to check that they are equal. + * @param original The original AverageWind message. + * @param decoded The decoded AverageWind message. + */ + public static void compareAverageWindMessages(AverageWind original, AverageWind decoded) { + + + assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + assertEquals(original.getTime(), decoded.getTime()); + + assertEquals(original.getRawPeriod(), decoded.getRawPeriod(), 100); + assertEquals(original.getRawSpeedKnots(), decoded.getRawSpeedKnots(), 0.01); + + assertEquals(original.getSampleTwoPeriod(), decoded.getSampleTwoPeriod(), 100); + assertEquals(original.getSampleTwoSpeedKnots(), decoded.getSampleTwoSpeedKnots(), 0.01); + + assertEquals(original.getSampleThreePeriod(), decoded.getSampleThreePeriod(), 100); + assertEquals(original.getSampleThreeSpeedKnots(), decoded.getSampleThreeSpeedKnots(), 0.01); + + assertEquals(original.getSampleFourPeriod(), decoded.getSampleFourPeriod(), 100); + assertEquals(original.getSampleFourSpeedKnots(), decoded.getSampleFourSpeedKnots(), 0.01); + + + } + + +} From 9c64b678e315cddb82b5eeb8134f5c03c85b9268 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 21:38:12 +1200 Subject: [PATCH 45/53] Added MarkRoundingEncoder. Refactored MarkRoundingDecoder - it now implements MessageDecoder. Tidied up MarkRounding - it is now documented and has getters. Also Created MarkRoundingBoatStatusEnum, MarkRoundingIDEnum, MarkRoundingSideEnum, MarkRoundingTypeEnum. Added MarkRoundingDecoderTest. issue #35 #36 #story[1095] --- .../java/network/BinaryMessageDecoder.java | 4 +- .../MessageDecoders/DecoderFactory.java | 2 +- .../MessageDecoders/MarkRoundingDecoder.java | 114 ++++++++---- .../MessageEncoders/EncoderFactory.java | 2 +- .../MessageEncoders/MarkRoundingEncoder.java | 57 ++++++ .../RaceVisionByteEncoder.java | 24 --- .../Enums/MarkRoundingBoatStatusEnum.java | 88 +++++++++ .../Messages/Enums/MarkRoundingIDEnum.java | 88 +++++++++ .../Messages/Enums/MarkRoundingSideEnum.java | 82 +++++++++ .../Messages/Enums/MarkRoundingTypeEnum.java | 82 +++++++++ .../java/network/Messages/MarkRounding.java | 173 ++++++++++++++---- .../MarkRoundingDecoderTest.java | 69 +++++++ 12 files changed, 685 insertions(+), 100 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingBoatStatusEnum.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingIDEnum.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingSideEnum.java create mode 100644 racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingTypeEnum.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/MarkRoundingDecoderTest.java diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 71765e12..9ae2ba47 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -189,8 +189,8 @@ public class BinaryMessageDecoder { case MARKROUNDING: //System.out.println("Mark Rounding Message!"); - MarkRoundingDecoder mrDecoder = new MarkRoundingDecoder(messageBody); - return mrDecoder.getMarkRounding(); + MarkRoundingDecoder mrDecoder = new MarkRoundingDecoder(); + return mrDecoder.decode(messageBody); case COURSEWIND: //System.out.println("Course Wind Message!"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java index 9b04803c..09ea4b95 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java @@ -47,7 +47,7 @@ public class DecoderFactory { case BOATLOCATION: return new BoatLocationDecoder(); - //case MARKROUNDING: return new MarkRoundingDecoder()//TODO; + case MARKROUNDING: return new MarkRoundingDecoder(); case COURSEWIND: return new CourseWindsDecoder(); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java index 000e86ee..58ad8f64 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java @@ -1,52 +1,90 @@ package network.MessageDecoders; +import network.Messages.AC35Data; +import network.Messages.Enums.MarkRoundingBoatStatusEnum; +import network.Messages.Enums.MarkRoundingSideEnum; +import network.Messages.Enums.MarkRoundingTypeEnum; import network.Messages.MarkRounding; import network.Utils.ByteConverter; import java.util.Arrays; /** - * Created by hba56 on 23/04/17. + * Decoder for {@link MarkRounding} messages. */ -public class MarkRoundingDecoder { - byte messageVersionNumber; - byte[] byteTime; - byte[] byteAck; - byte[] byteRaceID; - byte[] byteSourceID; - byte byteBoatStatus; - byte byteRoundingSide; - byte byteMarkType; - byte byteMarkID; - - MarkRounding markRounding; - - public MarkRoundingDecoder(byte[] encodedMarkRounding) { - messageVersionNumber = encodedMarkRounding[0]; - byteTime = Arrays.copyOfRange(encodedMarkRounding, 1, 7); - byteAck = Arrays.copyOfRange(encodedMarkRounding, 7, 9); - byteRaceID = Arrays.copyOfRange(encodedMarkRounding, 9, 13); - byteSourceID = Arrays.copyOfRange(encodedMarkRounding, 13, 17); - byteBoatStatus = encodedMarkRounding[17]; - byteRoundingSide = encodedMarkRounding[18]; - byteMarkType = encodedMarkRounding[19]; - byteMarkID = encodedMarkRounding[20]; - - int intMsgVer = ByteConverter.bytesToInt(messageVersionNumber); - long lngTime = ByteConverter.bytesToLong(byteTime); - int intAck = ByteConverter.bytesToInt(byteAck); - int intRaceID = ByteConverter.bytesToInt(byteRaceID); - int intSourceID = ByteConverter.bytesToInt(byteSourceID); - int intBoatState = ByteConverter.bytesToInt(byteBoatStatus); - int intRoundingSide = ByteConverter.bytesToInt(byteRoundingSide); - int intMarkType = ByteConverter.bytesToInt(byteMarkType); - int intMarkID = ByteConverter.bytesToInt(byteMarkID); - - markRounding = new MarkRounding(intMsgVer, lngTime, intAck, intRaceID, intSourceID, intBoatState, intRoundingSide, intMarkType, intMarkID); +public class MarkRoundingDecoder implements MessageDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private MarkRounding message; + + + /** + * Constructs a decoder to decode a given message. + */ + public MarkRoundingDecoder() { } - public MarkRounding getMarkRounding() { - return markRounding; + @Override + public AC35Data decode(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + byte messageVersionNumber = encodedMessage[0]; + + byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = ByteConverter.bytesToLong(byteTime); + + byte[] byteAck = Arrays.copyOfRange(encodedMessage, 7, 9); + int ackNumber = ByteConverter.bytesToInt(byteAck); + + byte[] byteRaceID = Arrays.copyOfRange(encodedMessage, 9, 13); + int raceID = ByteConverter.bytesToInt(byteRaceID); + + byte[] byteSourceID = Arrays.copyOfRange(encodedMessage, 13, 17); + int sourceID = ByteConverter.bytesToInt(byteSourceID); + + byte byteBoatStatus = encodedMessage[17]; + MarkRoundingBoatStatusEnum boatStatus = MarkRoundingBoatStatusEnum.fromByte(byteBoatStatus); + + byte byteRoundingSide = encodedMessage[18]; + MarkRoundingSideEnum roundingSide = MarkRoundingSideEnum.fromByte(byteRoundingSide); + + byte byteMarkType = encodedMessage[19]; + MarkRoundingTypeEnum markType = MarkRoundingTypeEnum.fromByte(byteMarkType); + + byte byteMarkID = encodedMessage[20]; + + + message = new MarkRounding( + messageVersionNumber, + time, + ackNumber, + raceID, + sourceID, + boatStatus, + roundingSide, + markType, + byteMarkID); + + + return message; + } + + /** + * Returns the decoded message. + * + * @return The decoded message. + */ + public MarkRounding getMessage() { + return message; } + } + diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index e023ffb7..b59150e4 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -47,7 +47,7 @@ public class EncoderFactory { case BOATLOCATION: return new BoatLocationEncoder(); - //case MARKROUNDING: return new MarkRoundingEncoder();//TODO + case MARKROUNDING: return new MarkRoundingEncoder(); case COURSEWIND: return new CourseWindsEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java new file mode 100644 index 00000000..de86691a --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java @@ -0,0 +1,57 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.MarkRounding; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link MarkRounding} message. + */ +public class MarkRoundingEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public MarkRoundingEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + MarkRounding markRounding = (MarkRounding) message; + + byte messageVersionNumber = markRounding.getMessageVersionNumber(); + byte[] byteTime = longToBytes(markRounding.getTime(), 6); + byte[] byteAck = intToBytes(markRounding.getAckNum(), 2); + byte[] byteRaceID = intToBytes(markRounding.getRaceID(), 4); + byte[] byteSourceID = intToBytes(markRounding.getSourceID(), 4); + byte[] byteBoatStatus = intToBytes(markRounding.getBoatStatus().getValue(), 1); + byte[] byteRoundingSide = intToBytes(markRounding.getRoundingSide().getValue(), 1); + byte[] byteMarkType = intToBytes(markRounding.getMarkType().getValue(), 1); + byte[] byteMarkID = intToBytes(markRounding.getMarkID(), 1); + + + ByteBuffer result = ByteBuffer.allocate(21); + + result.put(messageVersionNumber); + result.put(byteTime); + result.put(byteAck); + result.put(byteRaceID); + result.put(byteSourceID); + result.put(byteBoatStatus); + result.put(byteRoundingSide); + result.put(byteMarkType); + result.put(byteMarkID); + + return result.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index 154e25e4..b508030f 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -100,30 +100,6 @@ public class RaceVisionByteEncoder { } - public static byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){ - int messageVersionNumber = 0b1; - byte[] byteTime = longToBytes(time, 6); - byte[] byteAck = intToBytes(ackNumber, 2); - byte[] byteRaceID = intToBytes(raceID, 4); - byte[] byteSourceID = intToBytes(sourceID, 4); - byte[] byteBoatStatus = intToBytes(boatStatus, 1); - byte[] byteRoundingSide = intToBytes(roundingSide, 1); - byte[] byteMarkType = intToBytes(markType, 1); - byte[] byteMarkID = intToBytes(markID, 1); - - ByteBuffer result = ByteBuffer.allocate(21); - result.put(intToBytes(messageVersionNumber, 1)); - result.put(byteTime); - result.put(byteAck); - result.put(byteRaceID); - result.put(byteSourceID); - result.put(byteBoatStatus); - result.put(byteRoundingSide); - result.put(byteMarkType); - result.put(byteMarkID); - return result.array(); - } - /** diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingBoatStatusEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingBoatStatusEnum.java new file mode 100644 index 00000000..692dba42 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingBoatStatusEnum.java @@ -0,0 +1,88 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various statuses a boat can have when rounding a mark. + */ +public enum MarkRoundingBoatStatusEnum { + + UNKNOWN(0), + + /** + * The boat is actively racing. + */ + RACING(1), + + /** + * The boat has been disqualified. + */ + DSQ(2), + + /** + * The boat has withdrawn from the race. + */ + WITHDRAWN(3), + + NOT_A_STATUS(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a {@link MarkRoundingBoatStatusEnum} from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private MarkRoundingBoatStatusEnum(int value) { + this.value = (byte)value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + ///Stores a mapping between Byte values and MarkRoundingBoatStatusEnum values. + private static final Map byteToStatusMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToStatusMap. + */ + static { + for (MarkRoundingBoatStatusEnum type : MarkRoundingBoatStatusEnum.values()) { + byteToStatusMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param boatStatusByte Byte value to convert to a {@link MarkRoundingBoatStatusEnum} value. + * @return The {@link MarkRoundingBoatStatusEnum} value which corresponds to the given byte value. + */ + public static MarkRoundingBoatStatusEnum fromByte(byte boatStatusByte) { + //Gets the corresponding MarkRoundingBoatStatusEnum from the map. + MarkRoundingBoatStatusEnum type = byteToStatusMap.get(boatStatusByte); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_STATUS MarkRoundingBoatStatusEnum. + return MarkRoundingBoatStatusEnum.NOT_A_STATUS; + } + else { + //Otherwise, return the MarkRoundingBoatStatusEnum. + return type; + } + + } + +} diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingIDEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingIDEnum.java new file mode 100644 index 00000000..69b0756c --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingIDEnum.java @@ -0,0 +1,88 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various mark identities. + */ +public enum MarkRoundingIDEnum { + + UNKNOWN(0), + + + ENTRY_LIMIT_LINE(100), + + ENTRY_LINE(101), + + START_LINE(102), + + FINISH_LINE(103), + + SPEED_TEST_START(104), + + SPEED_TEST_FINISH(105), + + CLEAR_START(106), + + NOT_AN_ID(-1); + + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a {@link MarkRoundingIDEnum} from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private MarkRoundingIDEnum(int value) { + this.value = (byte)value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + ///Stores a mapping between Byte values and MarkRoundingIDEnum values. + private static final Map byteToIDMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToIDMap. + */ + static { + for (MarkRoundingIDEnum type : MarkRoundingIDEnum.values()) { + byteToIDMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param sideByte Byte value to convert to a {@link MarkRoundingIDEnum} value. + * @return The {@link MarkRoundingIDEnum} value which corresponds to the given byte value. + */ + public static MarkRoundingIDEnum fromByte(byte sideByte) { + //Gets the corresponding MarkRoundingIDEnum from the map. + MarkRoundingIDEnum type = byteToIDMap.get(sideByte); + + if (type == null) { + //If the byte value wasn't found, return the NOT_AN_ID MarkRoundingIDEnum. + return MarkRoundingIDEnum.NOT_AN_ID; + } + else { + //Otherwise, return the MarkRoundingIDEnum. + return type; + } + + } + +} diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingSideEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingSideEnum.java new file mode 100644 index 00000000..83aa75be --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingSideEnum.java @@ -0,0 +1,82 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various sides around which a boat may pass a mark. + */ +public enum MarkRoundingSideEnum { + + UNKNOWN(0), + + /** + * Boat rounded around port side of mark. + */ + PORT(1), + + /** + * Boat rounded around starboard side of mark. + */ + STARBOARD(2), + + NOT_A_SIDE(-1); + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a {@link MarkRoundingSideEnum} from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private MarkRoundingSideEnum(int value) { + this.value = (byte)value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + ///Stores a mapping between Byte values and MarkRoundingSideEnum values. + private static final Map byteToSideMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToSideMap. + */ + static { + for (MarkRoundingSideEnum type : MarkRoundingSideEnum.values()) { + byteToSideMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param sideByte Byte value to convert to a {@link MarkRoundingSideEnum} value. + * @return The {@link MarkRoundingSideEnum} value which corresponds to the given byte value. + */ + public static MarkRoundingSideEnum fromByte(byte sideByte) { + //Gets the corresponding MarkRoundingSideEnum from the map. + MarkRoundingSideEnum type = byteToSideMap.get(sideByte); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_SIDE MarkRoundingSideEnum. + return MarkRoundingSideEnum.NOT_A_SIDE; + } + else { + //Otherwise, return the MarkRoundingSideEnum. + return type; + } + + } + +} diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingTypeEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingTypeEnum.java new file mode 100644 index 00000000..559c17e9 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/MarkRoundingTypeEnum.java @@ -0,0 +1,82 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various types of marks that may be passed. + */ +public enum MarkRoundingTypeEnum { + + UNKNOWN(0), + + /** + * The mark is a singular mark. + */ + MARK(1), + + /** + * The mark is a gate (windward, leeward, start, finish, etc...). + */ + GATE(2), + + NOT_A_TYPE(-1); + + /** + * Primitive value of the enum. + */ + private byte value; + + + /** + * Ctor. Creates a {@link MarkRoundingTypeEnum} from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private MarkRoundingTypeEnum(int value) { + this.value = (byte)value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + ///Stores a mapping between Byte values and MarkRoundingTypeEnum values. + private static final Map byteToTypeMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToTypeMap. + */ + static { + for (MarkRoundingTypeEnum type : MarkRoundingTypeEnum.values()) { + byteToTypeMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param sideByte Byte value to convert to a {@link MarkRoundingTypeEnum} value. + * @return The {@link MarkRoundingTypeEnum} value which corresponds to the given byte value. + */ + public static MarkRoundingTypeEnum fromByte(byte sideByte) { + //Gets the corresponding MarkRoundingTypeEnum from the map. + MarkRoundingTypeEnum type = byteToTypeMap.get(sideByte); + + if (type == null) { + //If the byte value wasn't found, return the NOT_A_TYPE MarkRoundingTypeEnum. + return MarkRoundingTypeEnum.NOT_A_TYPE; + } + else { + //Otherwise, return the MarkRoundingTypeEnum. + return type; + } + + } + +} diff --git a/racevisionGame/src/main/java/network/Messages/MarkRounding.java b/racevisionGame/src/main/java/network/Messages/MarkRounding.java index a13f0ba7..eae2daed 100644 --- a/racevisionGame/src/main/java/network/Messages/MarkRounding.java +++ b/racevisionGame/src/main/java/network/Messages/MarkRounding.java @@ -1,47 +1,94 @@ package network.Messages; +import network.Messages.Enums.MarkRoundingBoatStatusEnum; +import network.Messages.Enums.MarkRoundingSideEnum; +import network.Messages.Enums.MarkRoundingTypeEnum; import network.Messages.Enums.MessageType; /** - * Created by fwy13 on 25/04/17. + * Represents a MarkRound message (see AC35 spec, 4.10). */ public class MarkRounding extends AC35Data { - private int msgVerNum; + /** + * The current messageVersionNumber according to the API spec. + */ + public static final byte currentMessageVersionNumber = 1; + + + /** + * Version number of the message. + */ + private byte messageVersionNumber; + + /** + * The time at which the mark was rounding. Milliseconds since unix epoch. + */ private long time; + + /** + * The ack number of the message. + */ private int ackNum; + + /** + * The raceID this message relates to. + */ private int raceID; + + /** + * The sourceID of the boat this message relates to. + */ private int sourceID; - private int boatStatus; - private int roundingSide; - private int markType; - private int markID; - - public static int BoatStatusUnknown = 0; - public static int BoatStatusRacing = 1; - public static int BoatStatusDSQ = 2; - public static int BoatStatusWithdrawn = 3; - - public static int RoundingSideUnknown = 0; - public static int RoundingSidePort = 1; - public static int RoundingSideStarboard = 2; - - public static int MarkTypeUnknown = 0; - public static int MarkTypeRoundingMark = 1; - public static int MarkTypeGate = 2; - - public static int MarkIDEntryLimitLine = 100; - public static int MarkIDEntryLine = 101; - public static int MarkIDRaceStartStartline = 102; - public static int MarkIDRaceFinishline = 103; - public static int MarkIDSpeedTestStart = 104; - public static int MarkIDSpeedTestFinish = 105; - public static int MarkIDClearStart = 106; - - public MarkRounding(int msgVerNum, long time, int ackNum, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){ + + /** + * The status of the boat. + */ + private MarkRoundingBoatStatusEnum boatStatus; + + /** + * The side around which the boat rounded. + */ + private MarkRoundingSideEnum roundingSide; + + /** + * The type of mark that was rounded. + */ + private MarkRoundingTypeEnum markType; + + /** + * The ID of the mark. This is not a source ID. + */ + private byte markID; + + + /** + * Creates a MarkRounding message with the given parameters. + * @param messageVersionNumber The version number of the message. + * @param time The time at which the message was created. + * @param ackNum The ack number of the message. + * @param raceID The raceID this message relates to. + * @param sourceID The sourceID of the boat this message relates to. + * @param boatStatus The status of the boat as it rounded the mark. + * @param roundingSide The side around which the boat rounded. + * @param markType The type of mark that was rounded. + * @param markID The ID number of the mark. Not a sourceID. See {@link network.Messages.Enums.MarkRoundingIDEnum}. + */ + public MarkRounding( + byte messageVersionNumber, + long time, + int ackNum, + int raceID, + int sourceID, + MarkRoundingBoatStatusEnum boatStatus, + MarkRoundingSideEnum roundingSide, + MarkRoundingTypeEnum markType, + byte markID ) { + super(MessageType.MARKROUNDING); - this.msgVerNum = msgVerNum; + + this.messageVersionNumber = messageVersionNumber; this.time = time; this.ackNum = ackNum; this.raceID = raceID; @@ -52,6 +99,40 @@ public class MarkRounding extends AC35Data { this.markID = markID; } + + /** + * Returns the version number of this message. + * @return Version number of this message. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } + + + /** + * Returns the timestamp for this message. + * @return Timestamp for this message. + */ + public long getTime() { + return time; + } + + /** + * Returns the ack number of this message. + * @return Ack number of this message. + */ + public int getAckNum() { + return ackNum; + } + + /** + * Returns the raceID this message relates to. + * @return RaceID this message relates to. + */ + public int getRaceID() { + return raceID; + } + /** * Returns the boat (source) ID for this message. * @return Boat ID for this message. @@ -61,10 +142,34 @@ public class MarkRounding extends AC35Data { } /** - * Returns the timestamp for this message. - * @return Timestamp for this message. + * Returns the status of the boat as it rounded the mark. + * @return Status of boat as it rounded mark. */ - public long getTime() { - return time; + public MarkRoundingBoatStatusEnum getBoatStatus() { + return boatStatus; + } + + /** + * Returns the side to which the boat rounded the mark. + * @return Side to which boat rounded mark. + */ + public MarkRoundingSideEnum getRoundingSide() { + return roundingSide; + } + + /** + * Returns the type of mark that was rounded. + * @return The type of mark that was rounded. + */ + public MarkRoundingTypeEnum getMarkType() { + return markType; + } + + /** + * Returns ID number of the mark. This is not a source ID. See {@link network.Messages.Enums.MarkRoundingIDEnum}. + * @return ID number of the mark. + */ + public byte getMarkID() { + return markID; } } diff --git a/racevisionGame/src/test/java/network/MessageDecoders/MarkRoundingDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/MarkRoundingDecoderTest.java new file mode 100644 index 00000000..3fce2176 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/MarkRoundingDecoderTest.java @@ -0,0 +1,69 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.Enums.MarkRoundingBoatStatusEnum; +import network.Messages.Enums.MarkRoundingSideEnum; +import network.Messages.Enums.MarkRoundingTypeEnum; +import network.Messages.MarkRounding; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Test for the MarkRounding encoder and decoder + */ +public class MarkRoundingDecoderTest { + + + /** + * Creates a MarkRounding message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ + @Test + public void markRoundingEncodeDecodeTest() throws Exception { + + MarkRounding markRounding = new MarkRounding( + MarkRounding.currentMessageVersionNumber, + System.currentTimeMillis(), + 567, + 42, + 125, + MarkRoundingBoatStatusEnum.RACING, + MarkRoundingSideEnum.PORT, + MarkRoundingTypeEnum.MARK, + (byte)45 + ); + + byte[] encodedMessage = RaceVisionByteEncoder.encode(markRounding); + + MarkRoundingDecoder markRoundingDecoder = new MarkRoundingDecoder(); + markRoundingDecoder.decode(encodedMessage); + MarkRounding markRoundingDecoded = markRoundingDecoder.getMessage(); + + compareMarkRoundingMessages(markRounding, markRoundingDecoded); + + } + + + /** + * Compares two MarkRounding messages to check that they are equal. + * @param original The original MarkRounding message. + * @param decoded The decoded MarkRounding message. + */ + public static void compareMarkRoundingMessages(MarkRounding original, MarkRounding decoded) { + + + assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + assertEquals(original.getTime(), decoded.getTime()); + assertEquals(original.getAckNum(), decoded.getAckNum()); + assertEquals(original.getRaceID(), decoded.getRaceID()); + assertEquals(original.getSourceID(), decoded.getSourceID()); + assertEquals(original.getBoatStatus(), decoded.getBoatStatus()); + assertEquals(original.getRoundingSide(), decoded.getRoundingSide()); + assertEquals(original.getMarkType(), decoded.getMarkType()); + assertEquals(original.getMarkID(), decoded.getMarkID()); + + } + + +} From 134586f4070a0853a80d7274649524ce8624ae60 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 22:25:43 +1200 Subject: [PATCH 46/53] The message encoders and decoders now catch exceptions, and throw InvalidMessageException. Removed the big switch statement from BinaryMessageDecoder - it now uses the decoder factory instead. issue #35 #36 #story[1095] --- .../java/network/BinaryMessageDecoder.java | 149 +++++------- .../MessageDecoders/AverageWindDecoder.java | 86 +++---- .../MessageDecoders/BoatActionDecoder.java | 14 +- .../MessageDecoders/BoatLocationDecoder.java | 219 +++++++++--------- .../MessageDecoders/BoatStatusDecoder.java | 9 +- .../MessageDecoders/CourseWindDecoder.java | 65 +++--- .../MessageDecoders/CourseWindsDecoder.java | 48 ++-- .../MessageDecoders/HeartBeatDecoder.java | 13 +- .../JoinAcceptanceDecoder.java | 28 ++- .../MessageDecoders/MarkRoundingDecoder.java | 63 ++--- .../MessageDecoders/MessageDecoder.java | 4 +- .../RaceStartStatusDecoder.java | 45 ++-- .../MessageDecoders/RaceStatusDecoder.java | 90 +++---- .../MessageDecoders/RequestToJoinDecoder.java | 23 +- .../MessageDecoders/XMLMessageDecoder.java | 53 +++-- .../MessageEncoders/AverageWindEncoder.java | 90 +++---- .../MessageEncoders/BoatActionEncoder.java | 23 +- .../MessageEncoders/BoatLocationEncoder.java | 117 +++++----- .../MessageEncoders/BoatStatusEncoder.java | 59 +++-- .../MessageEncoders/CourseWindEncoder.java | 58 +++-- .../MessageEncoders/CourseWindsEncoder.java | 39 ++-- .../MessageEncoders/HeartBeatEncoder.java | 23 +- .../JoinAcceptanceEncoder.java | 29 ++- .../MessageEncoders/MarkRoundingEncoder.java | 65 +++--- .../MessageEncoders/MessageEncoder.java | 4 +- .../RaceStartStatusEncoder.java | 41 ++-- .../MessageEncoders/RaceStatusEncoder.java | 89 +++---- .../RaceVisionByteEncoder.java | 2 + .../MessageEncoders/RequestToJoinEncoder.java | 21 +- .../MessageEncoders/XMLMessageEncoder.java | 51 ++-- .../gameController/ControllerClient.java | 15 +- .../gameController/ControllerServer.java | 19 +- .../BoatStatusDecoderTest.java | 4 +- .../CourseWindDecoderTest.java | 4 +- 34 files changed, 911 insertions(+), 751 deletions(-) diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 9ae2ba47..7bd9c233 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -17,42 +17,71 @@ import java.util.zip.CRC32; */ public class BinaryMessageDecoder { - ///Length of the header. + /** + * Length of the header. + */ private static final int headerLength = 15; - ///Length of the CRC. - private static final int CRCLength = 4;//TODO these should probably be static defined somewhere else to be shared. + /** + * Length of the CRC. + */ + private static final int CRCLength = 4; - ///The value the first sync byte should have. + /** + * The value the first sync byte should have. + */ private static final byte syncByte1 = (byte) 0x47; - //The value the second sync byte should have. + /** + * The value the second sync byte should have. + */ private static final byte syncByte2 = (byte) 0x83; - ///The full message. + /** + * The full message. + */ private byte[] fullMessage; - ///The messageHeader. + /** + * The messageHeader. + */ private byte[] messageHeader; - ///The messageBody. + /** + * The messageBody. + */ private byte[] messageBody; - ///The sync bytes from the header.. + /** + * The sync bytes from the header. + */ private byte headerSync1; private byte headerSync2; - ///The message type from the header. + /** + * The message type from the header. + */ private byte headerMessageType; - ///The timestamp from the header. + /** + * The timestamp from the header. + */ private long headerTimeStamp; - ///The source ID from the header. + /** + * The source ID from the header. + */ private int headerSourceID; - ///The message body length from the header. + /** + * The message body length from the header. + */ private int messageBodyLength; - ///CRC value read from message header. + /** + * CRC value read from message header. + */ private long messageCRCValue; - ///Calculated CRC value from message. + + /** + * Calculated CRC value from message. + */ private long calculatedCRCValue; @@ -114,104 +143,34 @@ public class BinaryMessageDecoder { //Check the message body length. throw new InvalidMessageException("MessageBody length in header does not equal the messageBody length. MessageBody length in header is: " + messageBodyLength + ", should be: " + messageBody.length); - }else if (headerSync1 != syncByte1) { + } else if (headerSync1 != syncByte1) { //Check the first sync byte. throw new InvalidMessageException("Sync byte 1 is wrong. Sync byte is: " + headerSync1 + ", should be: " + syncByte1); - }else if (headerSync2 != syncByte2) { + } else if (headerSync2 != syncByte2) { //Check the second sync byte. throw new InvalidMessageException("Sync byte 2 is wrong. Sync byte is: " + headerSync2 + ", should be: " + syncByte2); - }else if (calculatedCRCValue != messageCRCValue) { + } else if (calculatedCRCValue != messageCRCValue) { //Check the CRC value. throw new InvalidMessageException("CRC value is wrong. The calculated value is: " + calculatedCRCValue + ", should be: " + messageCRCValue); } //Now we create the message object based on what is actually in the message body. - MessageType mType = MessageType.fromByte(headerMessageType); + MessageType messageType = MessageType.fromByte(headerMessageType); - /*MessageDecoder decoder = null; + MessageDecoder decoder = null; try { - decoder = DecoderFactory.create(mType); + decoder = DecoderFactory.create(messageType); + } catch (InvalidMessageTypeException e) { - throw new InvalidMessageException("Could not create decoder for MessageType: " + mType, e); + throw new InvalidMessageException("Could not create decoder for MessageType: " + messageType, e); + } - return decoder.decode(messageBody);*/ - - - switch(mType) { - case HEARTBEAT: - //System.out.println("Decoding HeartBeat Message!"); - HeartBeatDecoder heartBeatDecoder = new HeartBeatDecoder(); - return heartBeatDecoder.decode(messageBody); - - case RACESTATUS: - //System.out.println("Race Status Message"); - RaceStatusDecoder rsdecoder = new RaceStatusDecoder(); - return rsdecoder.decode(messageBody); - - case DISPLAYTEXTMESSAGE: - //System.out.println("Display Text Message"); - //No decoder for this. - //throw new InvalidMessageException("Cannot decode DISPLAYTEXTMESSAGE - no decoder."); - - case XMLMESSAGE: - //System.out.println("XML Message!"); - XMLMessageDecoder xmdecoder = new XMLMessageDecoder(); - return xmdecoder.decode(messageBody); - - case RACESTARTSTATUS: - //System.out.println("Race Start Status Message"); - RaceStartStatusDecoder rssDecoder = new RaceStartStatusDecoder(); - return rssDecoder.decode(messageBody); - - case YACHTEVENTCODE: - //System.out.println("Yacht Action Code!"); - //No decoder for this. - //throw new InvalidMessageException("Cannot decode YACHTEVENTCODE - no decoder."); - - case YACHTACTIONCODE: - //System.out.println("Yacht Action Code!"); - //No decoder for this. - //throw new InvalidMessageException("Cannot decode YACHTACTIONCODE - no decoder."); - - case CHATTERTEXT: - //System.out.println("Chatter Text Message!"); - //No decoder for this. - //throw new InvalidMessageException("Cannot decode CHATTERTEXT - no decoder."); - - case BOATLOCATION: - //System.out.println("Boat Location Message!"); - BoatLocationDecoder blDecoder = new BoatLocationDecoder(); - return blDecoder.decode(messageBody); - - case MARKROUNDING: - //System.out.println("Mark Rounding Message!"); - MarkRoundingDecoder mrDecoder = new MarkRoundingDecoder(); - return mrDecoder.decode(messageBody); - - case COURSEWIND: - //System.out.println("Course Wind Message!"); - CourseWindsDecoder cwDecoder = new CourseWindsDecoder(); - return cwDecoder.decode(messageBody); - - case AVGWIND: - //System.out.println("Average Wind Message!"); - AverageWindDecoder awDecoder = new AverageWindDecoder(); - return awDecoder.decode(messageBody); - - case BOATACTION: - BoatActionDecoder baDecoder = new BoatActionDecoder(); - return baDecoder.decode(messageBody); - - default: - //System.out.println("Broken Message!"); - //throw new InvalidMessageException("Broken message! Did not recognise message type: " + headerMessageType + "."); - return null; + return decoder.decode(messageBody); - } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java index 1b0f12d5..139aed13 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.AverageWind; import network.Utils.ByteConverter; @@ -32,64 +33,67 @@ public class AverageWindDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; + try { - byte messageVersionNumber = encodedMessage[0]; + byte messageVersionNumber = encodedMessage[0]; - byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = ByteConverter.bytesToLong(byteTime); + byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = ByteConverter.bytesToLong(byteTime); - byte[] byteRawPeriod = Arrays.copyOfRange(encodedMessage, 7, 9); - int intRawPeriod = ByteConverter.bytesToInt(byteRawPeriod); - long rawPeriod = unpackAverageWindPeriod(intRawPeriod); + byte[] byteRawPeriod = Arrays.copyOfRange(encodedMessage, 7, 9); + int intRawPeriod = ByteConverter.bytesToInt(byteRawPeriod); + long rawPeriod = unpackAverageWindPeriod(intRawPeriod); - byte[] byteRawSpeed = Arrays.copyOfRange(encodedMessage, 9, 11); - int intRawSpeed = ByteConverter.bytesToInt(byteRawSpeed); - double rawSpeedKnots = unpackMMperSecToKnots(intRawSpeed); + byte[] byteRawSpeed = Arrays.copyOfRange(encodedMessage, 9, 11); + int intRawSpeed = ByteConverter.bytesToInt(byteRawSpeed); + double rawSpeedKnots = unpackMMperSecToKnots(intRawSpeed); - byte[] bytePeriod2 = Arrays.copyOfRange(encodedMessage, 11, 13); - int intPeriod2 = ByteConverter.bytesToInt(bytePeriod2); - long period2 = unpackAverageWindPeriod(intPeriod2); + byte[] bytePeriod2 = Arrays.copyOfRange(encodedMessage, 11, 13); + int intPeriod2 = ByteConverter.bytesToInt(bytePeriod2); + long period2 = unpackAverageWindPeriod(intPeriod2); - byte[] byteSpeed2 = Arrays.copyOfRange(encodedMessage, 13, 15); - int intSpeed2 = ByteConverter.bytesToInt(byteSpeed2); - double speed2Knots = unpackMMperSecToKnots(intSpeed2); + byte[] byteSpeed2 = Arrays.copyOfRange(encodedMessage, 13, 15); + int intSpeed2 = ByteConverter.bytesToInt(byteSpeed2); + double speed2Knots = unpackMMperSecToKnots(intSpeed2); - byte[] bytePeriod3 = Arrays.copyOfRange(encodedMessage, 15, 17); - int intPeriod3 = ByteConverter.bytesToInt(bytePeriod3); - long period3 = unpackAverageWindPeriod(intPeriod3); + byte[] bytePeriod3 = Arrays.copyOfRange(encodedMessage, 15, 17); + int intPeriod3 = ByteConverter.bytesToInt(bytePeriod3); + long period3 = unpackAverageWindPeriod(intPeriod3); - byte[] byteSpeed3 = Arrays.copyOfRange(encodedMessage, 17, 19); - int intSpeed3 = ByteConverter.bytesToInt(byteSpeed3); - double speed3Knots = unpackMMperSecToKnots(intSpeed3); + byte[] byteSpeed3 = Arrays.copyOfRange(encodedMessage, 17, 19); + int intSpeed3 = ByteConverter.bytesToInt(byteSpeed3); + double speed3Knots = unpackMMperSecToKnots(intSpeed3); - byte[] bytePeriod4 = Arrays.copyOfRange(encodedMessage, 19, 21); - int intPeriod4 = ByteConverter.bytesToInt(bytePeriod4); - long period4 = unpackAverageWindPeriod(intPeriod4); + byte[] bytePeriod4 = Arrays.copyOfRange(encodedMessage, 19, 21); + int intPeriod4 = ByteConverter.bytesToInt(bytePeriod4); + long period4 = unpackAverageWindPeriod(intPeriod4); - byte[] byteSpeed4 = Arrays.copyOfRange(encodedMessage, 21, 23); - int intSpeed4 = ByteConverter.bytesToInt(byteSpeed4); - double speed4Knots = unpackMMperSecToKnots(intSpeed4); + byte[] byteSpeed4 = Arrays.copyOfRange(encodedMessage, 21, 23); + int intSpeed4 = ByteConverter.bytesToInt(byteSpeed4); + double speed4Knots = unpackMMperSecToKnots(intSpeed4); + message = new AverageWind( + messageVersionNumber, + time, + rawPeriod, + rawSpeedKnots, + period2, + speed2Knots, + period3, + speed3Knots, + period4, + speed4Knots); + return message; - message = new AverageWind( - messageVersionNumber, - time, - rawPeriod, - rawSpeedKnots, - period2, - speed2Knots, - period3, - speed3Knots, - period4, - speed4Knots ); - - return message; + } catch (Exception e) { + throw new InvalidMessageException("Could not decode AverageWind message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java index a3a5d38a..1df1e2f0 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; @@ -29,14 +30,19 @@ public class BoatActionDecoder implements MessageDecoder { } @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(encodedMessage[0]); + try { + BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(encodedMessage[0]); - message = new BoatAction(boatActionEnum); + message = new BoatAction(boatActionEnum); - return message; + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode BoatAction message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java index 1e175588..2965e809 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatLocation; import network.Messages.Enums.BoatLocationDeviceEnum; @@ -39,115 +40,121 @@ public class BoatLocationDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); - byte messageVersionNumber = messageVersionNumberBytes[0]; - - byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = bytesToLong(timeBytes); - - byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); - int sourceID = bytesToInt(sourceIDBytes); - - byte[] seqNumBytes = Arrays.copyOfRange(encodedMessage, 11, 15); - int seqNum = bytesToInt(seqNumBytes); - - byte[] deviceTypeBytes = Arrays.copyOfRange(encodedMessage, 15, 16); - BoatLocationDeviceEnum deviceType = BoatLocationDeviceEnum.fromByte(deviceTypeBytes[0]); - - byte[] latitudeBytes = Arrays.copyOfRange(encodedMessage, 16, 20); - int numLatitude = bytesToInt(latitudeBytes); - double latitude = unpackGPS(numLatitude); - - byte[] longitudeBytes = Arrays.copyOfRange(encodedMessage, 20, 24); - int numLongitude = bytesToInt(longitudeBytes); - double longitude = unpackGPS(numLongitude); - - byte[] altitudeBytes = Arrays.copyOfRange(encodedMessage, 24, 28); - int numAltitude = bytesToInt(altitudeBytes); - - byte[] headingBytes = Arrays.copyOfRange(encodedMessage, 28, 30); - int numHeading = bytesToInt(headingBytes); - Bearing heading = Bearing.fromDegrees(unpackHeading(numHeading)); - - byte[] pitchBytes = Arrays.copyOfRange(encodedMessage, 30, 32); - short numPitch = bytesToShort(pitchBytes); - - byte[] rollBytes = Arrays.copyOfRange(encodedMessage, 32, 34); - short numRoll = bytesToShort(rollBytes); - - byte[] boatSpeedBytes = Arrays.copyOfRange(encodedMessage, 34, 36); - int numBoatSpeed = bytesToInt(boatSpeedBytes); - double boatSpeedKnots = unpackMMperSecToKnots(numBoatSpeed); - - byte[] cogBytes = Arrays.copyOfRange(encodedMessage, 36, 38); - int numCog = bytesToInt(cogBytes); - Bearing cog = Bearing.fromDegrees(unpackHeading(numCog)); - - byte[] sogBytes = Arrays.copyOfRange(encodedMessage, 38, 40); - int numSog = bytesToInt(sogBytes); - double sogKnots = unpackMMperSecToKnots(numSog); - - byte[] apparentWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 40, 42); - int numApparentWindSpeed = bytesToInt(apparentWindSpeedBytes); - double apparentWindSpeedKnots = unpackMMperSecToKnots(numApparentWindSpeed); - - byte[] apparentWindAngleBytes = Arrays.copyOfRange(encodedMessage, 42, 44); - short numApparentWindAngle = bytesToShort(apparentWindAngleBytes); - Azimuth apparentWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numApparentWindAngle)); - - byte[] trueWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 44, 46); - int numTrueWindSpeed = bytesToInt(trueWindSpeedBytes); - double trueWindSpeedKnots = unpackMMperSecToKnots(numTrueWindSpeed); - - byte[] trueWindDirectionBytes = Arrays.copyOfRange(encodedMessage, 46, 48); - short numTrueWindDirection = bytesToShort(trueWindDirectionBytes); - Bearing trueWindDirection = Bearing.fromDegrees(unpackHeading(numTrueWindDirection)); - - byte[] trueWindAngleBytes = Arrays.copyOfRange(encodedMessage, 48, 50); - short numTrueWindAngle = bytesToShort(trueWindAngleBytes); - Azimuth trueWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numTrueWindAngle)); - - byte[] currentDriftBytes = Arrays.copyOfRange(encodedMessage, 50, 52); - int numCurrentDrift = bytesToInt(currentDriftBytes); - double currentDriftKnots = unpackMMperSecToKnots(numCurrentDrift); - - byte[] currentSetBytes = Arrays.copyOfRange(encodedMessage, 52, 54); - int numCurrentSet = bytesToShort(currentSetBytes); - Bearing currentSet = Bearing.fromDegrees(unpackHeading(numCurrentSet)); - - byte[] rudderAngleBytes = Arrays.copyOfRange(encodedMessage, 54, 56); - short numRudderAngle = bytesToShort(rudderAngleBytes); - Azimuth rudderAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numRudderAngle)); - - - message = new BoatLocation( - messageVersionNumber, - time, - sourceID, - seqNum, - deviceType, - latitude, - longitude, - numAltitude, - heading, - numPitch, - numRoll, - boatSpeedKnots, - cog, - sogKnots, - apparentWindSpeedKnots, - apparentWindAngle, - trueWindSpeedKnots, - trueWindDirection, - trueWindAngle, - currentDriftKnots, - currentSet, - rudderAngle ); + try { - return message; + byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte messageVersionNumber = messageVersionNumberBytes[0]; + + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); + + byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int sourceID = bytesToInt(sourceIDBytes); + + byte[] seqNumBytes = Arrays.copyOfRange(encodedMessage, 11, 15); + int seqNum = bytesToInt(seqNumBytes); + + byte[] deviceTypeBytes = Arrays.copyOfRange(encodedMessage, 15, 16); + BoatLocationDeviceEnum deviceType = BoatLocationDeviceEnum.fromByte(deviceTypeBytes[0]); + + byte[] latitudeBytes = Arrays.copyOfRange(encodedMessage, 16, 20); + int numLatitude = bytesToInt(latitudeBytes); + double latitude = unpackGPS(numLatitude); + + byte[] longitudeBytes = Arrays.copyOfRange(encodedMessage, 20, 24); + int numLongitude = bytesToInt(longitudeBytes); + double longitude = unpackGPS(numLongitude); + + byte[] altitudeBytes = Arrays.copyOfRange(encodedMessage, 24, 28); + int numAltitude = bytesToInt(altitudeBytes); + + byte[] headingBytes = Arrays.copyOfRange(encodedMessage, 28, 30); + int numHeading = bytesToInt(headingBytes); + Bearing heading = Bearing.fromDegrees(unpackHeading(numHeading)); + + byte[] pitchBytes = Arrays.copyOfRange(encodedMessage, 30, 32); + short numPitch = bytesToShort(pitchBytes); + + byte[] rollBytes = Arrays.copyOfRange(encodedMessage, 32, 34); + short numRoll = bytesToShort(rollBytes); + + byte[] boatSpeedBytes = Arrays.copyOfRange(encodedMessage, 34, 36); + int numBoatSpeed = bytesToInt(boatSpeedBytes); + double boatSpeedKnots = unpackMMperSecToKnots(numBoatSpeed); + + byte[] cogBytes = Arrays.copyOfRange(encodedMessage, 36, 38); + int numCog = bytesToInt(cogBytes); + Bearing cog = Bearing.fromDegrees(unpackHeading(numCog)); + + byte[] sogBytes = Arrays.copyOfRange(encodedMessage, 38, 40); + int numSog = bytesToInt(sogBytes); + double sogKnots = unpackMMperSecToKnots(numSog); + + byte[] apparentWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 40, 42); + int numApparentWindSpeed = bytesToInt(apparentWindSpeedBytes); + double apparentWindSpeedKnots = unpackMMperSecToKnots(numApparentWindSpeed); + + byte[] apparentWindAngleBytes = Arrays.copyOfRange(encodedMessage, 42, 44); + short numApparentWindAngle = bytesToShort(apparentWindAngleBytes); + Azimuth apparentWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numApparentWindAngle)); + + byte[] trueWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 44, 46); + int numTrueWindSpeed = bytesToInt(trueWindSpeedBytes); + double trueWindSpeedKnots = unpackMMperSecToKnots(numTrueWindSpeed); + + byte[] trueWindDirectionBytes = Arrays.copyOfRange(encodedMessage, 46, 48); + short numTrueWindDirection = bytesToShort(trueWindDirectionBytes); + Bearing trueWindDirection = Bearing.fromDegrees(unpackHeading(numTrueWindDirection)); + + byte[] trueWindAngleBytes = Arrays.copyOfRange(encodedMessage, 48, 50); + short numTrueWindAngle = bytesToShort(trueWindAngleBytes); + Azimuth trueWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numTrueWindAngle)); + + byte[] currentDriftBytes = Arrays.copyOfRange(encodedMessage, 50, 52); + int numCurrentDrift = bytesToInt(currentDriftBytes); + double currentDriftKnots = unpackMMperSecToKnots(numCurrentDrift); + + byte[] currentSetBytes = Arrays.copyOfRange(encodedMessage, 52, 54); + int numCurrentSet = bytesToShort(currentSetBytes); + Bearing currentSet = Bearing.fromDegrees(unpackHeading(numCurrentSet)); + + byte[] rudderAngleBytes = Arrays.copyOfRange(encodedMessage, 54, 56); + short numRudderAngle = bytesToShort(rudderAngleBytes); + Azimuth rudderAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numRudderAngle)); + + + message = new BoatLocation( + messageVersionNumber, + time, + sourceID, + seqNum, + deviceType, + latitude, + longitude, + numAltitude, + heading, + numPitch, + numRoll, + boatSpeedKnots, + cog, + sogKnots, + apparentWindSpeedKnots, + apparentWindAngle, + trueWindSpeedKnots, + trueWindDirection, + trueWindAngle, + currentDriftKnots, + currentSet, + rudderAngle); + + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode BoatLocation message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java index ed2c6cf0..27b88a39 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.BoatStatus; import network.Messages.Enums.BoatStatusEnum; @@ -39,10 +40,12 @@ public class BoatStatusDecoder { * Decodes the contained message. * @param encodedMessage The message to decode. * @return The decoded message. + * @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded. */ - public BoatStatus decode(byte[] encodedMessage) { + public BoatStatus decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; + try { byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 0, 4); int sourceID = bytesToInt(sourceIDBytes); @@ -76,6 +79,10 @@ public class BoatStatusDecoder { return message; + } catch (Exception e) { + throw new InvalidMessageException("Could not decode BoatStatus message.", e); + } + } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java index 6a26e4ae..4941df10 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.CourseWind; import shared.model.Bearing; @@ -38,49 +39,55 @@ public class CourseWindDecoder { * Decodes the contained message. * @param encodedMessage The message to decode. * @return The decoded message. + * @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded. */ - public CourseWind decode(byte[] encodedMessage) { + public CourseWind decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - byte[] windId = Arrays.copyOfRange(encodedMessage, 0, 1); + try { - byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = bytesToLong(timeBytes); + byte[] windId = Arrays.copyOfRange(encodedMessage, 0, 1); - byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); - int raceIDInt = bytesToInt(raceIDBytes); + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); - byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 11, 13); - int windDirectionInt = bytesToInt(windDirectionBytes); - Bearing windDirection = Bearing.fromDegrees(unpackHeading(windDirectionInt)); + byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int raceIDInt = bytesToInt(raceIDBytes); - byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 13, 15); - int windSpeedInt = bytesToInt(windSpeedBytes); - double windSpeedKnots = unpackMMperSecToKnots(windSpeedInt); + byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 11, 13); + int windDirectionInt = bytesToInt(windDirectionBytes); + Bearing windDirection = Bearing.fromDegrees(unpackHeading(windDirectionInt)); - byte[] bestUpwindAngleBytes = Arrays.copyOfRange(encodedMessage, 15, 17); - int bestUpwindAngleInt = bytesToInt(bestUpwindAngleBytes); - Bearing bestUpwindAngle = Bearing.fromDegrees(unpackHeading(bestUpwindAngleInt)); + byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 13, 15); + int windSpeedInt = bytesToInt(windSpeedBytes); + double windSpeedKnots = unpackMMperSecToKnots(windSpeedInt); - byte[] bestDownwindAngleBytes = Arrays.copyOfRange(encodedMessage, 17, 19); - int bestDownwindAngleInt = bytesToInt(bestDownwindAngleBytes); - Bearing bestDownwindAngle = Bearing.fromDegrees(unpackHeading(bestDownwindAngleInt)); + byte[] bestUpwindAngleBytes = Arrays.copyOfRange(encodedMessage, 15, 17); + int bestUpwindAngleInt = bytesToInt(bestUpwindAngleBytes); + Bearing bestUpwindAngle = Bearing.fromDegrees(unpackHeading(bestUpwindAngleInt)); - byte[] flags = Arrays.copyOfRange(encodedMessage, 19, 20); + byte[] bestDownwindAngleBytes = Arrays.copyOfRange(encodedMessage, 17, 19); + int bestDownwindAngleInt = bytesToInt(bestDownwindAngleBytes); + Bearing bestDownwindAngle = Bearing.fromDegrees(unpackHeading(bestDownwindAngleInt)); + byte[] flags = Arrays.copyOfRange(encodedMessage, 19, 20); - message = new CourseWind( - windId[0], - time, - raceIDInt, - windDirection, - windSpeedKnots, - bestUpwindAngle, - bestDownwindAngle, - flags[0] ); + message = new CourseWind( + windId[0], + time, + raceIDInt, + windDirection, + windSpeedKnots, + bestUpwindAngle, + bestDownwindAngle, + flags[0]); - return message; + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode CourseWind message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java index f478ac6b..d6b7b70e 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindsDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.CourseWind; import network.Messages.CourseWinds; @@ -38,41 +39,46 @@ public class CourseWindsDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - //The header is three bytes. - byte messageVersionNumber = encodedMessage[0]; - byte byteWindID = encodedMessage[1]; - byte loopCount = encodedMessage[2]; + try { + //The header is three bytes. + byte messageVersionNumber = encodedMessage[0]; + byte byteWindID = encodedMessage[1]; + byte loopCount = encodedMessage[2]; - //A CourseWind object is 20 bytes. - final int courseWindByteLength = 20; - List loopMessages = new ArrayList(); + //A CourseWind object is 20 bytes. + final int courseWindByteLength = 20; - //The header is 3 bytes, so we need the remaining bytes. - byte[] loopMessagesBytes = Arrays.copyOfRange(encodedMessage, 3, courseWindByteLength * loopCount + 3); + List loopMessages = new ArrayList(); - for (int messageLoopIndex = 0; messageLoopIndex < (loopCount * courseWindByteLength); messageLoopIndex += courseWindByteLength) { + //The header is 3 bytes, so we need the remaining bytes. + byte[] loopMessagesBytes = Arrays.copyOfRange(encodedMessage, 3, courseWindByteLength * loopCount + 3); - byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex + courseWindByteLength); + for (int messageLoopIndex = 0; messageLoopIndex < (loopCount * courseWindByteLength); messageLoopIndex += courseWindByteLength) { - CourseWindDecoder courseWindDecoder = new CourseWindDecoder(); - CourseWind courseWind = courseWindDecoder.decode(messageBytes); + byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex + courseWindByteLength); - loopMessages.add(courseWind); - } + CourseWindDecoder courseWindDecoder = new CourseWindDecoder(); + CourseWind courseWind = courseWindDecoder.decode(messageBytes); + loopMessages.add(courseWind); + } - message = new CourseWinds( - messageVersionNumber, - byteWindID, - loopMessages ); + message = new CourseWinds( + messageVersionNumber, + byteWindID, + loopMessages); - return message; + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode CourseWinds message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java index 49cdcc5f..bc86244b 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/HeartBeatDecoder.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.BoatActionEnum; import network.Messages.HeartBeat; @@ -30,12 +31,18 @@ public class HeartBeatDecoder implements MessageDecoder { } @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - message = new HeartBeat(bytesToLong(encodedMessage)); + try { - return message; + message = new HeartBeat(bytesToLong(encodedMessage)); + + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode HeartBeat message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java index eaa82c18..5766b47f 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/JoinAcceptanceDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.JoinAcceptanceEnum; import network.Messages.JoinAcceptance; @@ -32,27 +33,32 @@ public class JoinAcceptanceDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - //SourceID is first four bytes. - byte[] sourceIdBytes = Arrays.copyOfRange(encodedMessage, 0, 4); + try { - //Next byte is acceptance type. - byte[] acceptanceBytes = Arrays.copyOfRange(encodedMessage, 4, 5); + //SourceID is first four bytes. + byte[] sourceIdBytes = Arrays.copyOfRange(encodedMessage, 0, 4); + //Next byte is acceptance type. + byte[] acceptanceBytes = Arrays.copyOfRange(encodedMessage, 4, 5); - //SourceID is an int. - int sourceID = ByteConverter.bytesToInt(sourceIdBytes); + //SourceID is an int. + int sourceID = ByteConverter.bytesToInt(sourceIdBytes); - //Acceptance enum is a byte. - JoinAcceptanceEnum acceptanceType = JoinAcceptanceEnum.fromByte(acceptanceBytes[0]); + //Acceptance enum is a byte. + JoinAcceptanceEnum acceptanceType = JoinAcceptanceEnum.fromByte(acceptanceBytes[0]); - message = new JoinAcceptance(acceptanceType, sourceID); + message = new JoinAcceptance(acceptanceType, sourceID); - return message; + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode JoinAcceptance message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java index 58ad8f64..6db46f27 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.MarkRoundingBoatStatusEnum; import network.Messages.Enums.MarkRoundingSideEnum; @@ -33,48 +34,54 @@ public class MarkRoundingDecoder implements MessageDecoder { } @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - byte messageVersionNumber = encodedMessage[0]; + try { - byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = ByteConverter.bytesToLong(byteTime); + byte messageVersionNumber = encodedMessage[0]; - byte[] byteAck = Arrays.copyOfRange(encodedMessage, 7, 9); - int ackNumber = ByteConverter.bytesToInt(byteAck); + byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = ByteConverter.bytesToLong(byteTime); - byte[] byteRaceID = Arrays.copyOfRange(encodedMessage, 9, 13); - int raceID = ByteConverter.bytesToInt(byteRaceID); + byte[] byteAck = Arrays.copyOfRange(encodedMessage, 7, 9); + int ackNumber = ByteConverter.bytesToInt(byteAck); - byte[] byteSourceID = Arrays.copyOfRange(encodedMessage, 13, 17); - int sourceID = ByteConverter.bytesToInt(byteSourceID); + byte[] byteRaceID = Arrays.copyOfRange(encodedMessage, 9, 13); + int raceID = ByteConverter.bytesToInt(byteRaceID); - byte byteBoatStatus = encodedMessage[17]; - MarkRoundingBoatStatusEnum boatStatus = MarkRoundingBoatStatusEnum.fromByte(byteBoatStatus); + byte[] byteSourceID = Arrays.copyOfRange(encodedMessage, 13, 17); + int sourceID = ByteConverter.bytesToInt(byteSourceID); - byte byteRoundingSide = encodedMessage[18]; - MarkRoundingSideEnum roundingSide = MarkRoundingSideEnum.fromByte(byteRoundingSide); + byte byteBoatStatus = encodedMessage[17]; + MarkRoundingBoatStatusEnum boatStatus = MarkRoundingBoatStatusEnum.fromByte(byteBoatStatus); - byte byteMarkType = encodedMessage[19]; - MarkRoundingTypeEnum markType = MarkRoundingTypeEnum.fromByte(byteMarkType); + byte byteRoundingSide = encodedMessage[18]; + MarkRoundingSideEnum roundingSide = MarkRoundingSideEnum.fromByte(byteRoundingSide); - byte byteMarkID = encodedMessage[20]; + byte byteMarkType = encodedMessage[19]; + MarkRoundingTypeEnum markType = MarkRoundingTypeEnum.fromByte(byteMarkType); + byte byteMarkID = encodedMessage[20]; - message = new MarkRounding( - messageVersionNumber, - time, - ackNumber, - raceID, - sourceID, - boatStatus, - roundingSide, - markType, - byteMarkID); + message = new MarkRounding( + messageVersionNumber, + time, + ackNumber, + raceID, + sourceID, + boatStatus, + roundingSide, + markType, + byteMarkID); - return message; + + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode AverageWind message.", e); + } } /** diff --git a/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java index 39b4370f..c20c653f 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; @@ -15,7 +16,8 @@ public interface MessageDecoder { * Decodes a given message. * @param encodedMessage The message to decode. * @return The decoded message. + * @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded. */ - public AC35Data decode(byte[] encodedMessage); + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException; } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java index 062f1212..2776ea6f 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.RaceStartTypeEnum; import network.Messages.RaceStartStatus; @@ -35,38 +36,42 @@ public class RaceStartStatusDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; + try { - byte messageVersion = encodedMessage[0]; + byte messageVersion = encodedMessage[0]; - byte[] timestamp = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = bytesToLong(timestamp); + byte[] timestamp = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timestamp); - byte[] ackNumber = Arrays.copyOfRange(encodedMessage, 7, 9); - short ack = bytesToShort(ackNumber); + byte[] ackNumber = Arrays.copyOfRange(encodedMessage, 7, 9); + short ack = bytesToShort(ackNumber); - byte[] raceStartTime = Arrays.copyOfRange(encodedMessage, 9, 15); - long startTime = bytesToLong(raceStartTime); + byte[] raceStartTime = Arrays.copyOfRange(encodedMessage, 9, 15); + long startTime = bytesToLong(raceStartTime); - byte[] raceIdentifier = Arrays.copyOfRange(encodedMessage, 15, 19); - int raceID = bytesToInt(raceIdentifier); + byte[] raceIdentifier = Arrays.copyOfRange(encodedMessage, 15, 19); + int raceID = bytesToInt(raceIdentifier); - byte notificationType = encodedMessage[19]; + byte notificationType = encodedMessage[19]; + message = new RaceStartStatus( + messageVersion, + time, + ack, + startTime, + raceID, + RaceStartTypeEnum.fromByte(notificationType) + ); - message = new RaceStartStatus( - messageVersion, - time, - ack, - startTime, - raceID, - RaceStartTypeEnum.fromByte(notificationType) - ); + return message; - return message; + } catch (Exception e) { + throw new InvalidMessageException("Could not decode RaceStartStatus message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index 15700f49..7e26e105 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatStatus; import network.Messages.Enums.RaceStatusEnum; @@ -43,70 +44,75 @@ public class RaceStatusDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; + try { - byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1); - byte versionNum = versionNumBytes[0]; + byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte versionNum = versionNumBytes[0]; - byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); - long time = bytesToLong(timeBytes); + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); - byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); - int raceID = bytesToInt(raceIDBytes); + byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int raceID = bytesToInt(raceIDBytes); - byte[] raceStatusBytes = Arrays.copyOfRange(encodedMessage, 11, 12); - RaceStatusEnum raceStatus = RaceStatusEnum.fromByte(raceStatusBytes[0]); + byte[] raceStatusBytes = Arrays.copyOfRange(encodedMessage, 11, 12); + RaceStatusEnum raceStatus = RaceStatusEnum.fromByte(raceStatusBytes[0]); - byte[] expectedStartBytes = Arrays.copyOfRange(encodedMessage, 12, 18); - long expectedStart = bytesToLong(expectedStartBytes); + byte[] expectedStartBytes = Arrays.copyOfRange(encodedMessage, 12, 18); + long expectedStart = bytesToLong(expectedStartBytes); - byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20); - int windDirectionInt = bytesToInt(windDirectionBytes); - Bearing windDirection = Bearing.fromDegrees(AC35UnitConverter.unpackHeading(windDirectionInt)); + byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20); + int windDirectionInt = bytesToInt(windDirectionBytes); + Bearing windDirection = Bearing.fromDegrees(AC35UnitConverter.unpackHeading(windDirectionInt)); - byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22); - int windSpeedInt = bytesToInt(windSpeedBytes); - double windSpeedKnots = AC35UnitConverter.unpackMMperSecToKnots(windSpeedInt); + byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22); + int windSpeedInt = bytesToInt(windSpeedBytes); + double windSpeedKnots = AC35UnitConverter.unpackMMperSecToKnots(windSpeedInt); - byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23); - int numberOfBoats = bytesToInt(numberOfBoatsBytes); + byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23); + int numberOfBoats = bytesToInt(numberOfBoatsBytes); - byte[] raceTypeBytes = Arrays.copyOfRange(encodedMessage, 23, 24); - RaceTypeEnum raceType = RaceTypeEnum.fromByte(raceTypeBytes[0]); + byte[] raceTypeBytes = Arrays.copyOfRange(encodedMessage, 23, 24); + RaceTypeEnum raceType = RaceTypeEnum.fromByte(raceTypeBytes[0]); - byte[] boatStatusesBytes = Arrays.copyOfRange(encodedMessage, 24, 25 + 20 * numberOfBoats); - List boatStatuses = new ArrayList<>(); + byte[] boatStatusesBytes = Arrays.copyOfRange(encodedMessage, 24, 25 + 20 * numberOfBoats); + List boatStatuses = new ArrayList<>(); - //BoatStatus is 20 bytes. - int boatStatusByteLength = 20; + //BoatStatus is 20 bytes. + int boatStatusByteLength = 20; - //Decode each BoatStatus. - for (int boatLoopIndex = 0; boatLoopIndex < (numberOfBoats * boatStatusByteLength); boatLoopIndex += boatStatusByteLength) { + //Decode each BoatStatus. + for (int boatLoopIndex = 0; boatLoopIndex < (numberOfBoats * boatStatusByteLength); boatLoopIndex += boatStatusByteLength) { - byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + boatStatusByteLength); + byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + boatStatusByteLength); - BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(); + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(); - boatStatuses.add(boatStatusDecoder.decode(boatStatusBytes)); - } + boatStatuses.add(boatStatusDecoder.decode(boatStatusBytes)); + } - message = new RaceStatus( - versionNum, - time, - raceID, - raceStatus, - expectedStart, - windDirection, - windSpeedKnots, - raceType, - boatStatuses ); + message = new RaceStatus( + versionNum, + time, + raceID, + raceStatus, + expectedStart, + windDirection, + windSpeedKnots, + raceType, + boatStatuses); - return message; + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode RaceStatus message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java index 1abf61a6..59c881d1 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RequestToJoinDecoder.java @@ -1,6 +1,7 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.RequestToJoinEnum; import network.Messages.RequestToJoin; @@ -31,20 +32,26 @@ public class RequestToJoinDecoder implements MessageDecoder{ @Override - public AC35Data decode(byte[] encodedRequest) { + public AC35Data decode(byte[] encodedRequest) throws InvalidMessageException { this.encodedRequest = encodedRequest; - //Request type is first four bytes. - byte[] requestTypeBytes = Arrays.copyOfRange(encodedRequest, 0, 4); + try { - //Request type is an integral type. - int requestTypeInt = ByteConverter.bytesToInt(requestTypeBytes); - RequestToJoinEnum requestType = RequestToJoinEnum.fromInt(requestTypeInt); + //Request type is first four bytes. + byte[] requestTypeBytes = Arrays.copyOfRange(encodedRequest, 0, 4); + //Request type is an integral type. + int requestTypeInt = ByteConverter.bytesToInt(requestTypeBytes); + RequestToJoinEnum requestType = RequestToJoinEnum.fromInt(requestTypeInt); - message = new RequestToJoin(requestType); - return message; + message = new RequestToJoin(requestType); + + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode RequestToJoin message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java index 99c56e28..80a62048 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.Enums.XMLMessageType; import network.Messages.XMLMessage; @@ -35,36 +36,42 @@ public class XMLMessageDecoder implements MessageDecoder { @Override - public AC35Data decode(byte[] encodedMessage) { + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { this.encodedMessage = encodedMessage; - byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); - byte[] ackNumberBytes = Arrays.copyOfRange(encodedMessage, 1, 3); - byte[] timeStampBytes = Arrays.copyOfRange(encodedMessage, 3, 9); - byte[] xmlMsgSubTypeBytes = Arrays.copyOfRange(encodedMessage, 9, 10); - byte[] sequenceNumberBytes = Arrays.copyOfRange(encodedMessage, 10, 12); - byte[] xmlMsgLengthBytes = Arrays.copyOfRange(encodedMessage, 12, 14); - byte[] xmlMessagebytes = Arrays.copyOfRange(encodedMessage, 14, encodedMessage.length); + try { + byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte[] ackNumberBytes = Arrays.copyOfRange(encodedMessage, 1, 3); + byte[] timeStampBytes = Arrays.copyOfRange(encodedMessage, 3, 9); + byte[] xmlMsgSubTypeBytes = Arrays.copyOfRange(encodedMessage, 9, 10); + byte[] sequenceNumberBytes = Arrays.copyOfRange(encodedMessage, 10, 12); + byte[] xmlMsgLengthBytes = Arrays.copyOfRange(encodedMessage, 12, 14); + byte[] xmlMessagebytes = Arrays.copyOfRange(encodedMessage, 14, encodedMessage.length); - byte messageVersionNumber = messageVersionNumberBytes[0]; - short ackNumber = bytesToShort(ackNumberBytes); - long timeStamp = bytesToLong(timeStampBytes); - XMLMessageType xmlMsgSubType = XMLMessageType.fromByte(xmlMsgSubTypeBytes[0]); - short sequenceNumber = bytesToShort(sequenceNumberBytes); - short xmlMsgLength = bytesToShort(xmlMsgLengthBytes); - String xmlMessage = new String(xmlMessagebytes); + byte messageVersionNumber = messageVersionNumberBytes[0]; + short ackNumber = bytesToShort(ackNumberBytes); + long timeStamp = bytesToLong(timeStampBytes); + XMLMessageType xmlMsgSubType = XMLMessageType.fromByte(xmlMsgSubTypeBytes[0]); + short sequenceNumber = bytesToShort(sequenceNumberBytes); + short xmlMsgLength = bytesToShort(xmlMsgLengthBytes); + String xmlMessage = new String(xmlMessagebytes); - message = new XMLMessage( - messageVersionNumber, - ackNumber, - timeStamp, - xmlMsgSubType, - sequenceNumber, - xmlMessage ); - return message; + message = new XMLMessage( + messageVersionNumber, + ackNumber, + timeStamp, + xmlMsgSubType, + sequenceNumber, + xmlMessage); + + return message; + + } catch (Exception e) { + throw new InvalidMessageException("Could not decode XMLMessage message.", e); + } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java index dc1966fc..b8bce3df 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/AverageWindEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.AverageWind; @@ -24,63 +25,68 @@ public class AverageWindEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - AverageWind averageWind = (AverageWind) message; + try { + //Downcast. + AverageWind averageWind = (AverageWind) message; - byte messageVersionNumber = averageWind.getMessageVersionNumber(); - long time = averageWind.getTime(); - byte[] byteTime = longToBytes(time,6); + byte messageVersionNumber = averageWind.getMessageVersionNumber(); - long rawPeriod = averageWind.getRawPeriod(); - int rawPeriodInt = packAverageWindPeriod(rawPeriod); - byte[] byteRawPeriod = intToBytes(rawPeriodInt, 2); + long time = averageWind.getTime(); + byte[] byteTime = longToBytes(time, 6); - double rawSampleSpeed = averageWind.getRawSpeedKnots(); - int rawSampleSpeedInt = packKnotsToMMperSec(rawSampleSpeed); - byte[] byteRawSpeed = intToBytes(rawSampleSpeedInt, 2); + long rawPeriod = averageWind.getRawPeriod(); + int rawPeriodInt = packAverageWindPeriod(rawPeriod); + byte[] byteRawPeriod = intToBytes(rawPeriodInt, 2); - long period2 = averageWind.getSampleTwoPeriod(); - int period2Int = packAverageWindPeriod(period2); - byte[] bytePeriod2 = intToBytes(period2Int, 2); + double rawSampleSpeed = averageWind.getRawSpeedKnots(); + int rawSampleSpeedInt = packKnotsToMMperSec(rawSampleSpeed); + byte[] byteRawSpeed = intToBytes(rawSampleSpeedInt, 2); - double speed2 = averageWind.getSampleTwoSpeedKnots(); - int speed2Int = packKnotsToMMperSec(speed2); - byte[] byteSpeed2 = intToBytes(speed2Int, 2); + long period2 = averageWind.getSampleTwoPeriod(); + int period2Int = packAverageWindPeriod(period2); + byte[] bytePeriod2 = intToBytes(period2Int, 2); - long period3 = averageWind.getSampleThreePeriod(); - int period3Int = packAverageWindPeriod(period3); - byte[] bytePeriod3 = intToBytes(period3Int, 2); + double speed2 = averageWind.getSampleTwoSpeedKnots(); + int speed2Int = packKnotsToMMperSec(speed2); + byte[] byteSpeed2 = intToBytes(speed2Int, 2); - double speed3 = averageWind.getSampleThreeSpeedKnots(); - int speed3Int = packKnotsToMMperSec(speed3); - byte[] byteSpeed3 = intToBytes(speed3Int, 2); + long period3 = averageWind.getSampleThreePeriod(); + int period3Int = packAverageWindPeriod(period3); + byte[] bytePeriod3 = intToBytes(period3Int, 2); - long period4 = averageWind.getSampleFourPeriod(); - int period4Int = packAverageWindPeriod(period4); - byte[] bytePeriod4 = intToBytes(period4Int, 2); + double speed3 = averageWind.getSampleThreeSpeedKnots(); + int speed3Int = packKnotsToMMperSec(speed3); + byte[] byteSpeed3 = intToBytes(speed3Int, 2); - double speed4 = averageWind.getSampleFourSpeedKnots(); - int speed4Int = packKnotsToMMperSec(speed4); - byte[] byteSpeed4 = intToBytes(speed4Int, 2); + long period4 = averageWind.getSampleFourPeriod(); + int period4Int = packAverageWindPeriod(period4); + byte[] bytePeriod4 = intToBytes(period4Int, 2); + double speed4 = averageWind.getSampleFourSpeedKnots(); + int speed4Int = packKnotsToMMperSec(speed4); + byte[] byteSpeed4 = intToBytes(speed4Int, 2); - ByteBuffer result = ByteBuffer.allocate(23); - result.put(messageVersionNumber); - result.put(byteTime); - result.put(byteRawPeriod); - result.put(byteRawSpeed); - result.put(bytePeriod2); - result.put(byteSpeed2); - result.put(bytePeriod3); - result.put(byteSpeed3); - result.put(bytePeriod4); - result.put(byteSpeed4); - return result.array(); + ByteBuffer result = ByteBuffer.allocate(23); + result.put(messageVersionNumber); + result.put(byteTime); + result.put(byteRawPeriod); + result.put(byteRawSpeed); + result.put(bytePeriod2); + result.put(byteSpeed2); + result.put(bytePeriod3); + result.put(byteSpeed3); + result.put(bytePeriod4); + result.put(byteSpeed4); + return result.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode AverageWind message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java index 8083487f..31e02386 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatActionEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatAction; @@ -22,19 +23,25 @@ public class BoatActionEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - BoatAction boatAction = (BoatAction) message; + try { - //Message is 1 byte. - ByteBuffer boatActionMessage = ByteBuffer.allocate(1); + //Downcast. + BoatAction boatAction = (BoatAction) message; - boatActionMessage.put(intToBytes(boatAction.getBoatAction().getValue(), 1)); + //Message is 1 byte. + ByteBuffer boatActionMessage = ByteBuffer.allocate(1); - byte [] result = boatActionMessage.array(); + boatActionMessage.put(intToBytes(boatAction.getBoatAction().getValue(), 1)); - return result; + byte[] result = boatActionMessage.array(); + + return result; + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode BoatAction message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java index 9c3833ac..b982a592 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatLocationEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatLocation; @@ -24,61 +25,67 @@ public class BoatLocationEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { - - //Downcast. - BoatLocation boatLocation = (BoatLocation) message; - - - int messageVersionNumber = 0b1; - byte[] messageVersionBytes = intToBytes(messageVersionNumber, 1); - byte[] time = longToBytes(boatLocation.getTime(), 6); - byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4); - byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4); - byte[] deviceType = intToBytes(boatLocation.getDeviceType().getValue(), 1); - byte[] latitude = intToBytes(packGPS(boatLocation.getLatitude()), 4); - byte[] longitude = intToBytes(packGPS(boatLocation.getLongitude()), 4); - byte[] altitude = intToBytes(boatLocation.getAltitude(), 4); - byte[] heading = intToBytes(packHeading(boatLocation.getHeading().degrees()), 2); - byte[] pitch = intToBytes(boatLocation.getPitch(), 2); - byte[] roll = intToBytes(boatLocation.getRoll(), 2); - byte[] boatSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSpeedKnots()), 2); - byte[] cog = intToBytes(packHeading(boatLocation.getBoatCOG().degrees()), 2); - byte[] sog = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSOGKnots()), 2); - byte[] apparentWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getApparentWindSpeedKnots()), 2); - byte[] apparentWindAngle = intToBytes(packTrueWindAngle(boatLocation.getApparentWindAngle().degrees()), 2); - byte[] trueWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getTrueWindSpeedKnots()), 2); - byte[] trueWindDirection = intToBytes(packHeading(boatLocation.getTrueWindDirection().degrees()), 2); - byte[] trueWindAngle = intToBytes(packTrueWindAngle(boatLocation.getTrueWindAngle().degrees()), 2); - byte[] currentDrift = intToBytes(packKnotsToMMperSec(boatLocation.getCurrentDriftKnots()), 2); - byte[] currentSet = intToBytes(packHeading(boatLocation.getCurrentSet().degrees()), 2); - byte[] rudderAngle = intToBytes(packTrueWindAngle(boatLocation.getRudderAngle().degrees()), 2); - - ByteBuffer result = ByteBuffer.allocate(56); - result.put(messageVersionBytes); - result.put(time); - result.put(sourceID); - result.put(seqNum); - result.put(deviceType); - result.put(latitude); - result.put(longitude); - result.put(altitude); - result.put(heading); - result.put(pitch); - result.put(roll); - result.put(boatSpeed); - result.put(cog); - result.put(sog); - result.put(apparentWindSpeed); - result.put(apparentWindAngle); - result.put(trueWindSpeed); - result.put(trueWindDirection); - result.put(trueWindAngle); - result.put(currentDrift); - result.put(currentSet); - result.put(rudderAngle); - - return result.array(); + public byte[] encode(AC35Data message) throws InvalidMessageException { + + try { + + //Downcast. + BoatLocation boatLocation = (BoatLocation) message; + + + int messageVersionNumber = 0b1; + byte[] messageVersionBytes = intToBytes(messageVersionNumber, 1); + byte[] time = longToBytes(boatLocation.getTime(), 6); + byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4); + byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4); + byte[] deviceType = intToBytes(boatLocation.getDeviceType().getValue(), 1); + byte[] latitude = intToBytes(packGPS(boatLocation.getLatitude()), 4); + byte[] longitude = intToBytes(packGPS(boatLocation.getLongitude()), 4); + byte[] altitude = intToBytes(boatLocation.getAltitude(), 4); + byte[] heading = intToBytes(packHeading(boatLocation.getHeading().degrees()), 2); + byte[] pitch = intToBytes(boatLocation.getPitch(), 2); + byte[] roll = intToBytes(boatLocation.getRoll(), 2); + byte[] boatSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSpeedKnots()), 2); + byte[] cog = intToBytes(packHeading(boatLocation.getBoatCOG().degrees()), 2); + byte[] sog = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSOGKnots()), 2); + byte[] apparentWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getApparentWindSpeedKnots()), 2); + byte[] apparentWindAngle = intToBytes(packTrueWindAngle(boatLocation.getApparentWindAngle().degrees()), 2); + byte[] trueWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getTrueWindSpeedKnots()), 2); + byte[] trueWindDirection = intToBytes(packHeading(boatLocation.getTrueWindDirection().degrees()), 2); + byte[] trueWindAngle = intToBytes(packTrueWindAngle(boatLocation.getTrueWindAngle().degrees()), 2); + byte[] currentDrift = intToBytes(packKnotsToMMperSec(boatLocation.getCurrentDriftKnots()), 2); + byte[] currentSet = intToBytes(packHeading(boatLocation.getCurrentSet().degrees()), 2); + byte[] rudderAngle = intToBytes(packTrueWindAngle(boatLocation.getRudderAngle().degrees()), 2); + + ByteBuffer result = ByteBuffer.allocate(56); + result.put(messageVersionBytes); + result.put(time); + result.put(sourceID); + result.put(seqNum); + result.put(deviceType); + result.put(latitude); + result.put(longitude); + result.put(altitude); + result.put(heading); + result.put(pitch); + result.put(roll); + result.put(boatSpeed); + result.put(cog); + result.put(sog); + result.put(apparentWindSpeed); + result.put(apparentWindAngle); + result.put(trueWindSpeed); + result.put(trueWindDirection); + result.put(trueWindAngle); + result.put(currentDrift); + result.put(currentSet); + result.put(rudderAngle); + + return result.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode BoatLocation message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java index 3a40cbba..fe7ecb18 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.BoatStatus; import java.nio.ByteBuffer; @@ -25,32 +26,40 @@ public class BoatStatusEncoder { * Encodes a given BoatStatus message. * @param message The message to encode. * @return The encoded message. + * @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded. */ - public byte[] encode(BoatStatus message) { - - //Downcast. - BoatStatus boatStatus = (BoatStatus) message; - - //BoatStatus is 20 bytes. - ByteBuffer boatStatusBuffer = ByteBuffer.allocate(20); - - byte[] sourceID = intToBytes(boatStatus.getSourceID()); - byte[] boatStatusBytes = intToBytes(boatStatus.getBoatStatus().getValue(), 1); - byte[] legNum = intToBytes(boatStatus.getLegNumber(), 1); - byte[] numPenalties = intToBytes(boatStatus.getNumPenaltiesAwarded(), 1); - byte[] numPenaltiesServed = intToBytes(boatStatus.getNumPenaltiesServed(), 1); - byte[] estNextMarkTime = longToBytes(boatStatus.getEstTimeAtNextMark(), 6); - byte[] estFinishTime = longToBytes(boatStatus.getEstTimeAtFinish(), 6); - - boatStatusBuffer.put(sourceID); - boatStatusBuffer.put(boatStatusBytes); - boatStatusBuffer.put(legNum); - boatStatusBuffer.put(numPenalties); - boatStatusBuffer.put(numPenaltiesServed); - boatStatusBuffer.put(estNextMarkTime); - boatStatusBuffer.put(estFinishTime); - - return boatStatusBuffer.array(); + public byte[] encode(BoatStatus message) throws InvalidMessageException { + + try { + + + //Downcast. + BoatStatus boatStatus = (BoatStatus) message; + + //BoatStatus is 20 bytes. + ByteBuffer boatStatusBuffer = ByteBuffer.allocate(20); + + byte[] sourceID = intToBytes(boatStatus.getSourceID()); + byte[] boatStatusBytes = intToBytes(boatStatus.getBoatStatus().getValue(), 1); + byte[] legNum = intToBytes(boatStatus.getLegNumber(), 1); + byte[] numPenalties = intToBytes(boatStatus.getNumPenaltiesAwarded(), 1); + byte[] numPenaltiesServed = intToBytes(boatStatus.getNumPenaltiesServed(), 1); + byte[] estNextMarkTime = longToBytes(boatStatus.getEstTimeAtNextMark(), 6); + byte[] estFinishTime = longToBytes(boatStatus.getEstTimeAtFinish(), 6); + + boatStatusBuffer.put(sourceID); + boatStatusBuffer.put(boatStatusBytes); + boatStatusBuffer.put(legNum); + boatStatusBuffer.put(numPenalties); + boatStatusBuffer.put(numPenaltiesServed); + boatStatusBuffer.put(estNextMarkTime); + boatStatusBuffer.put(estFinishTime); + + return boatStatusBuffer.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode BoatStatus message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java index b6e407c0..2f5f4897 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.CourseWind; import shared.model.Bearing; @@ -28,46 +29,53 @@ public class CourseWindEncoder { * Encodes a given CourseWind message. * @param message The message to encode. * @return The encoded message. + * @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded. */ - public byte[] encode(CourseWind message) { + public byte[] encode(CourseWind message) throws InvalidMessageException { - CourseWind courseWind = message; + try { + CourseWind courseWind = message; - //CourseWind is 20 bytes. - ByteBuffer courseWindBuffer = ByteBuffer.allocate(20); + //CourseWind is 20 bytes. + ByteBuffer courseWindBuffer = ByteBuffer.allocate(20); - byte[] windId = intToBytes(courseWind.getID(), 1); - byte[] timeBytes = longToBytes(courseWind.getTime(), 6); + byte[] windId = intToBytes(courseWind.getID(), 1); - byte[] raceIDBytes = intToBytes(courseWind.getRaceID(), 4); + byte[] timeBytes = longToBytes(courseWind.getTime(), 6); - int windDirectionInt = packHeading(courseWind.getWindDirection().degrees()); - byte[] windDirectionBytes = intToBytes(windDirectionInt, 2); + byte[] raceIDBytes = intToBytes(courseWind.getRaceID(), 4); - int windSpeedInt = packKnotsToMMperSec(courseWind.getWindSpeedKnots()); - byte[] windSpeedBytes = intToBytes(windSpeedInt, 2); + int windDirectionInt = packHeading(courseWind.getWindDirection().degrees()); + byte[] windDirectionBytes = intToBytes(windDirectionInt, 2); - int bestUpwindAngleInt = packHeading(courseWind.getBestUpwindAngle().degrees()); - byte[] bestUpwindAngleBytes = intToBytes(bestUpwindAngleInt, 2); + int windSpeedInt = packKnotsToMMperSec(courseWind.getWindSpeedKnots()); + byte[] windSpeedBytes = intToBytes(windSpeedInt, 2); - int bestDownwindAngleInt = packHeading(courseWind.getBestDownwindAngle().degrees()); - byte[] bestDownwindAngleBytes = intToBytes(bestDownwindAngleInt, 2); + int bestUpwindAngleInt = packHeading(courseWind.getBestUpwindAngle().degrees()); + byte[] bestUpwindAngleBytes = intToBytes(bestUpwindAngleInt, 2); - byte[] flags = intToBytes(courseWind.getFlags(), 1); + int bestDownwindAngleInt = packHeading(courseWind.getBestDownwindAngle().degrees()); + byte[] bestDownwindAngleBytes = intToBytes(bestDownwindAngleInt, 2); - courseWindBuffer.put(windId); - courseWindBuffer.put(timeBytes); - courseWindBuffer.put(raceIDBytes); - courseWindBuffer.put(windDirectionBytes); - courseWindBuffer.put(windSpeedBytes); - courseWindBuffer.put(bestUpwindAngleBytes); - courseWindBuffer.put(bestDownwindAngleBytes); - courseWindBuffer.put(flags); + byte[] flags = intToBytes(courseWind.getFlags(), 1); - return courseWindBuffer.array(); + courseWindBuffer.put(windId); + courseWindBuffer.put(timeBytes); + courseWindBuffer.put(raceIDBytes); + courseWindBuffer.put(windDirectionBytes); + courseWindBuffer.put(windSpeedBytes); + courseWindBuffer.put(bestUpwindAngleBytes); + courseWindBuffer.put(bestDownwindAngleBytes); + courseWindBuffer.put(flags); + + return courseWindBuffer.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode CourseWind message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java index 193144c3..86bd1197 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/CourseWindsEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatAction; import network.Messages.CourseWind; @@ -24,33 +25,39 @@ public class CourseWindsEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - CourseWinds courseWinds = (CourseWinds) message; + try { + //Downcast. + CourseWinds courseWinds = (CourseWinds) message; - byte messageVersionNumber = CourseWinds.currentMessageVersionNumber; - byte byteWindID = courseWinds.getSelectedWindID(); + byte messageVersionNumber = CourseWinds.currentMessageVersionNumber; - byte[] loopcount = intToBytes(courseWinds.getCourseWinds().size(), 1); + byte byteWindID = courseWinds.getSelectedWindID(); - ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.getCourseWinds().size()); + byte[] loopcount = intToBytes(courseWinds.getCourseWinds().size(), 1); - result.put(messageVersionNumber); - result.put(byteWindID); - result.put(loopcount); + ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.getCourseWinds().size()); - //Encode each CourseWind. - for (CourseWind wind: courseWinds.getCourseWinds()){ + result.put(messageVersionNumber); + result.put(byteWindID); + result.put(loopcount); - CourseWindEncoder courseWindEncoder = new CourseWindEncoder(); - byte[] encodedCourseWind = courseWindEncoder.encode(wind); + //Encode each CourseWind. + for (CourseWind wind : courseWinds.getCourseWinds()) { - result.put(encodedCourseWind); + CourseWindEncoder courseWindEncoder = new CourseWindEncoder(); + byte[] encodedCourseWind = courseWindEncoder.encode(wind); + + result.put(encodedCourseWind); + } + return result.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode CourseWinds message.", e); } - return result.array(); } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java index dcf64b61..ecbefe41 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.HeartBeat; @@ -22,18 +23,24 @@ public class HeartBeatEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - HeartBeat heartbeat = (HeartBeat) message; + try { - //Message is 4 bytes. - ByteBuffer heartBeat = ByteBuffer.allocate(4); - heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4)); + //Downcast. + HeartBeat heartbeat = (HeartBeat) message; - byte[] result = heartBeat.array(); + //Message is 4 bytes. + ByteBuffer heartBeat = ByteBuffer.allocate(4); + heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4)); - return result; + byte[] result = heartBeat.array(); + + return result; + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode HeartBeat message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java index 2b23c0dd..8f236454 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/JoinAcceptanceEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.JoinAcceptance; import network.Utils.ByteConverter; @@ -23,22 +24,28 @@ public class JoinAcceptanceEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - JoinAcceptance joinAcceptance = (JoinAcceptance) message; + try { - //Message is 5 bytes. - ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5); + //Downcast. + JoinAcceptance joinAcceptance = (JoinAcceptance) message; - //Source ID is first four bytes. - joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4)); - //Acceptance type is next byte. - joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1)); + //Message is 5 bytes. + ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5); - byte [] result = joinAcceptanceBuffer.array(); + //Source ID is first four bytes. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4)); + //Acceptance type is next byte. + joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1)); - return result; + byte[] result = joinAcceptanceBuffer.array(); + + return result; + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode JoinAcceptance message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java index de86691a..2c30b0cd 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/MarkRoundingEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.MarkRounding; @@ -23,35 +24,41 @@ public class MarkRoundingEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { - - //Downcast. - MarkRounding markRounding = (MarkRounding) message; - - byte messageVersionNumber = markRounding.getMessageVersionNumber(); - byte[] byteTime = longToBytes(markRounding.getTime(), 6); - byte[] byteAck = intToBytes(markRounding.getAckNum(), 2); - byte[] byteRaceID = intToBytes(markRounding.getRaceID(), 4); - byte[] byteSourceID = intToBytes(markRounding.getSourceID(), 4); - byte[] byteBoatStatus = intToBytes(markRounding.getBoatStatus().getValue(), 1); - byte[] byteRoundingSide = intToBytes(markRounding.getRoundingSide().getValue(), 1); - byte[] byteMarkType = intToBytes(markRounding.getMarkType().getValue(), 1); - byte[] byteMarkID = intToBytes(markRounding.getMarkID(), 1); - - - ByteBuffer result = ByteBuffer.allocate(21); - - result.put(messageVersionNumber); - result.put(byteTime); - result.put(byteAck); - result.put(byteRaceID); - result.put(byteSourceID); - result.put(byteBoatStatus); - result.put(byteRoundingSide); - result.put(byteMarkType); - result.put(byteMarkID); - - return result.array(); + public byte[] encode(AC35Data message) throws InvalidMessageException { + + try { + + //Downcast. + MarkRounding markRounding = (MarkRounding) message; + + byte messageVersionNumber = markRounding.getMessageVersionNumber(); + byte[] byteTime = longToBytes(markRounding.getTime(), 6); + byte[] byteAck = intToBytes(markRounding.getAckNum(), 2); + byte[] byteRaceID = intToBytes(markRounding.getRaceID(), 4); + byte[] byteSourceID = intToBytes(markRounding.getSourceID(), 4); + byte[] byteBoatStatus = intToBytes(markRounding.getBoatStatus().getValue(), 1); + byte[] byteRoundingSide = intToBytes(markRounding.getRoundingSide().getValue(), 1); + byte[] byteMarkType = intToBytes(markRounding.getMarkType().getValue(), 1); + byte[] byteMarkID = intToBytes(markRounding.getMarkID(), 1); + + + ByteBuffer result = ByteBuffer.allocate(21); + + result.put(messageVersionNumber); + result.put(byteTime); + result.put(byteAck); + result.put(byteRaceID); + result.put(byteSourceID); + result.put(byteBoatStatus); + result.put(byteRoundingSide); + result.put(byteMarkType); + result.put(byteMarkID); + + return result.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode MarkRounding message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java index d5ec4cd8..f91bb639 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/MessageEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; @@ -15,7 +16,8 @@ public interface MessageEncoder { * Encodes a given message. * @param message The message to encode. * @return Message in byte encoded form. + * @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded. */ - public byte[] encode(AC35Data message); + public byte[] encode(AC35Data message) throws InvalidMessageException; } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java index f2d41d20..ad9a65f0 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStartStatusEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.RaceStartStatus; import network.Utils.ByteConverter; @@ -24,28 +25,34 @@ public class RaceStartStatusEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - RaceStartStatus raceStartStatus = (RaceStartStatus) message; + try { + //Downcast. + RaceStartStatus raceStartStatus = (RaceStartStatus) message; - byte messageVersion = raceStartStatus.getMessageVersionNumber(); - byte[] timestamp = longToBytes(raceStartStatus.getTimestamp(), 6); - byte[] ackNumber = intToBytes(raceStartStatus.getAckNum(), 2); - byte[] raceStartTime = longToBytes(raceStartStatus.getRaceStartTime(), 6); - byte[] raceIdentifier = intToBytes(raceStartStatus.getRaceID()); - byte[] notificationType = intToBytes(raceStartStatus.getNotificationType().getValue(), 1); - ByteBuffer result = ByteBuffer.allocate(20); - result.put(messageVersion); - result.put(timestamp); - result.put(ackNumber); - result.put(raceStartTime); - result.put(raceIdentifier); - result.put(notificationType); + byte messageVersion = raceStartStatus.getMessageVersionNumber(); + byte[] timestamp = longToBytes(raceStartStatus.getTimestamp(), 6); + byte[] ackNumber = intToBytes(raceStartStatus.getAckNum(), 2); + byte[] raceStartTime = longToBytes(raceStartStatus.getRaceStartTime(), 6); + byte[] raceIdentifier = intToBytes(raceStartStatus.getRaceID()); + byte[] notificationType = intToBytes(raceStartStatus.getNotificationType().getValue(), 1); - return result.array(); + ByteBuffer result = ByteBuffer.allocate(20); + result.put(messageVersion); + result.put(timestamp); + result.put(ackNumber); + result.put(raceStartTime); + result.put(raceIdentifier); + result.put(notificationType); + + return result.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode RaceStartStatus message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java index fcb06d82..fd87cd0c 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.BoatStatus; import network.Messages.RaceStatus; @@ -28,68 +29,74 @@ public class RaceStatusEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - RaceStatus raceStatus = (RaceStatus) message; + try { + //Downcast. + RaceStatus raceStatus = (RaceStatus) message; - List boatStatuses = raceStatus.getBoatStatuses(); - //24 byte header, plus 20 bytes per boat status. - ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20 * boatStatuses.size()); + List boatStatuses = raceStatus.getBoatStatuses(); - //Version Number 1 bytes. this changes with the pdf. (2) - byte versionNum = 0b10; + //24 byte header, plus 20 bytes per boat status. + ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20 * boatStatuses.size()); - //time (6 bytes) - byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6); + //Version Number 1 bytes. this changes with the pdf. (2) + byte versionNum = 0b10; - //race identifier in case multiple races are going at once. - byte[] raceID = intToBytes(raceStatus.getRaceID()); + //time (6 bytes) + byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6); - //race status 0 - 10 - byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus().getValue(), 1); + //race identifier in case multiple races are going at once. + byte[] raceID = intToBytes(raceStatus.getRaceID()); - //number of milliseconds from Jan 1, 1970 for when the data is valid - byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6); + //race status 0 - 10 + byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus().getValue(), 1); - //North = 0x0000 East = 0x4000 South = 0x8000. - int windDirectionInt = AC35UnitConverter.packHeading(raceStatus.getWindDirection().degrees()); - byte[] raceWind = intToBytes(windDirectionInt, 2); + //number of milliseconds from Jan 1, 1970 for when the data is valid + byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6); - //mm/sec - int windSpeedInt = AC35UnitConverter.packKnotsToMMperSec(raceStatus.getWindSpeed()); - byte[] windSpeed = intToBytes(windSpeedInt, 2); + //North = 0x0000 East = 0x4000 South = 0x8000. + int windDirectionInt = AC35UnitConverter.packHeading(raceStatus.getWindDirection().degrees()); + byte[] raceWind = intToBytes(windDirectionInt, 2); + //mm/sec + int windSpeedInt = AC35UnitConverter.packKnotsToMMperSec(raceStatus.getWindSpeed()); + byte[] windSpeed = intToBytes(windSpeedInt, 2); - byte[] numBoats = intToBytes(boatStatuses.size(), 1); - //1 match race, 2 fleet race - byte[] bytesRaceType = intToBytes(raceStatus.getRaceType().getValue(), 1); + byte[] numBoats = intToBytes(boatStatuses.size(), 1); + //1 match race, 2 fleet race + byte[] bytesRaceType = intToBytes(raceStatus.getRaceType().getValue(), 1); - raceStatusMessage.put(versionNum); - raceStatusMessage.put(timeBytes); - raceStatusMessage.put(raceID); - raceStatusMessage.put(raceStatusByte); - raceStatusMessage.put(expectedStart); - raceStatusMessage.put(raceWind); - raceStatusMessage.put(windSpeed); - raceStatusMessage.put(numBoats); - raceStatusMessage.put(bytesRaceType); - //Encode each BoatStatus. - for (BoatStatus boatStatus : boatStatuses) { + raceStatusMessage.put(versionNum); + raceStatusMessage.put(timeBytes); + raceStatusMessage.put(raceID); + raceStatusMessage.put(raceStatusByte); + raceStatusMessage.put(expectedStart); + raceStatusMessage.put(raceWind); + raceStatusMessage.put(windSpeed); + raceStatusMessage.put(numBoats); + raceStatusMessage.put(bytesRaceType); - BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); + //Encode each BoatStatus. + for (BoatStatus boatStatus : boatStatuses) { - byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); + BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); - raceStatusMessage.put(boatStatusEncoded); - } + byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); + + raceStatusMessage.put(boatStatusEncoded); + } - return raceStatusMessage.array(); + return raceStatusMessage.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode RaceStatus message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index b508030f..f82aff38 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -113,8 +113,10 @@ public class RaceVisionByteEncoder { MessageEncoder encoder = null; try { encoder = EncoderFactory.create(message.getType()); + } catch (InvalidMessageTypeException e) { throw new InvalidMessageException("Could not create encoder for MessageType: " + message.getType(), e); + } byte[] encodedMessage = encoder.encode(message); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java index b01e92de..e5ef7eee 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RequestToJoinEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.RequestToJoin; import network.Utils.ByteConverter; @@ -21,19 +22,25 @@ public class RequestToJoinEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - RequestToJoin requestToJoin = (RequestToJoin) message; + try { + //Downcast. + RequestToJoin requestToJoin = (RequestToJoin) message; - ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4); - requestToJoinBuffer.put(ByteConverter.intToBytes(requestToJoin.getRequestType().getValue(), 4)); + ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4); - byte [] result = requestToJoinBuffer.array(); + requestToJoinBuffer.put(ByteConverter.intToBytes(requestToJoin.getRequestType().getValue(), 4)); - return result; + byte[] result = requestToJoinBuffer.array(); + + return result; + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode RequestToJoin message.", e); + } } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java index 92a75ca7..c0039d65 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/XMLMessageEncoder.java @@ -1,6 +1,7 @@ package network.MessageEncoders; +import network.Exceptions.InvalidMessageException; import network.Messages.AC35Data; import network.Messages.XMLMessage; @@ -24,39 +25,45 @@ public class XMLMessageEncoder implements MessageEncoder { @Override - public byte[] encode(AC35Data message) { + public byte[] encode(AC35Data message) throws InvalidMessageException { - //Downcast. - XMLMessage xmlMessage = (XMLMessage) message; + try { + //Downcast. + XMLMessage xmlMessage = (XMLMessage) message; - byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8); - //Message is 14 + xmlMessage.length bytes. - ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length); + byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8); - //ackNumber converted to bytes - byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2); + //Message is 14 + xmlMessage.length bytes. + ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length); - //Timestamp converted to bytes. - byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6); + //ackNumber converted to bytes + byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2); - //sequenceNumber converted to bytes - byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2); + //Timestamp converted to bytes. + byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6); - //xmlMsgLength converted to bytes - byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2); + //sequenceNumber converted to bytes + byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2); + //xmlMsgLength converted to bytes + byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2); - tempOutputByteBuffer.put(xmlMessage.getVersionNumber()); - tempOutputByteBuffer.put(ackNumberBytes); - tempOutputByteBuffer.put(timestampBytes); - tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue()); - tempOutputByteBuffer.put(sequenceNumberBytes); - tempOutputByteBuffer.put(xmlMsgLengthBytes); - tempOutputByteBuffer.put(messageBytes); - return tempOutputByteBuffer.array(); + tempOutputByteBuffer.put(xmlMessage.getVersionNumber()); + tempOutputByteBuffer.put(ackNumberBytes); + tempOutputByteBuffer.put(timestampBytes); + tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue()); + tempOutputByteBuffer.put(sequenceNumberBytes); + tempOutputByteBuffer.put(xmlMsgLengthBytes); + tempOutputByteBuffer.put(messageBytes); + + return tempOutputByteBuffer.array(); + + } catch (Exception e) { + throw new InvalidMessageException("Could not encode XMLMessage message.", e); + } } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java index 6c49cd69..bb91f2a4 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerClient.java @@ -56,18 +56,21 @@ public class ControllerClient { BoatAction boatAction = new BoatAction(protocolCode); //Encode BoatAction. - byte[] encodedBoatAction = new byte[0]; try { - encodedBoatAction = RaceVisionByteEncoder.encode(boatAction); + byte[] encodedBoatAction = RaceVisionByteEncoder.encode(boatAction); + + BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0, + (short) encodedBoatAction.length, encodedBoatAction); + + System.out.println("Sending out key: " + protocolCode); + outputStream.write(binaryMessage.getFullMessage()); + } catch (InvalidMessageException e) { Logger.getGlobal().log(Level.WARNING, "Could not encode BoatAction: " + boatAction, e); + } - BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0, - (short) encodedBoatAction.length, encodedBoatAction); - System.out.println("Sending out key: " + protocolCode); - outputStream.write(binaryMessage.getFullMessage()); } } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index df54d16d..d4c62d11 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -1,6 +1,7 @@ package visualiser.gameController; import network.BinaryMessageDecoder; +import network.Exceptions.InvalidMessageException; import network.MessageDecoders.BoatActionDecoder; import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; @@ -10,6 +11,8 @@ import visualiser.gameController.Keys.KeyFactory; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Service for dispatching key press data to race from client @@ -46,12 +49,22 @@ public class ControllerServer implements Runnable { byte[] message = new byte[20]; try { if (inputStream.available() > 0) { + inputStream.read(message); + BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message); BoatActionDecoder boatActionDecoder = new BoatActionDecoder(); - boatActionDecoder.decode(encodedMessage.getMessageBody()); - BoatAction boatAction = boatActionDecoder.getMessage(); - System.out.println("Received key: " + boatAction.getBoatAction()); + + try { + boatActionDecoder.decode(encodedMessage.getMessageBody()); + BoatAction boatAction = boatActionDecoder.getMessage(); + System.out.println("Received key: " + boatAction.getBoatAction()); + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not decode BoatAction message.", e); + } + + } } catch (IOException e) { e.printStackTrace(); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java index 088a85cb..45a2e1db 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.BoatStatusEncoder; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.BoatStatus; @@ -55,8 +56,9 @@ public class BoatStatusDecoderTest { * Encodes and decodes a BoatStatus, and returns it. * @param boatStatus The BoatStatus to encode and decode. * @return The decoded BoatStatus. + * @throws InvalidMessageException Thrown if message cannot be encoded or decoded. */ - private static BoatStatus encodeDecodeBoatStatus(BoatStatus boatStatus) { + private static BoatStatus encodeDecodeBoatStatus(BoatStatus boatStatus) throws InvalidMessageException { BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java index 0a8753db..cef452f2 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java @@ -1,5 +1,6 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.CourseWindEncoder; import network.Messages.BoatStatus; import network.Messages.CourseWind; @@ -43,8 +44,9 @@ public class CourseWindDecoderTest { * Encodes and decodes a CourseWind, and returns it. * @param courseWind The CourseWind to encode and decode. * @return The decoded CourseWind. + * @throws InvalidMessageException Thrown if message cannot be encoded or decoded. */ - private static CourseWind encodeDecodeCourseWind(CourseWind courseWind) { + private static CourseWind encodeDecodeCourseWind(CourseWind courseWind) throws InvalidMessageException { CourseWindEncoder courseWindEncoder = new CourseWindEncoder(); byte[] courseWindEncoded = courseWindEncoder.encode(courseWind); From 634d78ab707c9a13b7ce11e34888a55e8dda96d0 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Mon, 7 Aug 2017 13:42:50 +1200 Subject: [PATCH 47/53] Added names to threads created. MockOutput had a nested while loop, but it wasn't needed. Also tidied some of the error handling in MockOutput. #story[1095] --- .../java/mock/app/ConnectionAcceptor.java | 29 +++- .../src/main/java/mock/app/Event.java | 18 +- .../src/main/java/mock/app/MockOutput.java | 162 +++++++++--------- .../RaceVisionByteEncoder.java | 1 + 4 files changed, 111 insertions(+), 99 deletions(-) diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index 97a974a9..85548a45 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -51,10 +51,10 @@ public class ConnectionAcceptor implements Runnable { */ public ConnectionAcceptor(LatestMessages latestMessages) throws IOException { - this.latestMessages =latestMessages; + this.latestMessages = latestMessages; this.serverSocket = new ServerSocket(serverPort); CheckClientConnection checkClientConnection = new CheckClientConnection(mockOutputList); - new Thread(checkClientConnection).start(); + new Thread(checkClientConnection, "ConnectionAcceptor()->CheckClientConnection thread").start(); } public String getAddress() throws UnknownHostException { @@ -70,19 +70,30 @@ public class ConnectionAcceptor implements Runnable { */ @Override public void run() { - while(true){//should be connections not filled up + + while(mockOutputList.remainingCapacity() > 0) { + try { + System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE + Socket mockSocket = serverSocket.accept(); + //TODO at this point we need to assign the connection a boat source ID, if they requested to participate. + DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser); - ControllerServer controllerServer = new ControllerServer(mockSocket); - new Thread(mockOutput).start(); - new Thread(controllerServer).start(); + ControllerServer controllerServer = new ControllerServer(mockSocket); //TODO probably pass assigned boat source ID into ControllerServer. + + new Thread(mockOutput, "ConnectionAcceptor.run()->MockOutput thread" + mockOutput).start(); + new Thread(controllerServer, "ConnectionAcceptor.run()->ControllerServer thread" + controllerServer).start(); + mockOutputList.add(mockOutput); - System.out.println(String.format("%d number of Visualisers Connected.", mockOutputList.size())); + + System.out.println(String.format("%d number of Visualisers Connected.", mockOutputList.size()));//TEMP + } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace();//TODO handle this properly + } } @@ -111,7 +122,7 @@ public class ConnectionAcceptor implements Runnable { double timeSinceLastHeartBeat = System.currentTimeMillis(); while(true) { //System.out.println(mocks.size());//used to see current amount of visualisers connected. - ArrayBlockingQueue m = new ArrayBlockingQueue(16, true, mocks); + ArrayBlockingQueue m = new ArrayBlockingQueue<>(16, true, mocks); for (MockOutput mo : m) { try { mo.sendHeartBeat(); diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index b4b0586c..b98de4dc 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -35,7 +35,7 @@ public class Event { private Polars boatPolars; - private ConnectionAcceptor mockOutput; + private ConnectionAcceptor connectionAcceptor; private LatestMessages latestMessages; /** @@ -51,7 +51,7 @@ public class Event { this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv"); this.latestMessages = new LatestMessages(); - this.mockOutput = new ConnectionAcceptor(latestMessages); + this.connectionAcceptor = new ConnectionAcceptor(latestMessages); } catch (IOException e) { e.printStackTrace(); @@ -67,11 +67,11 @@ public class Event { } public String getAddress() throws UnknownHostException { - return mockOutput.getAddress(); + return connectionAcceptor.getAddress(); } public int getPort() { - return mockOutput.getServerPort(); + return connectionAcceptor.getServerPort(); } /** @@ -82,7 +82,7 @@ public class Event { * @throws InvalidRegattaDataException Thrown if the regatta xml file cannot be parsed. */ public void start() throws InvalidRaceDataException, XMLReaderException, InvalidBoatDataException, InvalidRegattaDataException { - new Thread(mockOutput).start(); + new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread").start(); sendXMLs(); @@ -94,7 +94,7 @@ public class Event { //Create and start race. RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); - new Thread(newRace).start(); + new Thread(newRace, "Event.Start()->RaceLogic thread").start(); } /** @@ -102,11 +102,11 @@ public class Event { */ private void sendXMLs() { - mockOutput.setRegattaXml(regattaXML); + connectionAcceptor.setRegattaXml(regattaXML); - mockOutput.setRaceXml(raceXML); + connectionAcceptor.setRaceXml(raceXML); - mockOutput.setBoatsXml(boatXML); + connectionAcceptor.setBoatsXml(boatXML); } /** diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index 328c5e79..87bc9f95 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -54,8 +54,6 @@ public class MockOutput implements Runnable private int heartbeatSequenceNum = 1; - private boolean stop = false; //whether or not hte thread keeps running - /** * Ctor. * @param latestMessages Latests Messages that the Mock is to send out @@ -231,119 +229,124 @@ public class MockOutput implements Runnable */ public void run() { - try { - while (!stop){ - - //Wait until all of the xml files have been set. - if (!this.latestMessages.hasAllXMLMessages()) { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - continue; - } + //Wait until all of the xml files have been set. + while (!this.latestMessages.hasAllXMLMessages()) { - long previousFrameTime = System.currentTimeMillis(); - boolean sentXMLs = false; + try { + Thread.sleep(500); + + } catch (InterruptedException e) { + //If we get interrupted, exit the function. + Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(waitForXMLs) was interrupted on thread: " + Thread.currentThread(), e); + //Re-set the interrupt flag. + Thread.currentThread().interrupt(); + return; + + } + } - while(true) { - try { - long currentFrameTime = System.currentTimeMillis(); + long previousFrameTime = System.currentTimeMillis(); + boolean sentXMLs = false; - //This is the time elapsed, in milliseconds, since the last server "frame". - long framePeriod = currentFrameTime - previousFrameTime; + try { + while (!Thread.interrupted()) { - //We only attempt to send packets every X milliseconds. - long minimumFramePeriod = 16; - if (framePeriod >= minimumFramePeriod) { + try { - //Send XML messages. - if (!sentXMLs) { - //Serialise them. + long currentFrameTime = System.currentTimeMillis(); - try { - byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage()); - byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage()); - byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage()); + //This is the time elapsed, in milliseconds, since the last server "frame". + long framePeriod = currentFrameTime - previousFrameTime; - //Send them. - outToVisualiser.write(raceXMLBlob); - outToVisualiser.write(regattaXMLBlob); - outToVisualiser.write(boatsXMLBlob); - sentXMLs = true; + //We only attempt to send packets every X milliseconds. + long minimumFramePeriod = 16; + if (framePeriod >= minimumFramePeriod) { - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode XMLMessage: " + latestMessages.getRaceXMLMessage(), e); - continue; //Go to next iteration. - } + //Send XML messages. + if (!sentXMLs) { + //Serialise them. + try { + byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage()); + byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage()); + byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage()); + + //Send them. + outToVisualiser.write(raceXMLBlob); + outToVisualiser.write(regattaXMLBlob); + outToVisualiser.write(boatsXMLBlob); + sentXMLs = true; + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode XMLMessage: " + latestMessages.getRaceXMLMessage(), e); + continue; //Go to next iteration. } - //Sends the RaceStatus message. - if (this.latestMessages.getRaceStatus() != null) { + } - try { - byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); + //Sends the RaceStatus message. + if (this.latestMessages.getRaceStatus() != null) { - this.outToVisualiser.write(raceStatusBlob); + try { + byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode RaceStatus: " + latestMessages.getRaceStatus(), e); - } + this.outToVisualiser.write(raceStatusBlob); + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode RaceStatus: " + latestMessages.getRaceStatus(), e); } + } - //Send all of the BoatLocation messages. - for (int sourceID : this.latestMessages.getBoatLocationMap().keySet()) { + //Send all of the BoatLocation messages. + for (int sourceID : this.latestMessages.getBoatLocationMap().keySet()) { - //Get the message. - BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID); - if (boatLocation != null) { + //Get the message. + BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID); + if (boatLocation != null) { - try { - //Encode. - byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); + try { + //Encode. + byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); - //Write it. - this.outToVisualiser.write(boatLocationBlob); + //Write it. + this.outToVisualiser.write(boatLocationBlob); - } catch (InvalidMessageException e) { - Logger.getGlobal().log(Level.WARNING, "Could not encode BoatLocation: " + boatLocation, e); - } + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode BoatLocation: " + boatLocation, e); + } - } } + } - previousFrameTime = currentFrameTime; + previousFrameTime = currentFrameTime; - } else { - //Wait until the frame period will be large enough. - long timeToWait = minimumFramePeriod - framePeriod; - - try { - Thread.sleep(timeToWait); - } catch (InterruptedException e) { - //If we get interrupted, exit the function. - e.printStackTrace(); - //Re-set the interrupt flag. - Thread.currentThread().interrupt(); - return; - } + } else { + //Wait until the frame period will be large enough. + long timeToWait = minimumFramePeriod - framePeriod; + try { + Thread.sleep(timeToWait); + } catch (InterruptedException e) { + //If we get interrupted, exit the function. + Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(framePeriod) was interrupted on thread: " + Thread.currentThread(), e); + //Re-set the interrupt flag. + Thread.currentThread().interrupt(); + return; } - } catch (SocketException e) { - break; } + } catch (SocketException e) { + break; } + } } catch (IOException e) { @@ -351,8 +354,5 @@ public class MockOutput implements Runnable } } - public void stop(){ - stop = true; - } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index f82aff38..54c10272 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -4,6 +4,7 @@ package network.MessageEncoders; import network.Exceptions.InvalidMessageException; import network.Exceptions.InvalidMessageTypeException; import network.Messages.*; +import network.Messages.Enums.MessageType; import static network.Utils.ByteConverter.*; From e76de1cbf994dd18241864de90c76f5b52e73145 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Mon, 7 Aug 2017 14:13:14 +1200 Subject: [PATCH 48/53] 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 49/53] 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 50/53] 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 From e021dd328dcaf940393092398230f51a96fcd231 Mon Sep 17 00:00:00 2001 From: Connor Taylor-Brown Date: Tue, 8 Aug 2017 23:56:59 +1200 Subject: [PATCH 51/53] Changed CompositeCommand stack to queue #story[1096] --- .../mock/model/commandFactory/CompositeCommand.java | 11 ++++++----- .../visualiser/gameController/ControllerServer.java | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java index 12690d29..74c5e95b 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java @@ -1,23 +1,24 @@ package mock.model.commandFactory; -import java.util.Stack; +import java.util.ArrayDeque; +import java.util.Queue; /** * Wraps multiple commands into a composite to execute queued commands during a frame. */ public class CompositeCommand implements Command { - private Stack commands; + private Queue commands; public CompositeCommand() { - this.commands = new Stack<>(); + this.commands = new ArrayDeque<>(); } public void addCommand(Command command) { - commands.push(command); + commands.add(command); } @Override public void execute() { - while(!commands.isEmpty()) commands.pop().execute(); + while(!commands.isEmpty()) commands.remove().execute(); } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index c95a0789..66e98d89 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -64,6 +64,7 @@ public class ControllerServer extends Observable implements Runnable { BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody()); action = boatActionDecoder.getBoatAction(); + // Notify observers of most recent action this.notifyObservers(); this.setChanged(); } From 39b5cc2edcd323af967b7b5781e037096143adf9 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 10 Aug 2017 12:44:42 +1200 Subject: [PATCH 52/53] MockBoat has autoVMG disabled by default. Wind/Tack commands disable autoVMG. VMG command enables it. Upwind command was turning until boat was at 0 degrees, instead of being aligned with wind direction, but that's fixed. #story[1096] --- racevisionGame/src/main/java/mock/model/MockBoat.java | 2 +- .../java/mock/model/commandFactory/TackGybeCommand.java | 3 +++ .../src/main/java/mock/model/commandFactory/VMGCommand.java | 1 + .../main/java/mock/model/commandFactory/WindCommand.java | 6 +++++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 104fa264..a2f74dd7 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -25,7 +25,7 @@ public class MockBoat extends Boat { /** * Stores whether the boat is on autoVMG or not */ - private boolean autoVMG = true; + private boolean autoVMG = false; diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index 150a1da8..8dcf1c48 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -18,6 +18,9 @@ public class TackGybeCommand implements Command { //The refactoring of MockRace will require changes to be made @Override public void execute() { + + boat.setAutoVMG(false); + /*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 64cc6a9f..1a1eeda5 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -18,6 +18,7 @@ public class VMGCommand implements Command { //The refactoring of MockRace will require changes to be made @Override public void execute() { + boat.setAutoVMG(true); /*VMG newVMG = boat.getPolars().calculateVMG( race.getWindDirection(), race.getWindSpeed(), diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java index 5ba5d5bf..530bf5bc 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java @@ -20,13 +20,17 @@ public class WindCommand implements Command { @Override public void execute() { + + boat.setAutoVMG(false); + double wind = race.getWindDirection().degrees(); double heading = boat.getBearing().degrees(); double offset = 3.0; offset *= direction; - if(wind - heading < 0) offset *= -1; + double headWindDelta = wind - heading; + if ((headWindDelta < 0) || (headWindDelta > 180)) offset *= -1; boat.setBearing(Bearing.fromDegrees(heading + offset)); } From 130ffcbf077d64a68135d14105d19fadc30ac8a9 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 10 Aug 2017 13:16:53 +1200 Subject: [PATCH 53/53] Fixed some merge issues. --- racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java | 2 +- .../main/java/visualiser/gameController/ControllerServer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index 5fb01ee9..75b0e721 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -88,7 +88,7 @@ public class ConnectionAcceptor implements Runnable { DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser); - ControllerServer controllerServer = new ControllerServer(mockSocket); //TODO probably pass assigned boat source ID into ControllerServer. + ControllerServer controllerServer = new ControllerServer(mockSocket, this.rl); //TODO probably pass assigned boat source ID into ControllerServer. new Thread(mockOutput, "ConnectionAcceptor.run()->MockOutput thread" + mockOutput).start(); new Thread(controllerServer, "ConnectionAcceptor.run()->ControllerServer thread" + controllerServer).start(); diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index 7469fc26..63c5b263 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -72,7 +72,7 @@ public class ControllerServer extends Observable implements Runnable { try { boatActionDecoder.decode(encodedMessage.getMessageBody()); BoatAction boatAction = boatActionDecoder.getMessage(); - action = boatActionDecoder.getBoatAction(); + action = boatAction.getBoatAction(); // Notify observers of most recent action this.notifyObservers();