diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index b611b49b..845b00a6 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -69,7 +69,18 @@ public class MockRace extends Race { this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary); - this.wind = new Wind(); + + //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(windGenerator.generateBaselineWind()); } /** @@ -106,6 +117,7 @@ public class MockRace extends Race { */ public void run() { initialiseBoats(); + initialiseWindDirection(); this.countdownTimer.start(); } @@ -253,8 +265,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(wind.getWindDirection().degrees()); - int windSpeedInt = (int) (wind.getWindSpeed() * 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( @@ -541,7 +553,12 @@ public class MockRace extends Race { //Find the VMG inside these bounds. - VMG bestVMG = boat.getPolars().calculateVMG(wind.getWindDirection(), wind.getWindSpeed(), boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound); + VMG bestVMG = boat.getPolars().calculateVMG( + this.getWindDirection(), + this.getWindSpeed(), + boat.calculateBearingToNextMarker(), + lowerAcceptableBound, + upperAcceptableBound); return bestVMG; @@ -811,11 +828,24 @@ 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() { - this.wind.changeWindDirection(); + + Wind nextWind = windGenerator.generateNextWind(raceWind.getValue()); + + 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 96bdf82a..aec57882 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -1,8 +1,9 @@ package shared.model; import javafx.beans.property.IntegerProperty; +import javafx.beans.property.Property; import javafx.beans.property.SimpleIntegerProperty; -import mock.model.Wind; +import javafx.beans.property.SimpleObjectProperty; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import network.Messages.LatestMessages; @@ -99,9 +100,9 @@ public abstract class Race implements Runnable { /** - * The current wind direction bearing. + * The race's wind. */ - protected Wind wind; + protected Property raceWind = new SimpleObjectProperty<>(); /** @@ -164,7 +165,9 @@ public abstract class Race implements Runnable { //Race type. this.raceType = raceDataSource.getRaceType(); - this.wind = new Wind(); + //Wind. + this.setWind(Bearing.fromDegrees(0), 0); + } @@ -244,12 +247,32 @@ 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); + 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); + } + + /** * Returns the wind bearing. * @return The wind bearing. */ public Bearing getWindDirection() { - return wind.getWindDirection(); + return raceWind.getValue().getWindDirection(); } /** @@ -258,7 +281,15 @@ public abstract class Race implements Runnable { * @return The wind speed. */ public double getWindSpeed() { - return wind.getWindSpeed(); + 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; + } + +} diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index a98a8345..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 direction - wind.setDegrees(raceStatus.getScaledWindDirection()); - - // Wind speed - wind.setWindSpeed(raceStatus.getWindSpeedKnots()); + //Wind. + this.setWind( + Bearing.fromDegrees(raceStatus.getScaledWindDirection()), + raceStatus.getWindSpeedKnots() ); //Current race time. this.raceClock.setUTCTime(raceStatus.getCurrentTime());