diff --git a/mock/src/main/java/seng302/Model/Polars.java b/mock/src/main/java/seng302/Model/Polars.java index 1e3251f3..cf7d448d 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/mock/src/main/java/seng302/Model/Polars.java @@ -118,10 +118,7 @@ public class Polars { //If the lower bound is greater than the upper bound, we have a "flipped" interval. That is for, e.g., [70, 55] the lower bound is greater than the upper bound, and so it checks that (VMGAngle >= 70 OR VMGAngle =< 55), instead of (VMGAngle >= 70 AND VMGAngle =< 55). - boolean flippedInterval = false; - if (bearingLowerBound.degrees() > bearingUpperBound.degrees()) { - flippedInterval = true; - } + boolean flippedInterval = Polars.isFlippedInterval(bearingLowerBound, bearingUpperBound); @@ -179,7 +176,7 @@ public class Polars { Bearing bestVMGAngle = Bearing.fromDegrees(0d); //Calculate the VMG for all possible angles at this wind speed. - for (double angleDegree = 0; angleDegree < 360; angleDegree += 0.1) { + for (double angleDegree = 0; angleDegree < 360; angleDegree += 1) { Bearing angle = Bearing.fromDegrees(angleDegree); //This is the true bearing of the boat, if it went at the angle against the wind. @@ -191,22 +188,11 @@ public class Polars { //Check that the boat's bearing would actually be acceptable. //We continue (skip to next iteration) if it is outside of the interval. - if (flippedInterval) { - //Bearing must be inside [lower, upper], where lower > upper. So, bearing must be >= lower, or bearing < upper. We use inverted logic since we are skipping if it is true. - if ((trueBoatBearing.degrees() < bearingLowerBound.degrees()) & (trueBoatBearing.degrees() > bearingUpperBound.degrees())) { - continue; - } - - } else { - //Bearing must be inside [lower, upper]. - if ((trueBoatBearing.degrees() < bearingLowerBound.degrees()) || (trueBoatBearing.degrees() > bearingUpperBound.degrees())) { - continue; - } - + if (!Polars.isBearingInsideInterval(trueBoatBearing, bearingLowerBound, bearingUpperBound)) { + continue; } - //Basic linear interpolation. Find the nearest two angles from the table, and interpolate between them. //Check which pair of adjacent angles the angle is between. @@ -313,6 +299,58 @@ public class Polars { } + /** + * Determines whether an interval is "flipped". This means that the lower bound is greater than the upper bound (e.g., [290, 43] degrees). + * @param lowerBound The lower bound. + * @param upperBound The upper bound. + * @return True if the interval is flipped, false otherwise. + */ + public static boolean isFlippedInterval(Bearing lowerBound, Bearing upperBound) { + + //If the lower bound is greater than the upper bound, we have a "flipped" interval. + boolean flippedInterval = false; + if (lowerBound.degrees() > upperBound.degrees()) { + flippedInterval = true; + } + + return flippedInterval; + } + + + /** + * Determines if a bearing is inside an interval. + * @param bearing The bearing to check. + * @param lowerBound The lower bound of the interval. + * @param upperBound The upper bound of the interval. + * @return True if the bearing is inside the interval, false otherwise. + */ + public static boolean isBearingInsideInterval(Bearing bearing, Bearing lowerBound, Bearing upperBound) { + + //Check if it's a flipped interval. + boolean flippedInterval = Polars.isFlippedInterval(lowerBound, upperBound); + + if (flippedInterval) { + //Bearing must be inside [lower, upper], where lower > upper. So, bearing must be >= lower, or bearing < upper. We use inverted logic since we are skipping if it is true. + if ((bearing.degrees() >= lowerBound.degrees()) || (bearing.degrees() <= upperBound.degrees())) { + return true; + } else { + return false; + } + + } else { + //Bearing must be inside [lower, upper]. + if ((bearing.degrees() >= lowerBound.degrees()) && (bearing.degrees() <= upperBound.degrees())) { + return true; + } else { + return false; + } + + } + + + } + + /** * Calculate the linear interpolation scalar for a value between two bounds. E.g., lower = 7, upper = 10, value = 8, therefore the scalar (or the proportion between the bounds) is 0.333. * Also assumes that the bounds are periodic - e.g., for angles a lower bound of 350deg and upper bound of 5deg is in interval of 15 degrees. diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 1a9b447d..d46840f5 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -502,67 +502,64 @@ public class Race implements Runnable { */ private VMG calculateVMG(Boat boat, Bearing[] bearingBounds) { - //How fast a boat can turn, in degrees per millisecond. - double turnRate = 0.03; - - //How much the boat is allowed to turn, considering how long since it last turned. - double turnAngle = turnRate * boat.getTimeSinceTackChange(); - //This ensures that the boat bounds don't flip around. - turnAngle = Math.min(turnAngle, 179.999); - - //Find the bounds on what angle the boat is allowed to travel at. The bounds cap out at [0, 360). - double bound1Degrees = boat.getBearing().degrees() - turnAngle; - double bound2Degrees = boat.getBearing().degrees() + turnAngle; - - Bearing bound1 = Bearing.fromDegrees(bound1Degrees); - Bearing bound2 = Bearing.fromDegrees(bound2Degrees); - - //Get the lower and upper acceptable bounds. Bearing lowerAcceptableBound = bearingBounds[0]; Bearing upperAcceptableBound = bearingBounds[1]; - //Find the bounds on what angle the boat can travel on, taking into account both turning rate and the acceptable bounds. - bound1Degrees = Math.max(bound1.degrees(), lowerAcceptableBound.degrees()); - bound2Degrees = Math.min(bound2.degrees(), upperAcceptableBound.degrees()); + //Find the VMG inside these bounds. + VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound); - bound1 = Bearing.fromDegrees(bound1Degrees); - bound2 = Bearing.fromDegrees(bound2Degrees); - //Calculate VMG using these bounds. - return boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), bound1, bound2); + return bestVMG; } /** - * Determines whether or not a given VMG improves the velocity of a boat. - * @param boat The boat to test. - * @param vmg The new VMG to test. + * 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. + * @param potentialVMG The new VMG to test. + * @param bearingToDestination The bearing between the boat and its destination. * @return True if the new VMG is improves velocity, false otherwise. */ - private boolean improvesVelocity(Boat boat, VMG vmg) { + private boolean improvesVelocity(VMG currentVMG, VMG potentialVMG, Bearing bearingToDestination) { //Calculates the angle between the boat and its destination. - Angle angleBetweenDestAndHeading = Angle.fromDegrees(boat.getBearing().degrees() - boat.calculateBearingToNextMarker().degrees()); + 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(vmg.getBearing().degrees() - boat.calculateBearingToNextMarker().degrees()); + Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(potentialVMG.getBearing().degrees() - bearingToDestination.degrees()); //Calculate the boat's current velocity. - double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * boat.getCurrentSpeed(); + double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * currentVMG.getSpeed(); //Calculate the potential velocity with the new VMG. - double vmgVelocity = Math.cos(angleBetweenDestAndNewVMG.radians()) * vmg.getSpeed(); + double vmgVelocity = Math.cos(angleBetweenDestAndNewVMG.radians()) * potentialVMG.getSpeed(); //Return whether or not the new VMG gives better velocity. return vmgVelocity > currentVelocity; } + /** + * Determines whether or not a given VMG improves the velocity of a boat. + * @param boat The boat to test. + * @param vmg The new VMG to test. + * @return True if the new VMG is improves velocity, false otherwise. + */ + private boolean improvesVelocity(Boat boat, VMG vmg) { + + //Get the boats "current" VMG. + VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing()); + + //Check if the new VMG is better than the boat's current VMG. + return this.improvesVelocity(boatVMG, vmg, boat.calculateBearingToNextMarker()); + + } + /** * Calculates the distance a boat has travelled and updates its current position according to this value. @@ -593,6 +590,7 @@ public class Race implements Runnable { //Calculate the boat's bearing bounds, to ensure that it doesn't go out of the course. Bearing[] bearingBounds = this.calculateBearingBounds(boat); + //Calculate the new VMG. VMG newVMG = this.calculateVMG(boat, bearingBounds); @@ -626,14 +624,13 @@ public class Race implements Runnable { Bearing[] bearings = new Bearing[2]; - Bearing lowerBearing = Bearing.fromDegrees(0); + Bearing lowerBearing = Bearing.fromDegrees(0.001); Bearing upperBearing = Bearing.fromDegrees(359.999); - boolean foundAnyBadBearing = false; - boolean foundLowerBearing = false; - boolean foundAnyGoodBearing = false; - boolean stopFindingUpperBearing = false; + + double lastAngle = -1; + boolean lastAngleWasGood = false; //Check all bearings between [0, 360). for (double angle = 0; angle < 360; angle += 1) { @@ -644,36 +641,28 @@ public class Race implements Runnable { //Check that if it is acceptable. boolean bearingIsGood = this.checkBearingInsideCourse(bearing, boat.getCurrentPosition()); - if (bearingIsGood) { - //The lower bearing will be the first acceptable bearing after finding any unacceptable bearing. - if (foundAnyBadBearing && !foundLowerBearing) { - lowerBearing = bearing; - foundLowerBearing = true; - foundAnyGoodBearing = true; - } + if (lastAngle != -1) { - //The upper bearing will be the last acceptable bearing before finding any unacceptable bearing. - if (!foundAnyBadBearing) { - upperBearing = bearing; - } else if (foundAnyBadBearing && foundAnyGoodBearing && !stopFindingUpperBearing) { - upperBearing = bearing; + 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); } - - } else { - - foundAnyBadBearing = true; - - if (foundAnyBadBearing && foundAnyGoodBearing) { - stopFindingUpperBearing = true; + 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; @@ -687,6 +676,7 @@ public class Race implements Runnable { * 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) {