From 5af3053537662af87e9201d2670129f5560fc646 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Tue, 1 Aug 2017 18:15:35 +1200 Subject: [PATCH 1/3] 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 2/3] 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 ed12ff659d336e03eba8abc6116e4cbf217f6dee Mon Sep 17 00:00:00 2001 From: fjc40 Date: Wed, 2 Aug 2017 13:59:19 +1200 Subject: [PATCH 3/3] 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); }