diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index bb6f0c16..1bb968d1 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -4,12 +4,12 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import seng302.gameServer.server.messages.BoatActionType; import seng302.model.Player; import seng302.model.Yacht; -import seng302.gameServer.server.messages.BoatActionType; +import seng302.model.mark.MarkOrder; /** * A Static class to hold information about the current state of the game (model) @@ -17,7 +17,10 @@ import seng302.gameServer.server.messages.BoatActionType; */ 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; @@ -28,10 +31,9 @@ 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; - // 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 @@ -58,9 +60,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() { @@ -71,20 +73,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); } @@ -112,6 +108,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/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..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; @@ -18,23 +22,6 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.zip.CRC32; import java.util.zip.Checksum; -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 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,57 +49,58 @@ 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; 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); - } - //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(); + 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" + ); + + GameState.addYacht(sourceId, yacht); + GameState.addPlayer(new Player(socket, yacht)); } static void serverLog(String message, int logLevel) { @@ -127,11 +115,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 +185,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 +253,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/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/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/gameServer/server/simulator/Simulator.java b/src/main/java/seng302/gameServer/server/simulator/Simulator.java deleted file mode 100644 index 338f011a..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/Simulator.java +++ /dev/null @@ -1,137 +0,0 @@ -package seng302.gameServer.server.simulator; - -import java.util.List; -import java.util.Observable; -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; - } -} 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 16df9123..d0203393 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; @@ -12,8 +10,12 @@ 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; +import seng302.utilities.GeoUtility; /** * Yacht class for the racing boat. @@ -29,6 +31,11 @@ public class Yacht { void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity, boolean sailIn); } + 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; @@ -38,12 +45,11 @@ public class Yacht { private String country; private Long estimateTimeAtFinish; - private Long timeTillNext; + private Integer currentMarkSeqID = 0; private Long markRoundTime; - private CompoundMark nextMark; + private Double distanceToCurrentMark; + private Long timeTillNext; private Double heading; - private Double lat; - private Double lon; private Integer legNumber = 0; //SERVER SIDE @@ -54,6 +60,13 @@ public class Yacht { 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 hasPassedLine; + private Boolean hasPassedThroughGate; + private Boolean finishedRace; + //CLIENT SIDE private List locationListeners = new ArrayList<>(); private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper(); @@ -73,8 +86,14 @@ public class Yacht { this.boatName = boatName; this.country = country; 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.hasPassedLine = false; + this.hasPassedThroughGate = false; + this.finishedRace = false; } /** @@ -110,10 +129,178 @@ public class Yacht { } } - Double metersCovered = velocity * secondsElapsed; - location = getGeoCoordinate(location, heading, metersCovered); + //UPDATE BOAT LOCATION + lastLocation = location; + location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); + + //CHECK FOR MARK ROUNDING + if (!finishedRace) { + checkForLegProgression(); + } + + // 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). 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 calcDistanceToCurrentMark() 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); + Double distance2 = GeoUtility.getDistance(location, sub2); + return (distance1 < distance2) ? distance1 : distance2; + } else { + return GeoUtility.getDistance(location, nextMark.getSubMark(1)); + } + } + + + /** + * 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) { + 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) { + Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint()); + if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) { + currentMarkSeqID++; + logMarkRounding(currentMark); + } + } + } + + + /** + * This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute + * of the yacht if so. + * A visual representation of this algorithm can be seen on the Wiki under + * 'mark passing algorithm' + */ + private void checkMarkRounding(CompoundMark currentMark) { + distanceToCurrentMark = calcDistanceToCurrentMark(); + 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; + } + + //In case current mark is a gate, loop through all marks just in case + for (Mark thisCurrentMark : currentMark.getMarks()) { + if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) { + hasPassedLine = true; + } + } + + if (hasPassedLine && hasEnteredRoundingZone) { + currentMarkSeqID++; + hasPassedLine = false; + hasEnteredRoundingZone = false; + hasPassedThroughGate = false; + logMarkRounding(currentMark); + } + } + + + /** + * Checks if a gate line has been crossed and in the correct direction + * + * @param currentMark The current gate + */ + private void checkGateRounding(CompoundMark currentMark) { + 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) { + 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(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 { + currentMarkSeqID++; + logMarkRounding(currentMark); + } + } + } + + /** + * If we pass the finish gate in the correct direction + * + * @param currentMark The current gate + */ + private void checkFinishLineCrossing(CompoundMark currentMark) { + 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) { + Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint()); + if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) { + currentMarkSeqID++; + finishedRace = true; + logMarkRounding(currentMark); + logger.debug(sourceId + " finished"); + // TODO: 8/08/17 wmu16 - Do something! + } + } + } + + public void adjustHeading(Double amount) { Double newVal = heading + amount; lastHeading = heading; @@ -192,7 +379,6 @@ public class Yacht { } private void turnTowardsHeading(Double newHeading) { - System.out.println(newHeading); if (heading < 90 && newHeading > 270) { adjustHeading(-TURN_STEP); } else { @@ -282,7 +468,6 @@ public class Yacht { this.velocityProperty.set(velocity); } - public void setMarkRoundingTime(Long markRoundingTime) { this.markRoundTime = markRoundingTime; } @@ -319,28 +504,21 @@ public class Yacht { this.lastMarkRounded = lastMarkRounded; } - public void setNextMark(CompoundMark nextMark) { - this.nextMark = nextMark; + public GeoPoint getLocation() { + return location; } - public CompoundMark getNextMark(){ - return nextMark; - } - - public Double getLat() { - return lat; - } - - public void setLat(Double lat) { - this.lat = lat; - } - - public Double getLon() { - return lon; - } - - public void setLon(Double lon) { - this.lon = lon; + /** + * 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); + location.setLng(lng); } public Double getHeading() { @@ -360,10 +538,6 @@ public class Yacht { return boatName; } - public GeoPoint getLocation() { - return location; - } - public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) { this.timeSinceLastMarkProperty.set(timeSinceLastMark); } @@ -397,21 +571,38 @@ public class Yacht { this.velocity = velocity; } + public Double getDistanceToCurrentMark() { + return distanceToCurrentMark; + } + public Boolean getClientSailsIn(){ return clientSailsIn; } - public void updateLocation (double lat, double lon, double heading, double velocity) { - this.lat = lat; - this.lon = 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, clientSailsIn); + yll.notifyLocation(this, lat, lng, heading, velocity); } } + private void logMarkRounding(CompoundMark currentMark) { + String typeString = "mark"; + if (currentMark.isGate()) { + typeString = "gate"; + } + 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) { locationListeners.add(listener); } diff --git a/src/main/java/seng302/model/mark/CompoundMark.java b/src/main/java/seng302/model/mark/CompoundMark.java index b1989845..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. @@ -86,4 +96,14 @@ public class CompoundMark { public List getMarks () { return marks; } + + @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/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java new file mode 100644 index 00000000..141d5c6d --- /dev/null +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -0,0 +1,133 @@ +package seng302.model.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.model.stream.xml.generator.Race; +import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.utilities.XMLGenerator; +import seng302.utilities.XMLParser; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Class to hold the order of the marks in the race. + */ +public class MarkOrder { + private List raceMarkOrder; + private Logger logger = LoggerFactory.getLogger(MarkOrder.class); + + 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); + } + + /** + * @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 Boolean isLastMark(Integer seqID) { + return seqID == raceMarkOrder.size() - 1; + } + + /** + * @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); + } + + public CompoundMark getCurrentMark(Integer currentSeqID) { + return raceMarkOrder.get(currentSeqID); + } + + /** + * @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); + } + + /** + * 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){ + + 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; + } + + RaceXMLData data = XMLParser.parseRace(doc); + + if (data != null){ + logger.debug("Loaded RaceXML for mark order"); + 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); + } + + return course; + } + + 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); + } +} \ No newline at end of file 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/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 54b0484a..f6968601 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -5,145 +5,233 @@ 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 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); - - 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 - - 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)); - - 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) { - - 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 - - if (result > 0) { - return 1; - } - else if (result < 0) { - return -1; - } - else { - return 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; + } - /** - * 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) { + /** + * 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); + } - Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); - Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); + /** + * 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()); - return new Point2D(endPointX, endPointY); + 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); + } + + /** + * 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 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)); + } + + /** + * 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 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; + } + } + + /** + * 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 0 if two line segment doesn't intersect, otherwise 1 if they intersect and + * 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) { + 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)) { + + return enteredDirection ? 1 : 2; + } + } + 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) { + + 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); + + } + + /** + * 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; + } + + /** + * Check if a geo point ins on the right hand side of the line segment, which + * 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 + * @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 + */ + public 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 + * + * @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 to v2) to (v1 to p) is less than 180 deg + boolean isCW = isClockwise(v1, v2, point); + + if (isClockwise(v2, v3, point) != isCW) { + return false; + } + + if (isClockwise(v3, v1, point) != isCW) { + return false; + } + + return true; + } } 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/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index 838c92b6..8cb9dd65 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 @@ -50,8 +54,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; @@ -73,15 +78,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(); @@ -130,9 +128,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); @@ -157,36 +161,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 68a3a25a..112ed05d 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); @@ -174,7 +175,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 63ce668a..8b7b49bb 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/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/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/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/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java new file mode 100644 index 00000000..42531563 --- /dev/null +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -0,0 +1,71 @@ +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 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; + } + + /** + * Test to ensure marks are loaded from XML + */ + @Test + public void testMarkOrderLoadedFromXML(){ + assertTrue(markOrder != null); + } + + + @Test + public void testIsLastMark() { + currentSeqID = 0; + assertFalse(markOrder.isLastMark(currentSeqID)); + + currentSeqID = markOrder.getMarkOrder().size() - 1; + assertTrue(markOrder.isLastMark(currentSeqID)); + } + + @Test + public void testGetNextMark() { + currentSeqID = 4; + CompoundMark nextMark = markOrder.getMarkOrder().get(4 + 1); + assertEquals(nextMark, markOrder.getNextMark(currentSeqID)); + + currentSeqID = 3; + nextMark = markOrder.getMarkOrder().get(3 + 1); + assertEquals(nextMark, markOrder.getNextMark(currentSeqID)); + } + + @Test + public void testGetCurrentMark() { + currentSeqID = 0; + CompoundMark currentMark = markOrder.getMarkOrder().get(0); + assertEquals(currentMark, markOrder.getCurrentMark(0)); + } + + @Test + public void testGetPreviousMark() { + currentSeqID = 1; + CompoundMark prevMark = markOrder.getMarkOrder().get(0); + assertEquals(prevMark, markOrder.getPreviousMark(currentSeqID)); + } + + @AfterClass + public static void tearDown(){ + markOrder = null; + } +} diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java new file mode 100644 index 00000000..ca2af0e9 --- /dev/null +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -0,0 +1,180 @@ +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.Test; +import seng302.model.GeoPoint; + +/** + * 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 + * 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 GeoPoint p5 = new GeoPoint(57.671829, 11.842049); + + 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); + } + + @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)); + + } + + + @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(37.40927, -122.62152); + + // 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); + } + + @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