From 6a6ed3ed44d531511b2551b2d2da7287643ecd64 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Thu, 18 May 2017 13:32:24 +1200 Subject: [PATCH 01/20] Server sends mark locations to test - Added a timer to send boat location messages containing the mark locations to test the receiver #story[891] --- .../java/seng302/server/ServerThread.java | 50 +++++++++++++++++++ .../seng302/server/simulator/Simulator.java | 17 +++++++ 2 files changed, 67 insertions(+) diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index 094845ab..4270b633 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -1,5 +1,7 @@ package seng302.server; +import seng302.server.simulator.mark.CompoundMark; +import seng302.server.simulator.mark.Mark; import seng302.server.messages.*; import seng302.server.simulator.Boat; import seng302.server.simulator.Simulator; @@ -257,6 +259,53 @@ public class ServerThread implements Runnable, Observer { //Delays the new course xml data for 25 seconds so the boats are able to pass the starting line } + /** + * Starts sending boat location messages containing the mark positions + * Marks are flipped by 90 degrees from their original position + */ + private void startUpdatingMarkPositions(){ + Timer t = new Timer(); + t.schedule(new TimerTask() { + + /** + * Send the mark location message + * @param m The mark to send + * @param offset How far to move the marks from their original position + */ + private void sendMark(Mark m, Double offset){ + Message markLocation = new BoatLocationMessage(m.getSourceID(), server.getSequenceNumber(), + m.getLat()-offset, m.getLng()+offset*2, 0, 0); + + try { + server.send(markLocation); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void run() { + for (CompoundMark m : raceSimulator.getMarks()){ + if (m == null){ + continue; + } + + Mark mark1 = m.getMark1(); + Mark mark2 = m.getMark2(); + + if (mark1 != null){ + sendMark(mark1, 0.0002); + } + + if (mark2 != null){ + sendMark(mark2, 0.0005); + } + + } + } + }, 21000, 1000); + } + public void run() { try{ server = new StreamingServerSocket(PORT_NUMBER); @@ -275,6 +324,7 @@ public class ServerThread implements Runnable, Observer { startSendingRaceStartStatusMessages(); startSendingRaceStatusMessages(); sendPostStartCourseXml(); + startUpdatingMarkPositions(); } /** diff --git a/src/main/java/seng302/server/simulator/Simulator.java b/src/main/java/seng302/server/simulator/Simulator.java index 72d2717a..c279fea5 100644 --- a/src/main/java/seng302/server/simulator/Simulator.java +++ b/src/main/java/seng302/server/simulator/Simulator.java @@ -1,12 +1,15 @@ package seng302.server.simulator; +import seng302.server.simulator.mark.CompoundMark; import seng302.server.simulator.mark.Corner; import seng302.server.simulator.mark.Mark; import seng302.server.simulator.mark.Position; import seng302.server.simulator.parsers.RaceParser; +import java.util.HashSet; import java.util.List; import java.util.Observable; +import java.util.Set; import java.util.concurrent.ThreadLocalRandom; public class Simulator extends Observable implements Runnable { @@ -138,4 +141,18 @@ public class Simulator extends Observable implements Runnable { public void setRaceStarted(boolean raceStarted) { isRaceStarted = raceStarted; } + + /** + * @return A list of marks in the race + */ + public Set getMarks(){ + Set marks = new HashSet<>(); + + for (Corner c : course){ + marks.add(c.getCompoundMark()); + marks.add(c.getCompoundMark()); + } + + return marks; + } } From 7885b3fae21e34e79718b2769094294edd7d7bc4 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Wed, 2 Aug 2017 22:03:10 +1200 Subject: [PATCH 02/20] Loading course mark order from RaceXML - Mark order is read from the generated RaceXML and stored - Added .getNextMark() to get the next mark in the race - Added .equals() and .hashCode() for Marks Tags: #story[1124] (Task 1) --- .../java/seng302/gameServer/GameState.java | 10 +- .../java/seng302/models/map/CanvasMap.java | 2 - src/main/java/seng302/models/mark/Mark.java | 38 ++++++ .../java/seng302/models/mark/MarkOrder.java | 116 ++++++++++++++++++ .../java/seng302/server/messages/Message.java | 2 - .../java/seng302/models/MarkOrderTest.java | 95 ++++++++++++++ 6 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 src/main/java/seng302/models/mark/MarkOrder.java create mode 100644 src/test/java/seng302/models/MarkOrderTest.java diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 16eb8050..918cd210 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,13 +1,14 @@ package seng302.gameServer; +import seng302.client.ClientPacketParser; +import seng302.models.Player; +import seng302.models.Yacht; +import seng302.server.messages.BoatActionType; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import seng302.client.ClientPacketParser; -import seng302.models.Player; -import seng302.models.Yacht; -import seng302.server.messages.BoatActionType; /** * A Static class to hold information about the current state of the game (model) @@ -43,7 +44,6 @@ public class GameState implements Runnable { yachts = new HashMap<>(); new Thread(this).start(); - } public static String getHostIpAddress() { diff --git a/src/main/java/seng302/models/map/CanvasMap.java b/src/main/java/seng302/models/map/CanvasMap.java index e6a00cfa..de6403c7 100644 --- a/src/main/java/seng302/models/map/CanvasMap.java +++ b/src/main/java/seng302/models/map/CanvasMap.java @@ -7,8 +7,6 @@ import seng302.utilities.GeoPoint; import javax.net.ssl.HttpsURLConnection; import java.net.URL; -import java.lang.Math; - /** * CanvasMap retrieves a map image with given geo boundary from Google Map server. * By passing a rectangle like geo boundary, it returns a map image with the diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java index 027bf6d3..362c77fd 100644 --- a/src/main/java/seng302/models/mark/Mark.java +++ b/src/main/java/seng302/models/mark/Mark.java @@ -145,4 +145,42 @@ public abstract class Mark { public int getCompoundMarkID() { return compoundMarkID; } + + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + + if (!(other instanceof Mark)){ + return false; + } + + Mark otherMark = (Mark) other; + + if (otherMark.getLatitude() != getLatitude() || 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() { + return getName().hashCode() + getMarkType().hashCode() + + Integer.hashCode(getCompoundMarkID()) + Double.hashCode(getLatitude()) + + Double.hashCode(getLongitude()); + } } diff --git a/src/main/java/seng302/models/mark/MarkOrder.java b/src/main/java/seng302/models/mark/MarkOrder.java new file mode 100644 index 00000000..bf6f5f40 --- /dev/null +++ b/src/main/java/seng302/models/mark/MarkOrder.java @@ -0,0 +1,116 @@ +package seng302.models.mark; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import seng302.models.stream.XMLParser; +import seng302.models.xml.Race; +import seng302.models.xml.XMLGenerator; +import seng302.server.messages.XMLMessageSubType; + +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.Collections; +import java.util.List; + +/** + * Class to hold the order of the marks in the race. + */ +public class MarkOrder { + private List raceMarkOrder; + private Logger logger = LoggerFactory.getLogger(MarkOrder.class); + + public MarkOrder(){ + loadRaceProperties(); + } + + /** + * @return An ordered list of marks in the race + * OR null if the mark order could not be loaded + */ + public List getMarkOrder(){ + if (raceMarkOrder == null){ + logger.warn("Race order accessed but not instantiated"); + return null; + } + + return Collections.unmodifiableList(raceMarkOrder); + } + + /** + * Returns the mark in the race after the previous mark + * @param previous The previous mark + * @return the next mark + * OR null if there is no next mark + */ + public Mark getNextMark(Mark previous){ + for (int i = 0; i < raceMarkOrder.size(); i++){ + Mark mark = raceMarkOrder.get(i); + + if (i + 1 >= raceMarkOrder.size()){ + return null; + } + + if (mark.equals(previous)){ + return raceMarkOrder.get(i+1); + } + } + + return null; + } + + /** + * Loads the race order from an XML string + * @param xml An AC35 RaceXML + * @return An ordered list of marks in the race + */ + private List loadRaceOrderFromXML(String xml){ + XMLParser xmlParser = new XMLParser(); + XMLParser.RaceXMLObject raceXMLObject; + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db; + Document doc; + + try { + db = dbf.newDocumentBuilder(); + doc = db.parse(new InputSource(new StringReader(xml))); + } catch (ParserConfigurationException | IOException | SAXException e) { + logger.error("Failed to read generated race XML"); + return null; + } + + xmlParser.constructXML(doc , XMLMessageSubType.RACE.getType()); + raceXMLObject = xmlParser.getRaceXML(); + + if (raceXMLObject != null){ + logger.debug("Loaded RaceXML for mark order"); + return raceXMLObject.getNonDupCompoundMarks(); + } + + return null; + } + + /** + * Load the raceXML and mark order + */ + private void loadRaceProperties(){ + XMLGenerator generator = new XMLGenerator(); + + generator.setRace(new Race()); + + String raceXML = generator.getRaceAsXml(); + + if (raceXML == null){ + logger.error("Failed to generate raceXML (for race properties)"); + return; + } + + raceMarkOrder = loadRaceOrderFromXML(raceXML); + } +} diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java index 398628ab..10afb8e5 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/server/messages/Message.java @@ -1,7 +1,5 @@ package seng302.server.messages; -import java.io.IOException; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; diff --git a/src/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java new file mode 100644 index 00000000..97db018c --- /dev/null +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -0,0 +1,95 @@ +package seng302.models; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import seng302.models.mark.Mark; +import seng302.models.mark.MarkOrder; +import seng302.models.mark.SingleMark; + +import static junit.framework.TestCase.*; +import static org.junit.Assert.assertNotEquals; + +public class MarkOrderTest { + private static MarkOrder markOrder; + + @BeforeClass + public static void setup(){ + markOrder = new MarkOrder(); + } + + /** + * Test to ensure marks are loaded from XML + */ + @Test + public void testMarkOrderLoadedFromXML(){ + assertTrue(markOrder != null); + } + + /** + * Test if .equals() method on returns true on two marks that are equal + */ + @Test + public void testMarkEqualsTrue(){ + Mark mark1 = new SingleMark("asd", 1.1, 2.2, 1, 2); + Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); + + assertEquals(mark1, mark2); + } + + /** + * Test if .equals() method on returns false on two marks that are NOT equal + */ + @Test + public void testMarkNotEquals(){ + Mark mark1 = new SingleMark("asf", 1.1, 2.2, 2, 2); + Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); + + assertNotEquals(mark1, mark2); + } + + /** + * 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; + } + + Mark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); + + assertEquals(null, markOrder.getNextMark(lastMark)); + } + + /** + * Test if .getNextMark() method on returns null if the mark does not exist in the race + */ + @Test + public void testNextMarkNotExists(){ + Mark someMark = new SingleMark("0-0-0-0-0-0-0", 0.0, 0.1, 2, 1); + + assertEquals(null, markOrder.getNextMark(someMark)); + } + + /** + * 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; + } + + Mark firstMark = markOrder.getMarkOrder().get(0); + + assertEquals(markOrder.getMarkOrder().get(1), markOrder.getNextMark(firstMark)); + } + + @AfterClass + public static void tearDown(){ + markOrder = null; + } +} From f9e6df46c145afddbc0a222586613f5ae768be16 Mon Sep 17 00:00:00 2001 From: Calum Date: Thu, 3 Aug 2017 13:23:43 +1200 Subject: [PATCH 03/20] Fixed issues caused by merge. #bug --- src/main/java/seng302/App.java | 6 +- .../java/seng302/gameServer/GameState.java | 20 +-- .../server/messages/BoatLocationMessage.java | 39 +++-- .../server/simulator/Simulator.java | 159 ------------------ .../java/seng302/model/mark/CompoundMark.java | 42 +++++ .../{models => model}/mark/MarkOrder.java | 52 +++--- .../seng302/visualiser/map/CanvasMap.java | 3 +- src/main/resources/server_config/race.xml | 2 +- .../java/seng302/models/MarkOrderTest.java | 59 ++++--- 9 files changed, 130 insertions(+), 252 deletions(-) delete mode 100644 src/main/java/seng302/gameServer/server/simulator/Simulator.java rename src/main/java/seng302/{models => model}/mark/MarkOrder.java (76%) diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index ab3ec79a..3b914350 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -7,7 +7,11 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Stage; -import org.apache.commons.cli.*; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import seng302.model.PolarTable; diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 8ab071c3..b49283c3 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -4,15 +4,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import seng302.client.ClientPacketParser; -import seng302.models.Player; -import seng302.models.Yacht; -import seng302.server.messages.BoatActionType; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import seng302.gameServer.server.messages.BoatActionType; +import seng302.model.Player; +import seng302.model.Yacht; /** * A Static class to hold information about the current state of the game (model) @@ -33,8 +27,6 @@ public class GameState implements Runnable { private static GameStages currentStage; private static long startTime; - // TODO: 26/07/17 cir27 - Super hackish fix until something more permanent can be made. - private static ObservableList observablePlayers = FXCollections.observableArrayList(); private static Map playerStringMap = new HashMap<>(); /* Ideally I would like to make this class an object instantiated by the server and given to @@ -73,20 +65,14 @@ public class GameState implements Runnable { return players; } - public static ObservableList getObservablePlayers () { - return observablePlayers; - } - public static void addPlayer(Player player) { players.add(player); String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName() + " " + player.getYacht().getCountry(); - Platform.runLater(() -> observablePlayers.add(playerText)); //Had to add this to handle javaFX window using array playerStringMap.put(player, playerText); } public static void removePlayer(Player player) { players.remove(player); - observablePlayers.remove(playerStringMap.get(player)); playerStringMap.remove(player); } diff --git a/src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java b/src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java index 6e85f4e1..72548c12 100644 --- a/src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java @@ -1,6 +1,7 @@ package seng302.gameServer.server.messages; public class BoatLocationMessage extends Message { + private final int MESSAGE_SIZE = 56; private long messageVersionNumber; @@ -28,6 +29,7 @@ public class BoatLocationMessage extends Message { /** * Describes the location, altitude and sensor data from the boat. + * * @param sourceId ID of the boat * @param sequenceNum Sequence number of the message * @param latitude The boats latitude @@ -35,7 +37,8 @@ public class BoatLocationMessage extends Message { * @param heading The boats heading * @param boatSpeed The boats speed */ - public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){ + public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, + double heading, long boatSpeed) { messageVersionNumber = 1; time = System.currentTimeMillis(); this.sourceId = sourceId; @@ -49,7 +52,7 @@ public class BoatLocationMessage extends Message { this.roll = 0; this.boatSpeed = boatSpeed; this.COG = 2; - this.SOG = boatSpeed ; + this.SOG = boatSpeed; this.apparentWindSpeed = 0; this.apparentWindAngle = 0; this.trueWindSpeed = 0; @@ -63,7 +66,7 @@ public class BoatLocationMessage extends Message { allocateBuffer(); writeHeaderToBuffer(); - long headingToSend = (long)((heading/360.0) * 65535.0); + long headingToSend = (long) ((heading / 360.0) * 65535.0); putByte((byte) messageVersionNumber); putInt(time, 6); @@ -94,56 +97,62 @@ public class BoatLocationMessage extends Message { /** * Convert binary latitude or longitude to floating point number + * * @param binaryPackedLatLon Binary packed lat OR lon * @return Floating point lat/lon */ - public static double binaryPackedToLatLon(long binaryPackedLatLon){ - return (double)binaryPackedLatLon * 180.0 / 2147483648.0; + public static double binaryPackedToLatLon(long binaryPackedLatLon) { + return (double) binaryPackedLatLon * 180.0 / 2147483648.0; } /** * Convert binary packed heading to floating point number + * * @param binaryPackedHeading Binary packed heading * @return heading as a decimal */ - public static double binaryPackedHeadingToDouble(long binaryPackedHeading){ - return (double)binaryPackedHeading * 360.0 / 65536.0; + public static double binaryPackedHeadingToDouble(long binaryPackedHeading) { + return (double) binaryPackedHeading * 360.0 / 65536.0; } /** * Convert binary packed wind angle to floating point number + * * @param binaryPackedWindAngle Binary packed wind angle * @return wind angle as a decimal */ - public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle){ - return (double)binaryPackedWindAngle*180.0/32768.0; + public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle) { + return (double) binaryPackedWindAngle * 180.0 / 32768.0; } /** * Convert a latitude or longitude to a binary packed long + * * @param latLon A floating point latitude/longitude * @return A binary packed lat/lon */ - public static long latLonToBinaryPackedLong(double latLon){ - return (long)((536870912 * latLon) / 45); + public static long latLonToBinaryPackedLong(double latLon) { + return (long) ((536870912 * latLon) / 45); } /** * Convert a heading to a binary packed long + * * @param heading A floating point heading * @return A binary packed heading */ - public static long headingToBinaryPackedLong(double heading){ - return (long)((8192*heading)/45); + public static long headingToBinaryPackedLong(double heading) { + return (long) ((8192 * heading) / 45); } /** * Convert a wind angle to a binary packed long + * * @param windAngle Floating point wind angle * @return A binary packed wind angle */ - public static long windAngleToBinaryPackedLong(double windAngle){ - return (long)((8192*windAngle)/45); + public static long windAngleToBinaryPackedLong(double windAngle) { + return (long) ((8192 * windAngle) / 45); } @Override diff --git a/src/main/java/seng302/gameServer/server/simulator/Simulator.java b/src/main/java/seng302/gameServer/server/simulator/Simulator.java deleted file mode 100644 index 9f638beb..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/Simulator.java +++ /dev/null @@ -1,159 +0,0 @@ -package seng302.server.simulator; - -import seng302.server.simulator.mark.Corner; -import seng302.server.simulator.mark.Mark; -import seng302.server.simulator.parsers.RaceParser; -import seng302.utilities.GeoPoint; -import seng302.utilities.GeoUtility; - -import java.util.HashSet; -import java.util.List; -import java.util.Observable; -import java.util.Set; -import java.util.concurrent.ThreadLocalRandom; -import seng302.model.mark.Mark; -import seng302.gameServer.server.simulator.parsers.RaceParser; -import seng302.model.GeoPoint; -import seng302.utilities.GeoUtility; - -public class Simulator extends Observable implements Runnable { - - private List course; - private List boats; - private long lapse; - private boolean isRaceStarted; - - /** - * Creates a simulator instance with given time lapse. - * @param lapse time duration in millisecond. - */ - public Simulator(long lapse) { - RaceParser rp = new RaceParser("/server_config/race.xml"); - course = rp.getCourse(); - boats = rp.getBoats(); - this.lapse = lapse; - isRaceStarted = false; - - setLegs(); - - // set start line's coordinate to boats - Double startLat = course.get(0).getCompoundMark().getSubMark(1).getLat(); - Double startLng = course.get(0).getCompoundMark().getSubMark(1).getLng(); - for (Boat boat : boats) { - boat.setLat(startLat); - boat.setLng(startLng); - boat.setLastPassedCorner(course.get(0)); - boat.setHeadingCorner(course.get(1)); - boat.setSpeed(ThreadLocalRandom.current().nextInt(40000, 60000 + 1)); - } - } - - @Override - public void run() { - - int numOfFinishedBoats = 0; - - while (numOfFinishedBoats < boats.size()) { - - // if race has started, then boat should start to move. - if (isRaceStarted) { - for (Boat boat : boats) { - numOfFinishedBoats += moveBoat(boat, lapse); - } - } - - setChanged(); - notifyObservers(boats); - - try { - Thread.sleep(lapse); - } catch (InterruptedException e) { - System.out.println("[Simulator] interrupted exception "); - } - } - } - - /** - * Moves a boat with given time duration. - * @param boat the boat to be moved - * @param duration the moving duration in milliseconds - * @return 1 if the boat has reached the final line, otherwise return 0 - */ - private int moveBoat(Boat boat, double duration) { - if (boat.getHeadingCorner() != null) { - - boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration); - - GeoPoint boatPos = new GeoPoint(boat.getLat(), boat.getLng()); - GeoPoint lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getSubMark(1); - - double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos); - // if a boat passes its heading mark - while (distanceFromLastMark >= boat.getLastPassedCorner().getDistanceToNextCorner()) { - double compensateDistance = distanceFromLastMark - boat.getLastPassedCorner().getDistanceToNextCorner(); - boat.setLastPassedCorner(boat.getHeadingCorner()); - boat.setHeadingCorner(boat.getLastPassedCorner().getNextCorner()); - - // heading corner == null means boat has reached the final mark - if (boat.getHeadingCorner() == null) { - boat.setFinished(true); - return 1; - } - - // move compensate distance for the mark just passed - GeoPoint pos = GeoUtility.getGeoCoordinate( - boat.getLastPassedCorner().getCompoundMark().getSubMark(1), - boat.getLastPassedCorner().getBearingToNextCorner(), - compensateDistance); - boat.setLat(pos.getLat()); - boat.setLng(pos.getLng()); - distanceFromLastMark = GeoUtility.getDistance(new GeoPoint(boat.getLat(), boat.getLng()), - boat.getLastPassedCorner().getCompoundMark().getSubMark(1)); - } - } - return 0; - } - - /** - * Link all the corners in the course list so that every corner knows its next - * corner, as well as the distance and bearing to its next corner. However, - * the last corner's heading is null, which means it is the final line. - */ - private void setLegs() { - // get the bearing from one mark to the next heading mark - for (int i = 0; i < course.size() - 1; i++) { - - Mark mark1 = course.get(i).getCompoundMark().getSubMark(1); - Mark mark2 = course.get(i + 1).getCompoundMark().getSubMark(1); - course.get(i).setDistanceToNextCorner(GeoUtility.getDistance(mark1, mark2)); - - course.get(i).setNextCorner(course.get(i + 1)); - - course.get(i).setBearingToNextCorner( - GeoUtility.getBearing(course.get(i).getCompoundMark().getSubMark(1), - course.get(i + 1).getCompoundMark().getSubMark(1))); - } - } - - public List getBoats(){ - return boats; - } - - public void setRaceStarted(boolean raceStarted) { - isRaceStarted = raceStarted; - } - - /** - * @return A list of marks in the race - */ - public Set getMarks(){ - Set marks = new HashSet<>(); - - for (Corner c : course){ - marks.add(c.getCompoundMark()); - marks.add(c.getCompoundMark()); - } - - return marks; - } -} diff --git a/src/main/java/seng302/model/mark/CompoundMark.java b/src/main/java/seng302/model/mark/CompoundMark.java index b1989845..af4cd8a8 100644 --- a/src/main/java/seng302/model/mark/CompoundMark.java +++ b/src/main/java/seng302/model/mark/CompoundMark.java @@ -86,4 +86,46 @@ public class CompoundMark { public List getMarks () { 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; + for (Mark mark : marks) { + hash += Double.hashCode(mark.getSourceID()) + Double.hashCode(mark.getLat()) + + Double.hashCode(mark.getLng()) + mark.getName().hashCode(); + } + return hash + getName().hashCode() + Integer.hashCode(getId()); + } } diff --git a/src/main/java/seng302/models/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java similarity index 76% rename from src/main/java/seng302/models/mark/MarkOrder.java rename to src/main/java/seng302/model/mark/MarkOrder.java index bf6f5f40..d3270c8f 100644 --- a/src/main/java/seng302/models/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -1,28 +1,29 @@ -package seng302.models.mark; +package seng302.model.mark; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +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; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import seng302.models.stream.XMLParser; -import seng302.models.xml.Race; -import seng302.models.xml.XMLGenerator; -import seng302.server.messages.XMLMessageSubType; - -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.Collections; -import java.util.List; +import seng302.model.stream.xml.generator.Race; +import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.utilities.XMLGenerator; +import seng302.utilities.XMLParser; /** * 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(){ @@ -33,7 +34,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; @@ -48,9 +49,9 @@ public class MarkOrder { * @return the next mark * OR null if there is no next mark */ - public Mark getNextMark(Mark previous){ + public CompoundMark getNextMark(CompoundMark previous){ for (int i = 0; i < raceMarkOrder.size(); i++){ - Mark mark = raceMarkOrder.get(i); + CompoundMark mark = raceMarkOrder.get(i); if (i + 1 >= raceMarkOrder.size()){ return null; @@ -60,7 +61,6 @@ public class MarkOrder { return raceMarkOrder.get(i+1); } } - return null; } @@ -69,9 +69,7 @@ public class MarkOrder { * @param xml An AC35 RaceXML * @return An ordered list of marks in the race */ - private List loadRaceOrderFromXML(String xml){ - XMLParser xmlParser = new XMLParser(); - XMLParser.RaceXMLObject raceXMLObject; + private List loadRaceOrderFromXML(String xml){ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db; @@ -84,13 +82,14 @@ public class MarkOrder { logger.error("Failed to read generated race XML"); return null; } + + RaceXMLData data = XMLParser.parseRace(doc); - xmlParser.constructXML(doc , XMLMessageSubType.RACE.getType()); - raceXMLObject = xmlParser.getRaceXML(); - - if (raceXMLObject != null){ + if (data != null){ logger.debug("Loaded RaceXML for mark order"); - return raceXMLObject.getNonDupCompoundMarks(); + List course = new ArrayList<>(data.getCompoundMarks().values()); + course.sort(Comparator.comparingInt(CompoundMark::getId)); + return course; } return null; @@ -110,7 +109,6 @@ public class MarkOrder { logger.error("Failed to generate raceXML (for race properties)"); return; } - raceMarkOrder = loadRaceOrderFromXML(raceXML); } } diff --git a/src/main/java/seng302/visualiser/map/CanvasMap.java b/src/main/java/seng302/visualiser/map/CanvasMap.java index 140a0b50..e79805e4 100644 --- a/src/main/java/seng302/visualiser/map/CanvasMap.java +++ b/src/main/java/seng302/visualiser/map/CanvasMap.java @@ -4,8 +4,7 @@ import java.net.URL; import javafx.geometry.Point2D; import javafx.scene.image.Image; import javax.net.ssl.HttpsURLConnection; -import java.net.URL; -import java.lang.Math; +import seng302.model.GeoPoint; /** * CanvasMap retrieves a map image with given geo boundary from Google Map server. diff --git a/src/main/resources/server_config/race.xml b/src/main/resources/server_config/race.xml index 5798aeb7..b2379fca 100644 --- a/src/main/resources/server_config/race.xml +++ b/src/main/resources/server_config/race.xml @@ -28,7 +28,7 @@ - + diff --git a/src/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java index 97db018c..ad9b700e 100644 --- a/src/test/java/seng302/models/MarkOrderTest.java +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -1,14 +1,13 @@ package seng302.models; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import seng302.models.mark.Mark; -import seng302.models.mark.MarkOrder; -import seng302.models.mark.SingleMark; - -import static junit.framework.TestCase.*; -import static org.junit.Assert.assertNotEquals; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.MarkOrder; public class MarkOrderTest { private static MarkOrder markOrder; @@ -26,27 +25,27 @@ public class MarkOrderTest { assertTrue(markOrder != null); } - /** - * Test if .equals() method on returns true on two marks that are equal - */ - @Test - public void testMarkEqualsTrue(){ - Mark mark1 = new SingleMark("asd", 1.1, 2.2, 1, 2); - Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); - - assertEquals(mark1, mark2); - } - - /** - * Test if .equals() method on returns false on two marks that are NOT equal - */ - @Test - public void testMarkNotEquals(){ - Mark mark1 = new SingleMark("asf", 1.1, 2.2, 2, 2); - Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); - - assertNotEquals(mark1, mark2); - } +// /** +// * Test if .equals() method on returns true on two marks that are equal +// */ +// @Test +// public void testMarkEqualsTrue(){ +// M mark1 = new SingleMark("asd", 1.1, 2.2, 1, 2); +// Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); +// +// assertEquals(mark1, mark2); +// } +// +// /** +// * Test if .equals() method on returns false on two marks that are NOT equal +// */ +// @Test +// public void testMarkNotEquals(){ +// Mark mark1 = new SingleMark("asf", 1.1, 2.2, 2, 2); +// Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); +// +// assertNotEquals(mark1, mark2); +// } /** * Test if .getNextMark() returns null if it is called with the final mark in the race @@ -58,7 +57,7 @@ public class MarkOrderTest { return; } - Mark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); + CompoundMark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); assertEquals(null, markOrder.getNextMark(lastMark)); } @@ -68,7 +67,7 @@ public class MarkOrderTest { */ @Test public void testNextMarkNotExists(){ - Mark someMark = new SingleMark("0-0-0-0-0-0-0", 0.0, 0.1, 2, 1); + CompoundMark someMark = new CompoundMark(1, "something"); assertEquals(null, markOrder.getNextMark(someMark)); } @@ -83,7 +82,7 @@ public class MarkOrderTest { return; } - Mark firstMark = markOrder.getMarkOrder().get(0); + CompoundMark firstMark = markOrder.getMarkOrder().get(0); assertEquals(markOrder.getMarkOrder().get(1), markOrder.getNextMark(firstMark)); } From 454e9ac9f17839d4cf4fd30db093bffd3a7e0b29 Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 3 Aug 2017 16:29:12 +1200 Subject: [PATCH 04/20] Added an attribute to each yacht: 'DistanceToNextMark' This attribute is calculated at each update of the boat as prompted by the game state regularly Removed the lat and lng attribute from the Yacht class and replaced its usage with the GeoPoint object instead Removed redundant test files and merged GeoUtility and testGeoUtil test classes into one tags: #story[1124] #pair[hyi25, wmu16] --- src/main/java/seng302/model/Yacht.java | 46 +++++-- src/test/java/seng302/BoatTest.java | 35 ----- src/test/java/seng302/TestGeoUtils.java | 67 --------- .../server/simulator/GeoUtilityTest.java | 76 ----------- src/test/java/seng302/model/YachtTest.java | 55 ++++++++ .../java/seng302/model/mark/MarkTest.java | 45 ------ .../seng302/utilities/GeoUtilityTest.java | 128 ++++++++++++++++++ 7 files changed, 218 insertions(+), 234 deletions(-) delete mode 100644 src/test/java/seng302/BoatTest.java delete mode 100644 src/test/java/seng302/TestGeoUtils.java delete mode 100644 src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java create mode 100644 src/test/java/seng302/model/YachtTest.java delete mode 100644 src/test/java/seng302/model/mark/MarkTest.java create mode 100644 src/test/java/seng302/utilities/GeoUtilityTest.java diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index aba80d1a..38104e9e 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -1,7 +1,5 @@ package seng302.model; -import static seng302.utilities.GeoUtility.getGeoCoordinate; - import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -14,6 +12,8 @@ import javafx.beans.property.ReadOnlyLongWrapper; import javafx.scene.paint.Color; import seng302.gameServer.GameState; import seng302.model.mark.CompoundMark; +import seng302.model.mark.Mark; +import seng302.utilities.GeoUtility; /** * Yacht class for the racing boat. @@ -38,11 +38,10 @@ public class Yacht { private Long estimateTimeAtFinish; private Long timeTillNext; + private Double distanceToNextMark; private Long markRoundTime; private CompoundMark nextMark; private Double heading; - private Double lat; - private Double lon; private Integer legNumber = 0; //SERVER SIDE @@ -110,7 +109,28 @@ public class Yacht { } Double metersCovered = velocity * secondsElapsed; - location = getGeoCoordinate(location, heading, metersCovered); + location = GeoUtility.getGeoCoordinate(location, heading, metersCovered); + distanceToNextMark = calcDistanceToNextMark(); + } + + + /** + * Calculates the distance to the next mark (closest of the two if a gate mark). + * + * @return A distance in metres. Returns -1 if there is no next mark + */ + public Double calcDistanceToNextMark() { + if (nextMark == null) { + return -1d; + } else if (nextMark.isGate()) { + Mark sub1 = nextMark.getSubMark(1); + Mark sub2 = nextMark.getSubMark(2); + Double distance1 = GeoUtility.getDistance(location, sub1); + Double distance2 = GeoUtility.getDistance(location, sub2); + return (distance1 < distance2) ? distance1 : distance2; + } else { + return GeoUtility.getDistance(location, nextMark.getSubMark(1)); + } } public void adjustHeading(Double amount) { @@ -326,19 +346,19 @@ public class Yacht { } public Double getLat() { - return lat; + return location.getLat(); } public void setLat(Double lat) { - this.lat = lat; + location.setLat(lat); } public Double getLon() { - return lon; + return location.getLng(); } public void setLon(Double lon) { - this.lon = lon; + location.setLng(lon); } public Double getHeading() { @@ -392,9 +412,13 @@ public class Yacht { this.velocity = velocity; } + public Double getDistanceToNextMark() { + return distanceToNextMark; + } + public void updateLocation (double lat, double lon, double heading, double velocity) { - this.lat = lat; - this.lon = lon; + location.setLat(lat); + location.setLng(lon); this.heading = heading; this.velocity = velocity; updateVelocityProperty(velocity); diff --git a/src/test/java/seng302/BoatTest.java b/src/test/java/seng302/BoatTest.java deleted file mode 100644 index 9ab5b66d..00000000 --- a/src/test/java/seng302/BoatTest.java +++ /dev/null @@ -1,35 +0,0 @@ -//package seng302; -// -//import org.junit.Test; -//import seng302.model.Boat; -// -//import static org.junit.Assert.assertEquals; -// -///** -// * Unit test for the Team class. -// */ -//public class BoatTest { -// -// @Test -// public void testBoatCreation() { -// Boat boat1 = new Boat("Team 1"); -// assertEquals(boat1.getTeamName(), "Team 1"); -// assertEquals(boat1.getVelocity(), (double) 10.0, 1e-15); -// } -// -// @Test -// public void testChangeTeamName() { -// Boat boat1 = new Boat("Team 1"); -// boat1.setTeamName("Team 2"); -// assertEquals(boat1.getTeamName(), "Team 2"); -// } -// -// @Test -// public void testSetVelocity() { -// Boat boat1 = new Boat("Team 1", 29.0, "", 100); -// assertEquals(boat1.getVelocity(), (double) 29.0, 1e-15); -// -// boat1.setVelocity(12.0); -// assertEquals(boat1.getVelocity(), (double)12.0, 1e-15); -// } -//} diff --git a/src/test/java/seng302/TestGeoUtils.java b/src/test/java/seng302/TestGeoUtils.java deleted file mode 100644 index 09232f55..00000000 --- a/src/test/java/seng302/TestGeoUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -package seng302; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - -import javafx.geometry.Point2D; -import org.junit.Before; -import org.junit.Test; -import seng302.utilities.GeoUtility; - -/** - * Test Class for the GeometryUtils class - * Created by wmu16 on 24/05/17. - */ -public class TestGeoUtils { - - //Line in x = y - private Point2D linePoint1 = new Point2D(0, 0); - private Point2D linePoint2 = new Point2D(1, 1); - - //Point below x = y - private Point2D arbitraryPoint1 = new Point2D(1, 0); - - //Point above x = y - private Point2D arbitraryPoint2 = new Point2D(0, 1); - - //Point on x = y - private Point2D arbitraryPoint3 = new Point2D(2, 2); - - @Before - public void setUp() throws Exception { - - } - - - @Test - public void testLineFunction() { - - Integer lineFunctionResult1 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint1); - Integer lineFunctionResult2 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint2); - Integer lineFunctionResult3 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint3); - - //Point1 and Point2 are on opposite sides - assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2)); - assertNotEquals(lineFunctionResult1, lineFunctionResult2); - - //Point3 is on the line - assertEquals((long) lineFunctionResult3, 0L); - } - - @Test - public void testMakeArbitraryVectorPoint() { - - //Make a point (1,0) from point (0,0) - Point2D newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 0d, 1d); - Point2D expected = new Point2D(1,0); - - assertEquals(expected.getX(), newPoint.getX(), 1E-6); - assertEquals(expected.getY(), newPoint.getY(), 1E-6); - - newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 90d, 1d); - expected = new Point2D(0, 1); - - assertEquals(expected.getX(), newPoint.getX(), 1E-6); - assertEquals(expected.getY(), newPoint.getY(), 1E-6); - } -} diff --git a/src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java b/src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java deleted file mode 100644 index 7078eace..00000000 --- a/src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package seng302.gameServer.server.simulator; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import seng302.model.GeoPoint; -import seng302.utilities.GeoUtility; - -/** - * To test methods in GeoUtility. - * Created by Haoming on 28/04/17. - */ -public class GeoUtilityTest { - - 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); - - private double toleranceRate = 0.01; - - @Test - public void getDistance() throws Exception { - double expected, actual; - - actual = GeoUtility.getDistance(p1, p2); - expected = 1000; - assertEquals(expected, actual, expected * toleranceRate); - - actual = GeoUtility.getDistance(p1, p3); - expected = 927; - assertEquals(expected, actual, expected * toleranceRate); - - actual = GeoUtility.getDistance(p2, p4); - expected = 7430180; - assertEquals(expected, actual, expected * toleranceRate); - } - - @Test - public void getBearing() throws Exception { - double expected, actual; - - actual = GeoUtility.getBearing(p1, p2); - expected = 82; - assertEquals(expected, actual, expected * toleranceRate); - - actual = GeoUtility.getBearing(p1, p3); - expected = 86; - assertEquals(expected, actual, expected * toleranceRate); - - actual = GeoUtility.getBearing(p2, p4); - expected = 78; - assertEquals(expected, actual, expected * toleranceRate); - } - - @Test - public void getGeoCoordinate() throws Exception { - GeoPoint expected, actual; - - actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0); - expected = p2; - assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); - assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); - - actual = GeoUtility.getGeoCoordinate(p1, 86.0, 927.0); - expected = p3; - assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); - assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); - - actual = GeoUtility.getGeoCoordinate(p2, 78.0, 7430180.0); - expected = p4; - assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); - assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); - } - -} \ No newline at end of file diff --git a/src/test/java/seng302/model/YachtTest.java b/src/test/java/seng302/model/YachtTest.java new file mode 100644 index 00000000..e3144a55 --- /dev/null +++ b/src/test/java/seng302/model/YachtTest.java @@ -0,0 +1,55 @@ +package seng302.model; + +import static org.junit.Assert.*; + +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.setLat(57.670333); + yacht.setLon(11.827833); + + compoundMark = new CompoundMark(0, "HaomingsMark"); + Mark subMark1 = new Mark("H", 57.671524, 11.844495, 0); + 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); + } + + +} \ No newline at end of file diff --git a/src/test/java/seng302/model/mark/MarkTest.java b/src/test/java/seng302/model/mark/MarkTest.java deleted file mode 100644 index 125f20cc..00000000 --- a/src/test/java/seng302/model/mark/MarkTest.java +++ /dev/null @@ -1,45 +0,0 @@ -//package seng302.model.mark; -// -//import static org.junit.Assert.assertEquals; -//import static org.junit.Assert.assertTrue; -// -//import org.junit.Before; -//import org.junit.Test; -// -///** -// * Created by Haoming on 17/3/17. -// */ -//public class MarkTest { -// -// private SingleMark singleMark1; -// private SingleMark singleMark2; -// private GateMark gateMark; -// -// @Before -// public void setUp() throws Exception { -// this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342, 1, 0); -// this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352, 2, 1); -// this.gateMark = new GateMark("testMark_GM", MarkType.OPEN_GATE, singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude(), 2); -// } -// -// @Test -// public void getName() throws Exception { -// assertEquals("testMark_SM1", this.singleMark1.getName()); -// assertEquals("testMark_GM", this.gateMark.getName()); -// } -// -// @Test -// public void getMarkType() throws Exception { -// assertTrue(this.singleMark2.getMarkType() == MarkType.SINGLE_MARK); -// assertTrue(this.gateMark.getMarkType() == MarkType.OPEN_GATE); -// } -// -// @Test -// public void getMarkContent() throws Exception { -// assertEquals(12.23234, this.singleMark1.getLatitude(), 1e-10); -// assertEquals(-34.342, this.singleMark1.getLongitude(), 1e-10); -// assertEquals("testMark_SM1", this.gateMark.getSingleMark1().getName()); -// assertEquals(-34.352, this.gateMark.getSingleMark2().getLongitude(), 1e-10); -// } -// -//} \ No newline at end of file diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java new file mode 100644 index 00000000..b7ad8f85 --- /dev/null +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -0,0 +1,128 @@ +package seng302.utilities; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import javafx.geometry.Point2D; +import org.junit.Before; +import org.junit.Test; +import seng302.model.GeoPoint; +import seng302.utilities.GeoUtility; + +/** + * To test methods in GeoUtility. + * Use this site to calculate distances + * https://rechneronline.de/geo-coordinates/#distance + * Created by Haoming on 28/04/17. + */ +public class GeoUtilityTest { + + + //Line in x = y + private Point2D linePoint1 = new Point2D(0, 0); + private Point2D linePoint2 = new Point2D(1, 1); + + private Point2D arbitraryPoint1 = new Point2D(1, 0); //Point below x = y + private Point2D arbitraryPoint2 = new Point2D(0, 1); //Point above x = y + private Point2D arbitraryPoint3 = new Point2D(2, 2); //Point on x = y + + 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); + + private double toleranceRate = 0.01; + + + @Test + public void getBearing() throws Exception { + double expected, actual; + + actual = GeoUtility.getBearing(p1, p2); + expected = 82; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getBearing(p1, p3); + expected = 86; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getBearing(p2, p4); + expected = 78; + assertEquals(expected, actual, expected * toleranceRate); + } + + @Test + public void getGeoCoordinate() throws Exception { + GeoPoint expected, actual; + + actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0); + expected = p2; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + + actual = GeoUtility.getGeoCoordinate(p1, 86.0, 927.0); + expected = p3; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + + actual = GeoUtility.getGeoCoordinate(p2, 78.0, 7430180.0); + expected = p4; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + } + + + @Test + public void testGetDistance() throws Exception { + double expected, actual; + + actual = GeoUtility.getDistance(p1, p2); + expected = 1000; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getDistance(p1, p3); + expected = 927; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getDistance(p2, p4); + expected = 7430180; + assertEquals(expected, actual, expected * toleranceRate); + + } + + @Test + public void testLineFunction() { + + Integer lineFunctionResult1 = GeoUtility + .lineFunction(linePoint1, linePoint2, arbitraryPoint1); + Integer lineFunctionResult2 = GeoUtility + .lineFunction(linePoint1, linePoint2, arbitraryPoint2); + Integer lineFunctionResult3 = GeoUtility + .lineFunction(linePoint1, linePoint2, arbitraryPoint3); + + //Point1 and Point2 are on opposite sides + assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2)); + assertNotEquals(lineFunctionResult1, lineFunctionResult2); + + //Point3 is on the line + assertEquals((long) lineFunctionResult3, 0L); + } + + @Test + public void testMakeArbitraryVectorPoint() { + + //Make a point (1,0) from point (0,0) + Point2D newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 0d, 1d); + Point2D expected = new Point2D(1, 0); + + assertEquals(expected.getX(), newPoint.getX(), 1E-6); + assertEquals(expected.getY(), newPoint.getY(), 1E-6); + + newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 90d, 1d); + expected = new Point2D(0, 1); + + assertEquals(expected.getX(), newPoint.getX(), 1E-6); + assertEquals(expected.getY(), newPoint.getY(), 1E-6); + } + +} \ No newline at end of file From 874cdec654d242bc9561fa58cec64b5591a9f9af Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 3 Aug 2017 17:39:07 +1200 Subject: [PATCH 05/20] Added booleans: has entered rounding zone, has crossed first line, has crossed second line All for purposes of checking mark rounding. Currently not yet finished tags: #story[1124] #pair[hyi25, wmu16] --- src/main/java/seng302/model/Yacht.java | 62 ++++++++++++------- .../java/seng302/utilities/GeoUtility.java | 5 +- src/test/java/seng302/model/YachtTest.java | 3 +- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 38104e9e..d7cd1f9e 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -28,6 +28,8 @@ 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 + //BOTH AFAIK private String boatType; private Integer sourceId; @@ -37,9 +39,10 @@ public class Yacht { private String country; private Long estimateTimeAtFinish; - private Long timeTillNext; - private Double distanceToNextMark; + private Long lastMark; private Long markRoundTime; + private Double distanceToNextMark; + private Long timeTillNext; private CompoundMark nextMark; private Double heading; private Integer legNumber = 0; @@ -51,6 +54,11 @@ 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 hasPassedSecondLine; //The line extrapolated from the last mark to the current mark //CLIENT SIDE private List locationListeners = new ArrayList<>(); @@ -71,8 +79,13 @@ public class Yacht { this.country = country; this.sailIn = false; this.location = new GeoPoint(57.670341, 11.826856); + this.lastLocation = location; this.heading = 120.0; //In degrees this.velocity = 0d; //in mms-1 + + this.hasEnteredRoundingZone = false; + this.hasPassedFirstLine = false; + this.hasPassedSecondLine = false; } /** @@ -108,9 +121,16 @@ public class Yacht { } } - Double metersCovered = velocity * secondsElapsed; - location = GeoUtility.getGeoCoordinate(location, heading, metersCovered); + //UPDATE BOAT LOCATION + location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); + + //CHECK FOR MARK ROUNDING distanceToNextMark = calcDistanceToNextMark(); + if (distanceToNextMark < ROUNDING_DISTANCE) { + hasEnteredRoundingZone = true; + } + + // TODO: 3/08/17 wmu16 - Implement line cross check here } @@ -345,20 +365,21 @@ public class Yacht { return nextMark; } - public Double getLat() { - return location.getLat(); + public GeoPoint getLocation() { + return location; } - public void setLat(Double lat) { + /** + * Sets the current location of the boat in lat and long whilst preserving the last location + * + * @param lat Latitude + * @param lng Longitude + */ + public void setLocation(Double lat, Double lng) { + lastLocation.setLat(location.getLat()); + lastLocation.setLng(location.getLng()); location.setLat(lat); - } - - public Double getLon() { - return location.getLng(); - } - - public void setLon(Double lon) { - location.setLng(lon); + location.setLng(lng); } public Double getHeading() { @@ -378,10 +399,6 @@ public class Yacht { return boatName; } - public GeoPoint getLocation() { - return location; - } - public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) { this.timeSinceLastMarkProperty.set(timeSinceLastMark); } @@ -416,14 +433,13 @@ public class Yacht { return distanceToNextMark; } - public void updateLocation (double lat, double lon, double heading, double velocity) { - location.setLat(lat); - location.setLng(lon); + public void updateLocation(double lat, double lng, double heading, double velocity) { + setLocation(lat, lng); this.heading = heading; this.velocity = velocity; updateVelocityProperty(velocity); for (YachtLocationListener yll : locationListeners) { - yll.notifyLocation(this, lat, lon, heading, velocity); + yll.notifyLocation(this, lat, lng, heading, velocity); } } diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 54b0484a..0a49c776 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -96,8 +96,9 @@ 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 + + /** + * 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 line, * return -1 then the point is on the other side of the line diff --git a/src/test/java/seng302/model/YachtTest.java b/src/test/java/seng302/model/YachtTest.java index e3144a55..7937b43c 100644 --- a/src/test/java/seng302/model/YachtTest.java +++ b/src/test/java/seng302/model/YachtTest.java @@ -31,8 +31,7 @@ public class YachtTest { "HaomingIsOk", "NZL"); - yacht.setLat(57.670333); - yacht.setLon(11.827833); + yacht.setLocation(57.670333, 11.827833); compoundMark = new CompoundMark(0, "HaomingsMark"); Mark subMark1 = new Mark("H", 57.671524, 11.844495, 0); From 281ce2d842442a89b081e0b5fa4d6d43e067a732 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Fri, 4 Aug 2017 13:20:50 +1200 Subject: [PATCH 06/20] Loading course mark order from RaceXML - Re-engineered code to work with the new marks - Fixed bug where race order wasn't correct (added RacePosition class to fix) - Rewrote tests to work with new RacePosition class Tags: #story[1124] (Task 1) --- .../java/seng302/model/mark/MarkOrder.java | 75 ++++++++++++------- .../java/seng302/model/mark/RacePosition.java | 55 ++++++++++++++ .../java/seng302/models/MarkOrderTest.java | 67 +++++++---------- 3 files changed, 131 insertions(+), 66 deletions(-) create mode 100644 src/main/java/seng302/model/mark/RacePosition.java diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java index d3270c8f..ca2b4356 100644 --- a/src/main/java/seng302/model/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -1,14 +1,5 @@ package seng302.model.mark; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -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; @@ -19,11 +10,21 @@ 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 List raceMarkOrder; private Logger logger = LoggerFactory.getLogger(MarkOrder.class); public MarkOrder(){ @@ -34,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,23 +46,25 @@ public class MarkOrder { /** * Returns the mark in the race after the previous mark - * @param previous The previous mark - * @return the next mark - * OR null if there is no next mark + * @param position The current race position + * @return the next race position + * OR null if there is no position */ - public CompoundMark getNextMark(CompoundMark previous){ - for (int i = 0; i < raceMarkOrder.size(); i++){ - CompoundMark mark = raceMarkOrder.get(i); + public RacePosition getNextPosition(RacePosition position){ + Mark previousMark = position.getNextMark(); + Mark nextMark; - if (i + 1 >= raceMarkOrder.size()){ - return null; - } + if (position.getPositionIndex() + 1 >= raceMarkOrder.size() - 1){ + RacePosition nextRacePosition = new RacePosition(raceMarkOrder.size() - 1, null, previousMark); + nextRacePosition.setFinishingLeg(); - if (mark.equals(previous)){ - return raceMarkOrder.get(i+1); - } + return nextRacePosition; } - return null; + + Integer nextPositionIndex = position.getPositionIndex() + 1; + RacePosition nextRacePosition = new RacePosition(nextPositionIndex, raceMarkOrder.get(nextPositionIndex), previousMark); + + return nextRacePosition; } /** @@ -69,7 +72,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; @@ -87,14 +90,32 @@ public class MarkOrder { if (data != null){ logger.debug("Loaded RaceXML for mark order"); - List course = new ArrayList<>(data.getCompoundMarks().values()); - course.sort(Comparator.comparingInt(CompoundMark::getId)); + List corners = data.getMarkSequence(); + Map marks = data.getCompoundMarks(); + List course = new ArrayList<>(); + + for (Corner corner : corners){ + CompoundMark compoundMark = marks.get(corner.getCompoundMarkID()); + course.add(compoundMark.getMarks().get(0)); + } + return course; } 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 */ diff --git a/src/main/java/seng302/model/mark/RacePosition.java b/src/main/java/seng302/model/mark/RacePosition.java new file mode 100644 index 00000000..fc160b10 --- /dev/null +++ b/src/main/java/seng302/model/mark/RacePosition.java @@ -0,0 +1,55 @@ +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/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java index ad9b700e..8db06ec8 100644 --- a/src/test/java/seng302/models/MarkOrderTest.java +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -1,13 +1,14 @@ package seng302.models; -import static junit.framework.TestCase.assertEquals; -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 seng302.model.mark.RacePosition; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; public class MarkOrderTest { private static MarkOrder markOrder; @@ -25,28 +26,6 @@ public class MarkOrderTest { assertTrue(markOrder != null); } -// /** -// * Test if .equals() method on returns true on two marks that are equal -// */ -// @Test -// public void testMarkEqualsTrue(){ -// M mark1 = new SingleMark("asd", 1.1, 2.2, 1, 2); -// Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); -// -// assertEquals(mark1, mark2); -// } -// -// /** -// * Test if .equals() method on returns false on two marks that are NOT equal -// */ -// @Test -// public void testMarkNotEquals(){ -// Mark mark1 = new SingleMark("asf", 1.1, 2.2, 2, 2); -// Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); -// -// assertNotEquals(mark1, mark2); -// } - /** * Test if .getNextMark() returns null if it is called with the final mark in the race */ @@ -57,19 +36,12 @@ public class MarkOrderTest { return; } - CompoundMark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); + Mark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); + Integer lastIndex = markOrder.getMarkOrder().size() - 1; - assertEquals(null, markOrder.getNextMark(lastMark)); - } + RacePosition lastRacePosition = new RacePosition(lastIndex, lastMark, null); - /** - * Test if .getNextMark() method on returns null if the mark does not exist in the race - */ - @Test - public void testNextMarkNotExists(){ - CompoundMark someMark = new CompoundMark(1, "something"); - - assertEquals(null, markOrder.getNextMark(someMark)); + assertEquals(null, markOrder.getNextPosition(lastRacePosition).getNextMark()); } /** @@ -82,9 +54,26 @@ public class MarkOrderTest { return; } - CompoundMark firstMark = markOrder.getMarkOrder().get(0); + RacePosition firstRacePos = new RacePosition(0, markOrder.getMarkOrder().get(0), null); - assertEquals(markOrder.getMarkOrder().get(1), markOrder.getNextMark(firstMark)); + assertEquals(markOrder.getMarkOrder().get(1).getName(), markOrder.getNextPosition(firstRacePos).getNextMark().getName()); + } + + /** + * Test if a whole race can be completed + */ + @Test + public void testMarkSequence(){ + RacePosition current = markOrder.getFirstPosition(); + + while (!current.getIsFinishingLeg()){ + + current = markOrder.getNextPosition(current); + + if (current.getIsFinishingLeg()){ + assertEquals(null, current.getNextMark()); + } + } } @AfterClass From 43788bd15396c08d2fe8dcac2de978036ab9d67c Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Sun, 6 Aug 2017 12:36:17 +1200 Subject: [PATCH 07/20] Added a method to test if a geo point is located in a triangle which is formed by other three geo points. The method helps to check the mark rounding. Also unit tests have been done for this method. tags: #story[1124] --- .../java/seng302/utilities/GeoUtility.java | 272 ++++++++++-------- .../seng302/utilities/GeoUtilityTest.java | 25 ++ 2 files changed, 179 insertions(+), 118 deletions(-) diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 0a49c776..9aa61b9d 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -5,146 +5,182 @@ import seng302.model.GeoPoint; public class GeoUtility { - private static double EARTH_RADIUS = 6378.137; + private static double EARTH_RADIUS = 6378.137; - /** - * Calculates the euclidean distance between two markers on the canvas using xy coordinates - * - * @param p1 first geographical position - * @param p2 second geographical position - * @return the distance in meter between two points in meters - */ - public static Double getDistance(GeoPoint p1, GeoPoint p2) { + /** + * Calculates the euclidean distance between two markers on the canvas using xy coordinates + * + * @param p1 first geographical position + * @param p2 second geographical position + * @return the distance in meter between two points in meters + */ + public static Double getDistance(GeoPoint p1, GeoPoint p2) { - double dLat = Math.toRadians(p2.getLat() - p1.getLat()); - double dLon = Math.toRadians(p2.getLng() - p1.getLng()); + double dLat = Math.toRadians(p2.getLat() - p1.getLat()); + double dLon = Math.toRadians(p2.getLng() - p1.getLng()); - double a = Math.pow(Math.sin(dLat / 2), 2.0) - + Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) - * Math.pow(Math.sin(dLon / 2), 2.0); + double a = Math.pow(Math.sin(dLat / 2), 2.0) + + Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) + * Math.pow(Math.sin(dLon / 2), 2.0); - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - double d = EARTH_RADIUS * c; + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + double d = EARTH_RADIUS * c; - return d * 1000; // distance from km to meter - } + return d * 1000; // distance from km to meter + } - /** - * Calculates the angle between to angular co-ordinates on a sphere. - * - * @param p1 the first geographical position, start point - * @param p2 the second geographical position, end point - * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). - * vertical up is 0 deg. horizontal right is 90 deg. - * - * NOTE: - * The final bearing will differ from the initial bearing by varying degrees - * according to distance and latitude (if you were to go from say 35°N,45°E - * (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60° - * and end up on a heading of 120° - */ - public static Double getBearing(GeoPoint p1, GeoPoint p2) { - return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0; - } + /** + * Calculates the angle between to angular co-ordinates on a sphere. + * + * @param p1 the first geographical position, start point + * @param p2 the second geographical position, end point + * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). vertical up + * is 0 deg. horizontal right is 90 deg. + * + * NOTE: The final bearing will differ from the initial bearing by varying degrees according to + * distance and latitude (if you were to go from say 35°N,45°E (≈ Baghdad) to 35°N,135°E (≈ + * Osaka), you would start on a heading of 60° and end up on a heading of 120° + */ + public static Double getBearing(GeoPoint p1, GeoPoint p2) { + return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0; + } - /** - * Calculates the angle between to angular co-ordinates on a sphere in radians. - * - * @param p1 the first geographical position, start point - * @param p2 the second geographical position, end point - * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). - * vertical up is 0 deg. horizontal right is 90 deg. - * - * NOTE: - * The final bearing will differ from the initial bearing by varying degrees - * according to distance and latitude (if you were to go from say 35°N,45°E - * (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60° - * and end up on a heading of 120° - */ - public static Double getBearingRad(GeoPoint p1, GeoPoint p2) { - double dLon = Math.toRadians(p2.getLng() - p1.getLng()); + /** + * Calculates the angle between to angular co-ordinates on a sphere in radians. + * + * @param p1 the first geographical position, start point + * @param p2 the second geographical position, end point + * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). vertical up + * is 0 deg. horizontal right is 90 deg. + * + * NOTE: The final bearing will differ from the initial bearing by varying degrees according to + * distance and latitude (if you were to go from say 35°N,45°E (≈ Baghdad) to 35°N,135°E (≈ + * Osaka), you would start on a heading of 60° and end up on a heading of 120° + */ + public static Double getBearingRad(GeoPoint p1, GeoPoint p2) { + double dLon = Math.toRadians(p2.getLng() - p1.getLng()); - double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat())); - double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat())) - - Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math.cos(dLon); + double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat())); + double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat())) + - Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math + .cos(dLon); - return Math.atan2(y, x); - } + return Math.atan2(y, x); + } - /** - * Given an existing point in lat/lng, distance in (in meter) and bearing - * (in degrees), calculates the new lat/lng. - * - * @param origin the original position within lat / lng - * @param bearing the bearing in degree, from original position to the new position - * @param distance the distance in meter, from original position to the new position - * @return the new position - */ - public static GeoPoint getGeoCoordinate(GeoPoint origin, Double bearing, Double distance) { - double b = Math.toRadians(bearing); // bearing to radians - double d = distance / 1000.0; // distance to km + /** + * Given an existing point in lat/lng, distance in (in meter) and bearing (in degrees), + * calculates the new lat/lng. + * + * @param origin the original position within lat / lng + * @param bearing the bearing in degree, from original position to the new position + * @param distance the distance in meter, from original position to the new position + * @return the new position + */ + public static GeoPoint getGeoCoordinate(GeoPoint origin, Double bearing, Double distance) { + double b = Math.toRadians(bearing); // bearing to radians + double d = distance / 1000.0; // distance to km - double originLat = Math.toRadians(origin.getLat()); - double originLng = Math.toRadians(origin.getLng()); + double originLat = Math.toRadians(origin.getLat()); + double originLng = Math.toRadians(origin.getLng()); - double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS) - + Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b)); - double endLng = originLng - + Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat), - Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); + double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS) + + Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b)); + double endLng = originLng + + Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat), + Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); - return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); - } + 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 line, - * return -1 then the point is on the other side of the line - * return 0 then the point is exactly on the line. - * @param linePoint1 One point of the line - * @param linePoint2 Second point of the line - * @param testPoint The point to test with this line - * @return A return value indicating which side of the line the point is on - */ - public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { + * 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 + * line, return -1 then the point is on the other side of the line return 0 then the point is + * exactly on the line. + * + * @param linePoint1 One point of the line + * @param linePoint2 Second point of the line + * @param testPoint The point to test with this line + * @return A return value indicating which side of the line the point is on + */ + public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { - Double x = testPoint.getX(); - Double y = testPoint.getY(); - Double x1 = linePoint1.getX(); - Double y1 = linePoint1.getY(); - Double x2 = linePoint2.getX(); - Double y2 = linePoint2.getY(); + Double x = testPoint.getX(); + Double y = testPoint.getY(); + Double x1 = linePoint1.getX(); + Double y1 = linePoint1.getY(); + Double x2 = linePoint2.getX(); + Double y2 = linePoint2.getY(); - Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function + Double result = (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1); //Line function - if (result > 0) { - return 1; - } - else if (result < 0) { - return -1; - } - else { - return 0; - } - } + if (result > 0) { + return 1; + } else if (result < 0) { + return -1; + } else { + return 0; + } + } - /** - * Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin - * point - * @param originPoint The point with which to use as the base for our vector addition - * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!) - * @param vectorLength The length out on this angle from the origin point to create the new point - * @return a Point2D - */ - public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) { + /** + * Given a point and a vector (angle and vector length) Will create a new point, that vector + * away from the origin point + * + * @param originPoint The point with which to use as the base for our vector addition + * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!) + * @param vectorLength The length out on this angle from the origin point to create the new + * point + * @return a Point2D + */ + public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, + Double vectorLength) { - Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); - Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); + Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); + Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); - return new Point2D(endPointX, endPointY); + return new Point2D(endPointX, endPointY); - } + } + + /** + * Define vector v1 = p1 - p0 to v2 = p2- p0. This function returns the difference of bearing + * from v1 to v2. For example, if bearing of v1 is 30 deg and bearing of v2 is 90 deg, then the + * difference is 60 deg. + * + * @param bearing1 the bearing of v1 + * @param bearing2 the bearing of v2 + * @return the difference of bearing from v1 to v2 + */ + private static double getBearingDiff(double bearing1, double bearing2) { + return ((360 - bearing1) + bearing2) % 360; + } + + /** + * Given three geo points to form a triangle, the method returns true if the fourth point is + * inside the triangle + * + * @param v1 the vertex of the triangle + * @param v2 the vertex of the triangle + * @param v3 the vertex of the triangle + * @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) { + // 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; + + if ((getBearingDiff(getBearing(v2, v3), getBearing(v2, point)) < 180) != sideFlag) { + return false; + } + + if ((getBearingDiff(getBearing(v3, v1), getBearing(v3, point)) < 180) != sideFlag) { + return false; + } + + return true; + } } diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java index b7ad8f85..8382bb8c 100644 --- a/src/test/java/seng302/utilities/GeoUtilityTest.java +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -1,7 +1,9 @@ package seng302.utilities; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import javafx.geometry.Point2D; import org.junit.Before; @@ -30,6 +32,7 @@ public class GeoUtilityTest { 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); + private GeoPoint p5 = new GeoPoint(57.671829, 11.842049); private double toleranceRate = 0.01; @@ -125,4 +128,26 @@ public class GeoUtilityTest { assertEquals(expected.getY(), newPoint.getY(), 1E-6); } + @Test + public void testIsPointInTriangle() { + GeoPoint v1 = new GeoPoint(57.670333, 11.842833); + GeoPoint v2 = new GeoPoint(57.671524, 11.844495); + GeoPoint v3 = new GeoPoint(57.671829, 11.842049); + GeoPoint p1 = new GeoPoint(57.670822, 11.843192); // inside triangle + GeoPoint p2 = new GeoPoint(57.670892, 11.843642); // outside triangle + + // benchmark test. 100,000 calculations for 0.336 seconds +// long startTime = System.nanoTime(); +// for (int i = 0; i < 100000; i++) { +// assertTrue(GeoUtility.isPointInTriangle(v1, v2, v3, p1)); +// } +// System.out.println((System.nanoTime() - startTime) / 1000000000.0); + + // test for different orders of vertices, which should not affect the result + assertTrue(GeoUtility.isPointInTriangle(v2, v1, v3, p1)); + assertTrue(GeoUtility.isPointInTriangle(v3, v1, v2, p1)); + + assertFalse(GeoUtility.isPointInTriangle(v1, v2, v3, p2)); + + } } \ No newline at end of file From 7f0329dda6ac175d5e08adc825ae8d41761cea88 Mon Sep 17 00:00:00 2001 From: William Muir Date: Mon, 7 Aug 2017 00:23:54 +1200 Subject: [PATCH 08/20] 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 09/20] 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 10/20] 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 11/20] 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 12/20] 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 249ad9e5c04c00bd796cb39375fa9b21be6ce1b1 Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 10 Aug 2017 12:02:19 +1200 Subject: [PATCH 13/20] 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 14/20] 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 15/20] 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 16/20] 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 1d7b5271308df390bbb05dcb0513182c5fedcf99 Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 10 Aug 2017 16:45:30 +1200 Subject: [PATCH 17/20] 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 0b8ad137b30e3769fc662da5fcbdcb8f24790780 Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 10 Aug 2017 17:47:24 +1200 Subject: [PATCH 18/20] 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 09c4f98056eb090c2b12b38fbe248335367ebffb Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Thu, 10 Aug 2017 19:01:30 +1200 Subject: [PATCH 19/20] 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 a7b8b0dbc3695369a26b139868ad6d7ced5f0c20 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Thu, 10 Aug 2017 20:15:08 +1200 Subject: [PATCH 20/20] 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"