diff --git a/.mailmap b/.mailmap index cad3a3a8..caf8a624 100644 --- a/.mailmap +++ b/.mailmap @@ -23,5 +23,5 @@ Haoming Yin Peter Galloway Peter Zhi You Tan zyt10 Zhi You Tan Ryan Tan -Alistair McIntyre alistairjmcintyre +Alistair McIntyre Calum cir27 \ No newline at end of file diff --git a/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java index e4cbf676..36164af2 100644 --- a/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java +++ b/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java @@ -8,10 +8,10 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import seng302.model.mark.CompoundMark; import seng302.gameServer.server.simulator.Corner; -import seng302.model.mark.Mark; import seng302.gameServer.server.simulator.RoundingType; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Mark; /** * Parses the race xml file to get course details @@ -84,17 +84,17 @@ public class CourseParser extends FileParser { NodeList marks = e.getElementsByTagName("Mark"); List subMarks = new ArrayList<>(); for (int i = 0; i < marks.getLength(); i++) { - Mark mark = getMark(marks.item(i)); + Mark mark = getMark(marks.item(i)); if (mark != null) { subMarks.add(mark); } - } + } return new CompoundMark(markID, name, subMarks); } - System.out.println("Failed to create compound mark."); - return null; - } + System.out.println("Failed to create compound mark."); + return null; + } private Mark getMark(Node node) { diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 6e49b132..a5d199f7 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -59,6 +59,8 @@ public class Yacht { private GeoPoint location; private Integer boatStatus; private Double velocity; + private Boolean isAuto; + private Double autoHeading; //MARK ROUNDING INFO private GeoPoint lastLocation; //For purposes of mark rounding calculations @@ -85,6 +87,8 @@ public class Yacht { this.shortName = shortName; this.boatName = boatName; this.country = country; + this.sailIn = false; + this.isAuto = false; this.location = new GeoPoint(57.670341, 11.826856); this.lastLocation = location; this.heading = 120.0; //In degrees @@ -129,6 +133,8 @@ public class Yacht { } } + runAutoPilot(); + //UPDATE BOAT LOCATION lastLocation = location; location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); @@ -301,15 +307,60 @@ public class Yacht { } + /** + * Adjusts the heading of the boat by a given amount, while recording the boats + * last heading. + * + * @param amount the amount by which to adjust the boat heading. + */ public void adjustHeading(Double amount) { Double newVal = heading + amount; lastHeading = heading; heading = (double) Math.floorMod(newVal.longValue(), 360L); } + /** + * Swaps the boats direction from one side of the wind to the other. + */ public void tackGybe(Double windDirection) { - Double normalizedHeading = normalizeHeading(); - adjustHeading(-2 * normalizedHeading); + if (isAuto) { + disableAutoPilot(); + } else { + Double normalizedHeading = normalizeHeading(); + Double newVal = (-2 * normalizedHeading) + heading; + Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L); + setAutoPilot(newHeading); + } + } + + /** + * Enables the boats auto pilot feature, which will move the boat towards a given heading. + * @param thisHeading The heading to move the boat towards. + */ + private void setAutoPilot(Double thisHeading) { + isAuto = true; + autoHeading = thisHeading; + } + + /** + * Disables the auto pilot function. + */ + public void disableAutoPilot() { + isAuto = false; + } + + /** + * Moves the boat towards the given heading when the auto pilot was set. Disables the auto pilot + * in the event that the boat is within the range of 1 turn step of its goal. + */ + public void runAutoPilot() { + if (isAuto) { + turnTowardsHeading(autoHeading); + if (Math.abs(heading - autoHeading) + <= TURN_STEP) { //Cancel when within 1 turn step of target. + isAuto = false; + } + } } public void toggleSailIn() { @@ -317,6 +368,7 @@ public class Yacht { } public void turnUpwind() { + disableAutoPilot(); Double normalizedHeading = normalizeHeading(); if (normalizedHeading == 0) { if (lastHeading < 180) { @@ -338,6 +390,7 @@ public class Yacht { } public void turnDownwind() { + disableAutoPilot(); Double normalizedHeading = normalizeHeading(); if (normalizedHeading == 0) { if (lastHeading < 180) { @@ -358,38 +411,59 @@ public class Yacht { } } + /** + * Takes the VMG from the polartable for upwind or downwind depending on the boats direction, + * and uses this to calculate a heading to move the yacht towards. + */ public void turnToVMG() { - Double normalizedHeading = normalizeHeading(); - Double optimalHeading; - HashMap optimalPolarMap; - - if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind - optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots()); - optimalHeading = optimalPolarMap.keySet().iterator().next(); + if (isAuto) { + disableAutoPilot(); } else { - optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots()); - optimalHeading = optimalPolarMap.keySet().iterator().next(); - } - // Take optimal heading and turn into correct - optimalHeading = - optimalHeading + (double) Math.floorMod(GameState.getWindDirection().longValue(), 360L); + Double normalizedHeading = normalizeHeading(); + Double optimalHeading; + HashMap optimalPolarMap; - turnTowardsHeading(optimalHeading); - - } - - private void turnTowardsHeading(Double newHeading) { - if (heading < 90 && newHeading > 270) { - adjustHeading(-TURN_STEP); - } else { - if (heading < newHeading) { - adjustHeading(TURN_STEP); + if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind + optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots()); } else { - adjustHeading(-TURN_STEP); + optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots()); } + optimalHeading = optimalPolarMap.keySet().iterator().next(); + + if (normalizedHeading > 180) { + optimalHeading = 360 - optimalHeading; + } + + // Take optimal heading and turn into a boat heading rather than a wind heading. + optimalHeading = + optimalHeading + GameState.getWindDirection(); + + setAutoPilot(optimalHeading); } } + /** + * Takes a given heading and rotates the boat towards that heading. + * This does not care about being upwind or downwind, just which direction will reach a given + * heading faster. + * + * @param newHeading The heading to turn the yacht towards. + */ + private void turnTowardsHeading(Double newHeading) { + Double newVal = heading - newHeading; + if (Math.floorMod(newVal.longValue(), 360L) > 180) { + adjustHeading(TURN_STEP); + } else { + adjustHeading(-TURN_STEP); + } + } + + /** + * Returns a heading normalized for the wind direction. Heading direction into the wind is 0, + * directly away is 180. + * + * @return The normalized heading accounting for wind direction. + */ private Double normalizeHeading() { Double normalizedHeading = heading - GameState.windDirection; normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L); diff --git a/src/main/java/seng302/model/mark/CompoundMark.java b/src/main/java/seng302/model/mark/CompoundMark.java index a4cc8d0c..fe5147de 100644 --- a/src/main/java/seng302/model/mark/CompoundMark.java +++ b/src/main/java/seng302/model/mark/CompoundMark.java @@ -15,7 +15,7 @@ public class CompoundMark { public CompoundMark(int markID, String name, List marks) { this.compoundMarkId = markID; - this.name = name; + this.name = name; this.marks.addAll(marks); if (marks.size() > 1) { this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1)); diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java index 141d5c6d..1b744fc2 100644 --- a/src/main/java/seng302/model/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -1,5 +1,14 @@ package seng302.model.mark; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -10,20 +19,11 @@ import seng302.model.stream.xml.parser.RaceXMLData; import seng302.utilities.XMLGenerator; import seng302.utilities.XMLParser; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - /** * Class to hold the order of the marks in the race. */ public class MarkOrder { + private List raceMarkOrder; private Logger logger = LoggerFactory.getLogger(MarkOrder.class); @@ -35,7 +35,7 @@ public class MarkOrder { * @return An ordered list of marks in the race * OR null if the mark order could not be loaded */ - public List getMarkOrder(){ + public List getMarkOrder() { if (raceMarkOrder == null){ logger.warn("Race order accessed but not instantiated"); return null; @@ -55,10 +55,9 @@ public class MarkOrder { /** * @param currentSeqID The seqID of the current mark the boat is heading to * @return The mark last passed - * @throws IndexOutOfBoundsException if there is no next mark. - * Check seqID != 0 first + * @throws IndexOutOfBoundsException if there is no next mark. Check seqID != 0 first */ - public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException{ + public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException { return raceMarkOrder.get(currentSeqID - 1); } @@ -69,10 +68,10 @@ public class MarkOrder { /** * @param currentSeqID The seqID of the current mark the boat is heading to * @return The mark following the mark that the boat is heading to - * @throws IndexOutOfBoundsException if there is no next mark. - * Check using {@link #isLastMark(Integer)} + * @throws IndexOutOfBoundsException if there is no next mark. Check using {@link + * #isLastMark(Integer)} */ - public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException{ + public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException { return raceMarkOrder.get(currentSeqID + 1); } @@ -81,7 +80,7 @@ public class MarkOrder { * @param xml An AC35 RaceXML * @return An ordered list of marks in the race */ - private List loadRaceOrderFromXML(String xml){ + private List loadRaceOrderFromXML(String xml) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db; diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 112ed05d..bf94070e 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -13,6 +13,8 @@ import javafx.scene.Node; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Pane; import seng302.gameServer.MainServerThread; +import seng302.gameServer.server.messages.BoatActionMessage; +import seng302.gameServer.server.messages.BoatActionType; import seng302.model.RaceState; import seng302.model.Yacht; import seng302.model.stream.packets.StreamPacket; @@ -22,8 +24,6 @@ import seng302.model.stream.parser.PositionUpdateData.DeviceType; import seng302.model.stream.parser.RaceStatusData; import seng302.model.stream.xml.parser.RaceXMLData; import seng302.model.stream.xml.parser.RegattaXMLData; -import seng302.gameServer.server.messages.BoatActionMessage; -import seng302.gameServer.server.messages.BoatActionType; import seng302.utilities.StreamParser; import seng302.utilities.XMLParser; import seng302.visualiser.controllers.LobbyController; diff --git a/src/test/java/seng302/model/mark/CompoundMarkTest.java b/src/test/java/seng302/model/mark/CompoundMarkTest.java index 55ccebf3..83b2b2bb 100644 --- a/src/test/java/seng302/model/mark/CompoundMarkTest.java +++ b/src/test/java/seng302/model/mark/CompoundMarkTest.java @@ -1,6 +1,8 @@ package seng302.model.mark; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.List; diff --git a/src/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java index 42531563..92bbc664 100644 --- a/src/test/java/seng302/models/MarkOrderTest.java +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -1,16 +1,15 @@ package seng302.models; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; + import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import seng302.model.mark.CompoundMark; -import seng302.model.mark.Mark; import seng302.model.mark.MarkOrder; -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertFalse; -import static junit.framework.TestCase.assertTrue; - public class MarkOrderTest { private static MarkOrder markOrder; private static Integer currentSeqID; diff --git a/src/test/java/seng302/models/YachtTest.java b/src/test/java/seng302/models/YachtTest.java new file mode 100644 index 00000000..fd610dfc --- /dev/null +++ b/src/test/java/seng302/models/YachtTest.java @@ -0,0 +1,89 @@ +package seng302.models; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import seng302.gameServer.GameState; +import seng302.model.PolarTable; +import seng302.model.Yacht; + + +public class YachtTest { + + private static Yacht y1; + //Yacht y2; + private static Double windDirection = 180d; + private static Double windSpeed = 20d; + private static GameState gs; + + @BeforeClass + public static void setUp() { + y1 = new Yacht("Yacht", 101, "Y1", "Y1", "Yacht 1", "C1"); + gs = new GameState("localhost"); + } + + @Test + public void tackGybeTest() { + HashMap values = new HashMap<>(); + values.put(280.0, 80.0); + values.put(270.0, 90.0); + values.put(359.0, 1.0); + values.put(180.0, 180.0); + values.put(75.0, 285.0); + + for (Double begin : values.keySet()) { + y1.setHeading(begin); + y1.tackGybe(windDirection); + for (int i = 0; i < 50; i++) { + y1.runAutoPilot(); + } + assertEquals(values.get(begin), y1.getHeading(), 5.0); + } + + } + + @Test + public void vmgTest() { + + PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); + Double upwind = PolarTable.getOptimalUpwindVMG(windSpeed).keySet().iterator().next(); + Double downwind = PolarTable.getOptimalDownwindVMG(windSpeed).keySet().iterator().next(); + + HashMap values = new HashMap<>(); + + upwind = (double) Math.floorMod(upwind.longValue() + windDirection.longValue(), 360L); + Double upwindRight = upwind; + Double upwindLeft = 360 - upwindRight; + downwind = (double) Math.floorMod(downwind.longValue() + windDirection.longValue(), 360L); + Double downwindRight = downwind; + Double downwindLeft = 360 - downwindRight; + + values.put(190d, upwindRight); + values.put(170d, upwindLeft); + values.put(10d, downwindLeft); + values.put(350d, downwindRight); + + for (Double begin : values.keySet()) { + System.out.println(begin); + y1.setHeading(begin); + y1.turnToVMG(); + for (int i = 0; i < 50; i++) { + y1.runAutoPilot(); + System.out.println(y1.getHeading()); + } + y1.disableAutoPilot(); + assertEquals(values.get(begin), y1.getHeading(), 5.0); + } + + } + + + @AfterClass + public static void tearDown() { + y1 = null; + } + +}