From 7f0329dda6ac175d5e08adc825ae8d41761cea88 Mon Sep 17 00:00:00 2001 From: William Muir Date: Mon, 7 Aug 2017 00:23:54 +1200 Subject: [PATCH 01/29] WIP: Implemented basic mark rounding algorithm. Removed RacePosition class. Instead marks are just grabbed from the mark order class when necessary. No marks are stored as an attribute in the yacht class but the 'currentMarkSeqID' which is used to get current, and surrounding marks. Works for all marks in between but not including starting and finishing gate as no angle can be made with them. Still to work out how to implement this #story[1124] --- .../java/seng302/gameServer/GameState.java | 13 ++- src/main/java/seng302/model/Yacht.java | 91 ++++++++++++++----- .../java/seng302/model/mark/MarkOrder.java | 64 +++++++------ .../java/seng302/model/mark/RacePosition.java | 55 ----------- src/test/java/seng302/model/YachtTest.java | 18 ++-- .../java/seng302/models/MarkOrderTest.java | 66 ++++++-------- 6 files changed, 148 insertions(+), 159 deletions(-) delete mode 100644 src/main/java/seng302/model/mark/RacePosition.java diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index ea8c1004..913f4f37 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -4,9 +4,12 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import seng302.gameServer.server.messages.BoatActionType; import seng302.model.Player; import seng302.model.Yacht; +import seng302.model.mark.MarkOrder; /** * A Static class to hold information about the current state of the game (model) @@ -14,6 +17,8 @@ import seng302.model.Yacht; */ public class GameState implements Runnable { + private Logger logger = LoggerFactory.getLogger(MarkOrder.class); + private static Integer STATE_UPDATES_PER_SECOND = 60; private static Long previousUpdateTime; @@ -25,6 +30,7 @@ public class GameState implements Runnable { private static Map yachts; private static Boolean isRaceStarted; private static GameStages currentStage; + private static MarkOrder markOrder; private static long startTime; private static Map playerStringMap = new HashMap<>(); @@ -53,8 +59,9 @@ public class GameState implements Runnable { //set this when game stage changes to prerace previousUpdateTime = System.currentTimeMillis(); yachts = new HashMap<>(); + markOrder = new MarkOrder(); //This could be instantiated at some point with a select map? - new Thread(this).start(); + new Thread(this).start(); //Run the auto updates on the game state } public static String getHostIpAddress() { @@ -100,6 +107,10 @@ public class GameState implements Runnable { GameState.currentStage = currentStage; } + public static MarkOrder getMarkOrder() { + return markOrder; + } + public static long getStartTime(){ return startTime; } diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index d7cd1f9e..1c9e7ec6 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -28,7 +28,7 @@ public class Yacht { void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity); } - private static final Double ROUNDING_DISTANCE = 15d; // TODO: 3/08/17 wmu16 - Look into this value further + private static final Double ROUNDING_DISTANCE = 50d; // TODO: 3/08/17 wmu16 - Look into this value further //BOTH AFAIK private String boatType; @@ -39,11 +39,10 @@ public class Yacht { private String country; private Long estimateTimeAtFinish; - private Long lastMark; + private Integer currentMarkSeqID = 1; private Long markRoundTime; private Double distanceToNextMark; private Long timeTillNext; - private CompoundMark nextMark; private Double heading; private Integer legNumber = 0; @@ -58,7 +57,6 @@ public class Yacht { private GeoPoint lastLocation; //For purposes of mark rounding calculations private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round private Boolean hasPassedFirstLine; //The line extrapolated from the next mark to the current mark - private Boolean hasPassedSecondLine; //The line extrapolated from the last mark to the current mark //CLIENT SIDE private List locationListeners = new ArrayList<>(); @@ -85,7 +83,6 @@ public class Yacht { this.hasEnteredRoundingZone = false; this.hasPassedFirstLine = false; - this.hasPassedSecondLine = false; } /** @@ -125,24 +122,24 @@ public class Yacht { location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); //CHECK FOR MARK ROUNDING - distanceToNextMark = calcDistanceToNextMark(); - if (distanceToNextMark < ROUNDING_DISTANCE) { - hasEnteredRoundingZone = true; - } + //Algorithm wont currently work on last gate and start gate + checkForMarkRounding(); // TODO: 3/08/17 wmu16 - Implement line cross check here } /** - * Calculates the distance to the next mark (closest of the two if a gate mark). - * + * Calculates the distance to the next mark (closest of the two if a gate mark). For purposes + * of mark rounding * @return A distance in metres. Returns -1 if there is no next mark + * @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race) + * Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)} */ - public Double calcDistanceToNextMark() { - if (nextMark == null) { - return -1d; - } else if (nextMark.isGate()) { + public Double calcDistanceToNextMark() throws IndexOutOfBoundsException { + CompoundMark nextMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID); + + if (nextMark.isGate()) { Mark sub1 = nextMark.getSubMark(1); Mark sub2 = nextMark.getSubMark(2); Double distance1 = GeoUtility.getDistance(location, sub1); @@ -153,6 +150,62 @@ public class Yacht { } } + + /** + * This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute + * of the yacht if so. + * The algorithm works by using the last mark, the current mark, the next mark and the change in + * boats location, like so: + * -Condition 1: + * The boat has entered the mark rounding distance + * -Condition 2: + * The boat has passed the line extending from the last mark to the current mark + * -Condition 3: + * The boat has passed the line extending from the next mark to the current mark + * + * A more visual representation of this algorithm can be seen on the Wiki under + * 'mark passing algorithm' + */ + private void checkForMarkRounding() { + if (!GameState.getMarkOrder().isLastMark(currentMarkSeqID) && currentMarkSeqID != 0) { + distanceToNextMark = calcDistanceToNextMark(); +// System.out.println("distanceToNextMark = " + distanceToNextMark); + CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); + CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID); + CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); + + //1 TEST FOR ENTERING THE ROUDNING DISTANCE + if (distanceToNextMark < ROUNDING_DISTANCE) { + hasEnteredRoundingZone = true; +// System.out.println("Entered rounding zone!"); + } + + //If the current mark is a gate mark we need to check both its marks for rounding, thus + //we loop + for (Mark thisCurrentMark : currentMark.getMarks()) { + + //2 TEST FOR CROSSING NEXT - CURRENT LINE FIRST + if (GeoUtility.isPointInTriangle(lastLocation, location, nextMark.getMarks().get(0), + thisCurrentMark)) { + hasPassedFirstLine = true; + System.out.println("Passed first line!"); + } + + //3 TEST FOR CROSSING PREV - CURRENT LINE SECOND + if (GeoUtility.isPointInTriangle(lastLocation, location, prevMark.getMarks().get(0), + thisCurrentMark)) { + if (hasPassedFirstLine && hasEnteredRoundingZone) { + currentMarkSeqID++; + hasPassedFirstLine = false; + hasEnteredRoundingZone = false; + System.out.println("SUCCESFUL ROUDNING!"); + break; + } + } + } + } + } + public void adjustHeading(Double amount) { Double newVal = heading + amount; lastHeading = heading; @@ -357,14 +410,6 @@ public class Yacht { this.lastMarkRounded = lastMarkRounded; } - public void setNextMark(CompoundMark nextMark) { - this.nextMark = nextMark; - } - - public CompoundMark getNextMark(){ - return nextMark; - } - public GeoPoint getLocation() { return location; } diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java index ca2b4356..141d5c6d 100644 --- a/src/main/java/seng302/model/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -24,7 +24,7 @@ import java.util.Map; * Class to hold the order of the marks in the race. */ public class MarkOrder { - private List raceMarkOrder; + private List raceMarkOrder; private Logger logger = LoggerFactory.getLogger(MarkOrder.class); public MarkOrder(){ @@ -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; @@ -45,26 +45,35 @@ public class MarkOrder { } /** - * Returns the mark in the race after the previous mark - * @param position The current race position - * @return the next race position - * OR null if there is no position + * @param seqID The seqID of the current mark the boat is heading to + * @return A Boolean indicating if this coming mark is the last one (finish line) */ - public RacePosition getNextPosition(RacePosition position){ - Mark previousMark = position.getNextMark(); - Mark nextMark; + public Boolean isLastMark(Integer seqID) { + return seqID == raceMarkOrder.size() - 1; + } - if (position.getPositionIndex() + 1 >= raceMarkOrder.size() - 1){ - RacePosition nextRacePosition = new RacePosition(raceMarkOrder.size() - 1, null, previousMark); - nextRacePosition.setFinishingLeg(); + /** + * @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 + */ + public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException{ + return raceMarkOrder.get(currentSeqID - 1); + } - return nextRacePosition; - } + public CompoundMark getCurrentMark(Integer currentSeqID) { + return raceMarkOrder.get(currentSeqID); + } - Integer nextPositionIndex = position.getPositionIndex() + 1; - RacePosition nextRacePosition = new RacePosition(nextPositionIndex, raceMarkOrder.get(nextPositionIndex), previousMark); - - return nextRacePosition; + /** + * @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)} + */ + public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException{ + return raceMarkOrder.get(currentSeqID + 1); } /** @@ -72,7 +81,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; @@ -92,11 +101,11 @@ public class MarkOrder { logger.debug("Loaded RaceXML for mark order"); List corners = data.getMarkSequence(); Map marks = data.getCompoundMarks(); - List course = new ArrayList<>(); + List course = new ArrayList<>(); for (Corner corner : corners){ CompoundMark compoundMark = marks.get(corner.getCompoundMarkID()); - course.add(compoundMark.getMarks().get(0)); + course.add(compoundMark); } return course; @@ -105,17 +114,6 @@ public class MarkOrder { return null; } - /** - * @return The first position in the race - */ - public RacePosition getFirstPosition(){ - if (raceMarkOrder.size() > 0){ - return new RacePosition(-1, raceMarkOrder.get(0), null); - } - - return null; - } - /** * Load the raceXML and mark order */ @@ -132,4 +130,4 @@ public class MarkOrder { } raceMarkOrder = loadRaceOrderFromXML(raceXML); } -} +} \ No newline at end of file diff --git a/src/main/java/seng302/model/mark/RacePosition.java b/src/main/java/seng302/model/mark/RacePosition.java deleted file mode 100644 index fc160b10..00000000 --- a/src/main/java/seng302/model/mark/RacePosition.java +++ /dev/null @@ -1,55 +0,0 @@ -package seng302.model.mark; - -/** - * Represents a boats position between two marks - */ -public class RacePosition { - private Integer positionIndex; - private Mark nextMark; - private Mark previousMark; - private Boolean isFinishingLeg; - - public RacePosition(Integer positionIndex, Mark nextMark, Mark previousMark){ - this.positionIndex = positionIndex; - this.nextMark = nextMark; - this.previousMark = previousMark; - isFinishingLeg = false; - } - - /** - * @return The position of the boat (0...number of marks in race - 1) - */ - public Integer getPositionIndex(){ - return positionIndex; - } - - /** - * @return The mark the boat is heading to - * will return NULL if this is the finishing legg - */ - public Mark getNextMark(){ - return nextMark; - } - - /** - * @return The mark the boat is heading away from, - * Will return NULL if this is the starting leg - */ - public Mark getPreviousMark(){ - return previousMark; - } - - /** - * Sets a flag that this is the last leg in the race - */ - public void setFinishingLeg(){ - isFinishingLeg = true; - } - - /** - * @return true if this is the last leg in the race - */ - public boolean getIsFinishingLeg() { - return isFinishingLeg; - } -} diff --git a/src/test/java/seng302/model/YachtTest.java b/src/test/java/seng302/model/YachtTest.java index 7937b43c..739c8a28 100644 --- a/src/test/java/seng302/model/YachtTest.java +++ b/src/test/java/seng302/model/YachtTest.java @@ -38,17 +38,19 @@ public class YachtTest { Mark subMark2 = new Mark("H", 57.670822, 11.843392, 0); compoundMark.addSubMarks(subMark1, subMark2); - yacht.setNextMark(compoundMark); } - @Test - public void testDistanceToNextMark() { - Double actual, expected; - actual = yacht.calcDistanceToNextMark(); - expected = 927d; - assertEquals(expected, actual, expected * toleranceRatio); - } + //This will no longer work as we cant set the next mark any more as we no longer hold it in + //yacht class as an attribute + +// @Test +// public void testDistanceToNextMark() { +// Double actual, expected; +// actual = yacht.calcDistanceToNextMark(); +// expected = 927d; +// assertEquals(expected, actual, expected * toleranceRatio); +// } } \ No newline at end of file diff --git a/src/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java index 8db06ec8..42531563 100644 --- a/src/test/java/seng302/models/MarkOrderTest.java +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -3,19 +3,22 @@ package seng302.models; 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 seng302.model.mark.RacePosition; 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; @BeforeClass public static void setup(){ markOrder = new MarkOrder(); + currentSeqID = 0; } /** @@ -26,54 +29,39 @@ public class MarkOrderTest { assertTrue(markOrder != null); } - /** - * Test if .getNextMark() returns null if it is called with the final mark in the race - */ + @Test - public void testNextMarkAtEnd(){ - // There are no marks in the XML, therefore this can't be tested - if (markOrder.getMarkOrder().size() == 0){ - return; - } + public void testIsLastMark() { + currentSeqID = 0; + assertFalse(markOrder.isLastMark(currentSeqID)); - Mark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); - Integer lastIndex = markOrder.getMarkOrder().size() - 1; - - RacePosition lastRacePosition = new RacePosition(lastIndex, lastMark, null); - - assertEquals(null, markOrder.getNextPosition(lastRacePosition).getNextMark()); + currentSeqID = markOrder.getMarkOrder().size() - 1; + assertTrue(markOrder.isLastMark(currentSeqID)); } - /** - * Test if .getNextMark() method returns the next mark in the race - */ @Test - public void testNextMark(){ - // There are not enough marks for this to be tested - if (markOrder.getMarkOrder().size() < 2){ - return; - } + public void testGetNextMark() { + currentSeqID = 4; + CompoundMark nextMark = markOrder.getMarkOrder().get(4 + 1); + assertEquals(nextMark, markOrder.getNextMark(currentSeqID)); - RacePosition firstRacePos = new RacePosition(0, markOrder.getMarkOrder().get(0), null); - - assertEquals(markOrder.getMarkOrder().get(1).getName(), markOrder.getNextPosition(firstRacePos).getNextMark().getName()); + currentSeqID = 3; + nextMark = markOrder.getMarkOrder().get(3 + 1); + assertEquals(nextMark, markOrder.getNextMark(currentSeqID)); } - /** - * Test if a whole race can be completed - */ @Test - public void testMarkSequence(){ - RacePosition current = markOrder.getFirstPosition(); + public void testGetCurrentMark() { + currentSeqID = 0; + CompoundMark currentMark = markOrder.getMarkOrder().get(0); + assertEquals(currentMark, markOrder.getCurrentMark(0)); + } - while (!current.getIsFinishingLeg()){ - - current = markOrder.getNextPosition(current); - - if (current.getIsFinishingLeg()){ - assertEquals(null, current.getNextMark()); - } - } + @Test + public void testGetPreviousMark() { + currentSeqID = 1; + CompoundMark prevMark = markOrder.getMarkOrder().get(0); + assertEquals(prevMark, markOrder.getPreviousMark(currentSeqID)); } @AfterClass From 4375b7325755d8a26b5678280fa358193278caea Mon Sep 17 00:00:00 2001 From: William Muir Date: Mon, 7 Aug 2017 17:28:12 +1200 Subject: [PATCH 02/29] Implemented algorithm for checking if boat passes through a mark. Mark rounding works for whole course (WITH BUGS) Still some gate logic to work out. Moved gate function to GeoUtil class tags: #story[1124] #pair[hyi25, wmu16] --- src/main/java/seng302/model/Yacht.java | 30 ++++++++++++++--- .../java/seng302/utilities/GeoUtility.java | 32 +++++++++++++++++++ .../java/seng302/visualiser/GameClient.java | 1 - .../controllers/RaceViewController.java | 2 +- .../seng302/utilities/GeoUtilityTest.java | 20 ++++++++++++ 5 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 1c9e7ec6..34bbb4f0 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -10,6 +10,8 @@ import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyLongProperty; import javafx.beans.property.ReadOnlyLongWrapper; import javafx.scene.paint.Color; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import seng302.gameServer.GameState; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; @@ -28,8 +30,11 @@ public class Yacht { void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity); } + private Logger logger = LoggerFactory.getLogger(Yacht.class); + private static final Double ROUNDING_DISTANCE = 50d; // TODO: 3/08/17 wmu16 - Look into this value further + //BOTH AFAIK private String boatType; private Integer sourceId; @@ -39,7 +44,7 @@ public class Yacht { private String country; private Long estimateTimeAtFinish; - private Integer currentMarkSeqID = 1; + private Integer currentMarkSeqID = 0; private Long markRoundTime; private Double distanceToNextMark; private Long timeTillNext; @@ -119,6 +124,7 @@ public class Yacht { } //UPDATE BOAT LOCATION + lastLocation = location; location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); //CHECK FOR MARK ROUNDING @@ -167,11 +173,22 @@ public class Yacht { * 'mark passing algorithm' */ private void checkForMarkRounding() { - if (!GameState.getMarkOrder().isLastMark(currentMarkSeqID) && currentMarkSeqID != 0) { + CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID); + + if (GameState.getMarkOrder().isLastMark(currentMarkSeqID) || currentMarkSeqID == 0) { + if (GeoUtility.checkCrossedLine(currentMark.getSubMark(1), + currentMark.getSubMark(2), lastLocation, location)) { + System.out.println( + "(" + currentMarkSeqID + ") Passed gate: " + currentMark.getMarks().get(0) + .getName() + + " ID(" + currentMark.getId() + ")"); + currentMarkSeqID++; + } + } else { + //ALL OTHER MARKS distanceToNextMark = calcDistanceToNextMark(); // System.out.println("distanceToNextMark = " + distanceToNextMark); CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); - CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID); CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); //1 TEST FOR ENTERING THE ROUDNING DISTANCE @@ -188,7 +205,6 @@ public class Yacht { if (GeoUtility.isPointInTriangle(lastLocation, location, nextMark.getMarks().get(0), thisCurrentMark)) { hasPassedFirstLine = true; - System.out.println("Passed first line!"); } //3 TEST FOR CROSSING PREV - CURRENT LINE SECOND @@ -198,7 +214,10 @@ public class Yacht { currentMarkSeqID++; hasPassedFirstLine = false; hasEnteredRoundingZone = false; - System.out.println("SUCCESFUL ROUDNING!"); + System.out.println( + "(" + currentMarkSeqID + ") Passed mark: " + currentMark.getMarks() + .get(0).getName() + + " ID(" + currentMark.getId() + ")"); break; } } @@ -206,6 +225,7 @@ public class Yacht { } } + public void adjustHeading(Double amount) { Double newVal = heading + amount; lastHeading = heading; diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 9aa61b9d..81acebe6 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -1,7 +1,10 @@ package seng302.utilities; import javafx.geometry.Point2D; +import seng302.gameServer.GameState; import seng302.model.GeoPoint; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Mark; public class GeoUtility { @@ -126,6 +129,35 @@ public class GeoUtility { } + /** + * Checks if a point passes across a line, either direction + * See the wiki Mark Rounding algorithm for more info + * + * @param mark1 One mark of the line + * @param mark2 The second mark of the line + * @param lastLocation The last location of the point crossing this line + * @param location The current location of the point crossing this line + * @return True if crossed since last location --> current location, false otherwise + */ + public static Boolean checkCrossedLine(GeoPoint mark1, GeoPoint mark2, GeoPoint lastLocation, + GeoPoint location) { + //START GATE OR FINISH GATE + Double alpha = GeoUtility.getBearing(mark1, lastLocation); + Double beta = GeoUtility.getBearing(mark1, mark2); + Double theta = GeoUtility.getBearing(mark1, location); + alpha = (alpha > 180) ? 360 - alpha : alpha; + beta = (beta > 180) ? 360 - beta : beta; + theta = (theta > 180) ? 360 - theta : theta; + + if (alpha < beta && theta > beta) { + if (!GeoUtility.isPointInTriangle(mark1, lastLocation, location, mark2)) { + return true; + } + } + return false; + } + + /** * Given a point and a vector (angle and vector length) Will create a new point, that vector * away from the origin point diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 915eef37..b00a6cda 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -174,7 +174,6 @@ public class GameClient { break; case BOAT_XML: - System.out.println("GOT SUM BOATS YAY :)"); allBoatsMap = XMLParser.parseBoats( StreamParser.extractXmlMessage(packet) ); diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index a6df4f61..2a166081 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -312,7 +312,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel updateRaceTime(); updateWindDirection(); updateOrder(); - updateSparkLine(); +// updateSparkLine(); } }, 0, 1000); } diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java index 8382bb8c..4bd1e128 100644 --- a/src/test/java/seng302/utilities/GeoUtilityTest.java +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -9,9 +9,11 @@ import javafx.geometry.Point2D; import org.junit.Before; import org.junit.Test; import seng302.model.GeoPoint; +import seng302.model.mark.CompoundMark; import seng302.utilities.GeoUtility; /** + * http://www.geoplaner.com/ For plotting geo points for visualisation * To test methods in GeoUtility. * Use this site to calculate distances * https://rechneronline.de/geo-coordinates/#distance @@ -150,4 +152,22 @@ public class GeoUtilityTest { assertFalse(GeoUtility.isPointInTriangle(v1, v2, v3, p2)); } + + + @Test + public void testCheckCrossedGate() { + GeoPoint mark1 = new GeoPoint(37.40937, -122.62233); + GeoPoint mark2 = new GeoPoint(37.40938, -122.62154); + GeoPoint location1 = new GeoPoint(37.40964, -122.62196); + GeoPoint location2 = new GeoPoint(37.40910, -122.62189); + GeoPoint location3 = new GeoPoint(37.40949, -122.62202); + GeoPoint location4 = new GeoPoint(34.40955, -122.62176); + GeoPoint location5 = new GeoPoint(37.40927, -122.62152); + GeoPoint location6 = new GeoPoint(34.40933, -122.62163); + + assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location1, location2)); + assertFalse(GeoUtility.checkCrossedLine(mark1, mark2, location4, location3)); + assertFalse(GeoUtility.checkCrossedLine(mark1, mark2, location1, location3)); + assertFalse(GeoUtility.checkCrossedLine(mark1, mark2, location5, location6)); + } } \ No newline at end of file From ed0a7833749e77371b8928bc195ad4d4d17658ab Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 8 Aug 2017 10:42:36 +1200 Subject: [PATCH 03/29] Fixed the bug that boats could round over a gate but still "across" it. Added unit test to ensure the algorithm works. tags: #story[1124] --- src/main/java/seng302/model/Yacht.java | 2 +- .../java/seng302/utilities/GeoUtility.java | 58 ++++++++++--------- .../seng302/utilities/GeoUtilityTest.java | 20 +++---- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 34bbb4f0..f0adde0a 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -177,7 +177,7 @@ public class Yacht { if (GameState.getMarkOrder().isLastMark(currentMarkSeqID) || currentMarkSeqID == 0) { if (GeoUtility.checkCrossedLine(currentMark.getSubMark(1), - currentMark.getSubMark(2), lastLocation, location)) { + currentMark.getSubMark(2), lastLocation, location) > 0) { System.out.println( "(" + currentMarkSeqID + ") Passed gate: " + currentMark.getMarks().get(0) .getName() diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 81acebe6..ab87c3be 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -1,10 +1,7 @@ package seng302.utilities; import javafx.geometry.Point2D; -import seng302.gameServer.GameState; import seng302.model.GeoPoint; -import seng302.model.mark.CompoundMark; -import seng302.model.mark.Mark; public class GeoUtility { @@ -96,7 +93,6 @@ public class GeoUtility { return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); } - /** * Performs the line function on two points of a line and a test point to test which side of the * line that point is on. If the return value is return 1, then the point is on one side of the @@ -128,36 +124,32 @@ public class GeoUtility { } } - /** - * Checks if a point passes across a line, either direction - * See the wiki Mark Rounding algorithm for more info + * Checks if the line formed by lastLocation and location doesn't intersect the line segment + * formed by mark1 and mark2 See the wiki Mark Rounding algorithm for more info * * @param mark1 One mark of the line * @param mark2 The second mark of the line * @param lastLocation The last location of the point crossing this line * @param location The current location of the point crossing this line - * @return True if crossed since last location --> current location, false otherwise + * @return 0 if two line segment doesn't intersect, otherwise 1 if they intersect and + * lastLocation is on RHS of the line segment (mark1 -> mark2) or 2 if lastLocation on LHS of + * the line segment (mark1 -> mark2) */ - public static Boolean checkCrossedLine(GeoPoint mark1, GeoPoint mark2, GeoPoint lastLocation, + public static Integer checkCrossedLine(GeoPoint mark1, GeoPoint mark2, GeoPoint lastLocation, GeoPoint location) { - //START GATE OR FINISH GATE - Double alpha = GeoUtility.getBearing(mark1, lastLocation); - Double beta = GeoUtility.getBearing(mark1, mark2); - Double theta = GeoUtility.getBearing(mark1, location); - alpha = (alpha > 180) ? 360 - alpha : alpha; - beta = (beta > 180) ? 360 - beta : beta; - theta = (theta > 180) ? 360 - theta : theta; + boolean enteredDirection = isClockwise(mark1, mark2, lastLocation); + boolean exitedDirection = isClockwise(mark1, mark2, location); + if (enteredDirection != exitedDirection) { + if (!isPointInTriangle(mark1, lastLocation, location, mark2) + && !isPointInTriangle(mark2, lastLocation, location, mark1)) { - if (alpha < beta && theta > beta) { - if (!GeoUtility.isPointInTriangle(mark1, lastLocation, location, mark2)) { - return true; + return enteredDirection ? 1 : 2; } } - return false; + return 0; } - /** * Given a point and a vector (angle and vector length) Will create a new point, that vector * away from the origin point @@ -187,10 +179,24 @@ public class GeoUtility { * @param bearing2 the bearing of v2 * @return the difference of bearing from v1 to v2 */ - private static double getBearingDiff(double bearing1, double bearing2) { + private static Double getBearingDiff(double bearing1, double bearing2) { return ((360 - bearing1) + bearing2) % 360; } + /** + * Check if a geo point ins on the right hand side of the line segment, which + * formed by two geo points v1 -> v2. (Algorithm: point is clockwise to the + * line if the bearing difference is less than 180 deg.) + * + * @param v1 one end of the line segment + * @param v2 another end of the line segment + * @param point the point to be tested + * @return true if the point is on the RHS of the line + */ + private static Boolean isClockwise(GeoPoint v1, GeoPoint v2, GeoPoint point) { + return getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180; + } + /** * Given three geo points to form a triangle, the method returns true if the fourth point is * inside the triangle @@ -201,15 +207,15 @@ public class GeoUtility { * @param point the point to be tested * @return true if the fourth point is inside the triangle */ - public static boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) { + public static Boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) { // true, if diff of bearing from (v1->v2) to (v1->p) is less than 180 deg - boolean sideFlag = getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180; + boolean isCW = isClockwise(v1, v2, point); - if ((getBearingDiff(getBearing(v2, v3), getBearing(v2, point)) < 180) != sideFlag) { + if (isClockwise(v2, v3, point) != isCW) { return false; } - if ((getBearingDiff(getBearing(v3, v1), getBearing(v3, point)) < 180) != sideFlag) { + if (isClockwise(v3, v1, point) != isCW) { return false; } diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java index 4bd1e128..109b1723 100644 --- a/src/test/java/seng302/utilities/GeoUtilityTest.java +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -6,11 +6,8 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import javafx.geometry.Point2D; -import org.junit.Before; import org.junit.Test; import seng302.model.GeoPoint; -import seng302.model.mark.CompoundMark; -import seng302.utilities.GeoUtility; /** * http://www.geoplaner.com/ For plotting geo points for visualisation @@ -161,13 +158,16 @@ public class GeoUtilityTest { GeoPoint location1 = new GeoPoint(37.40964, -122.62196); GeoPoint location2 = new GeoPoint(37.40910, -122.62189); GeoPoint location3 = new GeoPoint(37.40949, -122.62202); - GeoPoint location4 = new GeoPoint(34.40955, -122.62176); - GeoPoint location5 = new GeoPoint(37.40927, -122.62152); - GeoPoint location6 = new GeoPoint(34.40933, -122.62163); + GeoPoint location4 = new GeoPoint(37.40927, -122.62152); - assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location1, location2)); - assertFalse(GeoUtility.checkCrossedLine(mark1, mark2, location4, location3)); - assertFalse(GeoUtility.checkCrossedLine(mark1, mark2, location1, location3)); - assertFalse(GeoUtility.checkCrossedLine(mark1, mark2, location5, location6)); + // M1 -> M3 enters from CCW side + assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location1, location2) == 2); + // M1 -> M3 doesn't across + assertFalse(GeoUtility.checkCrossedLine(mark1, mark2, location1, location3) > 0); + // M2 -> M3 enters from CW side + assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location2, location3) == 1); + // order changes intersect direction + assertTrue(GeoUtility.checkCrossedLine(mark2, mark1, location2, location3) == 2); + assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location3, location2) == 2); } } \ No newline at end of file From b0e7dddaf38d3aac0242743ebebb4e225af4e1b1 Mon Sep 17 00:00:00 2001 From: William Muir Date: Tue, 8 Aug 2017 15:58:13 +1200 Subject: [PATCH 04/29] Fixed gate passing algorithm boats now must pass through the correct way. This works for start in-race and finish gates Refactored yacht algorithm code for better readability Logging function added or seeing mark roundings occur tags: #story[1124] #pair[hyi25, wmu16] --- src/main/java/seng302/model/Yacht.java | 177 +++++++++++++----- .../java/seng302/utilities/GeoUtility.java | 2 +- 2 files changed, 131 insertions(+), 48 deletions(-) diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index f0adde0a..c87b0b8f 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -58,10 +58,12 @@ public class Yacht { private GeoPoint location; private Integer boatStatus; private Double velocity; + //MARK ROUNDING INFO private GeoPoint lastLocation; //For purposes of mark rounding calculations private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round private Boolean hasPassedFirstLine; //The line extrapolated from the next mark to the current mark + private Boolean hasPassedThroughGate; //CLIENT SIDE private List locationListeners = new ArrayList<>(); @@ -88,6 +90,7 @@ public class Yacht { this.hasEnteredRoundingZone = false; this.hasPassedFirstLine = false; + this.hasPassedThroughGate = false; } /** @@ -129,7 +132,7 @@ public class Yacht { //CHECK FOR MARK ROUNDING //Algorithm wont currently work on last gate and start gate - checkForMarkRounding(); + checkForLegProgression(); // TODO: 3/08/17 wmu16 - Implement line cross check here } @@ -157,70 +160,139 @@ public class Yacht { } + /** + * 4 Different cases of progression in the race + * 1 - Passing the start line + * 2 - Passing any in-race Gate + * 3 - Passing any in-race Mark + * 4 - Passing the finish line + */ + private void checkForLegProgression() { + CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID); + if (currentMarkSeqID == 0) { + checkStartLineCrossing(currentMark); + } else if (GameState.getMarkOrder().isLastMark(currentMarkSeqID)) { + checkFinishLineCrossing(currentMark); + } else if (currentMark.isGate()) { + checkGateRounding(currentMark); + } else { + checkMarkRounding(currentMark); + } + } + + /** + * If we pass the start line gate in the correct direction, progress + * + * @param currentMark The current gate + */ + private void checkStartLineCrossing(CompoundMark currentMark) { + Integer crossedLine = GeoUtility.checkCrossedLine(currentMark.getSubMark(1), + currentMark.getSubMark(2), lastLocation, location); + if (crossedLine > 0) { + CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); + Boolean isClockwiseCross = GeoUtility.isClockwise(currentMark.getSubMark(1), + currentMark.getSubMark(2), + nextMark.getSubMark(1)); + if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) { + logMarkRounding(currentMark); + currentMarkSeqID++; + } + } + } + + /** * This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute * of the yacht if so. * The algorithm works by using the last mark, the current mark, the next mark and the change in * boats location, like so: * -Condition 1: - * The boat has entered the mark rounding distance + * The boat has entered the mark rounding distance * -Condition 2: - * The boat has passed the line extending from the last mark to the current mark + * The boat has passed the line extending from the last mark to the current mark * -Condition 3: - * The boat has passed the line extending from the next mark to the current mark + * The boat has passed the line extending from the next mark to the current mark * * A more visual representation of this algorithm can be seen on the Wiki under * 'mark passing algorithm' */ - private void checkForMarkRounding() { - CompoundMark currentMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID); + private void checkMarkRounding(CompoundMark currentMark) { + distanceToNextMark = calcDistanceToNextMark(); + CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); + CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); - if (GameState.getMarkOrder().isLastMark(currentMarkSeqID) || currentMarkSeqID == 0) { - if (GeoUtility.checkCrossedLine(currentMark.getSubMark(1), - currentMark.getSubMark(2), lastLocation, location) > 0) { - System.out.println( - "(" + currentMarkSeqID + ") Passed gate: " + currentMark.getMarks().get(0) - .getName() - + " ID(" + currentMark.getId() + ")"); - currentMarkSeqID++; + //1 TEST FOR ENTERING THE ROUNDING DISTANCE + if (distanceToNextMark < ROUNDING_DISTANCE) { + hasEnteredRoundingZone = true; + } + + //If the current mark is a gate mark we need to check both its marks for rounding, thus + //we loop + for (Mark thisCurrentMark : currentMark.getMarks()) { + //2 TEST FOR CROSSING NEXT - CURRENT LINE FIRST + if (GeoUtility + .isPointInTriangle(lastLocation, location, nextMark.getMarks().get(0), + thisCurrentMark)) { + hasPassedFirstLine = true; } - } else { - //ALL OTHER MARKS - distanceToNextMark = calcDistanceToNextMark(); -// System.out.println("distanceToNextMark = " + distanceToNextMark); - CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); + //3 TEST FOR CROSSING PREV - CURRENT LINE SECOND + if (GeoUtility + .isPointInTriangle(lastLocation, location, prevMark.getMarks().get(0), + thisCurrentMark)) { + if (hasPassedFirstLine && hasEnteredRoundingZone) { + currentMarkSeqID++; + hasPassedFirstLine = false; + hasEnteredRoundingZone = false; + hasPassedThroughGate = false; + logMarkRounding(currentMark); + break; + } + } + } + } + + + /** + * Checks if a gate line has been crossed and in the correct direction + * + * @param currentMark The current gate + */ + private void checkGateRounding(CompoundMark currentMark) { + Integer crossedLine = GeoUtility.checkCrossedLine(currentMark.getSubMark(1), + currentMark.getSubMark(2), lastLocation, location); + + //We have crossed the line + if (crossedLine > 0) { CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); - - //1 TEST FOR ENTERING THE ROUDNING DISTANCE - if (distanceToNextMark < ROUNDING_DISTANCE) { - hasEnteredRoundingZone = true; -// System.out.println("Entered rounding zone!"); + Boolean isClockwiseCross = GeoUtility.isClockwise(currentMark.getSubMark(1), + currentMark.getSubMark(2), + prevMark.getSubMark(1)); + if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) { + hasPassedThroughGate = true; } + } - //If the current mark is a gate mark we need to check both its marks for rounding, thus - //we loop - for (Mark thisCurrentMark : currentMark.getMarks()) { + if (hasPassedThroughGate) { + checkMarkRounding(currentMark); + } + } - //2 TEST FOR CROSSING NEXT - CURRENT LINE FIRST - if (GeoUtility.isPointInTriangle(lastLocation, location, nextMark.getMarks().get(0), - thisCurrentMark)) { - hasPassedFirstLine = true; - } - - //3 TEST FOR CROSSING PREV - CURRENT LINE SECOND - if (GeoUtility.isPointInTriangle(lastLocation, location, prevMark.getMarks().get(0), - thisCurrentMark)) { - if (hasPassedFirstLine && hasEnteredRoundingZone) { - currentMarkSeqID++; - hasPassedFirstLine = false; - hasEnteredRoundingZone = false; - System.out.println( - "(" + currentMarkSeqID + ") Passed mark: " + currentMark.getMarks() - .get(0).getName() - + " ID(" + currentMark.getId() + ")"); - break; - } - } + /** + * If we pass the finish gate in the correct direction // TODO: 8/08/17 wmu16 - do something + * + * @param currentMark The current gate + */ + private void checkFinishLineCrossing(CompoundMark currentMark) { + Integer crossedLine = GeoUtility.checkCrossedLine(currentMark.getSubMark(1), + currentMark.getSubMark(2), lastLocation, location); + if (crossedLine > 0) { + CompoundMark previousMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); + Boolean isClockwiseCross = GeoUtility.isClockwise(currentMark.getSubMark(1), + currentMark.getSubMark(2), + previousMark.getSubMark(1)); + if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) { + logMarkRounding(currentMark); + // TODO: 8/08/17 wmu16 - Do something! } } } @@ -508,6 +580,17 @@ public class Yacht { } } + private void logMarkRounding(CompoundMark currentMark) { + String typeString = "mark"; + if (currentMark.isGate()) { + typeString = "gate"; + } + System.out.println( + "(" + currentMarkSeqID + ") Passed " + typeString + ": " + currentMark.getMarks().get(0) + .getName() + + " ID(" + currentMark.getId() + ")"); + } + public void addLocationListener (YachtLocationListener listener) { locationListeners.add(listener); } diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index ab87c3be..260f5b34 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -193,7 +193,7 @@ public class GeoUtility { * @param point the point to be tested * @return true if the point is on the RHS of the line */ - private static Boolean isClockwise(GeoPoint v1, GeoPoint v2, GeoPoint point) { + public static Boolean isClockwise(GeoPoint v1, GeoPoint v2, GeoPoint point) { return getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180; } From a545e9dbc32e42562bf4132048820bec7d684759 Mon Sep 17 00:00:00 2001 From: William Muir Date: Tue, 8 Aug 2017 16:14:13 +1200 Subject: [PATCH 05/29] Removing '>' characters from docstrings to fix build Note: This is probably a XML style guid thing and is stupid. Can probably fix this --- src/main/java/seng302/utilities/GeoUtility.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 260f5b34..5d96f33e 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -133,8 +133,8 @@ public class GeoUtility { * @param lastLocation The last location of the point crossing this line * @param location The current location of the point crossing this line * @return 0 if two line segment doesn't intersect, otherwise 1 if they intersect and - * lastLocation is on RHS of the line segment (mark1 -> mark2) or 2 if lastLocation on LHS of - * the line segment (mark1 -> mark2) + * lastLocation is on RHS of the line segment (mark1 to mark2) or 2 if lastLocation on LHS of + * the line segment (mark1 to mark2) */ public static Integer checkCrossedLine(GeoPoint mark1, GeoPoint mark2, GeoPoint lastLocation, GeoPoint location) { @@ -185,7 +185,7 @@ public class GeoUtility { /** * Check if a geo point ins on the right hand side of the line segment, which - * formed by two geo points v1 -> v2. (Algorithm: point is clockwise to the + * formed by two geo points v1 to v2. (Algorithm: point is clockwise to the * line if the bearing difference is less than 180 deg.) * * @param v1 one end of the line segment @@ -208,7 +208,7 @@ public class GeoUtility { * @return true if the fourth point is inside the triangle */ public static Boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) { - // true, if diff of bearing from (v1->v2) to (v1->p) is less than 180 deg + // true, if diff of bearing from (v1 to v2) to (v1 to p) is less than 180 deg boolean isCW = isClockwise(v1, v2, point); if (isClockwise(v2, v3, point) != isCW) { From 9d02d2fbea64d465d88ffb301a99647a12624d0b Mon Sep 17 00:00:00 2001 From: alistairjmcintyre Date: Thu, 10 Aug 2017 02:04:51 +1200 Subject: [PATCH 06/29] Implemented a fairly simple auto pilot setting for the yacht, on update if the boat is set to autopilot it will adjust the heading towards the desired heading. Needs some refinement. #story[1105] --- .mailmap | 2 +- src/main/java/seng302/model/Yacht.java | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) 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/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index d7cd1f9e..9804314c 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -54,6 +54,9 @@ 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 private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round @@ -78,6 +81,7 @@ public class Yacht { 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 @@ -121,6 +125,13 @@ public class Yacht { } } + if (isAuto) { + turnTowardsHeading(autoHeading); + if (Math.abs(heading - autoHeading) <= 5) { + isAuto = false; + } + } + //UPDATE BOAT LOCATION location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); @@ -159,9 +170,18 @@ public class Yacht { heading = (double) Math.floorMod(newVal.longValue(), 360L); } + /** + * Should tell boat to auto pilot towards the autopilot heading. + */ public void tackGybe(Double windDirection) { Double normalizedHeading = normalizeHeading(); - adjustHeading(-2 * normalizedHeading); + setAutoPilot(-2 * normalizedHeading); + } + + private void setAutoPilot(Double thisHeading) { + isAuto = true; + Double newVal = heading + thisHeading; + autoHeading = (double) Math.floorMod(newVal.longValue(), 360L); } public void toggleSailIn() { From 249ad9e5c04c00bd796cb39375fa9b21be6ce1b1 Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 10 Aug 2017 12:02:19 +1200 Subject: [PATCH 07/29] Fixed Mark rounding Algorithm Algorithm now knows when a player has to round a gate or just pass right through #story[1124] #pair[wmu16, hyi25] --- src/main/java/seng302/model/Yacht.java | 64 +++++++++++++------ .../server_config/xml_templates/race.ftlh | 7 -- src/test/java/seng302/model/YachtTest.java | 2 +- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index c87b0b8f..95d117fe 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -32,6 +32,8 @@ public class Yacht { private Logger logger = LoggerFactory.getLogger(Yacht.class); + + private static final Integer SPEED_MULTIPLIER = 4; private static final Double ROUNDING_DISTANCE = 50d; // TODO: 3/08/17 wmu16 - Look into this value further @@ -46,7 +48,7 @@ public class Yacht { private Long estimateTimeAtFinish; private Integer currentMarkSeqID = 0; private Long markRoundTime; - private Double distanceToNextMark; + private Double distanceToCurrentMark; private Long timeTillNext; private Double heading; private Integer legNumber = 0; @@ -63,7 +65,9 @@ public class Yacht { private GeoPoint lastLocation; //For purposes of mark rounding calculations private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round private Boolean hasPassedFirstLine; //The line extrapolated from the next mark to the current mark + private Boolean hasPassedSecondLine; private Boolean hasPassedThroughGate; + private Boolean finishedRace; //CLIENT SIDE private List locationListeners = new ArrayList<>(); @@ -90,7 +94,9 @@ public class Yacht { this.hasEnteredRoundingZone = false; this.hasPassedFirstLine = false; + this.hasPassedSecondLine = false; this.hasPassedThroughGate = false; + this.finishedRace = false; } /** @@ -102,7 +108,7 @@ public class Yacht { Double windSpeedKnots = GameState.getWindSpeedKnots(); Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading); Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle); - Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000; + Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000 * SPEED_MULTIPLIER; if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) { if (velocity < maxBoatSpeed) { @@ -131,8 +137,9 @@ public class Yacht { location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); //CHECK FOR MARK ROUNDING - //Algorithm wont currently work on last gate and start gate - checkForLegProgression(); + if (!finishedRace) { + checkForLegProgression(); + } // TODO: 3/08/17 wmu16 - Implement line cross check here } @@ -145,7 +152,7 @@ public class Yacht { * @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race) * Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)} */ - public Double calcDistanceToNextMark() throws IndexOutOfBoundsException { + public Double calcDistanceToCurrentMark() throws IndexOutOfBoundsException { CompoundMark nextMark = GameState.getMarkOrder().getCurrentMark(currentMarkSeqID); if (nextMark.isGate()) { @@ -194,8 +201,8 @@ public class Yacht { currentMark.getSubMark(2), nextMark.getSubMark(1)); if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) { - logMarkRounding(currentMark); currentMarkSeqID++; + logMarkRounding(currentMark); } } } @@ -217,12 +224,12 @@ public class Yacht { * 'mark passing algorithm' */ private void checkMarkRounding(CompoundMark currentMark) { - distanceToNextMark = calcDistanceToNextMark(); + distanceToCurrentMark = calcDistanceToCurrentMark(); CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); //1 TEST FOR ENTERING THE ROUNDING DISTANCE - if (distanceToNextMark < ROUNDING_DISTANCE) { + if (distanceToCurrentMark < ROUNDING_DISTANCE) { hasEnteredRoundingZone = true; } @@ -239,16 +246,18 @@ public class Yacht { if (GeoUtility .isPointInTriangle(lastLocation, location, prevMark.getMarks().get(0), thisCurrentMark)) { - if (hasPassedFirstLine && hasEnteredRoundingZone) { - currentMarkSeqID++; - hasPassedFirstLine = false; - hasEnteredRoundingZone = false; - hasPassedThroughGate = false; - logMarkRounding(currentMark); - break; - } + hasPassedSecondLine = true; } } + + if (hasPassedSecondLine && hasPassedFirstLine && hasEnteredRoundingZone) { + currentMarkSeqID++; + hasPassedFirstLine = false; + hasPassedSecondLine = false; + hasEnteredRoundingZone = false; + hasPassedThroughGate = false; + logMarkRounding(currentMark); + } } @@ -272,8 +281,21 @@ public class Yacht { } } + Boolean prevMarkSide = GeoUtility.isClockwise(currentMark.getSubMark(1), + currentMark.getSubMark(2), + GameState.getMarkOrder().getPreviousMark(currentMarkSeqID).getSubMark(1)); + + Boolean nextMarkSide = GeoUtility.isClockwise(currentMark.getSubMark(1), + currentMark.getSubMark(2), + GameState.getMarkOrder().getNextMark(currentMarkSeqID).getSubMark(1)); + if (hasPassedThroughGate) { - checkMarkRounding(currentMark); + if (prevMarkSide == nextMarkSide) { + checkMarkRounding(currentMark); + } else { + currentMarkSeqID++; + logMarkRounding(currentMark); + } } } @@ -291,7 +313,10 @@ public class Yacht { currentMark.getSubMark(2), previousMark.getSubMark(1)); if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) { + currentMarkSeqID++; + finishedRace = true; logMarkRounding(currentMark); + System.out.println("YAY YOU FINISHED!"); // TODO: 8/08/17 wmu16 - Do something! } } @@ -376,7 +401,6 @@ public class Yacht { } private void turnTowardsHeading(Double newHeading) { - System.out.println(newHeading); if (heading < 90 && newHeading > 270) { adjustHeading(-TURN_STEP); } else { @@ -566,8 +590,8 @@ public class Yacht { this.velocity = velocity; } - public Double getDistanceToNextMark() { - return distanceToNextMark; + public Double getDistanceToCurrentMark() { + return distanceToCurrentMark; } public void updateLocation(double lat, double lng, double heading, double velocity) { diff --git a/src/main/resources/server_config/xml_templates/race.ftlh b/src/main/resources/server_config/xml_templates/race.ftlh index e38bf0b7..e8421c5c 100644 --- a/src/main/resources/server_config/xml_templates/race.ftlh +++ b/src/main/resources/server_config/xml_templates/race.ftlh @@ -36,13 +36,6 @@ - - - - - - - diff --git a/src/test/java/seng302/model/YachtTest.java b/src/test/java/seng302/model/YachtTest.java index 739c8a28..78192e70 100644 --- a/src/test/java/seng302/model/YachtTest.java +++ b/src/test/java/seng302/model/YachtTest.java @@ -47,7 +47,7 @@ public class YachtTest { // @Test // public void testDistanceToNextMark() { // Double actual, expected; -// actual = yacht.calcDistanceToNextMark(); +// actual = yacht.calcDistanceToCurrentMark(); // expected = 927d; // assertEquals(expected, actual, expected * toleranceRatio); // } From 87f2f1fe637976e9bfa44ec7e2c78a709aca635a Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 10 Aug 2017 12:07:47 +1200 Subject: [PATCH 08/29] Fixed Mark rounding Algorithm Algorithm now knows when a player has to round a gate or just pass right through #story[1124] #pair[wmu16, hyi25] --- src/main/resources/server_config/xml_templates/race.ftlh | 7 +++++++ src/test/java/seng302/models/MarkOrderTest.java | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/main/resources/server_config/xml_templates/race.ftlh b/src/main/resources/server_config/xml_templates/race.ftlh index e8421c5c..e38bf0b7 100644 --- a/src/main/resources/server_config/xml_templates/race.ftlh +++ b/src/main/resources/server_config/xml_templates/race.ftlh @@ -36,6 +36,13 @@ + + + + + + + diff --git a/src/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java index 42531563..d30f999a 100644 --- a/src/test/java/seng302/models/MarkOrderTest.java +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -42,6 +42,9 @@ public class MarkOrderTest { @Test public void testGetNextMark() { currentSeqID = 4; + for (CompoundMark mark : markOrder.getMarkOrder()) { + System.out.println(mark.getName()); + } CompoundMark nextMark = markOrder.getMarkOrder().get(4 + 1); assertEquals(nextMark, markOrder.getNextMark(currentSeqID)); From abb15f6edf04e7375acb76acd8bb883427da6488 Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 10 Aug 2017 12:08:03 +1200 Subject: [PATCH 09/29] Fixed Mark rounding Algorithm Algorithm now knows when a player has to round a gate or just pass right through #story[1124] #pair[wmu16, hyi25] --- src/test/java/seng302/models/MarkOrderTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java index d30f999a..42531563 100644 --- a/src/test/java/seng302/models/MarkOrderTest.java +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -42,9 +42,6 @@ public class MarkOrderTest { @Test public void testGetNextMark() { currentSeqID = 4; - for (CompoundMark mark : markOrder.getMarkOrder()) { - System.out.println(mark.getName()); - } CompoundMark nextMark = markOrder.getMarkOrder().get(4 + 1); assertEquals(nextMark, markOrder.getNextMark(currentSeqID)); From 9c79897e013e07e3a59fa0942e0b6954c00ff0f8 Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 10 Aug 2017 13:58:32 +1200 Subject: [PATCH 10/29] Tidied code, added MidPoint to CompoundMark class Compound Mark class is now constructed with a list of marks. A mid point is created on its construction for use in Geo Calculations #story[1124] #pair[wmu16, hyi25] --- .../simulator/parsers/CourseParser.java | 15 +-- src/main/java/seng302/model/Yacht.java | 95 ++++++++----------- .../java/seng302/model/mark/CompoundMark.java | 64 ++++--------- .../java/seng302/utilities/GeoUtility.java | 13 +++ .../java/seng302/utilities/XMLParser.java | 4 +- src/test/java/seng302/model/YachtTest.java | 9 +- 6 files changed, 87 insertions(+), 113 deletions(-) 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 66def8a1..e4cbf676 100644 --- a/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java +++ b/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java @@ -79,18 +79,19 @@ public class CourseParser extends FileParser { if (node.getNodeType() == Node.ELEMENT_NODE) { Element e = (Element) node; Integer markID = Integer.valueOf(e.getAttribute("CompoundMarkID")); - String name = e.getAttribute("Name"); - CompoundMark cMark = new CompoundMark(markID, name); NodeList marks = e.getElementsByTagName("Mark"); - for (int i = 0; i < marks.getLength(); i++) { + List subMarks = new ArrayList<>(); + for (int i = 0; i < marks.getLength(); i++) { Mark mark = getMark(marks.item(i)); - if (mark != null) - cMark.addSubMarks(mark); + if (mark != null) { + subMarks.add(mark); + } } - return cMark; - } + + return new CompoundMark(markID, name, subMarks); + } System.out.println("Failed to create compound mark."); return null; } diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 95d117fe..94f351c5 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -64,8 +64,7 @@ public class Yacht { //MARK ROUNDING INFO private GeoPoint lastLocation; //For purposes of mark rounding calculations private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round - private Boolean hasPassedFirstLine; //The line extrapolated from the next mark to the current mark - private Boolean hasPassedSecondLine; + private Boolean hasPassedLine; private Boolean hasPassedThroughGate; private Boolean finishedRace; @@ -93,8 +92,7 @@ public class Yacht { this.velocity = 0d; //in mms-1 this.hasEnteredRoundingZone = false; - this.hasPassedFirstLine = false; - this.hasPassedSecondLine = false; + this.hasPassedLine = false; this.hasPassedThroughGate = false; this.finishedRace = false; } @@ -193,13 +191,13 @@ public class Yacht { * @param currentMark The current gate */ private void checkStartLineCrossing(CompoundMark currentMark) { - Integer crossedLine = GeoUtility.checkCrossedLine(currentMark.getSubMark(1), - currentMark.getSubMark(2), lastLocation, location); + Mark mark1 = currentMark.getSubMark(1); + Mark mark2 = currentMark.getSubMark(2); + CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); + + Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location); if (crossedLine > 0) { - CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); - Boolean isClockwiseCross = GeoUtility.isClockwise(currentMark.getSubMark(1), - currentMark.getSubMark(2), - nextMark.getSubMark(1)); + Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint()); if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) { currentMarkSeqID++; logMarkRounding(currentMark); @@ -211,49 +209,31 @@ public class Yacht { /** * This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute * of the yacht if so. - * The algorithm works by using the last mark, the current mark, the next mark and the change in - * boats location, like so: - * -Condition 1: - * The boat has entered the mark rounding distance - * -Condition 2: - * The boat has passed the line extending from the last mark to the current mark - * -Condition 3: - * The boat has passed the line extending from the next mark to the current mark - * - * A more visual representation of this algorithm can be seen on the Wiki under + * A visual representation of this algorithm can be seen on the Wiki under * 'mark passing algorithm' */ private void checkMarkRounding(CompoundMark currentMark) { distanceToCurrentMark = calcDistanceToCurrentMark(); - CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); - CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); + GeoPoint nextPoint = GameState.getMarkOrder().getNextMark(currentMarkSeqID).getMidPoint(); + GeoPoint prevPoint = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID) + .getMidPoint(); + GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint); //1 TEST FOR ENTERING THE ROUNDING DISTANCE if (distanceToCurrentMark < ROUNDING_DISTANCE) { hasEnteredRoundingZone = true; } - //If the current mark is a gate mark we need to check both its marks for rounding, thus - //we loop + //In case current mark is a gate, loop through all marks just in case for (Mark thisCurrentMark : currentMark.getMarks()) { - //2 TEST FOR CROSSING NEXT - CURRENT LINE FIRST - if (GeoUtility - .isPointInTriangle(lastLocation, location, nextMark.getMarks().get(0), - thisCurrentMark)) { - hasPassedFirstLine = true; - } - //3 TEST FOR CROSSING PREV - CURRENT LINE SECOND - if (GeoUtility - .isPointInTriangle(lastLocation, location, prevMark.getMarks().get(0), - thisCurrentMark)) { - hasPassedSecondLine = true; + if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) { + hasPassedLine = true; } } - if (hasPassedSecondLine && hasPassedFirstLine && hasEnteredRoundingZone) { + if (hasPassedLine && hasEnteredRoundingZone) { currentMarkSeqID++; - hasPassedFirstLine = false; - hasPassedSecondLine = false; + hasPassedLine = false; hasEnteredRoundingZone = false; hasPassedThroughGate = false; logMarkRounding(currentMark); @@ -267,29 +247,28 @@ public class Yacht { * @param currentMark The current gate */ private void checkGateRounding(CompoundMark currentMark) { - Integer crossedLine = GeoUtility.checkCrossedLine(currentMark.getSubMark(1), - currentMark.getSubMark(2), lastLocation, location); + Mark mark1 = currentMark.getSubMark(1); + Mark mark2 = currentMark.getSubMark(2); + CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); + CompoundMark nextMark = GameState.getMarkOrder().getNextMark(currentMarkSeqID); + + Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location); //We have crossed the line if (crossedLine > 0) { - CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); - Boolean isClockwiseCross = GeoUtility.isClockwise(currentMark.getSubMark(1), - currentMark.getSubMark(2), - prevMark.getSubMark(1)); + Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint()); + + //Check we cross the line in the correct direction if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) { hasPassedThroughGate = true; } } - Boolean prevMarkSide = GeoUtility.isClockwise(currentMark.getSubMark(1), - currentMark.getSubMark(2), - GameState.getMarkOrder().getPreviousMark(currentMarkSeqID).getSubMark(1)); - - Boolean nextMarkSide = GeoUtility.isClockwise(currentMark.getSubMark(1), - currentMark.getSubMark(2), - GameState.getMarkOrder().getNextMark(currentMarkSeqID).getSubMark(1)); + Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint()); + Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint()); if (hasPassedThroughGate) { + //Check if we need to round this gate after passing through if (prevMarkSide == nextMarkSide) { checkMarkRounding(currentMark); } else { @@ -300,18 +279,18 @@ public class Yacht { } /** - * If we pass the finish gate in the correct direction // TODO: 8/08/17 wmu16 - do something + * If we pass the finish gate in the correct direction * * @param currentMark The current gate */ private void checkFinishLineCrossing(CompoundMark currentMark) { - Integer crossedLine = GeoUtility.checkCrossedLine(currentMark.getSubMark(1), - currentMark.getSubMark(2), lastLocation, location); + Mark mark1 = currentMark.getSubMark(1); + Mark mark2 = currentMark.getSubMark(2); + CompoundMark prevMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); + + Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location); if (crossedLine > 0) { - CompoundMark previousMark = GameState.getMarkOrder().getPreviousMark(currentMarkSeqID); - Boolean isClockwiseCross = GeoUtility.isClockwise(currentMark.getSubMark(1), - currentMark.getSubMark(2), - previousMark.getSubMark(1)); + Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint()); if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) { currentMarkSeqID++; finishedRace = true; diff --git a/src/main/java/seng302/model/mark/CompoundMark.java b/src/main/java/seng302/model/mark/CompoundMark.java index af4cd8a8..a4cc8d0c 100644 --- a/src/main/java/seng302/model/mark/CompoundMark.java +++ b/src/main/java/seng302/model/mark/CompoundMark.java @@ -1,8 +1,9 @@ package seng302.model.mark; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import seng302.model.GeoPoint; +import seng302.utilities.GeoUtility; public class CompoundMark { @@ -10,18 +11,17 @@ public class CompoundMark { private String name; private List marks = new ArrayList<>(); + private GeoPoint midPoint; - public CompoundMark(int markID, String name) { - this.compoundMarkId = markID; + public CompoundMark(int markID, String name, List marks) { + this.compoundMarkId = markID; this.name = name; - } - - public void addSubMarks(Mark... marks) { - this.marks.addAll(Arrays.asList(marks)); - } - - public void addSubMarks(List marks) { - this.marks.addAll(marks); + this.marks.addAll(marks); + if (marks.size() > 1) { + this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1)); + } else { + this.midPoint = marks.get(0); + } } /** @@ -68,6 +68,16 @@ public class CompoundMark { } } + /** + * NOTE: This is a 'dirty' mid point as it is simply calculated as an xy point would be. + * NO CHECKING FOR LAT / LNG WRAPPING IS DONE IN CREATION OF THIS MIDPOINT + * + * @return GeoPoint of the midpoint of the two marks, or the one mark if there is only one + */ + public GeoPoint getMidPoint() { + return midPoint; + } + /** * Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a * specific singleMark or the list of marks. @@ -87,38 +97,6 @@ public class CompoundMark { return marks; } - -// @Override -// public boolean equals(Object other) { -// if (other == null) { -// return false; -// } -// -// if (!(other instanceof Mark)){ -// return false; -// } -// -// Mark otherMark = (Mark) other; -// -// if (otherMark.getLat() != getLat() || otherMark.getLongitude() != getLongitude()) { -// return false; -// } -// -// if (otherMark.getCompoundMarkID() != getCompoundMarkID()){ -// return false; -// } -// -// if (otherMark.getId() != getId()){ -// return false; -// } -// -// if (!otherMark.getName().equals(name)){ -// return false; -// } -// -// return true; -// } - @Override public int hashCode() { int hash = 0; diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 5d96f33e..f6968601 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -45,6 +45,19 @@ public class GeoUtility { return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0; } + + /** + * WARNING: this function DOES NOT account for wrapping around on lats / longs etc. + * SO BE CAREFUL IN USING THIS FUNCTION + * + * @param p1 GeoPoint 1 + * @param p2 GeoPoint 2 + * @return GeoPoint midPoint + */ + public static GeoPoint getDirtyMidPoint(GeoPoint p1, GeoPoint p2) { + return new GeoPoint((p1.getLat() + p2.getLat()) / 2, (p1.getLng() + p2.getLng()) / 2); + } + /** * Calculates the angle between to angular co-ordinates on a sphere in radians. * diff --git a/src/main/java/seng302/utilities/XMLParser.java b/src/main/java/seng302/utilities/XMLParser.java index c231535d..c4c4348b 100644 --- a/src/main/java/seng302/utilities/XMLParser.java +++ b/src/main/java/seng302/utilities/XMLParser.java @@ -256,9 +256,9 @@ public class XMLParser { if (cMarkNode.getNodeName().equals("CompoundMark")) { cMark = new CompoundMark( XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"), - XMLParser.getNodeAttributeString(cMarkNode, "Name") + XMLParser.getNodeAttributeString(cMarkNode, "Name"), + createMarks(cMarkNode) ); - cMark.addSubMarks(createMarks(cMarkNode)); allMarks.add(cMark); } } diff --git a/src/test/java/seng302/model/YachtTest.java b/src/test/java/seng302/model/YachtTest.java index 78192e70..bc3b8c80 100644 --- a/src/test/java/seng302/model/YachtTest.java +++ b/src/test/java/seng302/model/YachtTest.java @@ -2,6 +2,8 @@ package seng302.model; import static org.junit.Assert.*; +import java.util.ArrayList; +import java.util.List; import org.junit.Before; import org.junit.Test; import seng302.model.mark.CompoundMark; @@ -33,11 +35,12 @@ public class YachtTest { yacht.setLocation(57.670333, 11.827833); - compoundMark = new CompoundMark(0, "HaomingsMark"); + List subMarks = new ArrayList<>(); Mark subMark1 = new Mark("H", 57.671524, 11.844495, 0); Mark subMark2 = new Mark("H", 57.670822, 11.843392, 0); - compoundMark.addSubMarks(subMark1, subMark2); - + subMarks.add(subMark1); + subMarks.add(subMark2); + compoundMark = new CompoundMark(0, "HaomingsMark", subMarks); } From 430779c94375bd4664d1dc3b76724746c4eae026 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Thu, 10 Aug 2017 14:53:24 +1200 Subject: [PATCH 11/29] Boat auto pilots correctly for tacking/gybing. Needs proper testing. #story[1105] --- src/main/java/seng302/model/Yacht.java | 86 +++++++++++++++++++------- 1 file changed, 63 insertions(+), 23 deletions(-) diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 9804314c..e1d7f5da 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -48,7 +48,7 @@ public class Yacht { private Integer legNumber = 0; //SERVER SIDE - private final Double TURN_STEP = 5.0; + private final Double TURN_STEP = 3.0; private Double lastHeading; private Boolean sailIn; private GeoPoint location; @@ -102,6 +102,7 @@ public class Yacht { Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading); Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle); Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000; + // TODO: 10/08/17 ajm412: this acceleration stuff should be its own method, and shouldn't have all these magic numbers, could possibly be modelled better. if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) { if (velocity < maxBoatSpeed) { @@ -112,7 +113,6 @@ public class Yacht { } } else { // Deceleration - if (velocity > 0d) { if (maxBoatSpeed != 0d) { velocity -= maxBoatSpeed / 600; @@ -126,10 +126,7 @@ public class Yacht { } if (isAuto) { - turnTowardsHeading(autoHeading); - if (Math.abs(heading - autoHeading) <= 5) { - isAuto = false; - } + runAutoPilot(); } //UPDATE BOAT LOCATION @@ -144,7 +141,6 @@ public class Yacht { // TODO: 3/08/17 wmu16 - Implement line cross check here } - /** * Calculates the distance to the next mark (closest of the two if a gate mark). * @@ -164,6 +160,12 @@ 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; @@ -171,17 +173,46 @@ public class Yacht { } /** - * Should tell boat to auto pilot towards the autopilot heading. + * Swaps the boats direction from one side of the wind to the other. */ public void tackGybe(Double windDirection) { Double normalizedHeading = normalizeHeading(); - setAutoPilot(-2 * normalizedHeading); + Double newVal = (-2 * normalizedHeading) + heading; + //newVal += 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; - Double newVal = heading + thisHeading; - autoHeading = (double) Math.floorMod(newVal.longValue(), 360L); + 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. + */ + private void runAutoPilot() { + if (autoHeading == null) { + isAuto = false; + // TODO: 10/08/17 possibly throw some sort of exception here maybe? autopilot shouldn't be true if there's no heading. + } + turnTowardsHeading(autoHeading); + if (Math.abs(heading - autoHeading) + <= TURN_STEP) { //Cancel when within 1 turn step of target. + isAuto = false; + } } public void toggleSailIn() { @@ -231,6 +262,7 @@ public class Yacht { } public void turnToVMG() { + // TODO: 10/08/17 ajm412: The way this works is absolute rubbish. Needs to determine upwind/downwind, then which side of the wind, then move to the correct values. Double normalizedHeading = normalizeHeading(); Double optimalHeading; HashMap optimalPolarMap; @@ -242,27 +274,37 @@ public class Yacht { optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots()); optimalHeading = optimalPolarMap.keySet().iterator().next(); } - // Take optimal heading and turn into correct + + // Take optimal heading and turn into a boat heading rather than a wind heading. optimalHeading = optimalHeading + (double) Math.floorMod(GameState.getWindDirection().longValue(), 360L); - turnTowardsHeading(optimalHeading); - + 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) { System.out.println(newHeading); - if (heading < 90 && newHeading > 270) { - adjustHeading(-TURN_STEP); + Double newVal = heading - newHeading; + if (Math.floorMod(newVal.longValue(), 360L) > 180) { + adjustHeading(TURN_STEP); } else { - if (heading < newHeading) { - adjustHeading(TURN_STEP); - } else { - adjustHeading(-TURN_STEP); - } + 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); @@ -431,7 +473,6 @@ public class Yacht { this.timeTillNext = timeTillNext; } - public Color getColour() { return colour; } @@ -440,7 +481,6 @@ public class Yacht { this.colour = colour; } - public Double getVelocity() { return velocity; } From 1d7b5271308df390bbb05dcb0513182c5fedcf99 Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 10 Aug 2017 16:45:30 +1200 Subject: [PATCH 12/29] Tidied code. Added tests #story[1124] #pair[wmu16, hyi25] --- src/test/java/seng302/model/YachtTest.java | 59 ----------------- .../seng302/model/mark/CompoundMarkTest.java | 66 +++++++++++++++++++ .../seng302/utilities/GeoUtilityTest.java | 7 ++ 3 files changed, 73 insertions(+), 59 deletions(-) delete mode 100644 src/test/java/seng302/model/YachtTest.java create mode 100644 src/test/java/seng302/model/mark/CompoundMarkTest.java diff --git a/src/test/java/seng302/model/YachtTest.java b/src/test/java/seng302/model/YachtTest.java deleted file mode 100644 index bc3b8c80..00000000 --- a/src/test/java/seng302/model/YachtTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package seng302.model; - -import static org.junit.Assert.*; - -import java.util.ArrayList; -import java.util.List; -import org.junit.Before; -import org.junit.Test; -import seng302.model.mark.CompoundMark; -import seng302.model.mark.Mark; - -/** - * Use this link to test geo distances - * http://www.csgnetwork.com/gpsdistcalc.html - * Created by wmu16 on 3/08/17. - */ -public class YachtTest { - - private Yacht yacht; - private CompoundMark compoundMark; - private Double toleranceRatio = 0.01; - private GeoPoint p1 = new GeoPoint(57.670333, 11.827833); - private GeoPoint p2 = new GeoPoint(57.671524, 11.844495); - private GeoPoint p3 = new GeoPoint(57.670822, 11.843392); - private GeoPoint p4 = new GeoPoint(25.694829, 98.392049); - - @Before - public void setup() { - yacht = new Yacht("Yacht", - 0, - "0", - "WillIsCool", - "HaomingIsOk", - "NZL"); - - yacht.setLocation(57.670333, 11.827833); - - List subMarks = new ArrayList<>(); - Mark subMark1 = new Mark("H", 57.671524, 11.844495, 0); - Mark subMark2 = new Mark("H", 57.670822, 11.843392, 0); - subMarks.add(subMark1); - subMarks.add(subMark2); - compoundMark = new CompoundMark(0, "HaomingsMark", subMarks); - } - - - //This will no longer work as we cant set the next mark any more as we no longer hold it in - //yacht class as an attribute - -// @Test -// public void testDistanceToNextMark() { -// Double actual, expected; -// actual = yacht.calcDistanceToCurrentMark(); -// expected = 927d; -// assertEquals(expected, actual, expected * toleranceRatio); -// } - - -} \ No newline at end of file diff --git a/src/test/java/seng302/model/mark/CompoundMarkTest.java b/src/test/java/seng302/model/mark/CompoundMarkTest.java new file mode 100644 index 00000000..55ccebf3 --- /dev/null +++ b/src/test/java/seng302/model/mark/CompoundMarkTest.java @@ -0,0 +1,66 @@ +package seng302.model.mark; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import seng302.model.GeoPoint; + +/** + * A class to test the compound mark calss + * Created by wmu16 on 10/08/17. + */ +public class CompoundMarkTest { + + private Mark mark1; + private Mark mark2; + private CompoundMark gateMark; + private CompoundMark singleMark; + + private static Double TOLERANCE_RATIO = 0.01; + + + @Before + public void setUp() throws Exception { + mark1 = new Mark("Mark1", 57.670333, 11.842833, 0); + mark2 = new Mark("Mark2", 57.671524, 11.844495, 1); + + List gateMarks = new ArrayList(); + gateMarks.add(mark1); + gateMarks.add(mark2); + + List singleMarks = new ArrayList(); + singleMarks.add(mark1); + + gateMark = new CompoundMark(0, "Fun Mark", gateMarks); + singleMark = new CompoundMark(1, "Awesome Mark", singleMarks); + } + + + @Test + public void getSubMark() throws Exception { + assertEquals(mark1, gateMark.getSubMark(1)); + assertEquals(mark2, gateMark.getSubMark(2)); + + assertEquals(mark1, singleMark.getSubMark(1)); + } + + @Test + public void getMidPoint() throws Exception { + GeoPoint result = gateMark.getMidPoint(); + assertEquals(57.6709285, result.getLat(), result.getLat() * TOLERANCE_RATIO); + assertEquals(11.843664, result.getLng(), result.getLng() * TOLERANCE_RATIO); + + result = singleMark.getMidPoint(); + assertEquals(result, mark1); + } + + @Test + public void isGate() throws Exception { + assertTrue(gateMark.isGate()); + assertFalse(singleMark.isGate()); + } + +} \ No newline at end of file diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java index 109b1723..ca2af0e9 100644 --- a/src/test/java/seng302/utilities/GeoUtilityTest.java +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -170,4 +170,11 @@ public class GeoUtilityTest { assertTrue(GeoUtility.checkCrossedLine(mark2, mark1, location2, location3) == 2); assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location3, location2) == 2); } + + @Test + public void testDirtyMiddlePoint() { + GeoPoint result = GeoUtility.getDirtyMidPoint(p1, p2); + assertEquals(57.6709285, result.getLat(), result.getLat() * toleranceRate); + assertEquals(11.836164, result.getLng(), result.getLng() * toleranceRate); + } } \ No newline at end of file From a746191dba85ed7fbf6407bfe0e48f14a71d0492 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Thu, 10 Aug 2017 17:46:28 +1200 Subject: [PATCH 13/29] VMG works correctly, auto pilot is interupted by a repeated keypress or an upwind/downwind press. tags: #story[1105] --- src/main/java/seng302/model/Yacht.java | 57 ++++++++++++++++---------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index e1d7f5da..b7abf817 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -176,11 +176,14 @@ public class Yacht { * Swaps the boats direction from one side of the wind to the other. */ public void tackGybe(Double windDirection) { - Double normalizedHeading = normalizeHeading(); - Double newVal = (-2 * normalizedHeading) + heading; - //newVal += heading; - Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L); - setAutoPilot(newHeading); + if (isAuto) { + disableAutoPilot(); + } else { + Double normalizedHeading = normalizeHeading(); + Double newVal = (-2 * normalizedHeading) + heading; + Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L); + setAutoPilot(newHeading); + } } /** @@ -220,6 +223,7 @@ public class Yacht { } public void turnUpwind() { + disableAutoPilot(); Double normalizedHeading = normalizeHeading(); if (normalizedHeading == 0) { if (lastHeading < 180) { @@ -241,6 +245,7 @@ public class Yacht { } public void turnDownwind() { + disableAutoPilot(); Double normalizedHeading = normalizeHeading(); if (normalizedHeading == 0) { if (lastHeading < 180) { @@ -261,25 +266,36 @@ 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() { - // TODO: 10/08/17 ajm412: The way this works is absolute rubbish. Needs to determine upwind/downwind, then which side of the wind, then move to the correct values. - 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()); + Double normalizedHeading = normalizeHeading(); + Double optimalHeading; + HashMap optimalPolarMap; + + if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind + optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots()); + } else { + 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 + (double) Math + .floorMod(GameState.getWindDirection().longValue(), 360L); + + setAutoPilot(optimalHeading); } - - // Take optimal heading and turn into a boat heading rather than a wind heading. - optimalHeading = - optimalHeading + (double) Math.floorMod(GameState.getWindDirection().longValue(), 360L); - - setAutoPilot(optimalHeading); } /** @@ -290,7 +306,6 @@ public class Yacht { * @param newHeading The heading to turn the yacht towards. */ private void turnTowardsHeading(Double newHeading) { - System.out.println(newHeading); Double newVal = heading - newHeading; if (Math.floorMod(newVal.longValue(), 360L) > 180) { adjustHeading(TURN_STEP); From 0b8ad137b30e3769fc662da5fcbdcb8f24790780 Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 10 Aug 2017 17:47:24 +1200 Subject: [PATCH 14/29] Minor fixes for merge #story[1124] #pair[wmu16, hyi25] --- src/main/java/seng302/model/Yacht.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 94f351c5..e676bd80 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -32,8 +32,6 @@ public class Yacht { private Logger logger = LoggerFactory.getLogger(Yacht.class); - - private static final Integer SPEED_MULTIPLIER = 4; private static final Double ROUNDING_DISTANCE = 50d; // TODO: 3/08/17 wmu16 - Look into this value further @@ -106,7 +104,7 @@ public class Yacht { Double windSpeedKnots = GameState.getWindSpeedKnots(); Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading); Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle); - Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000 * SPEED_MULTIPLIER; + Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000; if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) { if (velocity < maxBoatSpeed) { @@ -295,7 +293,7 @@ public class Yacht { currentMarkSeqID++; finishedRace = true; logMarkRounding(currentMark); - System.out.println("YAY YOU FINISHED!"); + logger.debug(sourceId + " finished"); // TODO: 8/08/17 wmu16 - Do something! } } @@ -588,10 +586,13 @@ public class Yacht { if (currentMark.isGate()) { typeString = "gate"; } - System.out.println( - "(" + currentMarkSeqID + ") Passed " + typeString + ": " + currentMark.getMarks().get(0) - .getName() - + " ID(" + currentMark.getId() + ")"); + logger.debug( + String.format("BoatID %d passed %s %s with id %d. Now on leg %d", + sourceId, + typeString, + currentMark.getMarks().get(0).getName(), + currentMark.getId(), + currentMarkSeqID)); } public void addLocationListener (YachtLocationListener listener) { From a2ee4411be7b748edffc0685f32b12061fe9bea8 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Thu, 10 Aug 2017 17:59:19 +1200 Subject: [PATCH 15/29] Merge branch 'develop' into Story71_TackAndGybeSmoothly --- .../simulator/parsers/CourseParser.java | 14 ++++---- .../java/seng302/model/mark/CompoundMark.java | 2 +- .../java/seng302/model/mark/MarkOrder.java | 35 +++++++++---------- .../java/seng302/visualiser/GameClient.java | 4 +-- .../seng302/model/mark/CompoundMarkTest.java | 4 ++- .../java/seng302/models/MarkOrderTest.java | 9 +++-- 6 files changed, 34 insertions(+), 34 deletions(-) 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/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 b00a6cda..5b3ba1f5 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; From 1c2870649ada04ede72aae8df9acf97467572308 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Thu, 10 Aug 2017 18:58:38 +1200 Subject: [PATCH 16/29] Added testing for the tack/gybe settings. tags: #story[1105] --- src/main/java/seng302/model/Yacht.java | 23 ++++----- src/test/java/seng302/models/YachtTest.java | 53 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 14 deletions(-) create mode 100644 src/test/java/seng302/models/YachtTest.java diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 0dfe0018..d8479a94 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -131,9 +131,7 @@ public class Yacht { } } - if (isAuto) { - runAutoPilot(); - } + runAutoPilot(); //UPDATE BOAT LOCATION lastLocation = location; @@ -353,15 +351,13 @@ public class Yacht { * 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. */ - private void runAutoPilot() { - if (autoHeading == null) { - isAuto = false; - // TODO: 10/08/17 possibly throw some sort of exception here maybe? autopilot shouldn't be true if there's no heading. - } - turnTowardsHeading(autoHeading); - if (Math.abs(heading - autoHeading) - <= TURN_STEP) { //Cancel when within 1 turn step of target. - isAuto = false; + public void runAutoPilot() { + if (isAuto) { + turnTowardsHeading(autoHeading); + if (Math.abs(heading - autoHeading) + <= TURN_STEP) { //Cancel when within 1 turn step of target. + isAuto = false; + } } } @@ -438,8 +434,7 @@ public class Yacht { // Take optimal heading and turn into a boat heading rather than a wind heading. optimalHeading = - optimalHeading + (double) Math - .floorMod(GameState.getWindDirection().longValue(), 360L); + optimalHeading + GameState.getWindDirection(); setAutoPilot(optimalHeading); } diff --git a/src/test/java/seng302/models/YachtTest.java b/src/test/java/seng302/models/YachtTest.java new file mode 100644 index 00000000..00d9dea6 --- /dev/null +++ b/src/test/java/seng302/models/YachtTest.java @@ -0,0 +1,53 @@ +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.Yacht; + + +public class YachtTest { + + private static Yacht y1; + //Yacht y2; + private static Double windDirection = 45d; + 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); + } + + } + + + @AfterClass + public static void tearDown() { + y1 = null; + } + +} From 09c4f98056eb090c2b12b38fbe248335367ebffb Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Thu, 10 Aug 2017 19:01:30 +1200 Subject: [PATCH 17/29] Implemented new client - server handshake protocol - Implemented new packet types - Changed server & client logic to use new protocol Tags: #story[1124] (Issue 39) --- .../java/seng302/gameServer/GameState.java | 1 + .../gameServer/ServerPacketParser.java | 27 ++-- .../gameServer/ServerToClientThread.java | 125 ++++++++++-------- .../server/messages/ClientType.java | 33 +++++ .../server/messages/MessageType.java | 6 +- .../messages/RegistrationRequestMessage.java | 22 +++ .../messages/RegistrationResponseMessage.java | 20 +++ .../messages/RegistrationResponseStatus.java | 44 ++++++ .../model/stream/packets/PacketType.java | 8 +- .../visualiser/ClientToServerThread.java | 98 +++++++++----- .../java/seng302/visualiser/GameClient.java | 1 + 11 files changed, 274 insertions(+), 111 deletions(-) create mode 100644 src/main/java/seng302/gameServer/server/messages/ClientType.java create mode 100644 src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java create mode 100644 src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java create mode 100644 src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 913f4f37..1bb968d1 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -20,6 +20,7 @@ public class GameState implements Runnable { private Logger logger = LoggerFactory.getLogger(MarkOrder.class); private static Integer STATE_UPDATES_PER_SECOND = 60; + public static Integer MAX_PLAYERS = 8; private static Long previousUpdateTime; public static Double windDirection; diff --git a/src/main/java/seng302/gameServer/ServerPacketParser.java b/src/main/java/seng302/gameServer/ServerPacketParser.java index 0d631eb1..21841f18 100644 --- a/src/main/java/seng302/gameServer/ServerPacketParser.java +++ b/src/main/java/seng302/gameServer/ServerPacketParser.java @@ -1,37 +1,26 @@ package seng302.gameServer; import java.util.Arrays; + +import seng302.gameServer.server.messages.ClientType; +import seng302.gameServer.server.messages.Message; import seng302.model.stream.packets.StreamPacket; import seng302.gameServer.server.messages.BoatActionType; public class ServerPacketParser { - public static BoatActionType extractBoatAction(StreamPacket packet) { byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; - long actionTypeValue = bytesToLong(Arrays.copyOfRange(payload, 0, 1)); + long actionTypeValue = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1)); return BoatActionType.getType((int) actionTypeValue); } - /** - * takes an array of up to 7 bytes and returns a positive - * long constructed from the input bytes - * - * @return a positive long if there is less than 7 bytes -1 otherwise - */ - private static long bytesToLong(byte[] bytes) { - long partialLong = 0; - int index = 0; - for (byte b : bytes) { - if (index > 6) { - return -1; - } - partialLong = partialLong | (b & 0xFFL) << (index * 8); - index++; - } - return partialLong; + public static ClientType extractClientType(StreamPacket packet){ + byte[] payload = packet.getPayload(); + long value = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1)); + return ClientType.getClientType((int) value); } } diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index ff2c4f23..20001fc1 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -18,6 +18,8 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.zip.CRC32; import java.util.zip.Checksum; + +import seng302.gameServer.server.messages.*; import seng302.model.Player; import seng302.model.Yacht; import seng302.model.stream.packets.PacketType; @@ -25,16 +27,6 @@ import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.xml.generator.Race; import seng302.model.stream.xml.generator.Regatta; import seng302.utilities.XMLGenerator; -import seng302.gameServer.server.messages.BoatActionType; -import seng302.gameServer.server.messages.BoatLocationMessage; -import seng302.gameServer.server.messages.BoatStatus; -import seng302.gameServer.server.messages.BoatSubMessage; -import seng302.gameServer.server.messages.Message; -import seng302.gameServer.server.messages.RaceStatus; -import seng302.gameServer.server.messages.RaceStatusMessage; -import seng302.gameServer.server.messages.RaceType; -import seng302.gameServer.server.messages.XMLMessage; -import seng302.gameServer.server.messages.XMLMessageSubType; /** * A class describing a single connection to a Client for the purposes of sending and receiving on @@ -62,10 +54,27 @@ public class ServerToClientThread implements Runnable, Observer { private Integer seqNo; private Integer sourceId; + private ClientType clientType; + private Boolean isRegistered = false; + private XMLGenerator xml; public ServerToClientThread(Socket socket) { this.socket = socket; + seqNo = 0; + + try{ + is = socket.getInputStream(); + os = socket.getOutputStream(); + } catch (IOException e) { + return; + } + + thread = new Thread(this); + thread.start(); + } + + private void setUpYacht(){ BufferedReader fn; String fName = ""; BufferedReader ln; @@ -74,45 +83,33 @@ public class ServerToClientThread implements Runnable, Observer { is = socket.getInputStream(); os = socket.getOutputStream(); fn = new BufferedReader( - new InputStreamReader( - ServerToClientThread.class.getResourceAsStream( - "/server_config/CSV_Database_of_First_Names.csv" + new InputStreamReader( + ServerToClientThread.class.getResourceAsStream( + "/server_config/CSV_Database_of_First_Names.csv" + ) ) - ) ); List all = fn.lines().collect(Collectors.toList()); fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); ln = new BufferedReader( - new InputStreamReader( - ServerToClientThread.class.getResourceAsStream( - "/server_config/CSV_Database_of_Last_Names.csv" + new InputStreamReader( + ServerToClientThread.class.getResourceAsStream( + "/server_config/CSV_Database_of_Last_Names.csv" + ) ) - ) ); all = ln.lines().collect(Collectors.toList()); lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); } catch (IOException e) { serverLog("IO error in server thread upon grabbing streams", 1); } - //Attempt threeway handshake with connection - sourceId = GameState.getUniquePlayerID(); - if (threeWayHandshake(sourceId)) { - serverLog("Successful handshake. Client allocated id: " + sourceId, 0); - Yacht yacht = new Yacht( - "Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" - ); -// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0); - GameState.addYacht(sourceId, yacht); - GameState.addPlayer(new Player(socket, yacht)); - } else { - serverLog("Unsuccessful handshake. Connection rejected", 1); - closeSocket(); - return; - } - seqNo = 0; - thread = new Thread(this); - thread.start(); + Yacht yacht = new Yacht( + "Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" + ); + + GameState.addYacht(sourceId, yacht); + GameState.addPlayer(new Player(socket, yacht)); } static void serverLog(String message, int logLevel) { @@ -127,11 +124,39 @@ public class ServerToClientThread implements Runnable, Observer { sendSetupMessages(); } + private void completeRegistration(ClientType clientType) throws IOException { + // Fail if not a player + if (!clientType.equals(ClientType.PLAYER)){ + RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_GENERAL); + os.write(responseMessage.getBuffer()); + return; + } + + if (GameState.getPlayers().size() >= GameState.MAX_PLAYERS){ + RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL); + os.write(responseMessage.getBuffer()); + return; + } + + Integer sourceId = GameState.getUniquePlayerID(); + RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(sourceId, RegistrationResponseStatus.SUCCESS_PLAYING); + + this.clientType = clientType; + this.sourceId = sourceId; + setUpYacht(); + + isRegistered = true; + os.write(responseMessage.getBuffer()); + sendSetupMessages(); + + } + public void run() { int sync1; int sync2; // TODO: 14/07/17 wmu16 - Work out how to fix this while loop + while (socket.isConnected()) { try { @@ -169,10 +194,17 @@ public class ServerToClientThread implements Runnable, Observer { switch (PacketType.assignPacketType(type, payload)) { case BOAT_ACTION: BoatActionType actionType = ServerPacketParser - .extractBoatAction( - new StreamPacket(type, payloadLength, timeStamp, payload)); + .extractBoatAction( + new StreamPacket(type, payloadLength, timeStamp, payload)); GameState.updateBoat(sourceId, actionType); break; + + case RACE_REGISTRATION_REQUEST: + ClientType requestedType = ServerPacketParser.extractClientType( + new StreamPacket(type, payloadLength, timeStamp, payload)); + + completeRegistration(requestedType); + break; } } else { serverLog("Packet has been dropped", 1); @@ -230,23 +262,6 @@ public class ServerToClientThread implements Runnable, Observer { * @return A boolean indicating if it was a successful handshake */ private Boolean threeWayHandshake(Integer id) { - Integer confirmationID = null; - Integer identificationAttempt = 0; - while (!userIdentified) { - try { - os.write(id); //Send out new ID looking for echo - confirmationID = is.read(); - } catch (IOException e) { - serverLog("Three way handshake failed", 1); - } - - if (id.equals(confirmationID)) { //ID is echoed back. Connection is a client - return true; - } else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home. - return false; - } - identificationAttempt++; - } return true; } diff --git a/src/main/java/seng302/gameServer/server/messages/ClientType.java b/src/main/java/seng302/gameServer/server/messages/ClientType.java new file mode 100644 index 00000000..b96ca5c7 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/ClientType.java @@ -0,0 +1,33 @@ +package seng302.gameServer.server.messages; + +public enum ClientType { + SPECTATOR(0x00), + PLAYER(0x01), + CONTROL_TUTORIAL(0x02), + GHOST_MODE(0x03); + + private int type; + + ClientType(int type){ + this.type = type; + } + + public int getCode(){ + return type; + } + + public static ClientType getClientType(int typeCode){ + switch (typeCode){ + case 0x00: + return SPECTATOR; + case 0x01: + return PLAYER; + case 0x02: + return CONTROL_TUTORIAL; + case 0x03: + return GHOST_MODE; + default: + return PLAYER; + } + } +} diff --git a/src/main/java/seng302/gameServer/server/messages/MessageType.java b/src/main/java/seng302/gameServer/server/messages/MessageType.java index 1d25e61d..4552d781 100644 --- a/src/main/java/seng302/gameServer/server/messages/MessageType.java +++ b/src/main/java/seng302/gameServer/server/messages/MessageType.java @@ -17,7 +17,9 @@ public enum MessageType { MARK_ROUNDING(38), COURSE_WIND(44), AVERAGE_WIND(47), - BOAT_ACTION(100); + BOAT_ACTION(100), + REGISTRATION_REQUEST(101), + REGISTRATION_RESPONSE(102); private int code; @@ -32,4 +34,6 @@ public enum MessageType { int getCode(){ return this.code; } + + } diff --git a/src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java b/src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java new file mode 100644 index 00000000..59757a18 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/RegistrationRequestMessage.java @@ -0,0 +1,22 @@ +package seng302.gameServer.server.messages; + + +public class RegistrationRequestMessage extends Message { + private static int MESSAGE_LENGTH = 2; + + public RegistrationRequestMessage(ClientType type){ + setHeader(new Header(MessageType.REGISTRATION_REQUEST, 1, (short) getSize())); + + allocateBuffer(); + writeHeaderToBuffer(); + + putInt(type.getCode(), 2); + + writeCRC(); + } + + @Override + public int getSize() { + return MESSAGE_LENGTH; + } +} diff --git a/src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java new file mode 100644 index 00000000..e2174da4 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseMessage.java @@ -0,0 +1,20 @@ +package seng302.gameServer.server.messages; + +public class RegistrationResponseMessage extends Message{ + + public RegistrationResponseMessage(int clientSourceID, RegistrationResponseStatus status){ + setHeader(new Header(MessageType.REGISTRATION_RESPONSE, 1, (short) getSize())); + allocateBuffer(); + writeHeaderToBuffer(); + + putInt(clientSourceID, 4); + putInt(status.getCode(), 1); + + writeCRC(); + } + + @Override + public int getSize() { + return 5; + } +} diff --git a/src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java new file mode 100644 index 00000000..2ae47a92 --- /dev/null +++ b/src/main/java/seng302/gameServer/server/messages/RegistrationResponseStatus.java @@ -0,0 +1,44 @@ +package seng302.gameServer.server.messages; + +public enum RegistrationResponseStatus { + SUCCESS_SPECTATING(0x00), + SUCCESS_PLAYING(0x01), + SUCCESS_TUTORIAL(0x02), + SUCCESS_GHOSTING(0x03), + + FAILURE_GENERAL(0x10), + FAILURE_FULL(0x11); + + private int code; + + RegistrationResponseStatus(int code){ + this.code = code; + } + + /** + * Get the message code (From the API Spec) + * @return the message code + */ + int getCode(){ + return this.code; + } + + public static RegistrationResponseStatus getResponseStatus(int typeCode){ + switch (typeCode){ + case 0x00: + return SUCCESS_SPECTATING; + case 0x01: + return SUCCESS_PLAYING; + case 0x02: + return SUCCESS_TUTORIAL; + case 0x03: + return SUCCESS_GHOSTING; + case 0x10: + return FAILURE_GENERAL; + case 0x11: + return FAILURE_FULL; + default: + return FAILURE_GENERAL; + } + } +} diff --git a/src/main/java/seng302/model/stream/packets/PacketType.java b/src/main/java/seng302/model/stream/packets/PacketType.java index e7f55b49..1a38ae14 100644 --- a/src/main/java/seng302/model/stream/packets/PacketType.java +++ b/src/main/java/seng302/model/stream/packets/PacketType.java @@ -19,7 +19,9 @@ public enum PacketType { COURSE_WIND, AVG_WIND, BOAT_ACTION, - OTHER; + OTHER, + RACE_REGISTRATION_REQUEST, + RACE_REGISTRATION_RESPONSE; public static PacketType assignPacketType(int packetType, byte[] payload){ switch(packetType){ @@ -56,6 +58,10 @@ public enum PacketType { return AVG_WIND; case 100: return BOAT_ACTION; + case 101: + return RACE_REGISTRATION_REQUEST; + case 102: + return RACE_REGISTRATION_RESPONSE; default: } return OTHER; diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index 414696c8..f59fa81d 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -7,6 +7,7 @@ import java.io.OutputStream; import java.net.Socket; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -15,9 +16,12 @@ import java.util.zip.Checksum; import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import seng302.gameServer.server.messages.*; +import seng302.model.stream.packets.PacketType; import seng302.model.stream.packets.StreamPacket; -import seng302.gameServer.server.messages.BoatActionMessage; -import seng302.gameServer.server.messages.Message; /** * A class describing a single connection to a Server for the purposes of sending and receiving on @@ -48,8 +52,9 @@ public class ClientToServerThread implements Runnable { private Socket socket; private InputStream is; private OutputStream os; + private Logger logger = LoggerFactory.getLogger(ClientToServerThread.class); - private int clientId; + private int clientId = -1; // private Boolean updateClient = true; private ByteArrayOutputStream crcBuffer; @@ -71,15 +76,8 @@ public class ClientToServerThread implements Runnable { socket = new Socket(ipAddress, portNumber); is = socket.getInputStream(); os = socket.getOutputStream(); - Integer allocatedID = threeWayHandshake(); - if (allocatedID != null) { - clientId = allocatedID; - clientLog("Successful handshake. Allocated ID: " + clientId, 1); - } else { - clientLog("Unsuccessful handshake", 1); - closeSocket(); - return; - } + + sendRegistrationRequest(); thread = new Thread(this); thread.start(); @@ -128,9 +126,15 @@ public class ClientToServerThread implements Runnable { if (streamPackets.size() > 0) { streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); } else { - streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); - for (ClientSocketListener csl : listeners) - csl.newPacket(); + if (PacketType.RACE_REGISTRATION_RESPONSE == PacketType.assignPacketType(type, payload)){ + processRegistrationResponse(new StreamPacket(type, payloadLength, timeStamp, payload)); + } + else { + if (clientId == -1) continue; // Do not continue if not registered + streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); + for (ClientSocketListener csl : listeners) + csl.newPacket(); + } } } else { clientLog("Packet has been dropped", 1); @@ -155,36 +159,60 @@ public class ClientToServerThread implements Runnable { /** - * Listens for an allocated sourceID and returns it to the server - * - * @return the sourceID allocated to us by the server + * Sends a request to the server asking for a source ID */ - private Integer threeWayHandshake() { - Integer ourSourceID = null; - while (true) { - try { - ourSourceID = is.read(); - } catch (IOException e) { - clientLog("Three way handshake failed", 1); - } - if (ourSourceID != null) { - try { - os.write(ourSourceID); - return ourSourceID; - } catch (IOException e) { - clientLog("Three way handshake failed", 1); - return null; - } - } + private void sendRegistrationRequest() { + RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER); + + try { + os.write(requestMessage.getBuffer()); + } catch (IOException e) { + logger.error("Could not send registration request. Exiting"); + System.exit(1); } } + /** + * Accepts a response to the registration request message, and updates the client OR quits + * @param packet The registration requests packet + */ + private void processRegistrationResponse(StreamPacket packet){ + int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 3)); + int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5)); + + RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode); + + if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){ + clientId = sourceId; + + return; + } + + logger.error("Server Denied Connection, Exiting"); + + final String alertErrorText; + + if (status.equals(RegistrationResponseStatus.FAILURE_FULL)){ + alertErrorText = "Server is full"; + } + else{ + alertErrorText = "Could not connect to server"; + } + + Platform.runLater(() -> { + new Alert(AlertType.ERROR, alertErrorText, ButtonType.OK).showAndWait(); + System.exit(1); + }); + } + /** * Send the post-start race course information * @param boatActionMessage The message to send */ public void sendBoatActionMessage(BoatActionMessage boatActionMessage) { + if (clientId == -1) return; + try { os.write(boatActionMessage.getBuffer()); } catch (IOException e) { diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index b00a6cda..c74a99ef 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -62,6 +62,7 @@ public class GameClient { ioe.printStackTrace(); System.out.println("Unable to connect to host..."); } + socketThread.addStreamObserver(this::parsePackets); LobbyController lobbyController = loadLobby(); lobbyController.setPlayerListSource(clientLobbyList); From 32b231e78a25c4ad5b9614ba69c4e6c1ee325e73 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Thu, 10 Aug 2017 19:19:41 +1200 Subject: [PATCH 18/29] Added testing for the vmg autopilot tags: #story[1107] --- src/test/java/seng302/models/YachtTest.java | 41 ++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/test/java/seng302/models/YachtTest.java b/src/test/java/seng302/models/YachtTest.java index 00d9dea6..9e33c91b 100644 --- a/src/test/java/seng302/models/YachtTest.java +++ b/src/test/java/seng302/models/YachtTest.java @@ -7,6 +7,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import seng302.gameServer.GameState; +import seng302.model.PolarTable; import seng302.model.Yacht; @@ -14,7 +15,7 @@ public class YachtTest { private static Yacht y1; //Yacht y2; - private static Double windDirection = 45d; + private static Double windDirection = 180d; private static Double windSpeed = 20d; private static GameState gs; @@ -44,6 +45,44 @@ public class YachtTest { } + @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; + + System.out.println( + String.format("%f %f %f %f", upwindLeft, upwindRight, downwindLeft, 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() { From a7b8b0dbc3695369a26b139868ad6d7ced5f0c20 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Thu, 10 Aug 2017 20:15:08 +1200 Subject: [PATCH 19/29] Removed redundant input & output streams --- .../gameServer/ServerToClientThread.java | 69 ++++++++----------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 20001fc1..8ac88848 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -1,12 +1,16 @@ package seng302.gameServer; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; +import seng302.gameServer.server.messages.*; +import seng302.model.Player; +import seng302.model.Yacht; +import seng302.model.stream.packets.PacketType; +import seng302.model.stream.packets.StreamPacket; +import seng302.model.stream.xml.generator.Race; +import seng302.model.stream.xml.generator.Regatta; +import seng302.utilities.XMLGenerator; + +import java.io.*; import java.net.Socket; import java.net.SocketException; import java.time.LocalDateTime; @@ -19,15 +23,6 @@ import java.util.stream.Collectors; import java.util.zip.CRC32; import java.util.zip.Checksum; -import seng302.gameServer.server.messages.*; -import seng302.model.Player; -import seng302.model.Yacht; -import seng302.model.stream.packets.PacketType; -import seng302.model.stream.packets.StreamPacket; -import seng302.model.stream.xml.generator.Race; -import seng302.model.stream.xml.generator.Regatta; -import seng302.utilities.XMLGenerator; - /** * A class describing a single connection to a Client for the purposes of sending and receiving on * its own thread. All server threads created and owned by the server thread handler which can @@ -79,30 +74,26 @@ public class ServerToClientThread implements Runnable, Observer { String fName = ""; BufferedReader ln; String lName = ""; - try { - is = socket.getInputStream(); - os = socket.getOutputStream(); - fn = new BufferedReader( - new InputStreamReader( - ServerToClientThread.class.getResourceAsStream( - "/server_config/CSV_Database_of_First_Names.csv" - ) - ) - ); - List all = fn.lines().collect(Collectors.toList()); - fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); - ln = new BufferedReader( - new InputStreamReader( - ServerToClientThread.class.getResourceAsStream( - "/server_config/CSV_Database_of_Last_Names.csv" - ) - ) - ); - all = ln.lines().collect(Collectors.toList()); - lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); - } catch (IOException e) { - serverLog("IO error in server thread upon grabbing streams", 1); - } + + fn = new BufferedReader( + new InputStreamReader( + ServerToClientThread.class.getResourceAsStream( + "/server_config/CSV_Database_of_First_Names.csv" + ) + ) + ); + List all = fn.lines().collect(Collectors.toList()); + fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); + ln = new BufferedReader( + new InputStreamReader( + ServerToClientThread.class.getResourceAsStream( + "/server_config/CSV_Database_of_Last_Names.csv" + ) + ) + ); + all = ln.lines().collect(Collectors.toList()); + lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); + Yacht yacht = new Yacht( "Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" From 6ddaaa0dfae1a53dd56acca50194e50691de5181 Mon Sep 17 00:00:00 2001 From: alistairjmcintyre Date: Thu, 10 Aug 2017 20:18:36 +1200 Subject: [PATCH 20/29] Removed extra print statement from test. --- src/test/java/seng302/models/YachtTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/seng302/models/YachtTest.java b/src/test/java/seng302/models/YachtTest.java index 9e33c91b..fd610dfc 100644 --- a/src/test/java/seng302/models/YachtTest.java +++ b/src/test/java/seng302/models/YachtTest.java @@ -61,9 +61,6 @@ public class YachtTest { Double downwindRight = downwind; Double downwindLeft = 360 - downwindRight; - System.out.println( - String.format("%f %f %f %f", upwindLeft, upwindRight, downwindLeft, downwindRight)); - values.put(190d, upwindRight); values.put(170d, upwindLeft); values.put(10d, downwindLeft); From d79b0421c275eb8f0eb62a77ff4404c677f875c4 Mon Sep 17 00:00:00 2001 From: Calum Date: Sun, 13 Aug 2017 19:00:06 +1200 Subject: [PATCH 21/29] Fixed merged conflicts with merge onto develop. #bug --- .../java/seng302/gameServer/GameState.java | 1 - .../gameServer/ServerPacketParser.java | 4 +- .../gameServer/ServerToClientThread.java | 41 +++++++++---------- .../visualiser/ClientToServerThread.java | 10 ++++- .../java/seng302/visualiser/GameClient.java | 20 +++++++-- .../RegularPacketsTest.java | 24 ++--------- 6 files changed, 52 insertions(+), 48 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 4e58bc50..9a54c7d1 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -7,7 +7,6 @@ import java.util.Map; import seng302.gameServer.server.messages.BoatAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import seng302.gameServer.server.messages.BoatActionType; import seng302.model.Player; import seng302.model.Yacht; import seng302.model.mark.MarkOrder; diff --git a/src/main/java/seng302/gameServer/ServerPacketParser.java b/src/main/java/seng302/gameServer/ServerPacketParser.java index 2c7538e4..84f8fd17 100644 --- a/src/main/java/seng302/gameServer/ServerPacketParser.java +++ b/src/main/java/seng302/gameServer/ServerPacketParser.java @@ -10,11 +10,11 @@ import seng302.gameServer.server.messages.BoatAction; public class ServerPacketParser { - public static BoatActionType extractBoatAction(StreamPacket packet) { + public static BoatAction extractBoatAction(StreamPacket packet) { byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; long actionTypeValue = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1)); - return BoatActionType.getType((int) actionTypeValue); + return BoatAction.getType((int) actionTypeValue); } public static ClientType extractClientType(StreamPacket packet){ diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index c4c9f9b8..2a35e430 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -1,16 +1,12 @@ package seng302.gameServer; -import seng302.gameServer.server.messages.*; -import seng302.model.Player; -import seng302.model.Yacht; -import seng302.model.stream.packets.PacketType; -import seng302.model.stream.packets.StreamPacket; -import seng302.model.stream.xml.generator.Race; -import seng302.model.stream.xml.generator.Regatta; -import seng302.utilities.XMLGenerator; - -import java.io.*; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; import java.net.Socket; import java.net.SocketException; import java.time.LocalDateTime; @@ -22,6 +18,19 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.zip.CRC32; import java.util.zip.Checksum; +import seng302.gameServer.server.messages.BoatAction; +import seng302.gameServer.server.messages.BoatLocationMessage; +import seng302.gameServer.server.messages.BoatStatus; +import seng302.gameServer.server.messages.BoatSubMessage; +import seng302.gameServer.server.messages.ClientType; +import seng302.gameServer.server.messages.Message; +import seng302.gameServer.server.messages.RaceStatus; +import seng302.gameServer.server.messages.RaceStatusMessage; +import seng302.gameServer.server.messages.RaceType; +import seng302.gameServer.server.messages.RegistrationResponseMessage; +import seng302.gameServer.server.messages.RegistrationResponseStatus; +import seng302.gameServer.server.messages.XMLMessage; +import seng302.gameServer.server.messages.XMLMessageSubType; import seng302.model.Player; import seng302.model.Yacht; import seng302.model.stream.packets.PacketType; @@ -29,16 +38,6 @@ import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.xml.generator.Race; import seng302.model.stream.xml.generator.Regatta; import seng302.utilities.XMLGenerator; -import seng302.gameServer.server.messages.BoatAction; -import seng302.gameServer.server.messages.BoatLocationMessage; -import seng302.gameServer.server.messages.BoatStatus; -import seng302.gameServer.server.messages.BoatSubMessage; -import seng302.gameServer.server.messages.Message; -import seng302.gameServer.server.messages.RaceStatus; -import seng302.gameServer.server.messages.RaceStatusMessage; -import seng302.gameServer.server.messages.RaceType; -import seng302.gameServer.server.messages.XMLMessage; -import seng302.gameServer.server.messages.XMLMessageSubType; /** * A class describing a single connection to a Client for the purposes of sending and receiving on @@ -201,7 +200,7 @@ public class ServerToClientThread implements Runnable, Observer { //System.out.println("RECEIVED A PACKET"); switch (PacketType.assignPacketType(type, payload)) { case BOAT_ACTION: - BoatActionType actionType = ServerPacketParser + BoatAction actionType = ServerPacketParser .extractBoatAction( new StreamPacket(type, payloadLength, timeStamp, payload)); GameState.updateBoat(sourceId, actionType); diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index cd0f8a90..bda0e6ec 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -18,9 +18,17 @@ import java.util.zip.Checksum; import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; -import seng302.model.stream.packets.StreamPacket; +import javafx.scene.control.ButtonType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import seng302.gameServer.server.messages.BoatAction; import seng302.gameServer.server.messages.BoatActionMessage; +import seng302.gameServer.server.messages.ClientType; import seng302.gameServer.server.messages.Message; +import seng302.gameServer.server.messages.RegistrationRequestMessage; +import seng302.gameServer.server.messages.RegistrationResponseStatus; +import seng302.model.stream.packets.PacketType; +import seng302.model.stream.packets.StreamPacket; /** * A class describing a single connection to a Server for the purposes of sending and receiving on diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 5655530d..ef22a45b 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -14,8 +14,6 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.Pane; import seng302.gameServer.MainServerThread; import seng302.gameServer.server.messages.BoatAction; -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; @@ -32,7 +30,8 @@ import seng302.visualiser.controllers.LobbyController.CloseStatus; import seng302.visualiser.controllers.RaceViewController; /** - * Created by cir27 on 20/07/17. + * This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated + * with a JavaFX Pane to insert itself into. */ public class GameClient { @@ -49,10 +48,20 @@ public class GameClient { private ObservableList clientLobbyList = FXCollections.observableArrayList(); + /** + * Create an instance of the game client. Does not do anything untill run with runAsClient() + * runAsHost(). + * @param holder The JavaFX Pane that the visual elements for the race will be inserted into. + */ public GameClient(Pane holder) { this.holderPane = holder; } + /** + * Connect to a game at the given address and starts the visualiser. + * @param ipAddress IP to connect to. + * @param portNumber Port to connect to. + */ public void runAsClient(String ipAddress, Integer portNumber) { try { socketThread = new ClientToServerThread(ipAddress, portNumber); @@ -69,6 +78,11 @@ public class GameClient { lobbyController.addCloseListener((exitCause) -> this.loadStartScreen()); } + /** + * Connect to a game as the host at the given address and starts the visualiser. + * @param ipAddress IP to connect to. + * @param portNumber Port to connect to. + */ public void runAsHost(String ipAddress, Integer portNumber) { server = new MainServerThread(); try { diff --git a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java index 41583cfe..e8064a6c 100644 --- a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java +++ b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java @@ -15,7 +15,6 @@ import seng302.visualiser.ClientToServerThread; /** * Test for checking how regularly packets are sent from ClientToServer Thread. */ -//@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class RegularPacketsTest { private MainServerThread serverThread; @@ -30,7 +29,7 @@ public class RegularPacketsTest { } @Test - public void Test1PacketsSentAtRegularIntervals () throws Exception { + public void packetsSentAtRegularIntervals () throws Exception { final double TEST_DISTANCE = 10.0; serverThread.startGame(); SleepThreadMaxDelay(); @@ -51,7 +50,7 @@ public class RegularPacketsTest { } @Test - public void Test2ArbitraryPacketSentOnRelease() throws Exception { + public void testArbitraryPacketSent() throws Exception { serverThread.startGame(); SleepThreadMaxDelay(); Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); @@ -61,21 +60,6 @@ public class RegularPacketsTest { Assert.assertEquals(startState, !yacht.getSailIn()); } - @Test - public void Test3ArbitraryPacketSentOnPress() throws Exception { - serverThread.startGame(); - SleepThreadMaxDelay(); - Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); - double heading = yacht.getHeading(); - double windDirection = GameState.getWindDirection(); - Yacht testYacht = new Yacht("", 0, "", "", "", ""); - testYacht.setHeading(heading); - testYacht.tackGybe(windDirection); - clientThread.sendBoatAction(BoatAction.TACK_GYBE); - SleepThreadMaxDelay(); - Assert.assertEquals(testYacht.getHeading(), yacht.getHeading(), 1); - } - /** * Give time for processing and packet sending. 200ms listed as absolute maximum for an * acceptable delay. @@ -90,7 +74,7 @@ public class RegularPacketsTest { serverThread.terminate(); clientThread.setSocketToClose(); GameState.setCurrentStage(GameStages.LOBBYING); - SleepThreadMaxDelay(); //Make sure socket is closed. - SleepThreadMaxDelay(); + for (int i = 0; i<6; i++) + SleepThreadMaxDelay(); //Make sure socket is closed. } } From 6218d5506b412f30fddc768e253b5e70a3bdabfa Mon Sep 17 00:00:00 2001 From: Calum Date: Sun, 13 Aug 2017 20:24:03 +1200 Subject: [PATCH 22/29] Fixed bug that stopped clients from getting updated xml data. #issue[48] #bug --- .../seng302/gameServer/MainServerThread.java | 15 +++-- .../gameServer/ServerToClientThread.java | 60 ++++++++----------- src/main/java/seng302/model/Yacht.java | 4 +- .../java/seng302/visualiser/GameClient.java | 12 ++-- 4 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index 1539730e..8c563258 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -44,6 +44,7 @@ public class MainServerThread extends Observable implements Runnable, ClientConn HeartbeatThread heartbeatThread; serverListenThread = new ServerListenThread(serverSocket, this); + heartbeatThread = new HeartbeatThread(this); heartbeatThread.start(); @@ -102,9 +103,11 @@ public class MainServerThread extends Observable implements Runnable, ClientConn public void clientConnected(ServerToClientThread serverToClientThread) { serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0); serverToClientThreads.add(serverToClientThread); - this.addObserver(serverToClientThread); - setChanged(); - notifyObservers(); + serverToClientThread.addConnectionListener(() -> { + for (ServerToClientThread thread : serverToClientThreads) { + thread.sendSetupMessages(); + } + }); } /** @@ -121,11 +124,15 @@ public class MainServerThread extends Observable implements Runnable, ClientConn serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0); GameState.removeYacht(player.getYacht().getSourceId()); GameState.removePlayer(player); + ServerToClientThread closedConnection = null; for (ServerToClientThread serverToClientThread : serverToClientThreads) { if (serverToClientThread.getSocket() == player.getSocket()) { - this.deleteObserver(serverToClientThread); + closedConnection = serverToClientThread; + } else { + serverToClientThread.sendSetupMessages(); } } + serverToClientThreads.remove(closedConnection); setChanged(); notifyObservers(); } diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 2a35e430..fde78482 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -12,8 +12,6 @@ import java.net.SocketException; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import java.util.Observable; -import java.util.Observer; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.zip.CRC32; @@ -44,7 +42,15 @@ import seng302.utilities.XMLGenerator; * its own thread. All server threads created and owned by the server thread handler which can * trigger client updates on its threads Created by wmu16 on 13/07/17. */ -public class ServerToClientThread implements Runnable, Observer { +public class ServerToClientThread implements Runnable { + + /** + * Called to notify listeners when this thread receives a connection correctly. + */ + @FunctionalInterface + interface ConnectionListener { + void notifyConnection (); + } private static final Integer LOG_LEVEL = 1; private static final Integer MAX_ID_ATTEMPTS = 10; @@ -57,8 +63,6 @@ public class ServerToClientThread implements Runnable, Observer { private ByteArrayOutputStream crcBuffer; - private Boolean userIdentified = false; - private Boolean connected = true; private Boolean updateClient = true; // private Boolean initialisedRace = true; @@ -70,6 +74,8 @@ public class ServerToClientThread implements Runnable, Observer { private XMLGenerator xml; + private List connectionListeners = new ArrayList<>(); + public ServerToClientThread(Socket socket) { this.socket = socket; seqNo = 0; @@ -85,7 +91,7 @@ public class ServerToClientThread implements Runnable, Observer { thread.start(); } - private void setUpYacht(){ + private void setUpPlayer(){ BufferedReader fn; String fName = ""; BufferedReader ln; @@ -114,7 +120,6 @@ public class ServerToClientThread implements Runnable, Observer { Yacht yacht = new Yacht( "Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" ); - GameState.addYacht(sourceId, yacht); GameState.addPlayer(new Player(socket, yacht)); } @@ -126,11 +131,6 @@ public class ServerToClientThread implements Runnable, Observer { } } - @Override - public void update(Observable o, Object arg) { - sendSetupMessages(); - } - private void completeRegistration(ClientType clientType) throws IOException { // Fail if not a player if (!clientType.equals(ClientType.PLAYER)){ @@ -150,12 +150,14 @@ public class ServerToClientThread implements Runnable, Observer { this.clientType = clientType; this.sourceId = sourceId; - setUpYacht(); - isRegistered = true; os.write(responseMessage.getBuffer()); - sendSetupMessages(); + setUpPlayer(); + + for (ConnectionListener listener : connectionListeners) { + listener.notifyConnection(); + } } public void run() { @@ -226,7 +228,7 @@ public class ServerToClientThread implements Runnable, Observer { } } - private void sendSetupMessages() { + public void sendSetupMessages() { xml = new XMLGenerator(); Race race = new Race(); @@ -257,22 +259,6 @@ public class ServerToClientThread implements Runnable, Observer { updateClient = true; } - - /** - * Tries to confirm the connection just accepted. - * Sends ID, expects that ID echoed for confirmation, - * if so, sends a confirmation packet back to that connection - * Creates a player instance with that ID and this thread and adds it to the GameState - * If not, close the socket and end the threads execution - * - * @param id the id to try and assign to the connection - * @return A boolean indicating if it was a successful handshake - */ - private Boolean threeWayHandshake(Integer id) { - - return true; - } - private void closeSocket() { try { socket.close(); @@ -281,7 +267,6 @@ public class ServerToClientThread implements Runnable, Observer { } } - private int readByte() throws Exception { int currentByte = -1; try { @@ -353,7 +338,6 @@ public class ServerToClientThread implements Runnable, Observer { public void sendRaceStatusMessage() { // variables taken from GameServerThread - List boatSubMessages = new ArrayList<>(); BoatStatus boatStatus; RaceStatus raceStatus; @@ -388,4 +372,12 @@ public class ServerToClientThread implements Runnable, Observer { public Socket getSocket() { return socket; } + + public void addConnectionListener(ConnectionListener listener) { + connectionListeners.add(listener); + } + + public void removeConnectionListener(ConnectionListener listener) { + connectionListeners.remove(listener); + } } diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 0bb046e8..f71e49da 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -450,9 +450,9 @@ public class Yacht { private void turnTowardsHeading(Double newHeading) { Double newVal = heading - newHeading; if (Math.floorMod(newVal.longValue(), 360L) > 180) { - adjustHeading(TURN_STEP); + adjustHeading(TURN_STEP / 5); } else { - adjustHeading(-TURN_STEP); + adjustHeading(-TURN_STEP / 5); } } diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index ef22a45b..e65cfbd9 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -49,7 +49,7 @@ public class GameClient { private ObservableList clientLobbyList = FXCollections.observableArrayList(); /** - * Create an instance of the game client. Does not do anything untill run with runAsClient() + * Create an instance of the game client. Does not do anything until run with runAsClient() * runAsHost(). * @param holder The JavaFX Pane that the visual elements for the race will be inserted into. */ @@ -106,10 +106,8 @@ public class GameClient { private void loadStartScreen() { socketThread.setSocketToClose(); - socketThread = null; if (server != null) { - // TODO: 26/07/17 cir27 - handle disconnecting -// server.shutDown(); + server.terminate(); server = null; } FXMLLoader fxmlLoader = new FXMLLoader( @@ -191,9 +189,9 @@ public class GameClient { StreamParser.extractXmlMessage(packet) ); clientLobbyList.clear(); - allBoatsMap.forEach((id, boat) -> { - clientLobbyList.add(id + " " + boat.getBoatName()); - }); + allBoatsMap.forEach((id, boat) -> + clientLobbyList.add(id + " " + boat.getBoatName()) + ); break; case RACE_START_STATUS: From 0ee12021e207663e9bd1536e6ae9c3e9b35abc2c Mon Sep 17 00:00:00 2001 From: Calum Date: Sun, 13 Aug 2017 20:29:10 +1200 Subject: [PATCH 23/29] Fixed bug that stopped clients from getting updated xml data. #issue[46] #bug --- .../gameServer/ServerToClientThread.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index fde78482..a3f87e2b 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -63,7 +63,6 @@ public class ServerToClientThread implements Runnable { private ByteArrayOutputStream crcBuffer; - private Boolean updateClient = true; // private Boolean initialisedRace = true; private Integer seqNo; @@ -169,20 +168,6 @@ public class ServerToClientThread implements Runnable { while (socket.isConnected()) { try { - //Perform a write if it is time to as delegated by the MainServerThread - if (updateClient) { - // TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream -// ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me"); -// sendMessage(chatterMessage); -// try { -// GameState.outputState(os); -// } catch (IOException e) { -// System.out.println("IO error in server thread upon writing to output stream"); -// } -// sendBoatLocationPackets(); - updateClient = false; - } - crcBuffer = new ByteArrayOutputStream(); sync1 = readByte(); sync2 = readByte(); @@ -256,7 +241,6 @@ public class ServerToClientThread implements Runnable { public void updateClient() { sendBoatLocationPackets(); - updateClient = true; } private void closeSocket() { @@ -317,7 +301,6 @@ public class ServerToClientThread implements Runnable { private void sendBoatLocationPackets() { ArrayList yachts = new ArrayList<>(GameState.getYachts().values()); for (Yacht yacht : yachts) { -// System.out.println("[SERVER] Lat: " + yacht.getLocation().getLat() + " Lon: " + yacht.getLocation().getLng()); BoatLocationMessage boatLocationMessage = new BoatLocationMessage( yacht.getSourceId(), From 2d5492601f952103ffbb04f377b5c0f512357a23 Mon Sep 17 00:00:00 2001 From: Calum Date: Sun, 13 Aug 2017 20:40:18 +1200 Subject: [PATCH 24/29] Fixed build issues caused by changes to a yacht step turn constant. #bug --- pom.xml | 15 --------------- src/test/java/seng302/models/YachtTest.java | 6 +++--- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index b329e067..fca06536 100644 --- a/pom.xml +++ b/pom.xml @@ -69,24 +69,9 @@ commons-cli 1.4 - - - org.testfx - testfx-core - 4.0.1-alpha - test - - - - org.testfx - testfx-junit - 4.0.6-alpha - test - - diff --git a/src/test/java/seng302/models/YachtTest.java b/src/test/java/seng302/models/YachtTest.java index fd610dfc..53e0ac41 100644 --- a/src/test/java/seng302/models/YachtTest.java +++ b/src/test/java/seng302/models/YachtTest.java @@ -37,12 +37,12 @@ public class YachtTest { for (Double begin : values.keySet()) { y1.setHeading(begin); y1.tackGybe(windDirection); - for (int i = 0; i < 50; i++) { + + for (int i = 0; i < 200; i++) { y1.runAutoPilot(); } assertEquals(values.get(begin), y1.getHeading(), 5.0); } - } @Test @@ -70,7 +70,7 @@ public class YachtTest { System.out.println(begin); y1.setHeading(begin); y1.turnToVMG(); - for (int i = 0; i < 50; i++) { + for (int i = 0; i < 200; i++) { y1.runAutoPilot(); System.out.println(y1.getHeading()); } From b7fe79a5ccb657329dcd4ef7c737687c7930d885 Mon Sep 17 00:00:00 2001 From: Calum Date: Sun, 13 Aug 2017 20:44:23 +1200 Subject: [PATCH 25/29] Attempted fix for build issues due to javaFX toolkit not being initialized. --- src/test/java/seng302/TestRaceTimer.java | 22 ------------------- src/test/java/seng302/models/YachtTest.java | 2 -- .../RegularPacketsTest.java | 4 ++-- 3 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 src/test/java/seng302/TestRaceTimer.java diff --git a/src/test/java/seng302/TestRaceTimer.java b/src/test/java/seng302/TestRaceTimer.java deleted file mode 100644 index 8fd5b645..00000000 --- a/src/test/java/seng302/TestRaceTimer.java +++ /dev/null @@ -1,22 +0,0 @@ -package seng302; - -import org.junit.Test; - - -public class TestRaceTimer { - @Test - public void testPositiveTimeString(){ -// RaceViewController controller = new RaceViewController(); -// String result = controller.convertTimeToMinutesSeconds(61); -// -// assertTrue(result.equals("01:01")); - } - - @Test - public void testNegativeTimeString(){ -// RaceViewController controller = new RaceViewController(); -// String result = controller.convertTimeToMinutesSeconds(-61); -// -// assertTrue(result.equals("-01:01")); - } -} diff --git a/src/test/java/seng302/models/YachtTest.java b/src/test/java/seng302/models/YachtTest.java index 53e0ac41..49b73daa 100644 --- a/src/test/java/seng302/models/YachtTest.java +++ b/src/test/java/seng302/models/YachtTest.java @@ -67,12 +67,10 @@ public class YachtTest { values.put(350d, downwindRight); for (Double begin : values.keySet()) { - System.out.println(begin); y1.setHeading(begin); y1.turnToVMG(); for (int i = 0; i < 200; i++) { y1.runAutoPilot(); - System.out.println(y1.getHeading()); } y1.disableAutoPilot(); assertEquals(values.get(begin), y1.getHeading(), 5.0); diff --git a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java index e8064a6c..bf5fcab1 100644 --- a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java +++ b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java @@ -74,7 +74,7 @@ public class RegularPacketsTest { serverThread.terminate(); clientThread.setSocketToClose(); GameState.setCurrentStage(GameStages.LOBBYING); - for (int i = 0; i<6; i++) - SleepThreadMaxDelay(); //Make sure socket is closed. + for (int i = 0; i<20; i++) + SleepThreadMaxDelay(); //Make sure socket is closed and toolkit remade. } } From a844585fafe5eea077f4cb1ce35668232ac5cafe Mon Sep 17 00:00:00 2001 From: Calum Date: Sun, 13 Aug 2017 20:54:13 +1200 Subject: [PATCH 26/29] Empty tests to check where bug is occuring --- .../RegularPacketsTest.java | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java index bf5fcab1..a2a944ee 100644 --- a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java +++ b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java @@ -1,15 +1,11 @@ package seng302.visualiser.ClientToServerTests; -import java.util.ArrayList; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import seng302.gameServer.GameStages; import seng302.gameServer.GameState; import seng302.gameServer.MainServerThread; -import seng302.gameServer.server.messages.BoatAction; -import seng302.model.Yacht; import seng302.visualiser.ClientToServerThread; /** @@ -30,34 +26,34 @@ public class RegularPacketsTest { @Test public void packetsSentAtRegularIntervals () throws Exception { - final double TEST_DISTANCE = 10.0; - serverThread.startGame(); - SleepThreadMaxDelay(); - Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); - double startAngle = yacht.getHeading(); - long startTime = System.currentTimeMillis(); - clientThread.sendBoatAction(BoatAction.UPWIND); - Thread.sleep(200); - while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) { - Thread.sleep(1); - } - clientThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); - long endTime = System.currentTimeMillis(); - SleepThreadMaxDelay(); - //Allowed to be two loops of delay due to loop delay and processing delay at client + server ends. - Assert.assertEquals(TEST_DISTANCE / Yacht.TURN_STEP * ClientToServerThread.PACKET_SENDING_INTERVAL_MS, - (endTime - startTime), 2 * ClientToServerThread.PACKET_SENDING_INTERVAL_MS); +// final double TEST_DISTANCE = 10.0; +// serverThread.startGame(); +// SleepThreadMaxDelay(); +// Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); +// double startAngle = yacht.getHeading(); +// long startTime = System.currentTimeMillis(); +// clientThread.sendBoatAction(BoatAction.UPWIND); +// Thread.sleep(200); +// while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) { +// Thread.sleep(1); +// } +// clientThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); +// long endTime = System.currentTimeMillis(); +// SleepThreadMaxDelay(); +// //Allowed to be two loops of delay due to loop delay and processing delay at client + server ends. +// Assert.assertEquals(TEST_DISTANCE / Yacht.TURN_STEP * ClientToServerThread.PACKET_SENDING_INTERVAL_MS, +// (endTime - startTime), 2 * ClientToServerThread.PACKET_SENDING_INTERVAL_MS); } @Test public void testArbitraryPacketSent() throws Exception { - serverThread.startGame(); - SleepThreadMaxDelay(); - Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); - boolean startState = yacht.getSailIn(); - clientThread.sendBoatAction(BoatAction.SAILS_IN); - SleepThreadMaxDelay(); - Assert.assertEquals(startState, !yacht.getSailIn()); +// serverThread.startGame(); +// SleepThreadMaxDelay(); +// Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); +// boolean startState = yacht.getSailIn(); +// clientThread.sendBoatAction(BoatAction.SAILS_IN); +// SleepThreadMaxDelay(); +// Assert.assertEquals(startState, !yacht.getSailIn()); } /** From 1755b0007992c35ee91b8fbdf917de11092008ec Mon Sep 17 00:00:00 2001 From: Calum Date: Sun, 13 Aug 2017 20:59:03 +1200 Subject: [PATCH 27/29] Unncommented test to see if it breaks the build at remote repo. --- .../RegularPacketsTest.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java index a2a944ee..f0a148b9 100644 --- a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java +++ b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java @@ -1,11 +1,15 @@ package seng302.visualiser.ClientToServerTests; +import java.util.ArrayList; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import seng302.gameServer.GameStages; import seng302.gameServer.GameState; import seng302.gameServer.MainServerThread; +import seng302.gameServer.server.messages.BoatAction; +import seng302.model.Yacht; import seng302.visualiser.ClientToServerThread; /** @@ -47,13 +51,13 @@ public class RegularPacketsTest { @Test public void testArbitraryPacketSent() throws Exception { -// serverThread.startGame(); -// SleepThreadMaxDelay(); -// Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); -// boolean startState = yacht.getSailIn(); -// clientThread.sendBoatAction(BoatAction.SAILS_IN); -// SleepThreadMaxDelay(); -// Assert.assertEquals(startState, !yacht.getSailIn()); + serverThread.startGame(); + SleepThreadMaxDelay(); + Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); + boolean startState = yacht.getSailIn(); + clientThread.sendBoatAction(BoatAction.SAILS_IN); + SleepThreadMaxDelay(); + Assert.assertEquals(startState, !yacht.getSailIn()); } /** From f2f750298ce8ba56a941738ea19a68ae1ccf4908 Mon Sep 17 00:00:00 2001 From: Calum Date: Sun, 13 Aug 2017 21:05:28 +1200 Subject: [PATCH 28/29] Added print statements for determining where javaFX is accessed from in the test. --- src/main/java/seng302/visualiser/ClientToServerThread.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index bda0e6ec..dc38e129 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -155,6 +155,7 @@ public class ClientToServerThread implements Runnable { } } } catch (ByteReadException e) { + e.printStackTrace(); closeSocket(); Platform.runLater(() -> { Alert alert = new Alert(AlertType.ERROR); From 52e10997f1f760bab8749f9e1c01e904796785bf Mon Sep 17 00:00:00 2001 From: Calum Date: Sun, 13 Aug 2017 21:07:21 +1200 Subject: [PATCH 29/29] Build issues caused by vm unable to open socket connections. Leaving as is for now. #bug --- .../RegularPacketsTest.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java index f0a148b9..bf5fcab1 100644 --- a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java +++ b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java @@ -30,23 +30,23 @@ public class RegularPacketsTest { @Test public void packetsSentAtRegularIntervals () throws Exception { -// final double TEST_DISTANCE = 10.0; -// serverThread.startGame(); -// SleepThreadMaxDelay(); -// Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); -// double startAngle = yacht.getHeading(); -// long startTime = System.currentTimeMillis(); -// clientThread.sendBoatAction(BoatAction.UPWIND); -// Thread.sleep(200); -// while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) { -// Thread.sleep(1); -// } -// clientThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); -// long endTime = System.currentTimeMillis(); -// SleepThreadMaxDelay(); -// //Allowed to be two loops of delay due to loop delay and processing delay at client + server ends. -// Assert.assertEquals(TEST_DISTANCE / Yacht.TURN_STEP * ClientToServerThread.PACKET_SENDING_INTERVAL_MS, -// (endTime - startTime), 2 * ClientToServerThread.PACKET_SENDING_INTERVAL_MS); + final double TEST_DISTANCE = 10.0; + serverThread.startGame(); + SleepThreadMaxDelay(); + Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); + double startAngle = yacht.getHeading(); + long startTime = System.currentTimeMillis(); + clientThread.sendBoatAction(BoatAction.UPWIND); + Thread.sleep(200); + while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) { + Thread.sleep(1); + } + clientThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); + long endTime = System.currentTimeMillis(); + SleepThreadMaxDelay(); + //Allowed to be two loops of delay due to loop delay and processing delay at client + server ends. + Assert.assertEquals(TEST_DISTANCE / Yacht.TURN_STEP * ClientToServerThread.PACKET_SENDING_INTERVAL_MS, + (endTime - startTime), 2 * ClientToServerThread.PACKET_SENDING_INTERVAL_MS); } @Test