diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 00000000..5352bdf8 --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index c8c6825b..214d9c10 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -22,6 +22,14 @@ public class MockBoat extends Boat { */ private long timeSinceTackChange = 0; + /** + * This stores the boats current status of rounding a mark + * 0: not started rounding + * 1: passed only first check + * 2: passed first and second check + */ + private Integer roundingStatus = 0; + /** @@ -63,6 +71,8 @@ public class MockBoat extends Boat { //Get the start and end points. GPSCoordinate currentPosition = this.getCurrentPosition(); GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate(); +// GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getMark1Position(); +// todo:may need to change this so that boats are send to corners of the gates rather than the middle //Calculate bearing. Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition); @@ -203,4 +213,57 @@ public class MockBoat extends Boat { return distanceTravelledMeters; } + /** + * Check if a mark is on the port side of the boat + * @param mark mark to be passed + * @return true if mark is on port side + */ + public boolean isPortSide(Mark mark){ + //if this boat is lower than the mark check which way it is facing + if(this.getCurrentPosition().getLongitude() < mark.getPosition().getLongitude()){ + return this.getBearing().degrees() <= 180; + }else{ + return this.getBearing().degrees() > 180; + } + } + + /** + * Check if a mark is on the starboard side of the boat + * @param mark mark to be passed + * @return true if mark is on starboard side + */ + public boolean isStarboardSide(Mark mark){ + //if this boat is lower than the mark check which way it is facing + if(this.getCurrentPosition().getLongitude() < mark.getPosition().getLongitude()){ + return this.getBearing().degrees() >= 180; + }else{ + return this.getBearing().degrees() < 180; + } + } + + /** + * Used to check if this boat is between a gate + * @param gate the gate to be checked + * @return true if the boat is between two marks that make up a gate + */ + public boolean isBetweenGate(CompoundMark gate){ + if ((this.isPortSide(gate.getMark1()) && this.isStarboardSide(gate.getMark2())) || + (this.isStarboardSide(gate.getMark2()) && this.isPortSide(gate.getMark1()))){ + return true; + }else{ + return false; + } + } + + public Integer getRoundingStatus() { + return Integer.valueOf(roundingStatus); + } + + public void increaseRoundingStatus() { + this.roundingStatus++; + } + + public void resetRoundingStatus() { + this.roundingStatus = 0; + } } diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 33884e3d..d4fc6f69 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -4,18 +4,17 @@ import javafx.animation.AnimationTimer; import network.Messages.BoatLocation; import network.Messages.BoatStatus; import network.Messages.Enums.BoatStatusEnum; +import network.Messages.Enums.RaceStatusEnum; 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; import shared.dataInput.RegattaDataSource; 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; @@ -767,6 +766,70 @@ public class MockRace extends Race { } + /** + * Checks to be run on boats rounding marks on the port side + * @param boat the boat that is rounding a mark + * @param roundingChecks the checks to run + */ + private void boatRoundingCheckPort(MockBoat boat, List roundingChecks){ + //boats must pass all checks in order to round a mark + switch (boat.getRoundingStatus()) { + case 0://hasn't started rounding +// System.out.println("round 0"); + if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && + GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), + roundingChecks.get(0), boat.getCurrentPosition())) { + boat.increaseRoundingStatus(); + } + break; + case 1://has been parallel to the mark +// System.out.println("round 1"); + if (boat.isPortSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && + GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), + roundingChecks.get(1), boat.getCurrentPosition())) { + boat.increaseRoundingStatus(); + } + break; + case 2://has traveled 180 degrees around the mark +// System.out.println("round 2"); + //Move boat on to next leg. + boat.resetRoundingStatus(); + Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + boat.setCurrentLeg(nextLeg); + break; + } + } + + /** + * Checks to be run on boats rounding marks on the starboard side + * @param boat the boat that is rounding a mark + * @param roundingChecks the checks to run + */ + private void boatRoundingCheckStarboard(MockBoat boat, List roundingChecks){ + //boats must pass all checks in order to round a mark + switch (boat.getRoundingStatus()) { + case 0://hasn't started rounding + if (boat.isStarboardSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && + GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), + roundingChecks.get(0), boat.getCurrentPosition())) { + boat.increaseRoundingStatus(); + } + break; + case 1://has been parallel to the mark + if (boat.isStarboardSide(boat.getCurrentLeg().getEndCompoundMark().getMark1()) && + GPSCoordinate.intersects(boat.getCurrentLeg().getEndCompoundMark().getMark1().getPosition(), + roundingChecks.get(1), boat.getCurrentPosition())) { + boat.increaseRoundingStatus(); + } + break; + case 2://has traveled 180 degrees around the mark + //Move boat on to next leg. + boat.resetRoundingStatus(); + Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + boat.setCurrentLeg(nextLeg); + break; + } + } /** * Checks if a boat has finished any legs, or has pulled out of race (DNF). @@ -776,30 +839,40 @@ public class MockRace extends Race { 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. + double epsilonNauticalMiles = 250.0 / Constants.NMToMetersConversion; //250 meters. if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { - //Boat has reached its target marker, and has moved on to a new leg. - + //Boat is within an acceptable distance from the mark. + GPSCoordinate startDirectionLinePoint = boat.getCurrentLeg().getStartCompoundMark().getMark1Position(); + //todo will need to change this for gates, so that the end point is the side of the gate needed to be rounded + GPSCoordinate endDirectionLinePoint = boat.getCurrentLeg().getEndCompoundMark().getMark1Position(); - //Calculate how much the boat overshot the marker by. - double overshootMeters = boat.calculateDistanceToNextMarker(); + Bearing bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, endDirectionLinePoint); + //use the direction line to create three invisible points that act as crossover lines a boat must cross + //to round a mark + GPSCoordinate roundCheck1 = GPSCoordinate.calculateNewPosition(startDirectionLinePoint, + epsilonNauticalMiles, Azimuth.fromDegrees(bearingOfDirectionLine.degrees() + 90));//adding 90 so the check line is parallel - //Move boat on to next leg. - Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); - boat.setCurrentLeg(nextLeg); + GPSCoordinate roundCheck2 = GPSCoordinate.calculateNewPosition(startDirectionLinePoint, + epsilonNauticalMiles, Azimuth.fromDegrees(bearingOfDirectionLine.degrees())); - //Add overshoot distance into the distance travelled for the next leg. - boat.setDistanceTravelledInLeg(overshootMeters); + List roundingChecks = new ArrayList(Arrays.asList(roundCheck1, roundCheck2)); - //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); + switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) { + case SP://Not yet implemented so these gates will be rounded port side + case Port: + boatRoundingCheckPort(boat, roundingChecks); + break; + case PS://not yet implemented so these gates will be rounded starboard side + case Starboard: + boatRoundingCheckStarboard(boat, roundingChecks); + break; + } //Check if the boat has finished or stopped racing. - if (this.isLastLeg(boat.getCurrentLeg())) { //Boat has finished. boat.setTimeFinished(timeElapsed); @@ -820,6 +893,60 @@ public class MockRace extends Race { } +//old method fo checking if boats passed a mark +// +// /** +// * 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); +// +// } 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); +// +// } +// +// } +// +// } + + /** diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java index 7e61b3de..135cd988 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java @@ -5,6 +5,7 @@ import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import shared.enums.RoundingType; import shared.enums.XMLFileType; import shared.exceptions.InvalidRaceDataException; import shared.exceptions.XMLReaderException; @@ -313,6 +314,8 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { return element.getAttribute("Name"); } + private String getCompoundMarkRounding(Element element){return element.getAttribute("Rounding");} + /** * Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements. @@ -331,12 +334,19 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { //Gets the ID number of this corner element. int cornerID = getCompoundMarkID(cornerElement); + //gets the Rounding of this corner element + String cornerRounding = getCompoundMarkRounding(cornerElement); + //Gets the CompoundMark associated with this corner. CompoundMark lastCompoundMark = this.compoundMarkMap.get(cornerID); //The name of the leg is the name of the first compoundMark in the leg. String legName = lastCompoundMark.getName(); + //Sets the rounding type of this compound mark + + lastCompoundMark.setRoundingType(RoundingType.getValueOf(cornerRounding)); + //For each following corner, create a leg between cornerN and cornerN+1. for(int i = 1; i < corners.getLength(); i++) { @@ -346,9 +356,15 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { //Gets the ID number of this corner element. cornerID = getCompoundMarkID(cornerElement); + //gets the Rounding of this corner element + cornerRounding = getCompoundMarkRounding(cornerElement); + //Gets the CompoundMark associated with this corner. CompoundMark currentCompoundMark = this.compoundMarkMap.get(cornerID); + //Sets the rounding type of this compound mark + currentCompoundMark.setRoundingType(RoundingType.valueOf(cornerRounding)); + //Create a leg from these two adjacent compound marks. Leg leg = new Leg(legName, lastCompoundMark, currentCompoundMark, i - 1); legs.add(leg); diff --git a/racevisionGame/src/main/java/shared/enums/RoundingType.java b/racevisionGame/src/main/java/shared/enums/RoundingType.java new file mode 100644 index 00000000..8f8e719a --- /dev/null +++ b/racevisionGame/src/main/java/shared/enums/RoundingType.java @@ -0,0 +1,49 @@ +package shared.enums; + +/** + * Enum for the types of rounding that can be done + */ +public enum RoundingType { + /** + * This is means it must be rounded port side + */ + Port, + + /** + * This is means it must be rounded starboard side + */ + Starboard, + + /** + * The boat within the compound mark with the SeqID + * of 1 should be rounded to starboard and the boat + * within the compound mark with the SeqID of 2 should + * be rounded to port. + */ + SP, + + /** + * The boat within the compound mark with the SeqID + * of 1 should be rounded to port and the boat + * within the compound mark with the SeqID of 2 should + * be rounded to starboard. + * + * opposite of SP + */ + PS; + + public static RoundingType getValueOf(String value) { + switch (value) { + case "Port": + return RoundingType.Port; + case "Starboard": + return RoundingType.Starboard; + case "SP": + return RoundingType.Port; + case "PS": + return RoundingType.Starboard; + default: + return null; + } + } +} diff --git a/racevisionGame/src/main/java/shared/model/CompoundMark.java b/racevisionGame/src/main/java/shared/model/CompoundMark.java index b9f45753..38e972dc 100644 --- a/racevisionGame/src/main/java/shared/model/CompoundMark.java +++ b/racevisionGame/src/main/java/shared/model/CompoundMark.java @@ -1,6 +1,8 @@ package shared.model; +import shared.enums.RoundingType; + /** * Represents a compound mark - that is, either one or two individual marks which form a single compound mark. */ @@ -31,6 +33,11 @@ public class CompoundMark { */ private GPSCoordinate averageGPSCoordinate; + /** + * The side that the mark must be rounded on + */ + private RoundingType roundingType; + /** * Constructs a compound mark from a single mark. @@ -141,4 +148,20 @@ public class CompoundMark { return averageCoordinate; } + + /** + * Used to get how this mark should be rounded + * @return rounding type for mark + */ + public RoundingType getRoundingType() { + return roundingType; + } + + /** + * Used to set the type of rounding for this mark + * @param roundingType rounding type to set + */ + public void setRoundingType(RoundingType roundingType) { + this.roundingType = roundingType; + } } diff --git a/racevisionGame/src/main/java/shared/model/GPSCoordinate.java b/racevisionGame/src/main/java/shared/model/GPSCoordinate.java index ee5eaaff..566413c6 100644 --- a/racevisionGame/src/main/java/shared/model/GPSCoordinate.java +++ b/racevisionGame/src/main/java/shared/model/GPSCoordinate.java @@ -142,7 +142,7 @@ public class GPSCoordinate { * @param coordinate The coordinate to test. * @return true if a line from the point intersects the two boundary points */ - private static boolean intersects(GPSCoordinate boundaryA, GPSCoordinate boundaryB, GPSCoordinate coordinate) { + public static boolean intersects(GPSCoordinate boundaryA, GPSCoordinate boundaryB, GPSCoordinate coordinate) { double boundaryALat = boundaryA.getLatitude(); double boundaryALon = boundaryA.getLongitude(); double boundaryBLat = boundaryB.getLatitude(); diff --git a/racevisionGame/src/main/java/shared/model/Mark.java b/racevisionGame/src/main/java/shared/model/Mark.java index 5781861a..19dc8f26 100644 --- a/racevisionGame/src/main/java/shared/model/Mark.java +++ b/racevisionGame/src/main/java/shared/model/Mark.java @@ -22,7 +22,6 @@ public class Mark { private GPSCoordinate position; - /** * Constructs a mark with a given source ID, name, and position. * @param sourceID The source ID of the mark. diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml b/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml index 83e36f85..e068e9b8 100644 --- a/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml +++ b/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml @@ -8,12 +8,12 @@ - - - - - - + + + + + + diff --git a/racevisionGame/src/test/java/mock/model/MockBoatTest.java b/racevisionGame/src/test/java/mock/model/MockBoatTest.java index b1ee551c..32c28ce1 100644 --- a/racevisionGame/src/test/java/mock/model/MockBoatTest.java +++ b/racevisionGame/src/test/java/mock/model/MockBoatTest.java @@ -1,7 +1,174 @@ package mock.model; -import static org.junit.Assert.*; +import mock.dataInput.PolarParser; +import mock.exceptions.InvalidPolarFileException; +import org.junit.Before; +import org.junit.Test; +import shared.model.Bearing; +import shared.model.CompoundMark; +import shared.model.GPSCoordinate; +import shared.model.Mark; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class MockBoatTest { -//TODO + + /** + * boat made for testing + */ + private MockBoat firstTestBoat; + + private Mark markToTest; + private Mark markToTest2; + + private GPSCoordinate highGPS; + private GPSCoordinate middleGPS; + private GPSCoordinate lowGPS; + + /** + * Creates the Polars object for the tests. + */ + @Before + public void setUp() { + //Read in polars. + try { + //Parse data file. + Polars polars = PolarParser.parse("mock/polars/acc_polars.csv"); + + firstTestBoat = new MockBoat(1, "test", "NZ", polars); + highGPS = new GPSCoordinate(-64.854000, 32.296577); + middleGPS = new GPSCoordinate(-64.854000, 32.292500); + lowGPS = new GPSCoordinate(-64.854000, 32.290000); + markToTest = new Mark(1, "test MARK", middleGPS); + markToTest2 = new Mark(2, "test MARK2", middleGPS); + } + catch (InvalidPolarFileException e) { + fail("Couldn't parse polar file."); + } + } + + //////////////////////////////Mark Higher//////////////////////////////// + + /** + * Tests if the boat is lower than the mark that the port side method works if + * boat is facing east + */ + @Test + public void testIsPortSide() { + firstTestBoat.setBearing(Bearing.fromDegrees(90)); + firstTestBoat.setCurrentPosition(lowGPS); + markToTest.setPosition(highGPS); + + assertEquals(firstTestBoat.isPortSide(markToTest), true); + } + + /** + * Tests if the boat is lower than the mark that the port side method works if + * boat is facing west + */ + @Test + public void testIsPortSideWrong() { + firstTestBoat.setBearing(Bearing.fromDegrees(270)); + firstTestBoat.setCurrentPosition(lowGPS); + markToTest.setPosition(highGPS); + + assertEquals(firstTestBoat.isPortSide(markToTest), false); + } + + /** + * Tests if the boat is lower than the mark that the starboard side method works if + * boat is facing east + */ + @Test + public void testIsStarboardSideWrong() { + firstTestBoat.setBearing(Bearing.fromDegrees(90)); + firstTestBoat.setCurrentPosition(lowGPS); + markToTest.setPosition(highGPS); + + assertEquals(firstTestBoat.isStarboardSide(markToTest), false); + } + + /** + * Tests if the boat is lower than the mark that the starboard side method works if + * boat is facing west + */ + @Test + public void testIsStarboardSide() { + firstTestBoat.setBearing(Bearing.fromDegrees(270)); + firstTestBoat.setCurrentPosition(lowGPS); + markToTest.setPosition(highGPS); + + assertEquals(firstTestBoat.isStarboardSide(markToTest), true); + } + + + //////////////////////////////Mark Lower//////////////////////////////// + + /** + * Tests if the boat is higher than the mark that the port side method works if + * boat is facing east + */ + @Test + public void testIsPortSideHigherWrong() { + firstTestBoat.setBearing(Bearing.fromDegrees(90)); + firstTestBoat.setCurrentPosition(highGPS); + markToTest.setPosition(lowGPS); + + assertEquals(firstTestBoat.isPortSide(markToTest), false); + } + + /** + * Tests if the boat is higher than the mark that the port side method works if + * boat is facing west + */ + @Test + public void testIsPortSideHigher() { + firstTestBoat.setBearing(Bearing.fromDegrees(270)); + firstTestBoat.setCurrentPosition(highGPS); + markToTest.setPosition(lowGPS); + + assertEquals(firstTestBoat.isPortSide(markToTest), true); + } + + /** + * Tests if the boat is higher than the mark that the starboard side method works if + * boat is facing east + */ + @Test + public void testIsStarboardSideHigher() { + firstTestBoat.setBearing(Bearing.fromDegrees(90)); + firstTestBoat.setCurrentPosition(highGPS); + markToTest.setPosition(lowGPS); + + assertEquals(firstTestBoat.isStarboardSide(markToTest), true); + } + + /** + * Tests if the boat is higher than the mark that the starboard side method works if + * boat is facing west + */ + @Test + public void testIsStarboardSideHigherWrong() { + firstTestBoat.setBearing(Bearing.fromDegrees(270)); + firstTestBoat.setCurrentPosition(highGPS); + markToTest.setPosition(lowGPS); + + assertEquals(firstTestBoat.isStarboardSide(markToTest), false); + } + + /** + * Tests if a boat is between a gate + */ + @Test + public void testIsBetweenGate(){ + markToTest.setPosition(highGPS); + markToTest2.setPosition(lowGPS); + CompoundMark testGate = new CompoundMark(1, "test GATE", markToTest, markToTest2); + + firstTestBoat.setCurrentPosition(middleGPS); + + assertEquals(firstTestBoat.isBetweenGate(testGate), true); + + } }