diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index f4aad3df..ab3ec79a 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -10,11 +10,10 @@ import javafx.stage.Stage; import org.apache.commons.cli.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import seng302.client.ClientPacketParser; -import seng302.client.ClientState; -import seng302.models.PolarTable; +import seng302.model.PolarTable; public class App extends Application { + private static Logger logger = LoggerFactory.getLogger(App.class); public static void parseArgs(String[] args) throws ParseException { @@ -22,15 +21,16 @@ public class App extends Application { CommandLineParser parser = new DefaultParser(); CommandLine cmd; - ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory + .getLogger(Logger.ROOT_LOGGER_NAME); options.addOption("debugLevel", true, "Set the application debug level"); cmd = parser.parse(options, args); - if (cmd.hasOption("debugLevel")){ + if (cmd.hasOption("debugLevel")) { - switch (cmd.getOptionValue("debugLevel")){ + switch (cmd.getOptionValue("debugLevel")) { case "DEBUG": rootLogger.setLevel(Level.DEBUG); break; @@ -56,7 +56,7 @@ public class App extends Application { default: rootLogger.setLevel(Level.ALL); } - } else{ + } else { rootLogger.setLevel(Level.WARN); } } @@ -65,22 +65,25 @@ public class App extends Application { public void start(Stage primaryStage) throws Exception { PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); - Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml")); + Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml")); primaryStage.setTitle("RaceVision"); - primaryStage.setScene(new Scene(root, 1530, 960)); - primaryStage.setMaxWidth(1530); - primaryStage.setMaxHeight(960); + Scene scene = new Scene(root, 1530, 960); + scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + primaryStage.setScene(scene); +// primaryStage.setMaxWidth(1530); +// primaryStage.setMaxHeight(960); primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png"))); // primaryStage.setMaximized(true); primaryStage.show(); primaryStage.setOnCloseRequest(e -> { - ClientPacketParser.appClose(); +// ClientPacketParser.appClose(); +// ClientPacketParser.appClose(); System.exit(0); }); - ClientState.primaryStage = primaryStage; +// ClientState.primaryStage = primaryStage; } public static void main(String[] args) { diff --git a/src/main/java/seng302/client/ClientPacketParser.java b/src/main/java/seng302/client/ClientPacketParser.java deleted file mode 100644 index bf58c4a1..00000000 --- a/src/main/java/seng302/client/ClientPacketParser.java +++ /dev/null @@ -1,638 +0,0 @@ -package seng302.client; - - -import java.io.IOException; -import java.io.StringReader; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Date; -import java.util.Map; -import java.util.TimeZone; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.PriorityBlockingQueue; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import org.w3c.dom.Document; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import seng302.models.Yacht; -import seng302.models.mark.Mark; -import seng302.models.stream.XMLParser; -import seng302.models.stream.packets.BoatPositionPacket; -import seng302.models.stream.packets.StreamPacket; - -/** - * The purpose of this class is to take in the stream of divided packets so they can be read - * and parsed in by turning the byte arrays into useful data. There are two public static hashmaps - * that are threadsafe so the visualiser can always access the latest speed and position available - * Created by kre39 on 23/04/17. - */ -public class ClientPacketParser { - - public static ConcurrentHashMap> markLocations = new ConcurrentHashMap<>(); - public static ConcurrentHashMap> boatLocations = new ConcurrentHashMap<>(); - private static boolean newRaceXmlReceived = false; - private static boolean raceStarted = false; - private static XMLParser xmlObject = new XMLParser(); - private static boolean raceFinished = false; - private static boolean streamStatus = false; - private static long timeSinceStart = -1; - private static Map boats = new ConcurrentHashMap<>(); - private static Map boatsPos = new ConcurrentSkipListMap<>(); - private static double windDirection = 0; - private static Double windSpeed = 0d; - private static Long currentTimeLong; - private static String currentTimeString; - private static boolean appRunning; - private static Map clientStateBoats = new ConcurrentHashMap<>(); - - //CONVERSION CONSTANTS - public static final Double MS_TO_KNOTS = 1.94384; - - /** - * Used to initialise the thread name and stream parser object so a thread can be executed - */ - public ClientPacketParser() { - } - - /** - * Looks at the type of the packet then sends it to the appropriate parser to extract the - * specific data associated with that packet type - * - * @param packet the packet to be looked at and processed - */ - public static void parsePacket(StreamPacket packet) { - try { - switch (packet.getType()) { - case HEARTBEAT: - extractHeartBeat(packet); - break; - case RACE_STATUS: - extractRaceStatus(packet); - break; - case DISPLAY_TEXT_MESSAGE: - extractDisplayMessage(packet); - break; - case XML_MESSAGE: - extractXmlMessage(packet); - break; - case RACE_START_STATUS: - extractRaceStartStatus(packet); - break; - case YACHT_EVENT_CODE: - extractYachtEventCode(packet); - break; - case YACHT_ACTION_CODE: - extractYachtActionCode(packet); - break; - case CHATTER_TEXT: - extractChatterText(packet); - break; - case BOAT_LOCATION: - extractBoatLocation(packet); - break; - case MARK_ROUNDING: - extractMarkRounding(packet); - break; - case COURSE_WIND: - extractCourseWind(packet); - break; - case AVG_WIND: - extractAvgWind(packet); - break; - } - } catch (NullPointerException e) { - System.out.println("Error parsing packet"); - } - } - - /** - * Extracts the seq num used in the heartbeat packet - * - * @param packet Packet parsed in to use the payload - */ - private static void extractHeartBeat(StreamPacket packet) { - long heartbeat = bytesToLong(packet.getPayload()); - } - - private static String getTimeZoneString() { - - Integer offset = xmlObject.getRegattaXML().getUtcOffset(); - StringBuilder utcOffset = new StringBuilder(); - utcOffset.append("GMT"); - if (offset > 0) { - utcOffset.append("+"); - utcOffset.append(offset); - } else if (offset < 0) { - utcOffset.append("-"); - utcOffset.append(offset); - } - return utcOffset.toString(); - - } - - /** - * Extracts the useful race status data from race status type packets. This method will also - * print to the console the current state of the race (if it has started/finished or is about to - * start), along side this it'll also display the amount of time since the race has started or - * time till it starts - * - * @param packet Packet parsed in to use the payload - */ - private static void extractRaceStatus(StreamPacket packet) { - byte[] payload = packet.getPayload(); - int messageVersionNo = payload[0]; - long currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); - long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11)); - int raceStatus = payload[11]; - long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18)); - long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); - long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); - - currentTimeLong = currentTime; - DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); - if (xmlObject.getRegattaXML() != null) { - format.setTimeZone(TimeZone.getTimeZone(getTimeZoneString())); - currentTimeString = format.format((new Date(currentTime)).getTime()); - } - long timeTillStart = - ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000; - - if (timeTillStart > 0) { - timeSinceStart = timeTillStart; - } else { - if (raceStatus == 4 || raceStatus == 8) { - raceFinished = true; - raceStarted = false; - ClientState.setRaceStarted(false); - } else if (!raceStarted) { - raceStarted = true; - ClientState.setRaceStarted(true); - raceFinished = false; - } - timeSinceStart = timeTillStart; - } - - double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc... - windDirection = windDir / windDirFactor; - windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS; - - int noBoats = payload[22]; - int raceType = payload[23]; - for (int i = 0; i < noBoats; i++) { - long boatStatusSourceID = bytesToLong( - Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20))); - int boatStatus = (int) payload[28 + (i * 20)]; - int boatLegNumber = (int) payload[29 + (i * 20)]; - int boatPenaltyAwarded = (int) payload[30 + (i * 20)]; - int boatPenaltyServed = (int) payload[31 + (i * 20)]; - long estTimeAtNextMark = bytesToLong( - Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20))); - long estTimeAtFinish = bytesToLong( - Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20))); - - Yacht boat = boats.get((int) boatStatusSourceID); - boat.setBoatStatus((boatStatus)); - setBoatLegPosition(boat, boatLegNumber); - boat.setPenaltiesAwarded(boatPenaltyAwarded); - boat.setPenaltiesServed(boatPenaltyServed); - boat.setEstimateTimeAtNextMark(estTimeAtNextMark); - boat.setEstimateTimeAtFinish(estTimeAtFinish); - - // Update Client State boats when receive race status packet. - // Potentially could replace boats in ClientPacketParser. - Yacht clientBoat = ClientState.getBoats().get((int) boatStatusSourceID); - clientBoat.setBoatStatus((boatStatus)); - setBoatLegPosition(clientBoat, boatLegNumber); - clientBoat.setPenaltiesAwarded(boatPenaltyAwarded); - clientBoat.setPenaltiesServed(boatPenaltyServed); - clientBoat.setEstimateTimeAtNextMark(estTimeAtNextMark); - clientBoat.setEstimateTimeAtFinish(estTimeAtFinish); - } - - // 3 is race started. - // ClientState race started flag will be set to true if race started, else set false. - if (raceStatus == 3) { - ClientState.setRaceStarted(true); - } else { - ClientState.setRaceStarted(false); - } - } - - private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){ - Integer placing = 1; - - if (/* TODO implement when we are getting this data /TODO leg != updatingBoat.getLegNumber() && */(raceStarted || raceFinished)) { - for (Yacht boat : boats.values()) { - placing = boat.getSourceId(); - /* See above to-do - if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){ - placing += 1; - }*/ - } - updatingBoat.setPosition(placing.toString()); - updatingBoat.setLegNumber(leg); - boatsPos.putIfAbsent(placing, updatingBoat); - boatsPos.replace(placing, updatingBoat); - } else if(updatingBoat.getLegNumber() == null){ - updatingBoat.setPosition("-"); - updatingBoat.setLegNumber(leg); - } - } - - /** - * Used to extract the messages passed through with the display message packet - * - * @param packet Packet parsed in to use the payload - */ - private static void extractDisplayMessage(StreamPacket packet) { - byte[] payload = packet.getPayload(); - int messageVersionNo = payload[0]; - int numOfLines = payload[3]; - int totalLen = 0; - for (int i = 0; i < numOfLines; i++) { - int lineNum = payload[4 + totalLen]; - int textLength = payload[5 + totalLen]; - byte[] messageTextBytes = Arrays - .copyOfRange(payload, 6 + totalLen, 6 + textLength + totalLen); - String messageText = new String(messageTextBytes); - totalLen += 2 + textLength; - } - } - - /** - * Used to read in the xml data. Will call the specific methods to create the course and boats - * - * @param packet Packet parsed in to use the payload - */ - private static void extractXmlMessage(StreamPacket packet) { - xmlObject = new XMLParser(); - byte[] payload = packet.getPayload(); - int messageType = payload[9]; - long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14)); - String xmlMessage = new String( - (Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim(); - - //Create XML document Object - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = null; - Document doc = null; - try { - db = dbf.newDocumentBuilder(); - doc = db.parse(new InputSource(new StringReader(xmlMessage))); - } catch (ParserConfigurationException | IOException | SAXException e) { - System.out.println("[ClientPacketParser] ParserConfigurationException | IOException | SAXException"); - } - - xmlObject.constructXML(doc, messageType); - - if (messageType == 7) { //7 is the boat XML - boats = xmlObject.getBoatXML().getCompetingBoats(); - // Set/Update the ClientState boats after receiving new boat xml. - // Flag boatsUpdated in ClientState to true. - ClientState.setBoats(xmlObject.getBoatXML().getCompetingBoats()); - ClientState.setBoatsUpdated(true); - } - if (messageType == 6) { //6 is race info xml - newRaceXmlReceived = true; - } - } - - /** - * Extracts the race start status from the packet, currently is unused within the app but - * is here for potential future use - * - * @param packet Packet parsed in to use the payload - */ - private static void extractRaceStartStatus(StreamPacket packet) { - byte[] payload = packet.getPayload(); - int messageVersionNo = payload[0]; - long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); - long raceStartTime = bytesToLong(Arrays.copyOfRange(payload, 9, 15)); - long raceId = bytesToLong(Arrays.copyOfRange(payload, 15, 19)); - int notificationType = payload[19]; - } - - /** - * When a yacht event occurs this will parse the byte array to retrieve the necessary info, - * currently unused - * - * @param packet Packet parsed in to use the payload - */ - private static void extractYachtEventCode(StreamPacket packet) { - byte[] payload = packet.getPayload(); - int messageVersionNo = payload[0]; - long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); - long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13)); - long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17)); - long incidentId = bytesToLong(Arrays.copyOfRange(payload, 17, 21)); - int eventId = payload[21]; - } - - /** - * When a yacht action occurs this will parse the parse the byte array to retrieve the necessary - * info, currently unused - * - * @param packet Packet parsed in to use the payload - */ - private static void extractYachtActionCode(StreamPacket packet) { - byte[] payload = packet.getPayload(); - int messageVersionNo = payload[0]; - long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); - long subjectId = bytesToLong(Arrays.copyOfRange(payload, 9, 13)); - long incidentId = bytesToLong(Arrays.copyOfRange(payload, 13, 17)); - int eventId = payload[17]; - } - - /** - * Strips the message from the chatter text type packets, currently the message is unused - * - * @param packet Packet parsed in to use the payload - */ - private static void extractChatterText(StreamPacket packet) { - byte[] payload = packet.getPayload(); - int messageVersionNo = payload[0]; - int messageType = payload[1]; - int length = payload[2]; - String message = new String(Arrays.copyOfRange(payload, 3, 3 + length)); - System.out.println(message); - } - - /** - * Used to breakdown the boatlocation packets so the boat coordinates, id and groundspeed are - * all used All the other extra data is still being read and translated however is unused. - * - * @param packet Packet parsed in to use the payload - */ - private static void extractBoatLocation(StreamPacket packet) { - byte[] payload = packet.getPayload(); - - int deviceType = (int) payload[15]; - long timeValid = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); - long seq = bytesToLong(Arrays.copyOfRange(payload, 11, 15)); - long boatId = bytesToLong(Arrays.copyOfRange(payload, 7, 11)); - long rawLat = bytesToLong(Arrays.copyOfRange(payload, 16, 20)); - long rawLon = bytesToLong(Arrays.copyOfRange(payload, 20, 24)); - //Converts the double to a usable lat/lon - double lat = ((180d * (double) rawLat) / Math.pow(2, 31)); - double lon = ((180d * (double) rawLon) / Math.pow(2, 31)); -// System.out.println("[CLIENT] Lat: " + lat + " Lon: " + lon); - long heading = bytesToLong(Arrays.copyOfRange(payload, 28, 30)); - double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0; - //type 1 is a racing yacht and type 3 is a mark, needed for updating positions of the mark and boat - if (deviceType == 1){ - Yacht boat = boats.get((int) boatId); - boat.setVelocity(groundSpeed); - BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed); - - //add a new priority que to the boatLocations HashMap - if (!boatLocations.containsKey(boatId)) { - boatLocations.put(boatId, - new PriorityBlockingQueue<>(256, new Comparator() { - @Override - public int compare(BoatPositionPacket p1, BoatPositionPacket p2) { - return (int) (p1.getTimeValid() - p2.getTimeValid()); - } - })); - } - boatLocations.get(boatId).put(boatPacket); - } else if (deviceType == 3) { - BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, - heading, groundSpeed); - - //add a new priority que to the boatLocations HashMap - if (!markLocations.containsKey(boatId)) { - markLocations.put(boatId, - new PriorityBlockingQueue<>(256, new Comparator() { - @Override - public int compare(BoatPositionPacket p1, BoatPositionPacket p2) { - return (int) (p1.getTimeValid() - p2.getTimeValid()); - } - })); - } - markLocations.get(boatId).put(markPacket); - } - } - - /** - * This packet type is received when a mark or gate is rounded by a boat - * - * @param packet The packet containing the payload - */ - private static void extractMarkRounding(StreamPacket packet) { - byte[] payload = packet.getPayload(); - int messageVersionNo = payload[0]; - long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); - long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13)); - long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17)); - int boatStatus = payload[17]; - int roundingSide = payload[18]; - int markType = payload[19]; - int markId = payload[20]; - - // assign mark rounding time to boat - boats.get((int)subjectId).setMarkRoundingTime(timeStamp); - - for (Mark mark : xmlObject.getRaceXML().getAllCompoundMarks()) { - if (mark.getCompoundMarkID() == markId) { - boats.get((int)subjectId).setLastMarkRounded(mark); - } - } - } - - /** - * This packet type contains periodic data on the state of the wind - * - * @param packet The packet containing the payload - */ - private static void extractCourseWind(StreamPacket packet) { - byte[] payload = packet.getPayload(); - int messageVersionNo = payload[0]; - int selectedWindId = payload[1]; - int loopCount = payload[2]; - ArrayList windInfo = new ArrayList<>(); - for (int i = 0; i < loopCount; i++) { - String wind = "WindId: " + payload[3 + (20 * i)]; - wind += - "\nTime: " + bytesToLong(Arrays.copyOfRange(payload, 4 + (20 * i), 10 + (20 * i))); - wind += "\nRaceId: " + bytesToLong( - Arrays.copyOfRange(payload, 10 + (20 * i), 14 + (20 * i))); - wind += "\nWindDirection: " + bytesToLong( - Arrays.copyOfRange(payload, 14 + (20 * i), 16 + (20 * i))); - wind += "\nWindSpeed: " + bytesToLong( - Arrays.copyOfRange(payload, 16 + (20 * i), 18 + (20 * i))); - wind += "\nBestUpWindAngle: " + bytesToLong( - Arrays.copyOfRange(payload, 18 + (20 * i), 20 + (20 * i))); - wind += "\nBestDownWindAngle: " + bytesToLong( - Arrays.copyOfRange(payload, 20 + (20 * i), 22 + (20 * i))); - wind += "\nFlags: " + String - .format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF)) - .replace(' ', '0'); - windInfo.add(wind); - } - } - - /** - * This packet conatins the average wind to ground speed - * - * @param packet The packet containing the paylaod - */ - private static void extractAvgWind(StreamPacket packet) { - byte[] payload = packet.getPayload(); - int messageVersionNo = payload[0]; - long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); - long rawPeriod = bytesToLong(Arrays.copyOfRange(payload, 7, 9)); - long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload, 9, 11)); - long period2 = bytesToLong(Arrays.copyOfRange(payload, 11, 13)); - long speed2 = bytesToLong(Arrays.copyOfRange(payload, 13, 15)); - long period3 = bytesToLong(Arrays.copyOfRange(payload, 15, 17)); - long speed3 = bytesToLong(Arrays.copyOfRange(payload, 17, 19)); - long period4 = bytesToLong(Arrays.copyOfRange(payload, 19, 21)); - long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23)); - } - - /** - * 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; - } - - /** - * returns false if race not started, true otherwise - * - * @return race started status - */ - public static boolean isRaceStarted() { - return raceStarted; - } - - /** - * returns false if stream not connected, true otherwise - * - * @return stream started status - */ - public static boolean isStreamStatus() { - return streamStatus; - } - - /** - * returns race timer - * - * @return race timer in long - */ - public static long getTimeSinceStart() { - return timeSinceStart; - } - - /** - * return false if race not finished, true otherwise - * - * @return race finished status - */ - public static boolean isRaceFinished() { - return raceFinished; - } - - /** - * return a map of boats with sourceID and the boat - * - * @return map of boats - */ - public static Map getBoats() { - return boats; - } - - - /** - * returns the latest updated object from xml parser - * - * @return the latest xml object - */ - public static XMLParser getXmlObject() { - return xmlObject; - } - - /** - * returns the wind direction in degrees - * - * @return a double wind direction value - */ - public static double getWindDirection() { - return windDirection; - } - - - /** - * Returns the wind speed in knots - * @return A double indicating the wind speed in knots - */ - public static Double getWindSpeed() { - return windSpeed; - } - - /** - * returns stream time in formatted string format - * - * @return String of stream time - */ - public static String getCurrentTimeString() { - return currentTimeString; - } - - /** - * used in boat position since tree map can sort position efficiently. - * - * @return a map of time to finish and boat. - */ - public static Map getBoatsPos() { - - return boatsPos; - } - - /** - * returns current time in stream in long - * - * @return a long value of current time - */ - public static Long getCurrentTimeLong() { - return currentTimeLong; - } - - public static void appClose() { - appRunning = false; - } - - /** - * Used to check if a new un-processed xml has been found, if so will return true before - * toggling off so that the next check will return false. - * - * @return the status of if new xml has been received - */ - public static boolean isNewRaceXmlReceived() { - if (newRaceXmlReceived) { - newRaceXmlReceived = false; - return true; - } else { - return false; - } - } -} - diff --git a/src/main/java/seng302/client/ClientState.java b/src/main/java/seng302/client/ClientState.java deleted file mode 100644 index 40656dbe..00000000 --- a/src/main/java/seng302/client/ClientState.java +++ /dev/null @@ -1,78 +0,0 @@ -package seng302.client; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import javafx.stage.Stage; -import seng302.models.Yacht; - -/** - * Used by the client to store static variables, which other threads and classes - * observer so that they can update their status accordingly. - */ -public class ClientState { - - private static String hostIp = ""; - private static Boolean isHost = false; - private static Boolean raceStarted = false; - private static Boolean connectedToHost = false; - private static Map boats = new ConcurrentHashMap<>(); - private static Boolean boatsUpdated = true; - private static String clientSourceId = ""; - public static Stage primaryStage; - - public static String getHostIp() { - return hostIp; - } - - public static void setHostIp(String hostIp) { - ClientState.hostIp = hostIp; - } - - public static Boolean isHost() { - return isHost; - } - - public static void setHost(Boolean isHost) { - ClientState.isHost = isHost; - } - - public static Boolean isRaceStarted() { - return raceStarted; - } - - public static void setRaceStarted(Boolean raceStarted) { - ClientState.raceStarted = raceStarted; - } - - public static Boolean isConnectedToHost() { - return connectedToHost; - } - - public static void setConnectedToHost(Boolean connectedToHost) { - ClientState.connectedToHost = connectedToHost; - } - - public static Map getBoats() { - return boats; - } - - public static Boolean isBoatsUpdated() { - return boatsUpdated; - } - - public static void setBoatsUpdated(Boolean boatsUpdated) { - ClientState.boatsUpdated = boatsUpdated; - } - - public static String getClientSourceId() { - return clientSourceId; - } - - public static void setClientSourceId(String clientSourceId) { - ClientState.clientSourceId = clientSourceId; - } - - public static void setBoats(Map boats) { - ClientState.boats = boats; - } -} diff --git a/src/main/java/seng302/client/ClientStateQueryingRunnable.java b/src/main/java/seng302/client/ClientStateQueryingRunnable.java deleted file mode 100644 index e704819d..00000000 --- a/src/main/java/seng302/client/ClientStateQueryingRunnable.java +++ /dev/null @@ -1,54 +0,0 @@ -package seng302.client; - -import java.util.Observable; - -/** - * Used by LobbyController to run a separate thread-loop - * updates the controller when change is detected. - */ -public class ClientStateQueryingRunnable extends Observable implements Runnable { - - private Boolean terminate = false; - - public ClientStateQueryingRunnable() {} - - /** - * Notifies observers(the lobby controller) that "game started" if ClientState - * raceStarted flag is true and terminates itself. Also, it notifies observers - * to add/remove players if ClientState boatsUpdated flag is true, then resets - * the flag to false; - */ - @Override - public void run() { - while(!terminate) { - // Sleeping the thread so it will respond to the if statement below - // if you know a better fix, pls tell me :) -ryan - try { - Thread.sleep(0); - } catch (InterruptedException e) { - continue; - } - - if (ClientState.isRaceStarted() && ClientState.isConnectedToHost()) { - setChanged(); - notifyObservers("game started"); - terminate(); - } - - if (ClientState.isBoatsUpdated()) { - setChanged(); - notifyObservers("update players"); - ClientState.setBoatsUpdated(false); - } - } - } - - /** - * Used to terminate the thread. - * - * Currently called by the main while loop when game started is detected. - */ - public void terminate() { - terminate = true; - } -} diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java deleted file mode 100644 index 4e8a8493..00000000 --- a/src/main/java/seng302/controllers/CanvasController.java +++ /dev/null @@ -1,597 +0,0 @@ -package seng302.controllers; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.PriorityBlockingQueue; -import javafx.animation.AnimationTimer; -import javafx.beans.property.SimpleDoubleProperty; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.geometry.Point2D; -import javafx.scene.Group; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.image.ImageView; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.scene.shape.Polygon; -import javafx.scene.text.Text; -import seng302.client.ClientPacketParser; -import seng302.client.ClientState; -import seng302.fxObjects.BoatGroup; -import seng302.models.Colors; -import seng302.models.Yacht; -import seng302.models.mark.GateMark; -import seng302.models.mark.Mark; -import seng302.fxObjects.MarkGroup; -import seng302.models.mark.MarkType; -import seng302.models.mark.SingleMark; -import seng302.models.map.Boundary; -import seng302.models.map.CanvasMap; -import seng302.models.stream.XMLParser; -import seng302.models.stream.XMLParser.RaceXMLObject.Limit; -import seng302.models.stream.XMLParser.RaceXMLObject.Participant; -import seng302.models.stream.packets.BoatPositionPacket; -import seng302.utilities.GeoPoint; -import seng302.utilities.GeoUtility; - -/** - * Created by ptg19 on 15/03/17. - * Modified by Haoming Yin (hyi25) on 20/3/2017. - */ -public class CanvasController { - - @FXML - private AnchorPane canvasPane; - - private RaceViewController raceViewController; - private ResizableCanvas canvas; - private Group group; - private GraphicsContext gc; - private ImageView mapImage; - - private final int BUFFER_SIZE = 50; - private final int PANEL_WIDTH = 1260; // it should be 1280 but, minors 40 to cancel the bias. - private final int PANEL_HEIGHT = 960; - private final int CANVAS_WIDTH = 1100; - private final int CANVAS_HEIGHT = 920; - private boolean horizontalInversion = false; - - private double distanceScaleFactor; - private ScaleDirection scaleDirection; - private Mark minLatPoint; - private Mark minLonPoint; - private Mark maxLatPoint; - private Mark maxLonPoint; - private double referencePointX; - private double referencePointY; - private double metersPerPixelX; - private double metersPerPixelY; - - private List markGroups = new ArrayList<>(); - private List boatGroups = new ArrayList<>(); - private Text FPSDisplay = new Text(); - private Polygon raceBorder = new Polygon(); - - //FRAME RATE - private Double frameRate = 60.0; - private final long[] frameTimes = new long[30]; - private int frameTimeIndex = 0; - private boolean arrayFilled = false; - - public AnimationTimer timer; - - private enum ScaleDirection { - HORIZONTAL, - VERTICAL - } - - public void setup(RaceViewController raceViewController) { - this.raceViewController = raceViewController; - } - - public void initialize() { - raceViewController = new RaceViewController(); - canvas = new ResizableCanvas(); - group = new Group(); - - // create image view for map, bind panel size to image - mapImage = new ImageView(); - canvasPane.getChildren().add(mapImage); - mapImage.fitWidthProperty().bind(canvasPane.widthProperty()); - mapImage.fitHeightProperty().bind(canvasPane.heightProperty()); - - canvasPane.getChildren().add(canvas); - canvasPane.getChildren().add(group); - // Bind canvas size to stack pane size. - canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH)); - canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT)); - - } - - public void initializeCanvas() { - - gc = canvas.getGraphicsContext2D(); - gc.setGlobalAlpha(0.5); - fitMarksToCanvas(); - drawGoogleMap(); - FPSDisplay.setLayoutX(5); - FPSDisplay.setLayoutY(20); - FPSDisplay.setStrokeWidth(2); - group.getChildren().add(FPSDisplay); - group.getChildren().add(raceBorder); - initializeMarks(); - initializeBoats(); - - timer = new AnimationTimer() { - private long lastTime = 0; - private int FPSCount = 30; - - @Override - public void handle(long now) { - if (lastTime == 0) { - lastTime = now; - } else { - if (now - lastTime >= (1e8 / 60)) { //Fix for framerate going above 60 when minimized - long oldFrameTime = frameTimes[frameTimeIndex]; - frameTimes[frameTimeIndex] = now; - frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length; - if (frameTimeIndex == 0) { - arrayFilled = true; - } - long elapsedNanos; - if (arrayFilled) { - elapsedNanos = now - oldFrameTime; - long elapsedNanosPerFrame = elapsedNanos / frameTimes.length; - frameRate = 1_000_000_000.0 / elapsedNanosPerFrame; - if (FPSCount-- == 0) { - FPSCount = 30; - drawFps(frameRate.intValue()); - } - raceViewController.updateSparkLine(); - } - updateGroups(); - if (ClientPacketParser.isRaceFinished()) { - this.stop(); - } - lastTime = now; - } - } - if (ClientPacketParser.isRaceFinished()) { - this.stop(); - switchToFinishScreen(); - } - } - }; - } - - private void switchToFinishScreen() { - try { - // canvas view -> anchor pane -> grid pane -> main view - GridPane gridPane = (GridPane) canvasPane.getParent().getParent(); - AnchorPane contentPane = (AnchorPane) gridPane.getParent(); - contentPane.getChildren().removeAll(); - contentPane.getChildren().clear(); - contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - contentPane.getChildren().addAll( - (Pane) FXMLLoader.load(getClass().getResource("/views/FinishScreenView.fxml"))); - } catch (javafx.fxml.LoadException e) { - System.out.println("[Controller] FXML load exception"); - } catch (IOException e) { - System.out.println("[Controller] IO exception"); - } - } - - /** - * First find the top right and bottom left points' geo locations, then retrieve - * map from google to display on image view. - Haoming 22/5/2017 - */ - private void drawGoogleMap() { - findMetersPerPixel(); - Point2D topLeftPoint = findScaledXY(maxLatPoint.getLatitude(), minLonPoint.getLongitude()); - // distance from top left extreme to panel origin (top left corner) - double distanceFromTopLeftToOrigin = Math.sqrt( - Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math - .pow(topLeftPoint.getY() * metersPerPixelY, 2)); - // angle from top left extreme to panel origin - double bearingFromTopLeftToOrigin = Math - .toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY())); - // the top left extreme - GeoPoint topLeftPos = new GeoPoint(maxLatPoint.getLatitude(), minLonPoint.getLongitude()); - GeoPoint originPos = GeoUtility - .getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin); - - // distance from origin corner to bottom right corner of the panel - double distanceFromOriginToBottomRight = Math.sqrt( - Math.pow(PANEL_HEIGHT * metersPerPixelY, 2) + Math - .pow(PANEL_WIDTH * metersPerPixelX, 2)); - double bearingFromOriginToBottomRight = Math - .toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT)); - GeoPoint bottomRightPos = GeoUtility - .getGeoCoordinate(originPos, bearingFromOriginToBottomRight, - distanceFromOriginToBottomRight); - - Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(), - bottomRightPos.getLat(), originPos.getLng()); - CanvasMap canvasMap = new CanvasMap(boundary); - mapImage.setImage(canvasMap.getMapImage()); - } - - /** - * Adds border marks to the canvas, taken from the XML file - * - * NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and - * CompoundMark which are named the same as those in the model package but are, however not the - * same, so they do not have things such as a type and must be derived from the number of marks - * in a compound mark etc.. - */ - private void addRaceBorder() { - XMLParser.RaceXMLObject raceXMLObject = ClientPacketParser.getXmlObject().getRaceXML(); - ArrayList courseLimits = raceXMLObject.getCourseLimit(); - raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1)); - raceBorder.setStrokeWidth(3); - raceBorder.setFill(new Color(0,0,0,0)); - List boundaryPoints = new ArrayList<>(); - for (Limit limit : courseLimits) { - Point2D location = findScaledXY(limit.getLat(), limit.getLng()); - boundaryPoints.add(location.getX()); - boundaryPoints.add(location.getY()); - } - raceBorder.getPoints().setAll(boundaryPoints); - } - - private void updateGroups() { - for (BoatGroup boatGroup : boatGroups) { - // some raceObjects will have multiple ID's (for instance gate marks) - //checking if the current "ID" has any updates associated with it - if (ClientPacketParser.boatLocations.containsKey(boatGroup.getRaceId())) { - updateBoatGroup(boatGroup); - } - } - for (MarkGroup markGroup : markGroups) { - for (Long id : markGroup.getRaceIds()) { - if (ClientPacketParser.markLocations.containsKey(id)) { - updateMarkGroup(id, markGroup); - } - } - } - checkForCourseChanges(); - } - - private void checkForCourseChanges() { - if (ClientPacketParser.isNewRaceXmlReceived()) { - addRaceBorder(); - } - } - - private void updateBoatGroup(BoatGroup boatGroup) { - PriorityBlockingQueue movementQueue = ClientPacketParser.boatLocations - .get(boatGroup.getRaceId()); - // giving the movementQueue a 5 packet buffer to account for slightly out of order packets - if (movementQueue.size() > 0) { - try { - BoatPositionPacket positionPacket = movementQueue.take(); - Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); - double heading = 360.0 / 0xffff * positionPacket.getHeading(); - boatGroup.setDestination( - p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), - positionPacket.getTimeValid(), frameRate); - } catch (InterruptedException e){ - System.out.println("[CanvasController] Interrupted Exception"); - } - } - } - - void updateMarkGroup (long raceId, MarkGroup markGroup) { - PriorityBlockingQueue movementQueue = ClientPacketParser.markLocations - .get(raceId); - if (movementQueue.size() > 0){ - try { - BoatPositionPacket positionPacket = movementQueue.take(); - Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); - markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId); - } catch (InterruptedException e) { - System.out.println("[CanvasController] Interrupted exception"); - } - } - } - - /** - * Draws all the boats. - */ - private void initializeBoats() { - Map boats = ClientPacketParser.getBoats(); - Group wakes = new Group(); - Group trails = new Group(); - Group annotations = new Group(); - - ArrayList participants = ClientPacketParser.getXmlObject().getRaceXML() - .getParticipants(); - ArrayList participantIDs = new ArrayList<>(); - for (Participant p : participants) { - participantIDs.add(p.getsourceID()); - } - - for (Yacht boat : boats.values()) { - if (participantIDs.contains(boat.getSourceId())) { - boat.setColour(Colors.getColor()); - BoatGroup boatGroup = new BoatGroup(boat, boat.getColour()); - if (boat.getSourceId().equals(Integer.parseInt(ClientState.getClientSourceId()))) { - boatGroup.setAsPlayer(); - boatGroups.add(boatGroup); - annotations.getChildren().add(boatGroup.getAnnotations()); - } else { - //Move annotations and boat to bottom of group keeping player ontop. - if (boatGroups.size() > 0) { - boatGroups.add(0, boatGroup); - } else { - boatGroups.add(boatGroup); - } - if (annotations.getChildren().size() > 0) { - annotations.getChildren().add(0, boatGroup.getAnnotations()); - } else { - annotations.getChildren().add(boatGroup.getAnnotations()); - } - } - trails.getChildren().add(boatGroup.getTrail()); - wakes.getChildren().add(boatGroup.getWake()); - } - } - group.getChildren().addAll(trails); - group.getChildren().addAll(wakes); - group.getChildren().addAll(annotations); - group.getChildren().addAll(boatGroups); - } - - private void initializeMarks() { - List allMarks = ClientPacketParser.getXmlObject().getRaceXML() - .getNonDupCompoundMarks(); - for (Mark mark : allMarks) { - if (mark.getMarkType() == MarkType.SINGLE_MARK) { - SingleMark sMark = (SingleMark) mark; - - MarkGroup markGroup = new MarkGroup(sMark, findScaledXY(sMark)); - markGroups.add(markGroup); - } else { - GateMark gMark = (GateMark) mark; - - MarkGroup markGroup = new MarkGroup(gMark, findScaledXY(gMark.getSingleMark1()), - findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list. - markGroups.add(markGroup); - } - } - group.getChildren().addAll(markGroups); - } - - class ResizableCanvas extends Canvas { - - ResizableCanvas() { - // Redraw canvas when size changes. - widthProperty().addListener(evt -> draw()); - heightProperty().addListener(evt -> draw()); - } - - private void draw() { - double width = getWidth(); - double height = getHeight(); - - GraphicsContext gc = getGraphicsContext2D(); - gc.clearRect(0, 0, width, height); - } - - @Override - public boolean isResizable() { - return true; - } - - @Override - public double prefWidth(double height) { - return getWidth(); - } - - @Override - public double prefHeight(double width) { - return getHeight(); - } - - } - - private void drawFps(int fps){ - if (raceViewController.isDisplayFps()){ - FPSDisplay.setVisible(true); - FPSDisplay.setText(String.format("%d FPS", fps)); - } else { - FPSDisplay.setVisible(false); - } - } - - /** - * Calculates x and y location for every marker that fits it to the canvas the race will be - * drawn on. - */ - private void fitMarksToCanvas() { - //Check is called once to avoid unnecessarily change the course limits once the race is running - ClientPacketParser.isNewRaceXmlReceived(); - findMinMaxPoint(); - double minLonToMaxLon = scaleRaceExtremities(); - calculateReferencePointLocation(minLonToMaxLon); - //givePointsXY(); - addRaceBorder(); - } - - - /** - * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker - * with the leftmost marker, rightmost marker, southern most marker and northern most marker - * respectively. - */ - private void findMinMaxPoint() { - List sortedPoints = new ArrayList<>(); - - for (Limit limit : ClientPacketParser.getXmlObject().getRaceXML().getCourseLimit()) { - sortedPoints.add(limit); - } - sortedPoints.sort(Comparator.comparingDouble(Limit::getLat)); - Limit minLatMark = sortedPoints.get(0); - Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1); - minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID(), minLatMark.getSeqID()); - maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID(), minLatMark.getSeqID()); - - sortedPoints.sort(Comparator.comparingDouble(Limit::getLng)); - //If the course is on a point on the earth where longitudes wrap around. - Limit minLonMark = sortedPoints.get(0); - Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1); - minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID(), minLonMark.getSeqID()); - maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID(), minLonMark.getSeqID()); - if (maxLonPoint.getLongitude() - minLonPoint.getLongitude() > 180) { - horizontalInversion = true; - } - } - - /** - * Calculates the location of a reference point, this is always the point with minimum latitude, - * in relation to the canvas. - * - * @param minLonToMaxLon The horizontal distance between the point of minimum longitude to - * maximum longitude. - */ - private void calculateReferencePointLocation(double minLonToMaxLon) { - Mark referencePoint = minLatPoint; - double referenceAngle; - - if (scaleDirection == ScaleDirection.HORIZONTAL) { - referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint)); - referencePointX = BUFFER_SIZE + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint); - - referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint)); - referencePointY = CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE); - referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint); - referencePointY = referencePointY / 2; - referencePointY += BUFFER_SIZE; - referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint); - } else { - referencePointY = CANVAS_HEIGHT - BUFFER_SIZE; - - referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint)); - referencePointX = BUFFER_SIZE; - referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint); - referencePointX += ((CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) - (minLonToMaxLon * distanceScaleFactor)) / 2; - } - if(horizontalInversion) { - referencePointX = CANVAS_WIDTH - BUFFER_SIZE - (referencePointX - BUFFER_SIZE); - } - } - - - /** - * Finds the scale factor necessary to fit all race markers within the onscreen map and assigns - * it to distanceScaleFactor Returns the max horizontal distance of the map. - */ - private double scaleRaceExtremities() { - - double vertAngle = Math.abs(Mark.calculateHeadingRad(minLatPoint, maxLatPoint)); - double vertDistance = - Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint); - double horiAngle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint); - - if (horiAngle <= (Math.PI / 2)) { - horiAngle = (Math.PI / 2) - horiAngle; - } else { - horiAngle = horiAngle - (Math.PI / 2); - } - double horiDistance = - Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint); - - double vertScale = (CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE)) / vertDistance; - - if ((horiDistance * vertScale) > (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE))) { - distanceScaleFactor = (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) / horiDistance; - scaleDirection = ScaleDirection.HORIZONTAL; - } else { - distanceScaleFactor = vertScale; - scaleDirection = ScaleDirection.VERTICAL; - } - return horiDistance; - } - - private Point2D findScaledXY(Mark unscaled) { - return findScaledXY(unscaled.getLatitude(), unscaled.getLongitude()); - } - - public Point2D findScaledXY (double unscaledLat, double unscaledLon) { - double distanceFromReference; - double angleFromReference; - int xAxisLocation = (int) referencePointX; - int yAxisLocation = (int) referencePointY; - - angleFromReference = Mark - .calculateHeadingRad(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, - unscaledLon); - distanceFromReference = Mark - .calculateDistance(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, - unscaledLon); - if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { - xAxisLocation += (int) Math - .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - yAxisLocation -= (int) Math - .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - } else if (angleFromReference >= 0) { - angleFromReference = angleFromReference - Math.PI / 2; - xAxisLocation += (int) Math - .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - yAxisLocation += (int) Math - .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - } else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) { - angleFromReference = Math.abs(angleFromReference); - xAxisLocation -= (int) Math - .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - yAxisLocation -= (int) Math - .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - } else { - angleFromReference = Math.abs(angleFromReference) - Math.PI / 2; - xAxisLocation -= (int) Math - .round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - yAxisLocation += (int) Math - .round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - } - if(horizontalInversion) { - xAxisLocation = CANVAS_WIDTH - BUFFER_SIZE - (xAxisLocation - BUFFER_SIZE); - } - return new Point2D(xAxisLocation, yAxisLocation); - } - - /** - * Find the number of meters per pixel. - */ - private void findMetersPerPixel() { - Point2D p1, p2; - Mark m1, m2; - double theta, distance, dx, dy, dHorizontal, dVertical; - m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1, 0); - m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2, 0); - p1 = findScaledXY(m1); - p2 = findScaledXY(m2); - theta = Mark.calculateHeadingRad(m1, m2); - distance = Mark.calculateDistance(m1, m2); - dHorizontal = Math.abs(Math.sin(theta) * distance); - dVertical = Math.abs(Math.cos(theta) * distance); - dx = Math.abs(p1.getX() - p2.getX()); - dy = Math.abs(p1.getY() - p2.getY()); - metersPerPixelX = dHorizontal / dx; - metersPerPixelY = dVertical / dy; - } - - List getBoatGroups() { - return boatGroups; - } - - List getMarkGroups() { - return markGroups; - } - -} \ No newline at end of file diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java deleted file mode 100644 index f1871192..00000000 --- a/src/main/java/seng302/controllers/Controller.java +++ /dev/null @@ -1,106 +0,0 @@ -package seng302.controllers; - -import java.io.IOException; -import java.net.URL; -import java.util.ResourceBundle; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.fxml.Initializable; -import javafx.scene.Parent; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.AnchorPane; -import seng302.client.ClientPacketParser; -import seng302.client.ClientState; -import seng302.client.ClientToServerThread; -import seng302.server.messages.BoatActionMessage; -import seng302.server.messages.BoatActionType; - -public class Controller implements Initializable { - - @FXML - private AnchorPane contentPane; - private ClientToServerThread clientToServerThread; - private long lastSendingTime; - private int KEY_STROKE_SENDING_FREQUENCY = 50; - - public Object setContentPane(String jfxUrl) { - try { - contentPane.getChildren().removeAll(); - contentPane.getChildren().clear(); - contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - FXMLLoader fxmlLoader = new FXMLLoader((getClass().getResource(jfxUrl))); - Parent view = fxmlLoader.load(); - contentPane.getChildren().addAll(view); - return fxmlLoader.getController(); - } catch (javafx.fxml.LoadException e) { - System.err.println(e.getCause()); - } catch (IOException e) { - System.err.println(e); - } - return null; - } - - @Override - public void initialize(URL location, ResourceBundle resources) { - setUpStartScreen(); - lastSendingTime = System.currentTimeMillis(); - } - - void setUpStartScreen() { - contentPane.getChildren().removeAll(); - contentPane.getChildren().clear(); - contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml"); - startScreenController.setController(this); - ClientPacketParser.boatLocations.clear(); - } - - - /** Handle the key-pressed event from the text field. */ - public void keyPressed(KeyEvent e) { - BoatActionMessage boatActionMessage; - long currentTime = System.currentTimeMillis(); - if (currentTime - lastSendingTime > KEY_STROKE_SENDING_FREQUENCY && ClientState.isRaceStarted()) { - lastSendingTime = currentTime; - switch (e.getCode()) { - case SPACE: // align with vmg - boatActionMessage = new BoatActionMessage(BoatActionType.VMG); - clientToServerThread.sendBoatActionMessage(boatActionMessage); - break; - case PAGE_UP: // upwind - boatActionMessage = new BoatActionMessage(BoatActionType.UPWIND); - clientToServerThread.sendBoatActionMessage(boatActionMessage); - break; - case PAGE_DOWN: // downwind - boatActionMessage = new BoatActionMessage(BoatActionType.DOWNWIND); - clientToServerThread.sendBoatActionMessage(boatActionMessage); - break; - case ENTER: // tack/gybe - boatActionMessage = new BoatActionMessage(BoatActionType.TACK_GYBE); - clientToServerThread.sendBoatActionMessage(boatActionMessage); - break; - //TODO Allow a zoom in and zoom out methods - case Z: // zoom in - System.out.println("Zoom in"); - break; - case X: // zoom out - System.out.println("Zoom out"); - break; - } - } - } - - public void keyReleased(KeyEvent e) { - switch (e.getCode()) { - //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) - case SHIFT: // sails in/sails out - BoatActionMessage boatActionMessage = new BoatActionMessage(BoatActionType.SAILS_IN); - clientToServerThread.sendBoatActionMessage(boatActionMessage); - break; - } - } - - public void setClientToServerThread(ClientToServerThread ctt) { - clientToServerThread = ctt; - } -} diff --git a/src/main/java/seng302/controllers/LobbyController.java b/src/main/java/seng302/controllers/LobbyController.java deleted file mode 100644 index b9d407fe..00000000 --- a/src/main/java/seng302/controllers/LobbyController.java +++ /dev/null @@ -1,235 +0,0 @@ -package seng302.controllers; - -import java.io.IOException; -import java.net.URL; -import java.util.*; - -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.fxml.Initializable; -import javafx.scene.control.Button; -import javafx.scene.control.ListView; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; -import javafx.scene.text.Text; -import seng302.client.ClientState; -import seng302.client.ClientStateQueryingRunnable; -import seng302.gameServer.GameStages; -import seng302.gameServer.GameState; -import seng302.gameServer.MainServerThread; - -/** - * A class describing the actions of the lobby screen - * Created by wmu16 on 10/07/17. - */ -public class LobbyController implements Initializable, Observer{ - @FXML - private GridPane lobbyScreen; - @FXML - private Text lobbyIpText; - @FXML - private Button readyButton; - @FXML - private ListView firstListView; - @FXML - private ListView secondListView; - @FXML - private ListView thirdListView; - @FXML - private ListView fourthListView; - @FXML - private ListView fifthListView; - @FXML - private ListView sixthListView; - @FXML - private ListView seventhListView; - @FXML - private ListView eighthListView; - @FXML - private ImageView firstImageView; - @FXML - private ImageView secondImageView; - @FXML - private ImageView thirdImageView; - @FXML - private ImageView fourthImageView; - @FXML - private ImageView fifthImageView; - @FXML - private ImageView sixthImageView; - @FXML - private ImageView seventhImageView; - @FXML - private ImageView eighthImageView; - - private static List> competitors = new ArrayList<>(); - private static ObservableList firstCompetitor = FXCollections.observableArrayList(); - private static ObservableList secondCompetitor = FXCollections.observableArrayList(); - private static ObservableList thirdCompetitor = FXCollections.observableArrayList(); - private static ObservableList fourthCompetitor = FXCollections.observableArrayList(); - private static ObservableList fifthCompetitor = FXCollections.observableArrayList(); - private static ObservableList sixthCompetitor = FXCollections.observableArrayList(); - private static ObservableList seventhCompetitor = FXCollections.observableArrayList(); - private static ObservableList eighthCompetitor = FXCollections.observableArrayList(); - private ClientStateQueryingRunnable clientStateQueryingRunnable; - private static List imageViews; - private static List listViews; - - private int MAX_NUM_PLAYERS = 8; - - private Boolean switchedPane = false; - private MainServerThread mainServerThread; - private Controller controller; - - private void setContentPane(String jfxUrl) { - try { - AnchorPane contentPane = (AnchorPane) lobbyScreen.getParent(); - contentPane.getChildren().removeAll(); - contentPane.getChildren().clear(); - contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - contentPane.getChildren() - .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); - } catch (javafx.fxml.LoadException e) { - System.out.println("[Controller] FXML load exception"); - } catch (IOException e) { - System.out.println("[Controller] IO exception"); - } catch (NullPointerException e) { -// System.out.println("[Controller] Null Pointer Exception"); - } - } - - @Override - public void initialize(URL location, ResourceBundle resources) { - if (ClientState.isHost()) { - lobbyIpText.setText("Lobby Host IP: " + ClientState.getHostIp()); - readyButton.setDisable(false); - } - else { - lobbyIpText.setText("Connected to IP: " + ClientState.getHostIp()); - readyButton.setDisable(true); - } - - // put all javafx objects in lists, so we can iterate though conveniently - imageViews = new ArrayList<>(); - Collections.addAll(imageViews, firstImageView, secondImageView, thirdImageView, fourthImageView, - fifthImageView, sixthImageView, seventhImageView, eighthImageView); - listViews = new ArrayList<>(); - Collections.addAll(listViews, firstListView, secondListView, thirdListView, fourthListView, fifthListView, - sixthListView, seventhListView, eighthListView); - competitors = new ArrayList<>(); - Collections.addAll(competitors, firstCompetitor, secondCompetitor, thirdCompetitor, - fourthCompetitor, fifthCompetitor, sixthCompetitor, seventhCompetitor, eighthCompetitor); - - initialiseListView(); - initialiseImageView(); // parrot gif init - - // set up client state query thread, so that when it receives the race-started packet - // it can switch to the race view - ClientStateQueryingRunnable clientStateQueryingRunnable = new ClientStateQueryingRunnable(); - clientStateQueryingRunnable.addObserver(this); - Thread clientStateQueryingThread = new Thread(clientStateQueryingRunnable, "Client State querying thread"); - clientStateQueryingThread.setDaemon(true); - clientStateQueryingThread.start(); - } - - /** - * Observers "ClientStateQueryingRunnable". - * When the clients state has been marked to "race start", the querying thread - * will notify this lobby to change the view - * @param o - * @param arg - */ - @Override - public void update(Observable o, Object arg) { - Platform.runLater(new Runnable() { - @Override - public void run() { - if (arg.equals("game started") && !switchedPane) { - switchToRaceView(); - } - if (arg.equals(("update players"))) { - initialiseListView(); - } - } - }); - } - - /** - * Reset all ListViews and ImageViews according to the current competitors - */ - private void initialiseListView() { - listViews.forEach(listView -> listView.getItems().clear()); - imageViews.forEach(gif -> gif.setVisible(false)); - competitors.forEach(ol -> ol.removeAll()); - - List ids = new ArrayList<>(ClientState.getBoats().keySet()); - for (int i = 0; i < ids.size(); i++) { - competitors.get(i).add(ClientState.getBoats().get(ids.get(i)).getBoatName()); - listViews.get(i).setItems(competitors.get(i)); - imageViews.get(i).setVisible(true); - } - } - - /** - * Loads preset images into imageViews - */ - private void initialiseImageView() { - for (int i = 0; i < MAX_NUM_PLAYERS; i++) { - imageViews.get(i).setImage(new Image(getClass().getResourceAsStream("/pics/sail.png"))); - } -// Image image1 = new Image(getClass().getResourceAsStream("/pics/sail.png")); -// firstImageView.setImage(image1); -// Image image2 = new Image(getClass().getResourceAsStream("/pics/sail.png")); -// secondImageView.setImage(image2); -// Image image3 = new Image(getClass().getResourceAsStream("/pics/sail.png")); -// thirdImageView.setImage(image3); -// Image image4 = new Image(getClass().getResourceAsStream("/pics/sail.png")); -// fourthImageView.setImage(image4); -// Image image5 = new Image(getClass().getResourceAsStream("/pics/sail.png")); -// fifthImageView.setImage(image5); -// Image image6 = new Image(getClass().getResourceAsStream("/pics/sail.png")); -// sixthImageView.setImage(image6); -// Image image7 = new Image(getClass().getResourceAsStream("/pics/sail.png")); -// seventhImageView.setImage(image7); -// Image image8 = new Image(getClass().getResourceAsStream("/pics/sail.png")); -// eighthImageView.setImage(image8); - } - - @FXML - public void leaveLobbyButtonPressed() { - if (ClientState.isHost()) { - GameState.setCurrentStage(GameStages.CANCELLED); - mainServerThread.terminate(); - } - ClientState.setConnectedToHost(false); - controller.setUpStartScreen(); - } - - @FXML - public void readyButtonPressed() { - GameState.setCurrentStage(GameStages.RACING); - mainServerThread.startGame(); - } - - - private void switchToRaceView() { - if (!switchedPane) { - switchedPane = true; - setContentPane("/views/RaceView.fxml"); - } - } - - public void setMainServerThread(MainServerThread mainServerThread) { - this.mainServerThread = mainServerThread; - } - - public void setController(Controller controller) { - this.controller = controller; - } -} diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java deleted file mode 100644 index 57447081..00000000 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ /dev/null @@ -1,668 +0,0 @@ -package seng302.controllers; - -import javafx.animation.KeyFrame; -import javafx.animation.Timeline; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.geometry.Point2D; -import javafx.scene.Scene; -import javafx.scene.chart.LineChart; -import javafx.scene.chart.NumberAxis; -import javafx.scene.chart.XYChart; -import javafx.scene.chart.XYChart.Series; -import javafx.scene.control.Button; -import javafx.scene.control.CheckBox; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Slider; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import javafx.scene.shape.Line; -import javafx.scene.text.Text; -import javafx.stage.Stage; -import javafx.stage.StageStyle; -import javafx.util.Duration; -import javafx.util.StringConverter; -import seng302.client.ClientPacketParser; -import seng302.utilities.GeoUtility; -import seng302.controllers.annotations.Annotation; -import seng302.controllers.annotations.ImportantAnnotationController; -import seng302.controllers.annotations.ImportantAnnotationDelegate; -import seng302.controllers.annotations.ImportantAnnotationsState; -import seng302.fxObjects.BoatGroup; -import seng302.fxObjects.MarkGroup; -import seng302.models.*; -import seng302.models.mark.GateMark; -import seng302.models.mark.Mark; -import seng302.models.mark.SingleMark; -import seng302.models.stream.XMLParser; - -import java.io.IOException; -import java.util.*; -import seng302.models.stream.XMLParser.RaceXMLObject.Participant; -import java.util.stream.Collectors; - -/** - * Created by ptg19 on 29/03/17. - */ -public class RaceViewController extends Thread implements ImportantAnnotationDelegate { - - @FXML - private Text windSpeedText; - @FXML - private LineChart raceSparkLine; - @FXML - private NumberAxis sparklineYAxis; - @FXML - private VBox positionVbox; - @FXML - private CheckBox toggleFps; - @FXML - private Text timerLabel; - @FXML - private AnchorPane contentAnchorPane; - @FXML - private Text windArrowText, windDirectionText; - @FXML - private Slider annotationSlider; - @FXML - private Button selectAnnotationBtn; - @FXML - private ComboBox boatSelectionComboBox; - @FXML - private CanvasController includedCanvasController; - - private static ArrayList startingBoats = new ArrayList<>(); - private boolean displayFps; - private Timeline timerTimeline; - private Stage stage; - private static HashMap> sparkLineData = new HashMap<>(); - private static ArrayList racingBoats = new ArrayList<>(); - private ImportantAnnotationsState importantAnnotations; - private Yacht selectedBoat; - - public void initialize() { - // Load a default important annotation state - importantAnnotations = new ImportantAnnotationsState(); - - //Formatting the y axis of the sparkline - raceSparkLine.getYAxis().setRotate(180); - raceSparkLine.getYAxis().setTickLabelRotation(180); - raceSparkLine.getYAxis().setTranslateX(-5); - raceSparkLine.getYAxis().setAutoRanging(false); - sparklineYAxis.setTickMarkVisible(false); - startingBoats = new ArrayList<>(ClientPacketParser.getBoats().values()); - - includedCanvasController.setup(this); - includedCanvasController.initializeCanvas(); - initializeUpdateTimer(); - initialiseFPSCheckBox(); - initialiseAnnotationSlider(); - initialiseBoatSelectionComboBox(); - includedCanvasController.timer.start(); - selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); - - } - - - /** - * The important annotations have been changed, update this view - * - * @param importantAnnotationsState The current state of the selected annotations - */ - public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) { - this.importantAnnotations = importantAnnotationsState; - setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations - } - - - /** - * Loads the "select annotations" view in a new window - */ - private void loadSelectAnnotationView() { - try { - FXMLLoader fxmlLoader = new FXMLLoader(); - Stage stage = new Stage(); - - // Set controller - ImportantAnnotationController controller = new ImportantAnnotationController(this, - stage); - fxmlLoader.setController(controller); - - // Load FXML and set CSS - fxmlLoader - .setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml")); - Scene scene = new Scene(fxmlLoader.load(), 469, 298); - scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - stage.initStyle(StageStyle.UNDECORATED); - stage.setScene(scene); - stage.show(); - - controller.loadState(importantAnnotations); - - } catch (IOException e) { - System.out.println("[RaceViewController] IO exception"); - } - } - - - private void initialiseFPSCheckBox() { - displayFps = true; - toggleFps.selectedProperty().addListener( - (observable, oldValue, newValue) -> displayFps = !displayFps); - } - - private void initialiseAnnotationSlider() { - annotationSlider.setLabelFormatter(new StringConverter() { - @Override - public String toString(Double n) { - if (n == 0) { - return "None"; - } - if (n == 1) { - return "Important"; - } - if (n == 2) { - return "All"; - } - - return "All"; - } - - @Override - public Double fromString(String s) { - switch (s) { - case "None": - return 0d; - case "Important": - return 1d; - case "All": - return 2d; - - default: - return 2d; - } - } - }); - - annotationSlider.valueProperty().addListener((obs, oldval, newVal) -> - setAnnotations((int) annotationSlider.getValue())); - - annotationSlider.setValue(2); - } - - - /** - * Used to add any new boats into the race that may have started late or not have had data received yet - */ - void updateSparkLine(){ - // Collect the racing boats that aren't already in the chart - ArrayList sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceId()) - && yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new)); - - // Obtain the qualifying boats to set the max on the Y axis - racingBoats = startingBoats.stream().filter(yacht -> - yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new)); - sparklineYAxis.setUpperBound(racingBoats.size() + 1); - - // Create a new data series for new boats - sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> { - Series yachtData = new Series<>(); - yachtData.setName(yacht.getBoatName()); - yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); - sparkLineData.put(yacht.getSourceId(), yachtData); - }); - - // Lambda function to sort the series in order of leg (later legs shown more to the right) - List> positions = new ArrayList<>(sparkLineData.values()); - Collections.sort(positions, (o1, o2) -> { - Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue()); - Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue()); - if (leg2 < leg1){ - return 1; - } else { - return -1; - } - }); - - // Adds the new data series to the sparkline (and set the colour of the series) - raceSparkLine.setCreateSymbols(false); - positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> { - raceSparkLine.getData().add(spark); - spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName())); - }); - } - - - /** - * Updates the yachts sparkline of the desired boat and using the new leg number - * @param yacht The yacht to be updated on the sparkline - * @param legNumber the leg number that the position will be assigned to - */ - public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){ - XYChart.Series positionData = sparkLineData.get(yacht.getSourceId()); - positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); - } - - - /** - * gets the rgb string of the boats colour to use for the chart via css - * @param boatName boat passed in to get the boats colour - * @return the colour as an rgb string - */ - private String getBoatColorAsRGB(String boatName){ - Color color = Color.WHITE; - for (Yacht yacht: startingBoats){ - if (Objects.equals(yacht.getBoatName(), boatName)){ - color = yacht.getColour(); - } - } - if (color == null){ - return String.format( "#%02X%02X%02X",255,255,255); - } - return String.format( "#%02X%02X%02X", - (int)( color.getRed() * 255 ), - (int)( color.getGreen() * 255 ), - (int)( color.getBlue() * 255 ) ); - } - - - /** - * Initalises a timer which updates elements of the RaceView such as wind direction, boat - * orderings etc.. which are dependent on the info from the stream parser constantly. - * Updates of each of these attributes are called ONCE EACH SECOND - */ - private void initializeUpdateTimer() { - timerTimeline = new Timeline(); - timerTimeline.setCycleCount(Timeline.INDEFINITE); - // Run timer update every second - timerTimeline.getKeyFrames().add( - new KeyFrame(Duration.seconds(1), - event -> { - updateRaceTime(); - updateWindDirection(); -// updateOrder(); - updateBoatSelectionComboBox(); - updateOrder(); - }) - ); - - // Start the timer - timerTimeline.playFromStart(); - } - - - /** - * Iterates over all corners until ones SeqID matches with the boats current leg number. - * Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark - * Returns null if no next mark found. - * @param bg The BoatGroup to find the next mark of - * @return The next Mark or null if none found - */ - private Mark getNextMark(BoatGroup bg) { - Integer legNumber = bg.getBoat().getLegNumber(); - - List markSequence = ClientPacketParser.getXmlObject() - .getRaceXML().getCompoundMarkSequence(); - - if (legNumber == 0) { - return null; - } else if (legNumber == markSequence.size() - 1) { - return null; - } - - for (XMLParser.RaceXMLObject.Corner corner : markSequence) { - if (legNumber + 2 == corner.getSeqID()) { - Integer thisCompoundMarkID = corner.getCompoundMarkID(); - - for (Mark mark : ClientPacketParser.getXmlObject().getRaceXML() - .getAllCompoundMarks()) { - if (mark.getCompoundMarkID() == thisCompoundMarkID) { - return mark; - } - } - } - } - - return null; - } - - - /** - * Updates the wind direction arrow and text as from info from the ClientPacketParser - */ - private void updateWindDirection() { - windDirectionText.setText(String.format("%.1f°", ClientPacketParser.getWindDirection())); - windArrowText.setRotate(ClientPacketParser.getWindDirection()); - windSpeedText.setText(String.format("%.1f Knots", ClientPacketParser.getWindSpeed())); - } - - - /** - * Updates the clock for the race - */ - private void updateRaceTime() { - if (ClientPacketParser.isRaceFinished()) { - timerLabel.setFill(Color.RED); - timerLabel.setText("Race Finished!"); - } else { - timerLabel.setText(getTimeSinceStartOfRace()); - } - } - - - /** - * Grabs the boats currently in the race as from the ClientPacketParser and sets them to be selectable - * in the boat selection combo box - */ - private void updateBoatSelectionComboBox() { - ObservableList observableBoats = FXCollections - .observableArrayList(ClientPacketParser.getBoatsPos().values()); - boatSelectionComboBox.setItems(observableBoats); - } - - - /** - * Updates the order of the boats as from the ClientPacketParser and sets them in the boat order - * section - */ - private void updateOrder() { - positionVbox.getChildren().clear(); - positionVbox.getChildren().removeAll(); - positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - - // list of racing boat id - ArrayList participants = ClientPacketParser.getXmlObject().getRaceXML() - .getParticipants(); - ArrayList participantIDs = new ArrayList<>(); - for (Participant p : participants) { - participantIDs.add(p.getsourceID()); - } - - if (ClientPacketParser.isRaceStarted()) { - /* - for (Yacht boat : ClientPacketParser.getBoatsPos().values()) { - System.out.println("Hi tjere" + boat.getBoatName()); - if (participantIDs.contains(boat.getSourceId()) || true - ) { // check if the boat is racing - if (boat.getBoatStatus() == 69) { // 3 is finish status - Text textToAdd = new Text(boat.getPosition() + ". " + - boat.getShortName() + " (Finished)"); - textToAdd.setFill(Paint.valueOf("#d3d3d3")); - positionVbox.getChildren().add(textToAdd); - - } else { - Text textToAdd = new Text(boat.getPosition() + ". " + - boat.getShortName() + " "); - textToAdd.setFill(Paint.valueOf("#d3d3d3")); - textToAdd.setStyle(""); - positionVbox.getChildren().add(textToAdd); - System.out.println("Adding " + textToAdd.getText()); - } - } - } - */ - for (Yacht boat : ClientPacketParser.getBoats().values()){ - Text textToAdd = new Text(boat.getSourceId() + ". " + boat.getShortName() + " "); - textToAdd.setFill(Paint.valueOf("#d3d3d3")); - textToAdd.setStyle(""); - positionVbox.getChildren().add(textToAdd); - } - } else { - for (Yacht boat : ClientPacketParser.getBoats().values()) { - if (participantIDs.contains(boat.getSourceId())) { // check if the boat is racing - Text textToAdd = new Text(boat.getPosition() + ". " + - boat.getShortName() + " "); - textToAdd.setFill(Paint.valueOf("#d3d3d3")); - textToAdd.setStyle(""); - positionVbox.getChildren().add(textToAdd); - } - } - } - } - - - private void updateLaylines(BoatGroup bg) { - - Mark nextMark = getNextMark(bg); - Boolean isUpwind = null; - // Can only calc leg direction if there is a next mark and it is a gate mark - if (nextMark != null) { - if (nextMark instanceof GateMark) { - if (bg.isUpwindLeg(includedCanvasController, nextMark)) { - isUpwind = true; - } else { - isUpwind = false; - } - - for(MarkGroup mg : includedCanvasController.getMarkGroups()) { - - mg.removeLaylines(); - - if (mg.getMainMark().getId() == nextMark.getId()) { - - SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1(); - SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2(); - Point2D markPoint1 = includedCanvasController.findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude()); - Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude()); - HashMap angleAndSpeed; - if (isUpwind) { - angleAndSpeed = PolarTable - .getOptimalUpwindVMG(ClientPacketParser.getWindSpeed()); - } else { - angleAndSpeed = PolarTable - .getOptimalDownwindVMG(ClientPacketParser.getWindSpeed()); - } - - Double resultingAngle = angleAndSpeed.keySet().iterator().next(); - - - Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY()); - Point2D gateMidPoint = markPoint1.midpoint(markPoint2); - Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2); - Line rightLayline = new Line(); - Line leftLayline = new Line(); - if (lineFuncResult == 1) { - rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, - ClientPacketParser - .getWindDirection()); - leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, - ClientPacketParser - .getWindDirection()); - } else if (lineFuncResult == -1) { - rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, - ClientPacketParser - .getWindDirection()); - leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, - ClientPacketParser - .getWindDirection()); - } - - leftLayline.setStrokeWidth(0.5); - leftLayline.setStroke(bg.getBoat().getColour()); - - rightLayline.setStrokeWidth(0.5); - rightLayline.setStroke(bg.getBoat().getColour()); - - bg.setLaylines(leftLayline, rightLayline); - mg.addLaylines(leftLayline, rightLayline); - - } - } - } - } - } - - - private Point2D getPointRotation(Point2D ref, Double distance, Double angle){ - Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle); - Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle); - - return new Point2D(newX, newY); - } - - - public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) { - - Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle); - Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY()); - return line; - - } - - - public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) { - - Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle); - Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY()); - return line; - - } - - - /** - * Initialised the combo box with any boats currently in the race and adds the required listener - * for the combobox to take action upon selection - */ - private void initialiseBoatSelectionComboBox() { - updateBoatSelectionComboBox(); - boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { - //This listener is fired whenever the combo box changes. This means when the values are updated - //We dont want to set the selected value if the values are updated but nothing clicked (null) - if (newValue != null && newValue != selectedBoat) { - Yacht thisYacht = (Yacht) newValue; - setSelectedBoat(thisYacht); - } - }); - } - - - /** - * Display the list of boats in the order they finished the race - */ - private void loadRaceResultView() { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml")); - - try { - contentAnchorPane.getChildren().removeAll(); - contentAnchorPane.getChildren().clear(); - contentAnchorPane.getChildren().addAll((Pane) loader.load()); - - } catch (javafx.fxml.LoadException e) { - System.err.println(e.getCause()); - } catch (IOException e) { - System.err.println(e); - } - } - - - /** - * Convert seconds to a string of the format mm:ss - * - * @param time the time in seconds - * @return a formatted string - */ - public String convertTimeToMinutesSeconds(int time) { - if (time < 0) { - return String.format("-%02d:%02d", (time * -1) / 60, (time * -1) % 60); - } - return String.format("%02d:%02d", time / 60, time % 60); - } - - private String getTimeSinceStartOfRace() { - String timerString = "0:00"; - if (ClientPacketParser.getTimeSinceStart() > 0) { - String timerMinute = Long.toString(ClientPacketParser.getTimeSinceStart() / 60); - String timerSecond = Long.toString(ClientPacketParser.getTimeSinceStart() % 60); - if (timerSecond.length() == 1) { - timerSecond = "0" + timerSecond; - } - timerString = "-" + timerMinute + ":" + timerSecond; - } else { - String timerMinute = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() / 60); - String timerSecond = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() % 60); - if (timerSecond.length() == 1) { - timerSecond = "0" + timerSecond; - } - timerString = timerMinute + ":" + timerSecond; - } - return timerString; - } - - - boolean isDisplayFps() { - return displayFps; - } - - private void setAnnotations(Integer annotationLevel) { - switch (annotationLevel) { - // No Annotations - case 0: - for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - bg.setVisibility(false, false, false, false, false, false); - } - break; - // Important Annotations - case 1: - for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - bg.setVisibility( - importantAnnotations.getAnnotationState(Annotation.NAME), - importantAnnotations.getAnnotationState(Annotation.SPEED), - importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK), - importantAnnotations.getAnnotationState(Annotation.LEGTIME), - importantAnnotations.getAnnotationState(Annotation.TRACK), - importantAnnotations.getAnnotationState(Annotation.WAKE) - ); - } - break; - // All Annotations - case 2: - for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - bg.setVisibility(true, true, true, true, true, true); - } - break; - } - } - - - /** - * Sets all the annotations of the selected boat to be visible and all others to be hidden - * - * @param yacht The yacht for which we want to view all annotations - */ - private void setSelectedBoat(Yacht yacht) { - for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - //We need to iterate over all race groups to get the matching boat group belonging to this boat if we - //are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup. - if (bg.getBoat().getHullID().equals(yacht.getHullID())) { - updateLaylines(bg); - bg.setIsSelected(true); - selectedBoat = yacht; - } else { - bg.setIsSelected(false); - } - } - } - - void setStage(Stage stage) { - this.stage = stage; - } - - Stage getStage() { - return stage; - } - - /** - * Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it) - * @param yachtId - * @return - */ - public static boolean sparkLineStatus(Integer yachtId) { - return sparkLineData.containsKey(yachtId); - } - -} \ No newline at end of file diff --git a/src/main/java/seng302/controllers/StartScreenController.java b/src/main/java/seng302/controllers/StartScreenController.java deleted file mode 100644 index b7376287..00000000 --- a/src/main/java/seng302/controllers/StartScreenController.java +++ /dev/null @@ -1,164 +0,0 @@ -package seng302.controllers; - -import java.net.Inet4Address; -import java.net.NetworkInterface; -import java.util.Enumeration; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.TextField; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; -import seng302.client.ClientState; -import seng302.client.ClientToServerThread; -import seng302.gameServer.GameState; -import seng302.gameServer.MainServerThread; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; - -/** - * A Class describing the actions of the start screen controller - * Created by wmu16 on 10/07/17. - */ -public class StartScreenController { - - @FXML - private TextField ipTextField; - @FXML - private TextField portTextField; - @FXML - private GridPane startScreen2; - - private Controller controller; - - /** - * Loads the fxml content into the parent pane - * @param jfxUrl - * @return the controller of the fxml - */ - private Object setContentPane(String jfxUrl) { - try { - AnchorPane contentPane = (AnchorPane) startScreen2.getParent(); - contentPane.getChildren().removeAll(); - contentPane.getChildren().clear(); - contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl)); - contentPane.getChildren().addAll((Pane) fxmlLoader.load()); - - return fxmlLoader.getController(); - } catch (javafx.fxml.LoadException e) { - System.out.println("[Controller] FXML load exception"); - } catch (IOException e) { - System.out.println("[Controller] IO exception"); - } - return null; - } - - - /** - * ATTEMPTS TO: - * Sets up a new game state with your IP address as designated as the host. - * Starts a thread to listen for incoming connections. - * Starts a client to server thread and connects to own ip. - * Switches to the lobby screen - */ - @FXML - public void hostButtonPressed() { - try { - String ipAddress = InetAddress.getLocalHost().getHostAddress(); - // get the lobby controller so that we can pass the game server thread to it - new GameState(getLocalHostIp()); - MainServerThread mainServerThread = new MainServerThread(); - ClientState.setHost(true); - // host will connect and handshake to itself after setting up the server - // TODO: 24/07/17 wmu16 - Make port number some static global type constant? - ClientToServerThread clientToServerThread = new ClientToServerThread(ClientState.getHostIp(), 4942); - ClientState.setConnectedToHost(true); - controller.setClientToServerThread(clientToServerThread); - LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml"); - lobbyController.setMainServerThread(mainServerThread); - lobbyController.setController(controller); - } catch (Exception e) { - Alert alert = new Alert(AlertType.ERROR); - alert.setHeaderText("Cannot host"); - alert.setContentText("Oops, failed to host, try to restart."); - alert.showAndWait(); - } - - - } - - /** - * ATTEMPTS TO: - * Connect to an ip address and port using the ip and port specified on start screen. - * Starts a Client To Server Thread to maintain connection to host. - * Switch view to lobby view. - */ - @FXML - public void connectButtonPressed() { - // TODO: 10/07/17 wmu16 - Finish function - try { - String ipAddress = ipTextField.getText().trim().toLowerCase(); - Integer port = Integer.valueOf(portTextField.getText().trim()); - - ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, port); - ClientState.setHost(false); - ClientState.setConnectedToHost(true); - - ClientState.setHostIp(ipAddress); - controller.setClientToServerThread(clientToServerThread); - LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml"); - lobbyController.setController(controller); - } catch (Exception e) { - Alert alert = new Alert(AlertType.ERROR); - alert.setHeaderText("Cannot reach the host"); - alert.setContentText("Please check your host IP address."); - alert.showAndWait(); - } - } - - public void setController(Controller controller) { - this.controller = controller; - } - - /** - * Gets the local host ip address and sets this ip to ClientState. - * Only runs by the host. - * - * @return the localhost ip address - */ - private String getLocalHostIp() { - String ipAddress = null; - try { - Enumeration e = NetworkInterface.getNetworkInterfaces(); - while (e.hasMoreElements()) { - NetworkInterface ni = e.nextElement(); - if (ni.isLoopback()) - continue; - if(ni.isPointToPoint()) - continue; - if(ni.isVirtual()) - continue; - - Enumeration addresses = ni.getInetAddresses(); - while(addresses.hasMoreElements()) { - InetAddress address = addresses.nextElement(); - if(address instanceof Inet4Address) { // skip all ipv6 - ipAddress = address.getHostAddress(); - } - } - } - } catch (Exception e) { - System.out.println("[StartScreenController] Exception"); - } - if (ipAddress == null) { - System.out.println("[HOST] Cannot obtain local host ip address."); - } - ClientState.setHostIp(ipAddress); - return ipAddress; - } -} diff --git a/src/main/java/seng302/fxObjects/BoatAnnotations.java b/src/main/java/seng302/fxObjects/BoatAnnotations.java deleted file mode 100644 index c9d7e90c..00000000 --- a/src/main/java/seng302/fxObjects/BoatAnnotations.java +++ /dev/null @@ -1,154 +0,0 @@ -package seng302.fxObjects; - -import javafx.scene.CacheHint; -import javafx.scene.Group; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import javafx.scene.text.Text; -import seng302.client.ClientPacketParser; -import seng302.models.Yacht; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; - -/** - * Collection of annotations for boats. - */ -public class BoatAnnotations extends Group{ - - //Text offset constants - private static final double X_OFFSET_TEXT = 18d; - private static final double Y_OFFSET_TEXT_INIT = -29d; - private static final double Y_OFFSET_PER_TEXT = 12d; - //Background constants - private static final double TEXT_BUFFER = 3; - private static final double BACKGROUND_X = X_OFFSET_TEXT - TEXT_BUFFER; - private static final double BACKGROUND_Y = Y_OFFSET_TEXT_INIT - TEXT_BUFFER; - private static final double BACKGROUND_H_PER_TEXT = 9.5d; - private static final double BACKGROUND_W = 125d; - private static final double BACKGROUND_ARC_SIZE = 10; - - private Rectangle background = new Rectangle(); - private Text teamNameObject; - private Text velocityObject; - private Text estTimeToNextMarkObject; - private Text legTimeObject; - private boolean isPlayer = false; - private Yacht boat; - - BoatAnnotations (Yacht boat, Color theme) { - super.setCache(true); - this.boat = boat; - background.setX(BACKGROUND_X); - background.setY(BACKGROUND_Y); - background.setWidth(BACKGROUND_W); - background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * 4); - background.setArcHeight(BACKGROUND_ARC_SIZE); - background.setArcWidth(BACKGROUND_ARC_SIZE); - background.setFill(new Color(1, 1, 1, 0.75)); - background.setStroke(theme); - background.setStrokeWidth(2); - background.setCache(true); - background.setCacheHint(CacheHint.SPEED); - - teamNameObject = getTextObject(boat.getShortName(), theme); - teamNameObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT); - - velocityObject = getTextObject("0 m/s", theme); - velocityObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 2); - velocityObject.setVisible(false); - - estTimeToNextMarkObject = getTextObject("Next mark: ", theme); - estTimeToNextMarkObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 3); - estTimeToNextMarkObject.setVisible(false); - - legTimeObject = getTextObject("Last mark: -", theme); - legTimeObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 4); - legTimeObject.setVisible(false); - - this.setVisible(true, false, false, false); - - super.getChildren().addAll(background, teamNameObject, velocityObject, estTimeToNextMarkObject, legTimeObject); - } - - /** - * Return a text object with caching and a color applied - * - * @param defaultText The default text to display - * @param fill The text fill color - * @return The text object - */ - private Text getTextObject(String defaultText, Color fill) { - Text text = new Text(defaultText); - text.setFill(fill); - text.setStrokeWidth(2); - text.setCacheHint(CacheHint.SPEED); - text.setCache(true); - return text; - } - - void update () { - teamNameObject.setText("Player: " + boat.getShortName()); - - velocityObject.setText(String.format(String.format("%.2f m/s", boat.getVelocityMMS()))); - - if (boat.getTimeTillNext() != null) { - DateFormat format = new SimpleDateFormat("mm:ss"); - String timeToNextMark = format - .format(boat.getTimeTillNext() - ClientPacketParser.getCurrentTimeLong()); - estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark); - } else { - estTimeToNextMarkObject.setText("Next mark: -"); - } - - if (boat.getMarkRoundTime() != null) { - DateFormat format = new SimpleDateFormat("mm:ss"); - String elapsedTime = format - .format(ClientPacketParser.getCurrentTimeLong() - boat.getMarkRoundTime()); - legTimeObject.setText("Last mark: " + elapsedTime); - }else { - legTimeObject.setText("Last mark: - "); - } - } - - void setVisible(boolean nameVisibility, boolean speedVisibility, - boolean estTimeVisibility, boolean lastMarkVisibility) { - int totalVisible = 0; - - /* - This is a temporary fix until the new annotation group is added along with the visualiser - overhaul. - */ - totalVisible = updateVisibility(nameVisibility, teamNameObject, totalVisible); - if (isPlayer) - totalVisible = updateVisibility(speedVisibility, velocityObject, totalVisible); -// totalVisible = updateVisibility(estTimeVisibility, estTimeToNextMarkObject, totalVisible); -// totalVisible = updateVisibility(lastMarkVisibility, legTimeObject, totalVisible); - if (totalVisible != 0) { - background.setVisible(true); - background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * totalVisible); - } else { - background.setVisible(false); - } - } - - private int updateVisibility (boolean visibility, Text text, int totalVisible) { - if (visibility){ - totalVisible ++; - text.setVisible(true); - text.setLayoutX(X_OFFSET_TEXT); - text.setLayoutY(Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * totalVisible); - } else { - text.setVisible(false); - } - return totalVisible; - } - - /** - * Sets these annotations to show more detailed info. - */ - public void setAsPlayer () { - isPlayer = true; - velocityObject.setVisible(true); - } -} diff --git a/src/main/java/seng302/fxObjects/BoatGroup.java b/src/main/java/seng302/fxObjects/BoatGroup.java deleted file mode 100644 index 2446f9d4..00000000 --- a/src/main/java/seng302/fxObjects/BoatGroup.java +++ /dev/null @@ -1,349 +0,0 @@ -package seng302.fxObjects; - -import java.util.ArrayList; - -import javafx.geometry.Point2D; -import javafx.scene.CacheHint; -import javafx.scene.Group; -import javafx.scene.paint.Color; -import javafx.scene.shape.Circle; -import javafx.scene.shape.Line; -import javafx.scene.shape.Polygon; -import javafx.scene.transform.Rotate; -import seng302.client.ClientPacketParser; -import seng302.models.Yacht; -import seng302.utilities.GeoUtility; -import seng302.controllers.CanvasController; -import seng302.models.mark.GateMark; -import seng302.models.mark.Mark; -import seng302.models.mark.SingleMark; - -/** - * BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 - * dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path, - * a wake object and two text labels to annotate the boat teams name and the boats velocity. The - * boat will update it's position onscreen everytime UpdatePosition is called unless the window is - * minimized in which case it attempts to store animations and apply them when the window is - * maximised. - */ -public class BoatGroup extends Group { - - //Constants for drawing - private static final double BOAT_HEIGHT = 15d; - private static final double BOAT_WIDTH = 10d; - //Variables for boat logic. - private boolean isStopped = true; - private double xIncrement; - private double yIncrement; - private long lastTimeValid = 0; - private Double lastRotation = 0.0; - private long framesToMove; - //Graphical objects - private Yacht boat; - private Group lineGroup = new Group(); - private Polygon boatPoly; - private Wake wake; - private Line leftLayLine; - private Line rightLayline; - private Double distanceTravelled = 0.0; - private Point2D lastPoint; - private boolean destinationSet; - private BoatAnnotations boatAnnotations; - private Color color; - private Boolean isSelected = true; //All boats are initialised as selected\ - private boolean isPlayer = false; - - /** - * Creates a BoatGroup with the default triangular boat polygon. - * - * @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used - * to tell which BoatGroup to update. - * @param color The colour of the boat polygon and the trailing line. - */ - public BoatGroup(Yacht boat, Color color) { - destinationSet = false; - this.boat = boat; - initChildren(color); - } - - /** - * Creates a BoatGroup with the boat being the default polygon. The head of the boat should be - * at point (0,0). - * - * @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used - * to tell which BoatGroup to update. - * @param color The colour of the boat polygon and the trailing line. - * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat - * polygon. - */ - public BoatGroup(Yacht boat, Color color, double... points) { - destinationSet = false; - this.boat = boat; - initChildren(color, points); - } - - /** - * Creates the javafx objects that will be the in the group by default. - * - * @param color The colour of the boat polygon and the trailing line. - * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat - * polygon. - */ - private void initChildren(Color color, double... points) { - this.color = color; - boatPoly = new Polygon(points); - boatPoly.setFill(this.color); - boatPoly.setOnMouseEntered(event -> { - boatPoly.setFill(Color.FLORALWHITE); - boatPoly.setStroke(Color.RED); - }); - boatPoly.setOnMouseExited(event -> { - boatPoly.setFill(this.color); - boatPoly.setStroke(Color.BLACK); - }); - boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected)); - boatPoly.setCache(true); - boatPoly.setCacheHint(CacheHint.SPEED); - boatAnnotations = new BoatAnnotations(boat, this.color); - - leftLayLine = new Line(); - rightLayline = new Line(); - - wake = new Wake(0, -BOAT_HEIGHT); - super.getChildren().addAll(boatPoly, boatAnnotations); - } - - /** - * Creates the javafx objects that will be the in the group by default. - * - * @param color The colour of the boat polygon and the trailing line. - */ - private void initChildren(Color color) { - initChildren(color, - -BOAT_WIDTH / 2, BOAT_HEIGHT / 2, - 0.0, -BOAT_HEIGHT / 2, - BOAT_WIDTH / 2, BOAT_HEIGHT / 2); - } - - /** - * Moves the boat and its children annotations from its current coordinates by specified - * amounts. - * - * @param dx The amount to move the X coordinate by - * @param dy The amount to move the Y coordinate by - */ - private void moveGroupBy(double dx, double dy) { - boatPoly.setLayoutX(boatPoly.getLayoutX() + dx); - boatPoly.setLayoutY(boatPoly.getLayoutY() + dy); - boatAnnotations.setLayoutX(boatAnnotations.getLayoutX() + dx); - boatAnnotations.setLayoutY(boatAnnotations.getLayoutY() + dy); - wake.setLayoutX(wake.getLayoutX() + dx); - wake.setLayoutY(wake.getLayoutY() + dy); - } - - - /** - * Moves the boat and its children annotations to coordinates specified - * - * @param x The X coordinate to move the boat to - * @param y The Y coordinate to move the boat to - */ - private void moveTo(double x, double y, double rotation) { - rotateTo(rotation); - boatPoly.setLayoutX(x); - boatPoly.setLayoutY(y); - boatAnnotations.setLayoutX(x); - boatAnnotations.setLayoutY(y); - wake.setLayoutX(x); - wake.setLayoutY(y); - wake.rotate(rotation); - } - - private void rotateTo(double rotation) { - boatPoly.getTransforms().setAll(new Rotate(rotation)); - } - - /** - * Sets the destination of the boat and the headng it should have once it reaches - * - * @param newXValue The X co-ordinate the boat needs to move to. - * @param newYValue The Y co-ordinate the boat needs to move to. - * @param rotation Rotation to move graphics to. - * @param timeValid the time the position values are valid for - */ - public void setDestination(double newXValue, double newYValue, double rotation, - double groundSpeed, long timeValid, double frameRate) { - - destinationSet = true; - Double dx = Math.abs(boatPoly.getLayoutX() - newXValue); - Double dy = Math.abs(boatPoly.getLayoutY() - newYValue); - moveTo(newXValue, newYValue, rotation); - - - rotateTo(rotation); - wake.setRotation(rotation, groundSpeed); - boat.setVelocity(groundSpeed); - isStopped = false; - lastRotation = rotation; - boatAnnotations.update(); - - distanceTravelled += Math.sqrt((dx * dx) + (dy * dy)); - - if (distanceTravelled > 10 && isPlayer) { - distanceTravelled = 0d; - - if (lastPoint != null) { - Line l = new Line( - lastPoint.getX(), - lastPoint.getY(), - boatPoly.getLayoutX(), - boatPoly.getLayoutY() - ); - l.getStrokeDashArray().setAll(3d, 7d); - l.setStroke(boat.getColour()); - l.setCache(true); - l.setCacheHint(CacheHint.SPEED); - lineGroup.getChildren().add(l); - } - if (destinationSet) { - lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); - } - } - } - - - /** - * This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next - * gates position and the current wind - * If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is - * going up wind, if they are on different sides of the gate, then the boat is going downwind - * @param canvasController - */ - public Boolean isUpwindLeg(CanvasController canvasController, Mark nextMark) { - - Double windAngle = ClientPacketParser.getWindDirection(); - GateMark thisGateMark = (GateMark) nextMark; - SingleMark nextMark1 = thisGateMark.getSingleMark1(); - SingleMark nextMark2 = thisGateMark.getSingleMark2(); - Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude()); - Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude()); - - Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); - Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d); - - - Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint); - Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint); - - - /* - If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the - boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going - with the wind. - */ - if (boatLineFuncResult == windLineFuncResult) { - return true; - } else { - return false; - } - - } - - - public void setIsSelected(Boolean isSelected) { - this.isSelected = isSelected; - setLineGroupVisible(isSelected); - setWakeVisible(isSelected); - boatAnnotations.setVisible(isSelected); - setLayLinesVisible(isSelected); - } - - public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime, boolean trail, boolean wake) { - boatAnnotations.setVisible(teamName, velocity, estTime, legTime); - this.wake.setVisible(wake); - this.lineGroup.setVisible(trail); - } - - public void setLineGroupVisible(Boolean visible) { - lineGroup.setVisible(visible); - } - - public void setWakeVisible(Boolean visible) { - wake.setVisible(visible); - } - - public void setLayLinesVisible(Boolean visible) { - leftLayLine.setVisible(visible); - rightLayline.setVisible(visible); - } - - public void setLaylines(Line line1, Line line2) { - this.leftLayLine = line1; - this.rightLayline = line2; - } - - public ArrayList getLaylines() { - ArrayList laylines = new ArrayList<>(); - laylines.add(leftLayLine); - laylines.add(rightLayline); - return laylines; - } - - public Yacht getBoat() { - return boat; - } - - /** - * Returns all raceIds associated with this group. For BoatGroups the ID's are for the boat. - * - * @return An array containing all ID's associated with this RaceObject. - */ - public long getRaceId() { - return boat.getSourceId(); - } - - public Group getWake () { - return wake; - } - - public Group getTrail() { - return lineGroup; - } - - public Group getAnnotations() { - return boatAnnotations; - } - - public Double getBoatLayoutX() { - return boatPoly.getLayoutX(); - } - - - public Double getBoatLayoutY() { - return boatPoly.getLayoutY(); - } - - public boolean isStopped() { - return isStopped; - } - - @Override - public String toString() { - return boat.toString(); - } - - /** - * Sets this boat to appear highlighted - */ - public void setAsPlayer() { - boatPoly.getPoints().setAll( - -BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75, - 0.0, -BOAT_HEIGHT / 1.75, - BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75 - ); - boatPoly.setStroke(Color.BLACK); - boatPoly.setStrokeWidth(3); - boatAnnotations.setAsPlayer(); - isPlayer = true; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/fxObjects/MarkGroup.java b/src/main/java/seng302/fxObjects/MarkGroup.java deleted file mode 100644 index 597338a1..00000000 --- a/src/main/java/seng302/fxObjects/MarkGroup.java +++ /dev/null @@ -1,167 +0,0 @@ -package seng302.fxObjects; - -import java.util.ArrayList; -import java.util.List; -import javafx.geometry.Point2D; -import javafx.scene.Group; -import javafx.scene.Node; -import javafx.scene.paint.Color; -import javafx.scene.shape.Circle; -import javafx.scene.shape.Line; -import seng302.models.mark.GateMark; -import seng302.models.mark.Mark; -import seng302.models.mark.MarkType; -import seng302.models.mark.SingleMark; - -/** - * Grouping of javaFX objects needed to represent a Mark on screen. - */ -public class MarkGroup extends Group { - - private static int MARK_RADIUS = 5; - private static int LINE_THICKNESS = 2; - private static double DASHED_GAP_LEN = 2d; - private static double DASHED_LINE_LEN = 5d; - - private List marks = new ArrayList<>(); - private Mark mainMark; - - /** - * Constructor for singleMark groups - * @param mark - * @param points - */ - public MarkGroup (SingleMark mark, Point2D points) { - marks.add(mark); - mainMark = mark; - Color color = Color.BLACK; - if (mark.getName().equals("Start")){ - color = Color.GREEN; - } else if (mark.getName().equals("Finish")){ - color = Color.RED; - } - Circle markCircle; - markCircle = new Circle( - points.getX(), - points.getY(), - MARK_RADIUS, - color - ); - super.getChildren().add(markCircle); - } - - public void addLaylines(Line line1, Line line2) { - - super.getChildren().addAll(line1, line2); - } - - - public void removeLaylines() { - ArrayList toRemove = new ArrayList<>(); - for(Node node : super.getChildren()) { - if (node instanceof Line) { - Line layLine = (Line) node; - - /*** - * OOHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHhhh - */ - if (layLine.getStrokeWidth() == 0.5){ - toRemove.add(layLine); - } - } - } - super.getChildren().removeAll(toRemove); - } - - public MarkGroup(GateMark mark, Point2D points1, Point2D points2) { - marks.add(mark.getSingleMark1()); - marks.add(mark.getSingleMark2()); - mainMark = mark; - Color color = Color.BLACK; - if (mark.getName().equals("Start")){ - color = Color.GREEN; - } else if (mark.getName().equals("Finish")){ - color = Color.RED; - } - Circle markCircle; - markCircle = new Circle( - points1.getX(), - points1.getY(), - MARK_RADIUS, - color - ); - super.getChildren().add(markCircle); - - markCircle = new Circle( - points2.getX(), - points2.getY(), - MARK_RADIUS, - color - ); - super.getChildren().add(markCircle); - Line line = new Line( - points1.getX(), - points1.getY(), - points2.getX(), - points2.getY() - ); - line.setStrokeWidth(LINE_THICKNESS); - line.setStroke(color); - if (mark.getMarkType() == MarkType.OPEN_GATE) { - line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN); - } - super.getChildren().add(line); - - } - - public void moveMarkTo (double x, double y, long raceId) - { - if (mainMark.getMarkType() == MarkType.SINGLE_MARK) { - Circle markCircle = (Circle) super.getChildren().get(0); - //One of the test streams produced frequent, jittery movements. Added this as a fix. - if (Math.abs(markCircle.getCenterX() - x) > 5 || Math.abs(markCircle.getCenterY() - y) > 5) { - markCircle.setCenterX(x); - markCircle.setCenterY(y); - } - } else { - Circle markCircle1 = (Circle) super.getChildren().get(0); - Circle markCircle2 = (Circle) super.getChildren().get(1); - Line connectingLine = (Line) super.getChildren().get(2); - if (marks.get(0).getId() == raceId) { - if (Math.abs(markCircle1.getCenterX() - x) > 5 || Math.abs(markCircle1.getCenterY() - y) > 5) { - markCircle1.setCenterX(x); - markCircle1.setCenterY(y); - connectingLine.setStartX(markCircle1.getCenterX()); - connectingLine.setStartY(markCircle1.getCenterY()); - } - } else if (marks.get(1).getId() == raceId) { - if (Math.abs(markCircle2.getCenterX() - x) > 5 || Math.abs(markCircle2.getCenterY() - y) > 5) { - markCircle2.setCenterX(x); - markCircle2.setCenterY(y); - connectingLine.setEndX(markCircle2.getCenterX()); - connectingLine.setEndY(markCircle2.getCenterY()); - } - } - } - } - - public boolean hasRaceId (int... raceIds) { - for (int id : raceIds) - for (Mark mark : marks) - if (id == mark.getId()) - return true; - return false; - } - - public long[] getRaceIds () { - long[] idArray = new long[marks.size()]; - int i = 0; - for (Mark mark : marks) - idArray[i++] = mark.getId(); - return idArray; - } - - public Mark getMainMark() { - return mainMark; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/gameServer/ClientConnectionDelegate.java b/src/main/java/seng302/gameServer/ClientConnectionDelegate.java index fab71cd7..e44029c0 100644 --- a/src/main/java/seng302/gameServer/ClientConnectionDelegate.java +++ b/src/main/java/seng302/gameServer/ClientConnectionDelegate.java @@ -1,6 +1,6 @@ package seng302.gameServer; -import seng302.models.Player; +import seng302.model.Player; public interface ClientConnectionDelegate { /** diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 918cd210..8ab071c3 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,5 +1,9 @@ package seng302.gameServer; +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; @@ -27,14 +31,29 @@ public class GameState implements Runnable { private static Map yachts; private static Boolean isRaceStarted; private static GameStages currentStage; + private static long startTime; - private static long startTime = System.currentTimeMillis(); - + // 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 + it's created threads if necessary. Outside of that I think the dependencies on it + (atm only Yacht & GameClient) can be removed from most other classes. The observable list of + players could be pulled directly from the server by the GameClient since it instantiates it + and it is reasonable for it to pull data. The current setup of publicly available statics is + pretty meh IMO because anything can change it making it unreliable and like people did with + the old ServerParser class everything that needs shared just gets thrown in the static + collections and things become a real mess. + */ public GameState(String hostIpAddress) { windDirection = 180d; windSpeed = 10000d; this.hostIpAddress = hostIpAddress; + yachts = new HashMap<>(); + players = new ArrayList<>(); + GameState.hostIpAddress = hostIpAddress; players = new ArrayList<>(); currentStage = GameStages.LOBBYING; isRaceStarted = false; @@ -53,13 +72,22 @@ public class GameState implements Runnable { public static List getPlayers() { 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); } public static void addYacht(Integer sourceId, Yacht yacht) { @@ -99,7 +127,7 @@ public class GameState implements Runnable { } public static Double getWindSpeedKnots() { - return windSpeed / 1000 * ClientPacketParser.MS_TO_KNOTS; + return windSpeed / 1000 * 1.943844492; // TODO: 26/07/17 cir27 - remove magic numbers } public static Map getYachts() { @@ -147,7 +175,6 @@ public class GameState implements Runnable { } } - /** * Generates a new ID based off the size of current players + 1 * @return a playerID to be allocated to a new connetion diff --git a/src/main/java/seng302/gameServer/HeartbeatThread.java b/src/main/java/seng302/gameServer/HeartbeatThread.java index 0b7ce19a..f15868c3 100644 --- a/src/main/java/seng302/gameServer/HeartbeatThread.java +++ b/src/main/java/seng302/gameServer/HeartbeatThread.java @@ -1,11 +1,12 @@ package seng302.gameServer; -import seng302.models.Player; -import seng302.server.messages.Heartbeat; -import seng302.server.messages.Message; - import java.io.IOException; -import java.util.*; +import java.util.Stack; +import java.util.Timer; +import java.util.TimerTask; +import seng302.model.Player; +import seng302.gameServer.server.messages.Heartbeat; +import seng302.gameServer.server.messages.Message; /** * Send Heartbeat messages to connected player at a specified interval diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index 78b87123..6e827d95 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -1,20 +1,13 @@ package seng302.gameServer; -import java.time.LocalDateTime; -import java.util.Observable; -import seng302.client.ClientPacketParser; -import seng302.models.Player; -import seng302.models.stream.PacketBufferDelegate; -import seng302.models.stream.packets.StreamPacket; - import java.io.IOException; import java.net.ServerSocket; -import java.net.Socket; +import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Observable; import java.util.Timer; import java.util.TimerTask; -import java.util.concurrent.PriorityBlockingQueue; -import java.util.logging.Logger; +import seng302.model.Player; /** * A class describing the overall server, which creates and collects server threads for each client @@ -76,7 +69,6 @@ public class MainServerThread extends Observable implements Runnable, ClientConn else if (GameState.getCurrentStage() == GameStages.FINISHED) { } - } // TODO: 14/07/17 wmu16 - Send out disconnect packet to clients diff --git a/src/main/java/seng302/gameServer/ServerListenThread.java b/src/main/java/seng302/gameServer/ServerListenThread.java index 475f2cd9..60469bb7 100644 --- a/src/main/java/seng302/gameServer/ServerListenThread.java +++ b/src/main/java/seng302/gameServer/ServerListenThread.java @@ -1,12 +1,8 @@ package seng302.gameServer; -import seng302.models.Player; - import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; /** * A class for a thread to listen to connections diff --git a/src/main/java/seng302/gameServer/ServerPacketParser.java b/src/main/java/seng302/gameServer/ServerPacketParser.java index 155ebe04..0d631eb1 100644 --- a/src/main/java/seng302/gameServer/ServerPacketParser.java +++ b/src/main/java/seng302/gameServer/ServerPacketParser.java @@ -1,8 +1,8 @@ package seng302.gameServer; import java.util.Arrays; -import seng302.models.stream.packets.StreamPacket; -import seng302.server.messages.BoatActionType; +import seng302.model.stream.packets.StreamPacket; +import seng302.gameServer.server.messages.BoatActionType; public class ServerPacketParser { diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 7b2641bd..ff2c4f23 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -3,7 +3,6 @@ package seng302.gameServer; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; -import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -15,30 +14,27 @@ import java.util.ArrayList; import java.util.List; import java.util.Observable; import java.util.Observer; -import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.zip.CRC32; import java.util.zip.Checksum; - -import seng302.models.Player; -import seng302.models.Yacht; -import seng302.models.stream.packets.PacketType; -import seng302.models.stream.packets.StreamPacket; -import seng302.models.xml.Race; -import seng302.models.xml.Regatta; -import seng302.models.xml.XMLGenerator; -import seng302.server.messages.BoatActionType; -import seng302.server.messages.BoatLocationMessage; -import seng302.server.messages.BoatStatus; -import seng302.server.messages.BoatSubMessage; -import seng302.server.messages.Message; - -import seng302.server.messages.RaceStatus; -import seng302.server.messages.RaceStatusMessage; -import seng302.server.messages.RaceType; -import seng302.server.messages.XMLMessage; -import seng302.server.messages.XMLMessageSubType; +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 @@ -170,7 +166,7 @@ public class ServerToClientThread implements Runnable, Observer { long packetCrc = Message.bytesToLong(getBytes(4)); if (computedCrc == packetCrc) { //System.out.println("RECEIVED A PACKET"); - switch (PacketType.assignPacketType(type)) { + switch (PacketType.assignPacketType(type, payload)) { case BOAT_ACTION: BoatActionType actionType = ServerPacketParser .extractBoatAction( @@ -271,6 +267,7 @@ public class ServerToClientThread implements Runnable, Observer { currentByte = is.read(); crcBuffer.write(currentByte); } catch (IOException e) { + e.printStackTrace(); serverLog("Socket read failed", 1); } if (currentByte == -1) { @@ -321,7 +318,7 @@ public class ServerToClientThread implements Runnable, Observer { yacht.getLocation().getLat(), yacht.getLocation().getLng(), yacht.getHeading(), - (long) yacht.getVelocityMMS()); + yacht.getVelocity().longValue()); sendMessage(boatLocationMessage); } diff --git a/src/main/java/seng302/server/messages/BoatActionMessage.java b/src/main/java/seng302/gameServer/server/messages/BoatActionMessage.java similarity index 85% rename from src/main/java/seng302/server/messages/BoatActionMessage.java rename to src/main/java/seng302/gameServer/server/messages/BoatActionMessage.java index cf4ea918..cfa596f7 100644 --- a/src/main/java/seng302/server/messages/BoatActionMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/BoatActionMessage.java @@ -1,8 +1,4 @@ -package seng302.server.messages; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; +package seng302.gameServer.server.messages; /** * Created by kre39 on 12/07/17. diff --git a/src/main/java/seng302/server/messages/BoatActionType.java b/src/main/java/seng302/gameServer/server/messages/BoatActionType.java similarity index 94% rename from src/main/java/seng302/server/messages/BoatActionType.java rename to src/main/java/seng302/gameServer/server/messages/BoatActionType.java index f8318af7..53fc6018 100644 --- a/src/main/java/seng302/server/messages/BoatActionType.java +++ b/src/main/java/seng302/gameServer/server/messages/BoatActionType.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java similarity index 98% rename from src/main/java/seng302/server/messages/BoatLocationMessage.java rename to src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java index ec0a4c0e..6e85f4e1 100644 --- a/src/main/java/seng302/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java @@ -1,7 +1,4 @@ -package seng302.server.messages; - -import java.io.IOException; -import java.io.OutputStream; +package seng302.gameServer.server.messages; public class BoatLocationMessage extends Message { private final int MESSAGE_SIZE = 56; diff --git a/src/main/java/seng302/server/messages/BoatStatus.java b/src/main/java/seng302/gameServer/server/messages/BoatStatus.java similarity index 87% rename from src/main/java/seng302/server/messages/BoatStatus.java rename to src/main/java/seng302/gameServer/server/messages/BoatStatus.java index 94418000..2f09dd4f 100644 --- a/src/main/java/seng302/server/messages/BoatStatus.java +++ b/src/main/java/seng302/gameServer/server/messages/BoatStatus.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; /** * The current status of a boat diff --git a/src/main/java/seng302/server/messages/BoatSubMessage.java b/src/main/java/seng302/gameServer/server/messages/BoatSubMessage.java similarity index 98% rename from src/main/java/seng302/server/messages/BoatSubMessage.java rename to src/main/java/seng302/gameServer/server/messages/BoatSubMessage.java index 0be35246..d0d57888 100644 --- a/src/main/java/seng302/server/messages/BoatSubMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/BoatSubMessage.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; import java.nio.ByteBuffer; diff --git a/src/main/java/seng302/server/messages/ChatterMessage.java b/src/main/java/seng302/gameServer/server/messages/ChatterMessage.java similarity index 95% rename from src/main/java/seng302/server/messages/ChatterMessage.java rename to src/main/java/seng302/gameServer/server/messages/ChatterMessage.java index 8480a9d5..1295b725 100644 --- a/src/main/java/seng302/server/messages/ChatterMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/ChatterMessage.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; /** * Created by kre39 on 20/07/17. diff --git a/src/main/java/seng302/server/messages/DeviceType.java b/src/main/java/seng302/gameServer/server/messages/DeviceType.java similarity index 82% rename from src/main/java/seng302/server/messages/DeviceType.java rename to src/main/java/seng302/gameServer/server/messages/DeviceType.java index d245c2b1..4f6d7b80 100644 --- a/src/main/java/seng302/server/messages/DeviceType.java +++ b/src/main/java/seng302/gameServer/server/messages/DeviceType.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; public enum DeviceType { UNKNOWN(0), diff --git a/src/main/java/seng302/server/messages/Header.java b/src/main/java/seng302/gameServer/server/messages/Header.java similarity index 94% rename from src/main/java/seng302/server/messages/Header.java rename to src/main/java/seng302/gameServer/server/messages/Header.java index 2b520611..074a8ab0 100644 --- a/src/main/java/seng302/server/messages/Header.java +++ b/src/main/java/seng302/gameServer/server/messages/Header.java @@ -1,9 +1,6 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; -import java.math.BigInteger; import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Collections; public class Header { // From API spec diff --git a/src/main/java/seng302/server/messages/Heartbeat.java b/src/main/java/seng302/gameServer/server/messages/Heartbeat.java similarity index 85% rename from src/main/java/seng302/server/messages/Heartbeat.java rename to src/main/java/seng302/gameServer/server/messages/Heartbeat.java index c86baac3..688aaf40 100644 --- a/src/main/java/seng302/server/messages/Heartbeat.java +++ b/src/main/java/seng302/gameServer/server/messages/Heartbeat.java @@ -1,7 +1,4 @@ -package seng302.server.messages; - -import java.io.IOException; -import java.io.OutputStream; +package seng302.gameServer.server.messages; public class Heartbeat extends Message { private final int MESSAGE_SIZE = 4; diff --git a/src/main/java/seng302/server/messages/MarkRoundingMessage.java b/src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java similarity index 85% rename from src/main/java/seng302/server/messages/MarkRoundingMessage.java rename to src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java index 5a085255..b683930e 100644 --- a/src/main/java/seng302/server/messages/MarkRoundingMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/MarkRoundingMessage.java @@ -1,7 +1,4 @@ -package seng302.server.messages; - -import java.io.IOException; -import java.io.OutputStream; +package seng302.gameServer.server.messages; public class MarkRoundingMessage extends Message{ private final long MESSAGE_VERSION_NUMBER = 1; @@ -15,9 +12,16 @@ public class MarkRoundingMessage extends Message{ private RoundingSide roundingSide; private long markId; + /** * This message is sent when a boat passes a mark, start line, or finish line * The purpose of this is to record the time when yachts cross marks + * @param ackNumber ackNumber + * @param raceId raceId + * @param sourceId sourceId + * @param roundingBoatStatus roundingBoatStatus + * @param roundingSide roundingSide + * @param markId markId */ public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus, RoundingSide roundingSide, int markId){ diff --git a/src/main/java/seng302/server/messages/MarkType.java b/src/main/java/seng302/gameServer/server/messages/MarkType.java similarity index 85% rename from src/main/java/seng302/server/messages/MarkType.java rename to src/main/java/seng302/gameServer/server/messages/MarkType.java index abbacc6f..6e6ae116 100644 --- a/src/main/java/seng302/server/messages/MarkType.java +++ b/src/main/java/seng302/gameServer/server/messages/MarkType.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; /** * Types of marks boats can round diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/gameServer/server/messages/Message.java similarity index 97% rename from src/main/java/seng302/server/messages/Message.java rename to src/main/java/seng302/gameServer/server/messages/Message.java index 10afb8e5..cabba2b3 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/gameServer/server/messages/Message.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -172,7 +172,7 @@ public abstract class Message { * Convert an integer to an array of bytes * @param val The value to add * @param len The width of the integer in the buffer - * @return + * @return A byte array to be sent */ public static byte[] intToByteArray(long val, int len){ int index = 0; @@ -191,6 +191,7 @@ public abstract class Message { * takes an array of up to 7 bytes in little endian format and * returns a positive long constructed from the input bytes * + * @param bytes the bytes to be converted to long * @return a positive long if there is less than 8 bytes -1 otherwise */ public static long bytesToLong(byte[] bytes){ diff --git a/src/main/java/seng302/server/messages/MessageType.java b/src/main/java/seng302/gameServer/server/messages/MessageType.java similarity index 93% rename from src/main/java/seng302/server/messages/MessageType.java rename to src/main/java/seng302/gameServer/server/messages/MessageType.java index fccf8d45..1d25e61d 100644 --- a/src/main/java/seng302/server/messages/MessageType.java +++ b/src/main/java/seng302/gameServer/server/messages/MessageType.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; /** * Enum containing the types of messages diff --git a/src/main/java/seng302/server/messages/RaceStartNotificationType.java b/src/main/java/seng302/gameServer/server/messages/RaceStartNotificationType.java similarity index 88% rename from src/main/java/seng302/server/messages/RaceStartNotificationType.java rename to src/main/java/seng302/gameServer/server/messages/RaceStartNotificationType.java index 29db3f1e..7f9543ff 100644 --- a/src/main/java/seng302/server/messages/RaceStartNotificationType.java +++ b/src/main/java/seng302/gameServer/server/messages/RaceStartNotificationType.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; /** * The types of race start status messages diff --git a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java b/src/main/java/seng302/gameServer/server/messages/RaceStartStatusMessage.java similarity index 94% rename from src/main/java/seng302/server/messages/RaceStartStatusMessage.java rename to src/main/java/seng302/gameServer/server/messages/RaceStartStatusMessage.java index 24158d62..ddb960c0 100644 --- a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/RaceStartStatusMessage.java @@ -1,7 +1,4 @@ -package seng302.server.messages; - -import java.io.IOException; -import java.io.OutputStream; +package seng302.gameServer.server.messages; public class RaceStartStatusMessage extends Message { private final int MESSAGE_SIZE = 20; diff --git a/src/main/java/seng302/server/messages/RaceStatus.java b/src/main/java/seng302/gameServer/server/messages/RaceStatus.java similarity index 91% rename from src/main/java/seng302/server/messages/RaceStatus.java rename to src/main/java/seng302/gameServer/server/messages/RaceStatus.java index 7f123c2d..6880ab06 100644 --- a/src/main/java/seng302/server/messages/RaceStatus.java +++ b/src/main/java/seng302/gameServer/server/messages/RaceStatus.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; /** * The current status of the race diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/gameServer/server/messages/RaceStatusMessage.java similarity index 96% rename from src/main/java/seng302/server/messages/RaceStatusMessage.java rename to src/main/java/seng302/gameServer/server/messages/RaceStatusMessage.java index 0310216e..5cddf0bf 100644 --- a/src/main/java/seng302/server/messages/RaceStatusMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/RaceStatusMessage.java @@ -1,7 +1,5 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; -import java.io.IOException; -import java.io.OutputStream; import java.util.List; import java.util.zip.CRC32; diff --git a/src/main/java/seng302/server/messages/RaceType.java b/src/main/java/seng302/gameServer/server/messages/RaceType.java similarity index 85% rename from src/main/java/seng302/server/messages/RaceType.java rename to src/main/java/seng302/gameServer/server/messages/RaceType.java index 182b5dfd..5d601083 100644 --- a/src/main/java/seng302/server/messages/RaceType.java +++ b/src/main/java/seng302/gameServer/server/messages/RaceType.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; /** * Enum containing the types of races diff --git a/src/main/java/seng302/server/messages/RoundingBoatStatus.java b/src/main/java/seng302/gameServer/server/messages/RoundingBoatStatus.java similarity index 86% rename from src/main/java/seng302/server/messages/RoundingBoatStatus.java rename to src/main/java/seng302/gameServer/server/messages/RoundingBoatStatus.java index 32eb2447..3b0a6d09 100644 --- a/src/main/java/seng302/server/messages/RoundingBoatStatus.java +++ b/src/main/java/seng302/gameServer/server/messages/RoundingBoatStatus.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; /** * The status of a boat rounding a mark diff --git a/src/main/java/seng302/server/messages/RoundingSide.java b/src/main/java/seng302/gameServer/server/messages/RoundingSide.java similarity index 85% rename from src/main/java/seng302/server/messages/RoundingSide.java rename to src/main/java/seng302/gameServer/server/messages/RoundingSide.java index 5cc4097c..6bb4c553 100644 --- a/src/main/java/seng302/server/messages/RoundingSide.java +++ b/src/main/java/seng302/gameServer/server/messages/RoundingSide.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; /** * The side the boat rounded the mark diff --git a/src/main/java/seng302/server/messages/XMLMessage.java b/src/main/java/seng302/gameServer/server/messages/XMLMessage.java similarity index 94% rename from src/main/java/seng302/server/messages/XMLMessage.java rename to src/main/java/seng302/gameServer/server/messages/XMLMessage.java index 57d10a00..6bcd42ca 100644 --- a/src/main/java/seng302/server/messages/XMLMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/XMLMessage.java @@ -1,7 +1,4 @@ -package seng302.server.messages; - -import java.io.IOException; -import java.io.OutputStream; +package seng302.gameServer.server.messages; public class XMLMessage extends Message{ private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE; @@ -20,6 +17,7 @@ public class XMLMessage extends Message{ * XML Message from the AC35 Streaming data spec * @param content The XML content * @param type The XML Message Sub Type + * @param sequenceNum sequenceNum */ public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){ this.content = content; diff --git a/src/main/java/seng302/server/messages/XMLMessageSubType.java b/src/main/java/seng302/gameServer/server/messages/XMLMessageSubType.java similarity index 86% rename from src/main/java/seng302/server/messages/XMLMessageSubType.java rename to src/main/java/seng302/gameServer/server/messages/XMLMessageSubType.java index 2e146c5a..3b4bdc91 100644 --- a/src/main/java/seng302/server/messages/XMLMessageSubType.java +++ b/src/main/java/seng302/gameServer/server/messages/XMLMessageSubType.java @@ -1,4 +1,4 @@ -package seng302.server.messages; +package seng302.gameServer.server.messages; /** * Enum containing the types of XML messages diff --git a/src/main/java/seng302/server/simulator/Boat.java b/src/main/java/seng302/gameServer/server/simulator/Boat.java similarity index 95% rename from src/main/java/seng302/server/simulator/Boat.java rename to src/main/java/seng302/gameServer/server/simulator/Boat.java index 435a70b6..c083dbf2 100644 --- a/src/main/java/seng302/server/simulator/Boat.java +++ b/src/main/java/seng302/gameServer/server/simulator/Boat.java @@ -1,7 +1,6 @@ -package seng302.server.simulator; +package seng302.gameServer.server.simulator; -import seng302.server.simulator.mark.Corner; -import seng302.utilities.GeoPoint; +import seng302.model.GeoPoint; import seng302.utilities.GeoUtility; public class Boat { diff --git a/src/main/java/seng302/server/simulator/mark/Corner.java b/src/main/java/seng302/gameServer/server/simulator/Corner.java similarity index 95% rename from src/main/java/seng302/server/simulator/mark/Corner.java rename to src/main/java/seng302/gameServer/server/simulator/Corner.java index 136212f2..11c26104 100644 --- a/src/main/java/seng302/server/simulator/mark/Corner.java +++ b/src/main/java/seng302/gameServer/server/simulator/Corner.java @@ -1,4 +1,6 @@ -package seng302.server.simulator.mark; +package seng302.gameServer.server.simulator; + +import seng302.model.mark.CompoundMark; public class Corner { diff --git a/src/main/java/seng302/server/simulator/mark/RoundingType.java b/src/main/java/seng302/gameServer/server/simulator/RoundingType.java similarity index 94% rename from src/main/java/seng302/server/simulator/mark/RoundingType.java rename to src/main/java/seng302/gameServer/server/simulator/RoundingType.java index de6f6133..aa1bca8e 100644 --- a/src/main/java/seng302/server/simulator/mark/RoundingType.java +++ b/src/main/java/seng302/gameServer/server/simulator/RoundingType.java @@ -1,4 +1,4 @@ -package seng302.server.simulator.mark; +package seng302.gameServer.server.simulator; public enum RoundingType { diff --git a/src/main/java/seng302/server/simulator/Simulator.java b/src/main/java/seng302/gameServer/server/simulator/Simulator.java similarity index 84% rename from src/main/java/seng302/server/simulator/Simulator.java rename to src/main/java/seng302/gameServer/server/simulator/Simulator.java index 7439c802..9f638beb 100644 --- a/src/main/java/seng302/server/simulator/Simulator.java +++ b/src/main/java/seng302/gameServer/server/simulator/Simulator.java @@ -1,6 +1,5 @@ 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.parsers.RaceParser; @@ -12,6 +11,10 @@ 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 { @@ -34,8 +37,8 @@ public class Simulator extends Observable implements Runnable { setLegs(); // set start line's coordinate to boats - Double startLat = course.get(0).getCompoundMark().getMark1().getLat(); - Double startLng = course.get(0).getCompoundMark().getMark1().getLng(); + 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); @@ -82,7 +85,7 @@ public class Simulator extends Observable implements Runnable { boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration); GeoPoint boatPos = new GeoPoint(boat.getLat(), boat.getLng()); - GeoPoint lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getMark1(); + GeoPoint lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getSubMark(1); double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos); // if a boat passes its heading mark @@ -99,13 +102,13 @@ public class Simulator extends Observable implements Runnable { // move compensate distance for the mark just passed GeoPoint pos = GeoUtility.getGeoCoordinate( - boat.getLastPassedCorner().getCompoundMark().getMark1(), + 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().getMark1()); + boat.getLastPassedCorner().getCompoundMark().getSubMark(1)); } } return 0; @@ -120,15 +123,15 @@ public class Simulator extends Observable implements Runnable { // 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().getMark1(); - Mark mark2 = course.get(i + 1).getCompoundMark().getMark1(); + 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().getMark1(), - course.get(i + 1).getCompoundMark().getMark1())); + GeoUtility.getBearing(course.get(i).getCompoundMark().getSubMark(1), + course.get(i + 1).getCompoundMark().getSubMark(1))); } } diff --git a/src/main/java/seng302/server/simulator/parsers/BoatsParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/BoatsParser.java similarity index 80% rename from src/main/java/seng302/server/simulator/parsers/BoatsParser.java rename to src/main/java/seng302/gameServer/server/simulator/parsers/BoatsParser.java index 5d552a00..17a46eae 100644 --- a/src/main/java/seng302/server/simulator/parsers/BoatsParser.java +++ b/src/main/java/seng302/gameServer/server/simulator/parsers/BoatsParser.java @@ -1,7 +1,6 @@ -package seng302.server.simulator.parsers; +package seng302.gameServer.server.simulator.parsers; import org.w3c.dom.Document; -import org.w3c.dom.NodeList; /** diff --git a/src/main/java/seng302/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java similarity index 90% rename from src/main/java/seng302/server/simulator/parsers/CourseParser.java rename to src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java index f7be46cd..66def8a1 100644 --- a/src/main/java/seng302/server/simulator/parsers/CourseParser.java +++ b/src/main/java/seng302/gameServer/server/simulator/parsers/CourseParser.java @@ -1,18 +1,17 @@ -package seng302.server.simulator.parsers; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import seng302.server.simulator.mark.CompoundMark; -import seng302.server.simulator.mark.Corner; -import seng302.server.simulator.mark.Mark; -import seng302.server.simulator.mark.RoundingType; +package seng302.gameServer.server.simulator.parsers; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import seng302.model.mark.CompoundMark; +import seng302.gameServer.server.simulator.Corner; +import seng302.model.mark.Mark; +import seng302.gameServer.server.simulator.RoundingType; /** * Parses the race xml file to get course details @@ -67,7 +66,7 @@ public class CourseParser extends FileParser { for (int i = 0; i < cMarks.getLength(); i++) { CompoundMark cMark = getCompoundMark(cMarks.item(i)); if (cMark != null) - compoundMarksMap.put(cMark.getMarkID(), cMark); + compoundMarksMap.put(cMark.getId(), cMark); } return compoundMarksMap; @@ -88,7 +87,7 @@ public class CourseParser extends FileParser { for (int i = 0; i < marks.getLength(); i++) { Mark mark = getMark(marks.item(i)); if (mark != null) - cMark.addMark(mark.getSeqID(), mark); + cMark.addSubMarks(mark); } return cMark; } diff --git a/src/main/java/seng302/server/simulator/parsers/FileParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/FileParser.java similarity index 96% rename from src/main/java/seng302/server/simulator/parsers/FileParser.java rename to src/main/java/seng302/gameServer/server/simulator/parsers/FileParser.java index d724e0bc..87021893 100644 --- a/src/main/java/seng302/server/simulator/parsers/FileParser.java +++ b/src/main/java/seng302/gameServer/server/simulator/parsers/FileParser.java @@ -1,12 +1,11 @@ -package seng302.server.simulator.parsers; +package seng302.gameServer.server.simulator.parsers; -import org.w3c.dom.Document; -import org.xml.sax.InputSource; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import java.io.InputStream; import java.io.StringReader; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; /** * Created by Haoming Yin (hyi25) on 16/3/2017 diff --git a/src/main/java/seng302/server/simulator/parsers/RaceParser.java b/src/main/java/seng302/gameServer/server/simulator/parsers/RaceParser.java similarity index 91% rename from src/main/java/seng302/server/simulator/parsers/RaceParser.java rename to src/main/java/seng302/gameServer/server/simulator/parsers/RaceParser.java index 14bf7bb8..960ff8fc 100644 --- a/src/main/java/seng302/server/simulator/parsers/RaceParser.java +++ b/src/main/java/seng302/gameServer/server/simulator/parsers/RaceParser.java @@ -1,14 +1,13 @@ -package seng302.server.simulator.parsers; +package seng302.gameServer.server.simulator.parsers; +import java.util.ArrayList; +import java.util.List; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import seng302.server.simulator.Boat; -import seng302.server.simulator.mark.Corner; - -import java.util.ArrayList; -import java.util.List; +import seng302.gameServer.server.simulator.Boat; +import seng302.gameServer.server.simulator.Corner; /** * Parses the race xml file to get course details diff --git a/src/main/java/seng302/models/Colors.java b/src/main/java/seng302/model/Colors.java similarity index 82% rename from src/main/java/seng302/models/Colors.java rename to src/main/java/seng302/model/Colors.java index 0078e505..72ff3ba5 100644 --- a/src/main/java/seng302/models/Colors.java +++ b/src/main/java/seng302/model/Colors.java @@ -1,9 +1,9 @@ -package seng302.models; +package seng302.model; import javafx.scene.paint.Color; /** - * Enum for randomly generating colours. + * Enum for generating colours. */ public enum Colors { RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE; diff --git a/src/main/java/seng302/utilities/GeoPoint.java b/src/main/java/seng302/model/GeoPoint.java similarity index 76% rename from src/main/java/seng302/utilities/GeoPoint.java rename to src/main/java/seng302/model/GeoPoint.java index a3d5c54b..29938946 100644 --- a/src/main/java/seng302/utilities/GeoPoint.java +++ b/src/main/java/seng302/model/GeoPoint.java @@ -1,12 +1,12 @@ -package seng302.utilities; +package seng302.model; /** - * A class represent Geo location (latitude, longitude). + * A class represent Geo location (latitude, lnggitude). * Created by Haoming on 15/5/2017 */ public class GeoPoint { - double lat, lng; + private double lat, lng; public GeoPoint(double lat, double lng) { this.lat = lat; diff --git a/src/main/java/seng302/model/Limit.java b/src/main/java/seng302/model/Limit.java new file mode 100644 index 00000000..e93ed2fe --- /dev/null +++ b/src/main/java/seng302/model/Limit.java @@ -0,0 +1,18 @@ +package seng302.model; + +/** + * Stores data on the border of a race + */ +public class Limit extends GeoPoint { + + private Integer seqID; + + public Limit(Integer seqID, Double lat, Double lng) { + super(lat, lng); + this.seqID = seqID; + } + + public Integer getSeqID() { + return seqID; + } +} \ No newline at end of file diff --git a/src/main/java/seng302/models/Player.java b/src/main/java/seng302/model/Player.java similarity index 91% rename from src/main/java/seng302/models/Player.java rename to src/main/java/seng302/model/Player.java index 71260d9c..175b7a45 100644 --- a/src/main/java/seng302/models/Player.java +++ b/src/main/java/seng302/model/Player.java @@ -1,10 +1,6 @@ -package seng302.models; +package seng302.model; -import javafx.scene.paint.Color; - -import java.io.IOException; import java.net.Socket; -import java.nio.channels.SocketChannel; /** * A Class defining a player and their respective details in the game as held by the model diff --git a/src/main/java/seng302/models/PolarTable.java b/src/main/java/seng302/model/PolarTable.java similarity index 98% rename from src/main/java/seng302/models/PolarTable.java rename to src/main/java/seng302/model/PolarTable.java index e76dcaa3..dee22278 100644 --- a/src/main/java/seng302/models/PolarTable.java +++ b/src/main/java/seng302/model/PolarTable.java @@ -1,4 +1,4 @@ -package seng302.models; +package seng302.model; import java.io.BufferedReader; import java.io.IOException; @@ -26,6 +26,7 @@ public final class PolarTable { * Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed. * These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap * as a value + * @param polarFile polarFile to be parsed */ public static void parsePolarFile(InputStream polarFile) { polarTable = new HashMap<>(); diff --git a/src/main/java/seng302/model/RaceState.java b/src/main/java/seng302/model/RaceState.java new file mode 100644 index 00000000..e426dc09 --- /dev/null +++ b/src/main/java/seng302/model/RaceState.java @@ -0,0 +1,72 @@ +package seng302.model; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.TimeZone; +import seng302.model.stream.parser.RaceStartData; +import seng302.model.stream.parser.RaceStatusData; + +/** + * Class for storing race data that does not relate to specific vessels or marks such as time or wind. + * Calculates the state of critical race attributes when relevant data is added. + */ +public class RaceState { + +// private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); + private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); + + private double windSpeed; + private double windDirection; + private long raceTime; + private long expectedStartTime; + private boolean isRaceStarted = false; +// long timeTillStart; + + public RaceState() { + } + + public void updateState (RaceStatusData data) { + this.windSpeed = data.getWindSpeed(); + this.windDirection = data.getWindDirection(); + this.raceTime = data.getCurrentTime(); + this.expectedStartTime = data.getExpectedStartTime(); + this.isRaceStarted = data.isRaceStarted(); + } + + public void setTimeZone (TimeZone timeZone) { + DATE_TIME_FORMAT.setTimeZone(timeZone); + } + + public void updateState (RaceStartData data) { +// this.timeTillStart = data.getRaceStartTime(); + System.out.println(data.getRaceStartTime()); + } + + public String getRaceTimeStr () { + return DATE_TIME_FORMAT.format(raceTime); + } + + public long getTimeTillStart () { + return (expectedStartTime - raceTime) / 1000; + } + + public double getWindSpeed() { + return windSpeed; + } + + public double getWindDirection() { + return windDirection; + } + + public long getRaceTime() { + return raceTime; + } + + public long getExpectedStartTime() { + return expectedStartTime; + } + + public boolean isRaceStarted () { + return isRaceStarted; + } +} diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/model/Yacht.java similarity index 63% rename from src/main/java/seng302/models/Yacht.java rename to src/main/java/seng302/model/Yacht.java index 67ab3373..17cdeb87 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -1,16 +1,19 @@ -package seng302.models; +package seng302.model; import static seng302.utilities.GeoUtility.getGeoCoordinate; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyDoubleWrapper; +import javafx.beans.property.ReadOnlyLongProperty; +import javafx.beans.property.ReadOnlyLongWrapper; import javafx.scene.paint.Color; -import seng302.client.ClientPacketParser; -import seng302.controllers.RaceViewController; import seng302.gameServer.GameState; -import seng302.models.mark.Mark; -import seng302.utilities.GeoPoint; +import seng302.model.mark.CompoundMark; /** * Yacht class for the racing boat. @@ -20,15 +23,17 @@ import seng302.utilities.GeoPoint; */ public class Yacht { - private final Double TURN_STEP = 5.0; + @FunctionalInterface + public interface YachtLocationListener { + void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity); + } - private Double lastHeading; - private Boolean sailIn; - - - // Used in boat group - private Color colour; + @FunctionalInterface + public interface YachtPositionListener { + void notifyPosition(int position); + } + //BOTH AFAIK private String boatType; private Integer sourceId; private String hullID; //matches HullNum in the XML spec. @@ -36,25 +41,34 @@ public class Yacht { private String boatName; private String country; - // Situational data - - - // Boat status - private Integer boatStatus; - private Integer legNumber; - private Integer penaltiesAwarded; - private Integer penaltiesServed; private Long estimateTimeAtFinish; - private String position; - private GeoPoint location; - private Double heading; - private Double velocity; private Long timeTillNext; private Long markRoundTime; + private Double heading; + private Double lat; + private Double lon; + private Integer legNumber = 0; - // Mark rounding - private Mark lastMarkRounded; - private Mark nextMark; + //SERVER SIDE + private final Double TURN_STEP = 5.0; + private Double lastHeading; + private Boolean sailIn; + private GeoPoint location; + private Integer boatStatus; + private Double velocity; + + //CLIENT SIDE + private List locationListeners = new ArrayList<>(); + private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper(); + private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper(); + private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper(); +// private ReadOnlyDoubleWrapper headingProperty = new ReadOnlyDoubleWrapper(); +// private ReadOnlyDoubleWrapper latitudeProperty = new ReadOnlyDoubleWrapper(); +// private ReadOnlyDoubleWrapper longitudeProperty = new ReadOnlyDoubleWrapper(); + private CompoundMark lastMarkRounded; + private CompoundMark nextMark; + private Integer positionInt = 0; + private Color colour; /** @@ -71,8 +85,10 @@ public class Yacht { /** * Used in EventTest and RaceTest. - * - * @param boatName Create a yacht object with name. + * @param boatName boatName + * @param shortName shortName + * @param location location + * @param heading heading */ public Yacht(String boatName, String shortName, GeoPoint location, Double heading) { this.boatName = boatName; @@ -89,6 +105,7 @@ public class Yacht { * @param boatName The name of the team sailing the boat * @param boatVelocity The speed of the boat in meters/second * @param shortName A shorter version of the teams name + * @param id The id for the yacht */ public Yacht(String boatName, double boatVelocity, String shortName, int id) { this.boatName = boatName; @@ -107,7 +124,6 @@ public class Yacht { this.shortName = shortName; this.boatName = boatName; this.country = country; - this.position = "-"; this.sailIn = false; this.location = new GeoPoint(57.670341, 11.826856); this.heading = 120.0; //In degrees @@ -123,7 +139,7 @@ public class Yacht { Double windSpeedKnots = GameState.getWindSpeedKnots(); Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading); Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle); - Double maxBoatSpeed = boatSpeedInKnots / ClientPacketParser.MS_TO_KNOTS * 1000; + Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000; if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) { if (velocity < maxBoatSpeed) { @@ -145,18 +161,22 @@ public class Yacht { velocity = 0d; } } - } - +// if (sailIn) { +// Double secondsElapsed = timeInterval / 1000000.0; +// Double windSpeedKnots = GameState.getWindSpeedKnots(); +// Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading); +// Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle); +// velocity = boatSpeedInKnots / 1.943844492 * 1000; // TODO: 26/07/17 cir27 - Remove magic number +// Double metersCovered = velocity * secondsElapsed; +// location = getGeoCoordinate(location, heading, metersCovered); +// } else { +// velocity = 0d; +// } Double metersCovered = velocity * secondsElapsed; location = getGeoCoordinate(location, heading, metersCovered); } - - public Double getHeading() { - return heading; - } - public void adjustHeading(Double amount) { Double newVal = heading + amount; lastHeading = heading; @@ -295,31 +315,14 @@ public class Yacht { } public void setLegNumber(Integer legNumber) { - if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus( - sourceId)) { - RaceViewController.updateYachtPositionSparkline(this, legNumber); - } +// if (colour != null && position != "-" && legNumber != this.legNumber) { +// RaceViewController.updateYachtPositionSparkline(this, legNumber); +// } this.legNumber = legNumber; } - public Integer getPenaltiesAwarded() { - return penaltiesAwarded; - } - - public void setPenaltiesAwarded(Integer penaltiesAwarded) { - this.penaltiesAwarded = penaltiesAwarded; - } - - public Integer getPenaltiesServed() { - return penaltiesServed; - } - - public void setPenaltiesServed(Integer penaltiesServed) { - this.penaltiesServed = penaltiesServed; - } - - public void setEstimateTimeAtNextMark(Long estimateTimeAtNextMark) { - timeTillNext = estimateTimeAtNextMark; + public void setEstimateTimeTillNextMark(Long estimateTimeTillNextMark) { + timeTillNext = estimateTimeTillNextMark; } public String getEstimateTimeAtFinish() { @@ -331,37 +334,36 @@ public class Yacht { this.estimateTimeAtFinish = estimateTimeAtFinish; } - public String getPosition() { - return position; + public Integer getPositionInteger() { + return positionInt; } - public void setPosition(String position) { - this.position = position; + public void setPositionInteger(Integer position) { + this.positionInt = position; } - public Color getColour() { - return colour; + public void updateVelocityProperty(double velocity) { + this.velocityProperty.set(velocity); } - public void setColour(Color colour) { - this.colour = colour; - } - - public void setVelocity(double velocity) { - this.velocity = velocity; - } - - public void setMarkRoundingTime(Long markRoundingTime) { this.markRoundTime = markRoundingTime; } + public ReadOnlyDoubleProperty getVelocityProperty() { + return velocityProperty.getReadOnlyProperty(); + } + public double getVelocityMMS() { return velocity; } + public ReadOnlyLongProperty timeTillNextProperty() { + return timeTillNextProperty.getReadOnlyProperty(); + } + public Double getVelocityKnots() { - return velocity / 1000 * ClientPacketParser.MS_TO_KNOTS; + return velocity / 1000 * 1.943844492; // TODO: 26/07/17 cir27 - remove magic number } public Long getTimeTillNext() { @@ -372,22 +374,46 @@ public class Yacht { return markRoundTime; } - public Mark getLastMarkRounded() { + public CompoundMark getLastMarkRounded() { return lastMarkRounded; } - public void setLastMarkRounded(Mark lastMarkRounded) { + public void setLastMarkRounded(CompoundMark lastMarkRounded) { this.lastMarkRounded = lastMarkRounded; } - public void setNextMark(Mark nextMark) { + public void setNextMark(CompoundMark nextMark) { this.nextMark = nextMark; } - public Mark getNextMark(){ + 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; + } + + public Double getHeading() { + return heading; + } + + public void setHeading(Double heading) { + this.heading = heading; + } + public Boolean getSailIn() { return sailIn; } @@ -401,4 +427,76 @@ public class Yacht { return location; } + public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) { + this.timeSinceLastMarkProperty.set(timeSinceLastMark); + } + + public ReadOnlyLongProperty timeSinceLastMarkProperty () { + return timeSinceLastMarkProperty.getReadOnlyProperty(); + } + + public void setTimeTillNext(Long timeTillNext) { + this.timeTillNext = timeTillNext; + } + + + public Color getColour() { + return colour; + } + + public void setColour(Color colour) { + this.colour = colour; + } + + + public Double getVelocity() { + return velocity; + } + + public void setVelocity(Double velocity) { + this.velocity = velocity; + } + +// public void updateLatitudeProperty (Double lat) { +// latitudeProperty.set(lat); +// } +// +// public void updateLongitudeProperty (double lon) { +// longitudeProperty.set(lon); +// } +// +// public void updateHeadingProperty (double heading) { +// headingProperty.set(heading); +// } +// +// public ReadOnlyDoubleProperty latitudeProperty () { +// return latitudeProperty.getReadOnlyProperty(); +// } +// +// public ReadOnlyDoubleProperty longitudeProperty () { +// return longitudeProperty.getReadOnlyProperty(); +// } +// +// public ReadOnlyDoubleProperty headingProperty () { +// return headingProperty; +// } + + public void updateLocation (double lat, double lon, double heading, double velocity) { + this.lat = lat; + this.lon = lon; + this.heading = heading; + this.velocity = velocity; + updateVelocityProperty(velocity); + for (YachtLocationListener yll : locationListeners) { + yll.notifyLocation(this, lat, lon, heading, velocity); + } + } + + public void addLocationListener (YachtLocationListener listener) { + locationListeners.add(listener); + } + + public void addPositionListener (YachtPositionListener listener) { + + } } diff --git a/src/main/java/seng302/model/mark/CompoundMark.java b/src/main/java/seng302/model/mark/CompoundMark.java new file mode 100644 index 00000000..b1989845 --- /dev/null +++ b/src/main/java/seng302/model/mark/CompoundMark.java @@ -0,0 +1,89 @@ +package seng302.model.mark; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class CompoundMark { + + private int compoundMarkId; + private String name; + + private List marks = new ArrayList<>(); + + public CompoundMark(int markID, String name) { + 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); + } + + /** + * Prints out compoundMark's info and its marks, good for testing + * @return a string showing its details + */ + @Override + public String toString(){ + String info = String.format( + "CompoundMark: %d (%s), [%s", compoundMarkId, name, marks.get(0).toString() + ); + if (marks.size() > 1) { + info += String.format(", %s", marks.get(1).toString()); + } + return info + "]"; + } + + public int getId() { + return compoundMarkId; + } + + public void setId (int markID) { + this.compoundMarkId = markID; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Returns the mark contained in the compound mark. Marks are numbered 1 to n; + * @param singleMarkId the id of the desired mark contained in this compound mark. + * @return the desired mark. Returns null if the ID is not in range (1, NUM_MARKS) + */ + public Mark getSubMark(int singleMarkId) { + try { + return marks.get(singleMarkId - 1); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + /** + * Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a + * specific singleMark or the list of marks. + * + * @return True if the compound mark is a gate, false otherwise. + */ + public boolean isGate () { + return marks.size() > 1; + } + + /** + * Returns the list of marks in the compoundMark + * + * @return All marks contained in this mark. + */ + public List getMarks () { + return marks; + } +} diff --git a/src/main/java/seng302/model/mark/Corner.java b/src/main/java/seng302/model/mark/Corner.java new file mode 100644 index 00000000..c911ce9c --- /dev/null +++ b/src/main/java/seng302/model/mark/Corner.java @@ -0,0 +1,35 @@ +package seng302.model.mark; + +/** + * Stores the data for the cornering of a mark. + */ +public class Corner { + + private Integer seqID; + private Integer compoundMarkID; + private String rounding; + private Integer zoneSize; + + public Corner(Integer seqID, Integer compoundMarkID, String rounding, Integer zoneSize) { + this.seqID = seqID; + this.compoundMarkID = compoundMarkID; + this.rounding = rounding; + this.zoneSize = zoneSize; + } + + public Integer getSeqID() { + return seqID; + } + + public Integer getCompoundMarkID() { + return compoundMarkID; + } + + public String getRounding() { + return rounding; + } + + public Integer getZoneSize() { + return zoneSize; + } +} \ No newline at end of file diff --git a/src/main/java/seng302/server/simulator/mark/Mark.java b/src/main/java/seng302/model/mark/Mark.java similarity index 57% rename from src/main/java/seng302/server/simulator/mark/Mark.java rename to src/main/java/seng302/model/mark/Mark.java index 0b6f1f3b..57d04974 100644 --- a/src/main/java/seng302/server/simulator/mark/Mark.java +++ b/src/main/java/seng302/model/mark/Mark.java @@ -1,6 +1,8 @@ -package seng302.server.simulator.mark; +package seng302.model.mark; -import seng302.utilities.GeoPoint; +import java.util.ArrayList; +import java.util.List; +import seng302.model.GeoPoint; /** * An abstract class to represent general marks @@ -8,9 +10,15 @@ import seng302.utilities.GeoPoint; */ public class Mark extends GeoPoint { + @FunctionalInterface + public interface PositionListener { + void notifyPositionChange(Mark mark, double lat, double lon); + } + private int seqID; private String name; private int sourceID; + private List positionListeners = new ArrayList<>(); public Mark(String name, double lat, double lng, int sourceID) { super(lat, lng); @@ -50,6 +58,22 @@ public class Mark extends GeoPoint { public void setSourceID(int sourceID) { this.sourceID = sourceID; } + + public void updatePosition (double lat, double lon) { + this.setLat(lat); + this.setLng(lon); + for (PositionListener listener : positionListeners) { + listener.notifyPositionChange(this, lat, lon); + } + } + + public void addPositionListener (PositionListener listener) { + positionListeners.add(listener); + } + + public void removePositionListener (PositionListener listener) { + positionListeners.remove(listener); + } } diff --git a/src/main/java/seng302/models/stream/packets/PacketType.java b/src/main/java/seng302/model/stream/packets/PacketType.java similarity index 71% rename from src/main/java/seng302/models/stream/packets/PacketType.java rename to src/main/java/seng302/model/stream/packets/PacketType.java index 6737d53f..e7f55b49 100644 --- a/src/main/java/seng302/models/stream/packets/PacketType.java +++ b/src/main/java/seng302/model/stream/packets/PacketType.java @@ -1,4 +1,4 @@ -package seng302.models.stream.packets; +package seng302.model.stream.packets; /** * Created by Kusal on 4/24/2017. @@ -7,7 +7,9 @@ public enum PacketType { HEARTBEAT, RACE_STATUS, DISPLAY_TEXT_MESSAGE, - XML_MESSAGE, + RACE_XML, + REGATTA_XML, + BOAT_XML, RACE_START_STATUS, YACHT_EVENT_CODE, YACHT_ACTION_CODE, @@ -19,7 +21,7 @@ public enum PacketType { BOAT_ACTION, OTHER; - public static PacketType assignPacketType(int packetType){ + public static PacketType assignPacketType(int packetType, byte[] payload){ switch(packetType){ case 1: return HEARTBEAT; @@ -28,7 +30,14 @@ public enum PacketType { case 20: return DISPLAY_TEXT_MESSAGE; case 26: - return XML_MESSAGE; + switch (payload[9]) { //The type of XML message + case 5: + return REGATTA_XML; + case 6: + return RACE_XML; + case 7: + return BOAT_XML; + } case 27: return RACE_START_STATUS; case 29: diff --git a/src/main/java/seng302/models/stream/packets/StreamPacket.java b/src/main/java/seng302/model/stream/packets/StreamPacket.java similarity index 87% rename from src/main/java/seng302/models/stream/packets/StreamPacket.java rename to src/main/java/seng302/model/stream/packets/StreamPacket.java index 22f2fe56..41442e84 100644 --- a/src/main/java/seng302/models/stream/packets/StreamPacket.java +++ b/src/main/java/seng302/model/stream/packets/StreamPacket.java @@ -1,4 +1,4 @@ -package seng302.models.stream.packets; +package seng302.model.stream.packets; /** * Created by kre39 on 23/04/17. @@ -13,7 +13,7 @@ public class StreamPacket { private byte[] payload; public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) { - this.type = PacketType.assignPacketType(type); + this.type = PacketType.assignPacketType(type, payload); this.messageLength = messageLength; this.timeStamp = timeStamp; this.payload = payload; diff --git a/src/main/java/seng302/model/stream/parser/MarkRoundingData.java b/src/main/java/seng302/model/stream/parser/MarkRoundingData.java new file mode 100644 index 00000000..70971317 --- /dev/null +++ b/src/main/java/seng302/model/stream/parser/MarkRoundingData.java @@ -0,0 +1,36 @@ +package seng302.model.stream.parser; + + +/** + * Simple data wrapper for mark rounding data packet. + */ +public class MarkRoundingData { + + private int boatId; + private int markId; + private int roundingSide; + private long timeStamp; + + public MarkRoundingData(int boatId, int markId, int roundingSide, long timeStamp) { + this.boatId = boatId; + this.markId = markId; + this.roundingSide = roundingSide; + this.timeStamp = timeStamp; + } + + public int getBoatId() { + return boatId; + } + + public int getMarkId() { + return markId; + } + + public int getRoundingSide() { + return roundingSide; + } + + public long getTimeStamp() { + return timeStamp; + } +} diff --git a/src/main/java/seng302/model/stream/parser/PositionUpdateData.java b/src/main/java/seng302/model/stream/parser/PositionUpdateData.java new file mode 100644 index 00000000..6f1af7c0 --- /dev/null +++ b/src/main/java/seng302/model/stream/parser/PositionUpdateData.java @@ -0,0 +1,50 @@ +package seng302.model.stream.parser; + +public class PositionUpdateData { + + public enum DeviceType { + YACHT_TYPE, + MARK_TYPE + } + + private int deviceId; + private DeviceType type; + private double lat; + private double lon; + private double heading; + private double groundSpeed; + + public PositionUpdateData(int deviceId, DeviceType type, double lat, double lon, + double heading, double groundSpeed) { + this.deviceId = deviceId; + this.type = type; + this.lat = lat; + this.lon = lon; + this.heading = heading; + this.groundSpeed = groundSpeed; + } + + public int getDeviceId() { + return deviceId; + } + + public DeviceType getType() { + return type; + } + + public double getLat() { + return lat; + } + + public double getLon() { + return lon; + } + + public double getHeading() { + return heading; + } + + public double getGroundSpeed() { + return groundSpeed; + } +} diff --git a/src/main/java/seng302/model/stream/parser/RaceStartData.java b/src/main/java/seng302/model/stream/parser/RaceStartData.java new file mode 100644 index 00000000..0bd4d926 --- /dev/null +++ b/src/main/java/seng302/model/stream/parser/RaceStartData.java @@ -0,0 +1,35 @@ +package seng302.model.stream.parser; + +/** + * Class for storing data parsed from race start status packet + */ +public class RaceStartData { + + private long raceId; + private long raceStartTime; + private int notificationType; + private long timeStamp; + + public RaceStartData (long raceId, long raceStartTime, int notificationType, long timeStamp) { + this.raceId = raceId; + this.raceStartTime = raceStartTime; + this.notificationType = notificationType; + this.timeStamp = timeStamp; + } + + public long getRaceId() { + return raceId; + } + + public long getRaceStartTime() { + return raceStartTime; + } + + public int getNotificationType() { + return notificationType; + } + + public long getTimeStamp() { + return timeStamp; + } +} diff --git a/src/main/java/seng302/model/stream/parser/RaceStatusData.java b/src/main/java/seng302/model/stream/parser/RaceStatusData.java new file mode 100644 index 00000000..ba836442 --- /dev/null +++ b/src/main/java/seng302/model/stream/parser/RaceStatusData.java @@ -0,0 +1,65 @@ +package seng302.model.stream.parser; + +import java.util.ArrayList; +import java.util.List; + +/** + * Stores parsed data from race status packets + */ +public class RaceStatusData { + + //CONVERSION CONSTANTS + private static final double WIND_DIR_FACTOR = 0x4000 / 90; //0x4000 is 90 degrees + private static final double MS_TO_KNOTS = 1.94384; + + private double windDirection; + private double windSpeed; + private boolean raceStarted = false; + private long currentTime; + private long expectedStartTime; + private List boatData = new ArrayList<>(); + + public RaceStatusData( + long windDir, long rawWindSpeed, int raceStatus, long currentTime, long expectedStartTime) { + + windDirection = windDir / WIND_DIR_FACTOR; + windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS; + raceStarted = raceStatus == 3; + this.currentTime = currentTime; + this.expectedStartTime = expectedStartTime; + } + + public void addBoatData (long boatID, long estTimeToNextMark, long estTimeToFinish, int leg, int boatStatus) { + boatData.add(new long[] {boatID, estTimeToNextMark, estTimeToFinish, leg, boatStatus}); + } + + public double getWindDirection() { + return windDirection; + } + + public double getWindSpeed() { + return windSpeed; + } + + public boolean isRaceStarted() { + return raceStarted; + } + + public long getCurrentTime() { + return currentTime; + } + + public long getExpectedStartTime() { + return expectedStartTime; + } + + /** + * Returns the data for boats collected form race status packets. + * + * @return A list of boat data. Boat data is in the form + * [boatID, estTimeToNextMark, estTimeToFinish, legNumber]. + */ + public List getBoatData () { + return boatData; + } +} diff --git a/src/main/java/seng302/models/xml/Race.java b/src/main/java/seng302/model/stream/xml/generator/Race.java similarity index 93% rename from src/main/java/seng302/models/xml/Race.java rename to src/main/java/seng302/model/stream/xml/generator/Race.java index 9be61f37..c9f8cded 100644 --- a/src/main/java/seng302/models/xml/Race.java +++ b/src/main/java/seng302/model/stream/xml/generator/Race.java @@ -1,11 +1,10 @@ -package seng302.models.xml; - -import seng302.models.Yacht; +package seng302.model.stream.xml.generator; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import seng302.model.Yacht; /** * A Race object that can be parsed into XML diff --git a/src/main/java/seng302/models/xml/Regatta.java b/src/main/java/seng302/model/stream/xml/generator/Regatta.java similarity index 97% rename from src/main/java/seng302/models/xml/Regatta.java rename to src/main/java/seng302/model/stream/xml/generator/Regatta.java index 733b7a0a..4a90368a 100644 --- a/src/main/java/seng302/models/xml/Regatta.java +++ b/src/main/java/seng302/model/stream/xml/generator/Regatta.java @@ -1,4 +1,4 @@ -package seng302.models.xml; +package seng302.model.stream.xml.generator; /** * A Race regatta that can be parsed into XML diff --git a/src/main/java/seng302/model/stream/xml/parser/RaceXMLData.java b/src/main/java/seng302/model/stream/xml/parser/RaceXMLData.java new file mode 100644 index 00000000..13a0bdbc --- /dev/null +++ b/src/main/java/seng302/model/stream/xml/parser/RaceXMLData.java @@ -0,0 +1,46 @@ +package seng302.model.stream.xml.parser; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import seng302.model.Limit; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Corner; + +/** + * Process a Document object containing race data in XML format and stores the data. + */ +public class RaceXMLData { + + private List participants; + private Map compoundMarks; + private List markSequence; + private List courseLimit; + + public RaceXMLData(List participants, List compoundMarks, + List markSequence, List courseLimit) { + this.participants = participants; + this.markSequence = markSequence; + this.courseLimit = courseLimit; + this.compoundMarks = new HashMap<>(); + for (CompoundMark cMark : compoundMarks) { + this.compoundMarks.put(cMark.getId(), cMark); + } + } + + public List getParticipants() { + return participants; + } + + public Map getCompoundMarks() { + return compoundMarks; + } + + public List getMarkSequence() { + return markSequence; + } + + public List getCourseLimit() { + return courseLimit; + } +} diff --git a/src/main/java/seng302/model/stream/xml/parser/RegattaXMLData.java b/src/main/java/seng302/model/stream/xml/parser/RegattaXMLData.java new file mode 100644 index 00000000..a4dc783d --- /dev/null +++ b/src/main/java/seng302/model/stream/xml/parser/RegattaXMLData.java @@ -0,0 +1,49 @@ +package seng302.model.stream.xml.parser; + +/** + * Stores data from regatta xml packet. + */ +public class RegattaXMLData { + //Regatta Info + private Integer regattaID; + private String regattaName; + private String courseName; + private Double centralLat; + private Double centralLng; + private Integer utcOffset; + + public RegattaXMLData (Integer regattaID, String regattaName, String courseName, + Double centralLat, Double centralLng, Integer utcOffset) { + this.regattaID = regattaID; + this.regattaName = regattaName; + this.courseName = courseName; + this.centralLat = centralLat; + this.centralLng = centralLng; + this.utcOffset = utcOffset; + } + + public Integer getRegattaID() { + return regattaID; + } + + public String getRegattaName() { + return regattaName; + } + + public String getCourseName() { + return courseName; + } + + public Double getCentralLat() { + return centralLat; + } + + public Double getCentralLng() { + return centralLng; + } + + public Integer getUtcOffset() { + return utcOffset; + } + +} diff --git a/src/main/java/seng302/models/mark/GateMark.java b/src/main/java/seng302/models/mark/GateMark.java deleted file mode 100644 index 8459b882..00000000 --- a/src/main/java/seng302/models/mark/GateMark.java +++ /dev/null @@ -1,49 +0,0 @@ -package seng302.models.mark; - -/** - * To represent a gate mark which contains two single marks. - * Created by ptg19 on 16/03/17. - * Modified by Haoming Yin (hyi25) on 17/3/2017. - */ -public class GateMark extends Mark { - - private SingleMark singleMark1; - private SingleMark singleMark2; - - /** - * Create an instance of Gate Mark which contains two single mark - * @param name the name of the gate mark - * @param singleMark1 one single mark inside of the gate mark - * @param singleMark2 the second mark inside of the gate mark - */ - public GateMark(String name, MarkType type, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude, int compoundMarkID) { - super(name, type, latitude, longitude, compoundMarkID); - this.singleMark1 = singleMark1; - this.singleMark2 = singleMark2; - } - - public SingleMark getSingleMark1() { - return singleMark1; - } - - public void setSingleMark1(SingleMark singleMark1) { - this.singleMark1 = singleMark1; - } - - public SingleMark getSingleMark2() { - return singleMark2; - } - - public void setSingleMark2(SingleMark singleMark2) { - this.singleMark2 = singleMark2; - } - - public double getLatitude(){ - return (this.getSingleMark1().getLatitude()); - } - - public double getLongitude(){ - return (this.getSingleMark1().getLongitude()); - } - -} diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java deleted file mode 100644 index 362c77fd..00000000 --- a/src/main/java/seng302/models/mark/Mark.java +++ /dev/null @@ -1,186 +0,0 @@ -package seng302.models.mark; - -/** - * An abstract class to represent general marks - * Created by Haoming Yin (hyi25) on 17/3/17. - */ -public abstract class Mark { - - private String name; - private MarkType markType; - private double latitude; - private double longitude; - private long id; - private int compoundMarkID; - - /** - * Create a mark instance by passing its name and type - * @param name the name of the mark - * @param markType the type of mark. either GATE_MARK or SINGLE_MARK. - */ - public Mark (String name, MarkType markType, int sourceID, int compoundMarkID) { - this.name = name; - this.markType = markType; - this.id = sourceID; - this.compoundMarkID = compoundMarkID; - } - - public Mark(String name, MarkType markType, double latitude, double longitude, int compoundMarkID) { - this.name = name; - this.markType = markType; - this.latitude = latitude; - this.longitude = longitude; - this.id = 0; - this.compoundMarkID = compoundMarkID; - } - - /** - * Calculated the heading in radians from first Mark to the second Mark. - * - * @param pointOne First Mark - * @param pointTwo Second Mark - * @return Heading in radians - */ - public static Double calculateHeadingRad(Mark pointOne, Mark pointTwo) { - Double longitude1 = pointOne.getLongitude(); - Double longitude2 = pointTwo.getLongitude(); - Double latitude1 = pointOne.getLatitude(); - Double latitude2 = pointTwo.getLatitude(); - return calculateHeadingRad(latitude1, longitude1, latitude2, longitude2); - } - - /** - * Calculate the heading in radians from geographical location with latitude1, longitude 1 to - * geographical latitude2, longitude 2 - * - * @param longitude1 Longitude of first point in degrees - * @param longitude2 Longitude of second point in degrees - * @param latitude1 Latitude of first point in degrees - * @param latitude2 Latitude of first point in degrees - * @return Heading in radians - */ - public static double calculateHeadingRad(Double latitude1, Double longitude1, Double latitude2, - Double longitude2) { - latitude1 = Math.toRadians(latitude1); - latitude2 = Math.toRadians(latitude2); - Double longDiff = Math.toRadians(longitude2 - longitude1); - Double y = Math.sin(longDiff) * Math.cos(latitude2); - Double x = - Math.cos(latitude1) * Math.sin(latitude2) - Math.sin(latitude1) * Math.cos(latitude2) - * Math.cos(longDiff); - return Math.atan2(y, x); - } - - /** - * Calculates the distance in meters from the first Mark to a second Mark - * - * @param pointOne First Mark - * @param pointTwo Second Mark - * @return Distance in meters - */ - public static Double calculateDistance(Mark pointOne, Mark pointTwo) { - Double longitude1 = pointOne.getLongitude(); - Double longitude2 = pointTwo.getLongitude(); - Double latitude1 = pointOne.getLatitude(); - Double latitude2 = pointTwo.getLatitude(); - return calculateDistance(latitude1, longitude1, latitude2, longitude2); - } - - /** - * Calculate the distance in meters from geographical location with latitude1, longitude 1 to - * geographical latitude2, longitude 2 - * - * @param longitude1 Longitude of first point in degrees - * @param longitude2 Longitude of second point in degrees - * @param latitude1 Latitude of first point in degrees - * @param latitude2 Latitude of first point in degrees - * @return Distance in meters - */ - public static Double calculateDistance(Double latitude1, Double longitude1, Double latitude2, - Double longitude2) { - Double theta = longitude1 - longitude2; - Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) + - Math.cos(Math.toRadians(latitude1)) * Math.cos(Math.toRadians(latitude2)) * - Math.cos(Math.toRadians(theta)); - dist = Math.acos(dist); - dist = Math.toDegrees(dist); - dist = dist * 60 - * 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute) - dist = dist * 1609.344; //ratio of miles to metres - return dist; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public MarkType getMarkType() { - return markType; - } - - public void setMarkType(MarkType markType) { - this.markType = markType; - } - - public double getLatitude() { - return latitude; - } - - public double getLongitude() { - return longitude; - } - - public long getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - 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/MarkType.java b/src/main/java/seng302/models/mark/MarkType.java deleted file mode 100644 index 92b3c93b..00000000 --- a/src/main/java/seng302/models/mark/MarkType.java +++ /dev/null @@ -1,9 +0,0 @@ -package seng302.models.mark; - -/** - * To represent two types of mark - * Created by Haoming Yin (hyi25) on 17/3/17. - */ -public enum MarkType { - SINGLE_MARK, OPEN_GATE -} diff --git a/src/main/java/seng302/models/mark/SingleMark.java b/src/main/java/seng302/models/mark/SingleMark.java deleted file mode 100644 index 56ba9dc6..00000000 --- a/src/main/java/seng302/models/mark/SingleMark.java +++ /dev/null @@ -1,34 +0,0 @@ -package seng302.models.mark; - -/** - * Represents the marker as a single mark - * - * Created by Haoming Yin (hyi25) on 17/3/2017 - */ -public class SingleMark extends Mark { - private double lat; - private double lon; - private String name; - - /** - * Represents a marker - * - * @param name, the name of the marker* - * @param lat, the latitude of the marker - * @param lon, the longitude of the marker - */ - public SingleMark(String name, double lat, double lon, int sourceID, int compoundMarkID) { - super(name, MarkType.SINGLE_MARK, sourceID, compoundMarkID); - this.lat = lat; - this.lon = lon; - } - - - public double getLatitude() { - return this.lat; - } - - public double getLongitude() { - return this.lon; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/models/stream/PacketBufferDelegate.java b/src/main/java/seng302/models/stream/PacketBufferDelegate.java deleted file mode 100644 index 847b0de5..00000000 --- a/src/main/java/seng302/models/stream/PacketBufferDelegate.java +++ /dev/null @@ -1,7 +0,0 @@ -package seng302.models.stream; - -import seng302.models.stream.packets.StreamPacket; - -public interface PacketBufferDelegate { - boolean addToBuffer(StreamPacket streamPacket); -} diff --git a/src/main/java/seng302/models/stream/XMLParser.java b/src/main/java/seng302/models/stream/XMLParser.java deleted file mode 100644 index 733bcb54..00000000 --- a/src/main/java/seng302/models/stream/XMLParser.java +++ /dev/null @@ -1,610 +0,0 @@ -package seng302.models.stream; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import seng302.models.Yacht; -import seng302.models.mark.GateMark; -import seng302.models.mark.Mark; -import seng302.models.mark.MarkType; -import seng302.models.mark.SingleMark; - -/** - * Class to create an XML object from the XML Packet Messages. - * - * Example usage: - * - * Document doc; // some xml document - * Integer xmlMessageType; // an Integer of value 5, 6, 7 - * - * xmlP = new XMLParser(doc, xmlMessageType); - * RegattaXMLObject rXmlObj = xmlP.createRegattaXML(); // creates a regattaXML object. - */ -public class XMLParser { - - private Document xmlDoc; - - private RaceXMLObject raceXML; - private RegattaXMLObject regattaXML; - private BoatXMLObject boatXML; - - public XMLParser() { - } - - /** - * Constructor for XMLParser - * - * @param doc Document to create XML object. - * @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7). - */ - public void constructXML(Document doc, Integer messageType) { - this.xmlDoc = doc; - switch (messageType) { - case 5: - regattaXML = new RegattaXMLObject(this.xmlDoc); - break; - case 6: - raceXML = new RaceXMLObject(this.xmlDoc); - break; - case 7: - boatXML = new BoatXMLObject(this.xmlDoc); - break; - } - } - - public RaceXMLObject getRaceXML() { - return raceXML; - } - - public RegattaXMLObject getRegattaXML() { - return regattaXML; - } - - public BoatXMLObject getBoatXML() { - return boatXML; - } - - - /** - * Returns the text content of a given child element tag, assuming it exists, as an Integer. - * - * @param ele Document Element with child elements. - * @param tag Tag to find in document elements child elements. - * @return Text content from tag if found, null otherwise. - */ - private static Integer getElementInt(Element ele, String tag) { - NodeList tagList = ele.getElementsByTagName(tag); - if (tagList.getLength() > 0) { - return Integer.parseInt(tagList.item(0).getTextContent()); - } else { - return null; - } - } - - /** - * Returns the text content of a given child element tag, assuming it exists, as an String. - * - * @param ele Document Element with child elements. - * @param tag Tag to find in document elements child elements. - * @return Text content from tag if found, null otherwise. - */ - private static String getElementString(Element ele, String tag) { - NodeList tagList = ele.getElementsByTagName(tag); - if (tagList.getLength() > 0) { - return tagList.item(0).getTextContent(); - } else { - return null; - } - } - - /** - * Returns the text content of a given child element tag, assuming it exists, as a Double. - * - * @param ele Document Element with child elements. - * @param tag Tag to find in document elements child elements. - * @return Text content from tag if found, null otherwise. - */ - private static Double getElementDouble(Element ele, String tag) { - NodeList tagList = ele.getElementsByTagName(tag); - if (tagList.getLength() > 0) { - return Double.parseDouble(tagList.item(0).getTextContent()); - } else { - return null; - } - } - - /** - * Returns the text content of an attribute of a given Node, assuming it exists, as a String. - * - * @param n A node object that should have some attributes - * @param attr The attribute you want to get from the given node. - * @return The String representation of the text content of an attribute in the given node, else - * returns null. - */ - private static String getNodeAttributeString(Node n, String attr) { - Node attrItem = n.getAttributes().getNamedItem(attr); - if (attrItem != null) { - return attrItem.getTextContent(); - } else { - return null; - } - } - - /** - * Returns the text content of an attribute of a given Node, assuming it exists, as an Integer. - * - * @param n A node object that should have some attributes - * @param attr The attribute you want to get from the given node. - * @return The Integer representation of the text content of an attribute in the given node, - * else returns null. - */ - private static Integer getNodeAttributeInt(Node n, String attr) { - Node attrItem = n.getAttributes().getNamedItem(attr); - if (attrItem != null) { - return Integer.parseInt(attrItem.getTextContent()); - } else { - return null; - } - } - - /** - * Returns the text content of an attribute of a given Node, assuming it exists, as a Double. - * - * @param n A node object that should have some attributes - * @param attr The attribute you want to get from the given node. - * @return The Double representation of the text content of an attribute in the given node, else - * returns null. - */ - private static Double getNodeAttributeDouble(Node n, String attr) { - Node attrItem = n.getAttributes().getNamedItem(attr); - if (attrItem != null) { - return Double.parseDouble(attrItem.getTextContent()); - } else { - return null; - } - } - - public class RegattaXMLObject { - - //Regatta Info - private Integer regattaID; - private String regattaName; - private String courseName; - private Double centralLat; - private Double centralLng; - private Integer utcOffset; - - /** - * Constructor for a RegattaXMLObject. - * Takes the information from a Document object and creates a more usable format. - * - * @param doc XML Document Object - */ - RegattaXMLObject(Document doc) { - Element docEle = doc.getDocumentElement(); - - this.regattaID = getElementInt(docEle, "RegattaID"); - this.regattaName = getElementString(docEle, "RegattaName"); - this.courseName = getElementString(docEle, "CourseName"); - this.centralLat = getElementDouble(docEle, "CentralLatitude"); - this.centralLng = getElementDouble(docEle, "CentralLongitude"); - this.utcOffset = getElementInt(docEle, "UtcOffset"); - } - - public Integer getRegattaID() { - return regattaID; - } - - public String getRegattaName() { - return regattaName; - } - - public String getCourseName() { - return courseName; - } - - public Double getCentralLat() { - return centralLat; - } - - public Double getCentralLng() { - return centralLng; - } - - public Integer getUtcOffset() { - return utcOffset; - } - - } - - public class RaceXMLObject { - - // Race Info - private Integer raceID; - private String raceType; - private String creationTimeDate; // XML Creation Time - - //Race Start Details - private String raceStartTime; - private Boolean postponeStatus; - - //Non atomic race attributes - private ArrayList participants; - private ArrayList allMarks; - private ArrayList nonDuplicateMarks; - private ArrayList compoundMarkSequence; - private ArrayList courseLimit; - - // ensures there's no duplicate marks. - private List seenSourceIDs = new ArrayList(); - - /** - * Constructor for a RaceXMLObject. - * Takes the information from a Document object and creates a more usable format. - * - * @param doc XML Document Object - */ - RaceXMLObject(Document doc) { - Element docEle = doc.getDocumentElement(); - - //Atomic and Semi-Atomic Elements - this.raceID = getElementInt(docEle, "RaceID"); - this.raceType = getElementString(docEle, "RaceType"); - this.creationTimeDate = getElementString(docEle, "CreationTimeDate"); - - Node raceStart = docEle.getElementsByTagName("RaceStartTime").item(0); - this.raceStartTime = getNodeAttributeString(raceStart, "Start"); - this.postponeStatus = Boolean - .parseBoolean(getNodeAttributeString(raceStart, "Postpone")); - - //Participants - participants = new ArrayList<>(); - - NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes(); - for (int i = 0; i < pList.getLength(); i++) { - Node pNode = pList.item(i); - String entry; - if (pNode.getNodeName().equals("Yacht")) { - Integer sourceID = getNodeAttributeInt(pNode, "SourceID"); - - if (pNode.getAttributes().getLength() == 2) { - entry = getNodeAttributeString(pNode, "Entry"); - } else { - entry = null; - } - - Participant pa = new Participant(sourceID, entry); - participants.add(pa); - } - } - - //Course - allMarks = new ArrayList<>(); - nonDuplicateMarks = new ArrayList<>(); - createCompoundMarks(docEle); - - //Course Mark Sequence - compoundMarkSequence = new ArrayList<>(); - - NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0) - .getChildNodes(); - for (int i = 0; i < cornerList.getLength(); i++) { - Node cornerNode = cornerList.item(i); - if (cornerNode.getNodeName().equals("Corner")) { - Corner corner = new Corner(cornerNode); - compoundMarkSequence.add(corner); - } - } - - //Course Limits - courseLimit = new ArrayList<>(); - - NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes(); - for (int i = 0; i < limitList.getLength(); i++) { - Node limitNode = limitList.item(i); - if (limitNode.getNodeName().equals("Limit")) { - Limit limit = new Limit(limitNode); - courseLimit.add(limit); - } - } - } - - - private void createCompoundMarks(Element docEle) { - - NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes(); - for (int i = 0; i < cMarkList.getLength(); i++) { - Node cMarkNode = cMarkList.item(i); - if (cMarkNode.getNodeName().equals("CompoundMark")) { - createAndAddMark(cMarkNode); - } - } - } - - - private void createAndAddMark(Node compoundMark) { - - Boolean markSeen = false; - List marksList = new ArrayList<>(); - Integer compoundMarkID = getNodeAttributeInt(compoundMark, "CompoundMarkID"); - String cMarkName = getNodeAttributeString(compoundMark, "Name"); - - NodeList childMarks = compoundMark.getChildNodes(); - - for (int i = 0; i < childMarks.getLength(); i++) { - Node markNode = childMarks.item(i); - if (markNode.getNodeName().equals("Mark")) { - - Integer sourceID = getNodeAttributeInt(markNode, "SourceID"); - String markName = getNodeAttributeString(markNode, "Name"); - Double targetLat = getNodeAttributeDouble(markNode, "TargetLat"); - Double targetLng = getNodeAttributeDouble(markNode, "TargetLng"); - - SingleMark mark = new SingleMark(markName, targetLat, targetLng, sourceID, compoundMarkID); - marksList.add(mark); - } - } - - for (SingleMark mark : marksList) { - if (seenSourceIDs.contains(mark.getId())) { - markSeen = true; - } else { - seenSourceIDs.add(mark.getId()); - } - } - - - if (marksList.size() == 1) { - if (!markSeen) { - nonDuplicateMarks.add(marksList.get(0)); - } - allMarks.add(marksList.get(0)); - } else if (marksList.size() == 2) { - GateMark thisGateMark = new GateMark(cMarkName, MarkType.OPEN_GATE, marksList.get(0), - marksList.get(1), marksList.get(0).getLatitude(), - marksList.get(0).getLongitude(), compoundMarkID); - if(!markSeen) { - nonDuplicateMarks.add(thisGateMark); - } - allMarks.add(thisGateMark); - } - - } - - public Integer getRaceID() { - return raceID; - } - - public String getRaceType() { - return raceType; - } - - public String getCreationTimeDate() { - return creationTimeDate; - } - - public String getRaceStartTime() { - return raceStartTime; - } - - public Boolean getPostponeStatus() { - return postponeStatus; - } - - public ArrayList getParticipants() { - return participants; - } - - /** - * @return Returns ALL compound marks as stated in the RaceXML (INCLUDING DUPLICATE MARKS) - */ - public List getAllCompoundMarks() { - return allMarks; - } - - /** - * @return Returns Marks from the race XML without any duplicates - */ - public List getNonDupCompoundMarks() { - return nonDuplicateMarks; - } - - public ArrayList getCompoundMarkSequence() { - return compoundMarkSequence; - } - - public ArrayList getCourseLimit() { - return courseLimit; - } - - public class Participant { - - Integer sourceID; - String entry; - - Participant(Integer sourceID, String entry) { - this.sourceID = sourceID; - this.entry = entry; - } - - public Integer getsourceID() { - return sourceID; - } - - public String getEntry() { - return entry; - } - } - - public class Corner { - - private Integer seqID; - private Integer compoundMarkID; - private String rounding; - private Integer zoneSize; - - Corner(Node cornerNode) { - this.seqID = getNodeAttributeInt(cornerNode, "SeqID"); - this.compoundMarkID = getNodeAttributeInt(cornerNode, "CompoundMarkID"); - this.rounding = getNodeAttributeString(cornerNode, "Rounding"); - this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize"); - } - - public Integer getSeqID() { - return seqID; - } - - public Integer getCompoundMarkID() { - return compoundMarkID; - } - - public String getRounding() { - return rounding; - } - - public Integer getZoneSize() { - return zoneSize; - } - } - - public class Limit { - - private Integer seqID; - private Double lat; - private Double lng; - - Limit(Node limitNode) { - this.seqID = getNodeAttributeInt(limitNode, "SeqID"); - this.lat = getNodeAttributeDouble(limitNode, "Lat"); - this.lng = getNodeAttributeDouble(limitNode, "Lon"); - } - - public Integer getSeqID() { - return seqID; - } - - public Double getLat() { - return lat; - } - - public Double getLng() { - return lng; - } - } - - } - - public class BoatXMLObject { - - private String lastModified; - private Integer version; - - //Settings for the boat type in the race. This may end up having to be reworked if multiple boat types compete. - private String boatType; - private Double boatLength; - private Double hullLength; - private Double markZoneSize; - private Double courseZoneSize; - private ArrayList zoneLimits;// will only contain 5 elements. Limits 1-5 - - //Boats - ArrayList boats; - //Competing boats - Map competingBoats = new HashMap<>(); - - /** - * Constructor for a BoatXMLObject. - * Takes the information from a Document object and creates a more usable format. - * - * @param doc XML Document Object - */ - BoatXMLObject(Document doc) { - - Element docEle = doc.getDocumentElement(); - - this.lastModified = getElementString(docEle, "Modified"); - this.version = getElementInt(docEle, "Version"); - - NodeList settingsList = docEle.getElementsByTagName("Settings").item(0).getChildNodes(); - this.boatType = getNodeAttributeString(settingsList.item(1), "Type"); - this.boatLength = getNodeAttributeDouble(settingsList.item(3), "BoatLength"); - this.hullLength = getNodeAttributeDouble(settingsList.item(3), "HullLength"); - this.markZoneSize = getNodeAttributeDouble(settingsList.item(5), "MarkZoneSize"); - this.courseZoneSize = getNodeAttributeDouble(settingsList.item(5), "CourseZoneSize"); - - Node zoneLimitsList = settingsList.item(7); - this.zoneLimits = new ArrayList<>(); - for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) { - String tag = String.format("Limit%d", i + 1); - this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag)); - } - - this.boats = new ArrayList<>(); - NodeList boatsList = docEle.getElementsByTagName("Boats").item(0).getChildNodes(); - for (int i = 0; i < boatsList.getLength(); i++) { - Node currentBoat = boatsList.item(i); - if (currentBoat.getNodeName().equals("Boat")) { -// Boat boat = new Boat(currentBoat); - Yacht boat = new Yacht(getNodeAttributeString(currentBoat, "Type"), - getNodeAttributeInt(currentBoat, "SourceID"), - getNodeAttributeString(currentBoat, "HullNum"), - getNodeAttributeString(currentBoat, "ShortName"), - getNodeAttributeString(currentBoat, "BoatName"), - getNodeAttributeString(currentBoat, "Country")); - this.boats.add(boat); - if (boat.getBoatType().equals("Yacht")) { - competingBoats.put(boat.getSourceId(), boat); - } - } - } - - } - - public String getLastModified() { - return lastModified; - } - - public Integer getVersion() { - return version; - } - - public String getBoatType() { - return boatType; - } - - public Double getBoatLength() { - return boatLength; - } - - public Double getHullLength() { - return hullLength; - } - - public Double getMarkZoneSize() { - return markZoneSize; - } - - public Double getCourseZoneSize() { - return courseZoneSize; - } - - public ArrayList getZoneLimits() { - return zoneLimits; - } - - public ArrayList getBoats() { - return boats; - } - - public Map getCompetingBoats() { - return competingBoats; - } - - } - -} \ No newline at end of file diff --git a/src/main/java/seng302/models/stream/packets/BoatPositionPacket.java b/src/main/java/seng302/models/stream/packets/BoatPositionPacket.java deleted file mode 100644 index 859223e0..00000000 --- a/src/main/java/seng302/models/stream/packets/BoatPositionPacket.java +++ /dev/null @@ -1,39 +0,0 @@ -package seng302.models.stream.packets; - -public class BoatPositionPacket { - private long boatId; - private long timeValid; - private double lat; - private double lon; - private double heading; - private double groundSpeed; - - public BoatPositionPacket(long boatId, long timeValid, double lat, double lon, double heading, double groundSpeed) { - this.boatId = boatId; - this.timeValid = timeValid; - this.lat = lat; - this.lon = lon; - this.heading = heading; - this.groundSpeed = groundSpeed; - } - - public long getTimeValid() { - return timeValid; - } - - public double getLat() { - return lat; - } - - public double getLon() { - return lon; - } - - public double getHeading() { - return heading; - } - - public double getGroundSpeed() { - return groundSpeed; - } -} diff --git a/src/main/java/seng302/server/simulator/mark/CompoundMark.java b/src/main/java/seng302/server/simulator/mark/CompoundMark.java deleted file mode 100644 index 489a4a12..00000000 --- a/src/main/java/seng302/server/simulator/mark/CompoundMark.java +++ /dev/null @@ -1,70 +0,0 @@ -package seng302.server.simulator.mark; - -public class CompoundMark { - - private int markID; - private String name; - - private Mark mark1; - private Mark mark2; - - public CompoundMark(int markID, String name) { - this.markID = markID; - this.name = name; - } - - public void addMark(int seqId, Mark mark) { - if (seqId == 1) { - setMark1(mark); - } else if (seqId == 2) { - setMark2(mark); - } - } - - /** - * Prints out compoundMark's info and its marks, good for testing - * @return a string showing its details - */ - @Override - public String toString(){ - if (mark2 == null) - return String.format("CompoundMark: %d (%s), [%s]", - markID, name, mark1.toString()); - return String.format("CompoundMark: %d (%s), [%s; %s]", - markID, name, mark1.toString(), mark2.toString()); - } - - public int getMarkID() { - return markID; - } - - public void setMarkID(int markID) { - this.markID = markID; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Mark getMark1() { - return mark1; - } - - public void setMark1(Mark mark1) { - this.mark1 = mark1; - mark1.setSeqID(1); - } - - public Mark getMark2() { - return mark2; - } - - public void setMark2(Mark mark2) { - this.mark2 = mark2; - mark2.setSeqID(2); - } -} diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 0e17ff1a..54b0484a 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -1,6 +1,7 @@ package seng302.utilities; import javafx.geometry.Point2D; +import seng302.model.GeoPoint; public class GeoUtility { @@ -43,16 +44,31 @@ public class GeoUtility { * 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); + - Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math.cos(dLon); - double bearing = Math.toDegrees(Math.atan2(y, x)); - - return (bearing + 360.0) % 360.0; + return Math.atan2(y, x); } /** diff --git a/src/main/java/seng302/utilities/StreamParser.java b/src/main/java/seng302/utilities/StreamParser.java new file mode 100644 index 00000000..0f4c48c0 --- /dev/null +++ b/src/main/java/seng302/utilities/StreamParser.java @@ -0,0 +1,432 @@ +package seng302.utilities; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import seng302.model.stream.packets.PacketType; +import seng302.model.stream.packets.StreamPacket; +import seng302.model.stream.parser.MarkRoundingData; +import seng302.model.stream.parser.PositionUpdateData; +import seng302.model.stream.parser.PositionUpdateData.DeviceType; +import seng302.model.stream.parser.RaceStartData; +import seng302.model.stream.parser.RaceStatusData; + +/** + * StreamParser is a utilities class for taking byte data, formatted according to the AC35 + * streaming protocol, and parsing it into basic data types or collections. + * + * Created by kre39 on 23/04/17. + */ +public class StreamParser { + + /** + * Extracts and returns the seq num used in the heartbeat packet. + * + * @param packet Packet parsed in to use the payload + * @return the packet sequence number if the packet is of type HEARTBEAT, null otherwise. + */ + public static Long extractHeartBeat(StreamPacket packet) { + if (packet.getType() != PacketType.HEARTBEAT) + return null; + long heartbeat = bytesToLong(packet.getPayload()); + System.out.println("heartbeat = " + heartbeat); + return heartbeat; + } + + /** + * Extracts the useful race status data from race status type packets. This method will also + * print to the console the current state of the race (if it has started/finished or is about to + * start), along side this it'll also display the amount of time since the race has started or + * time till it starts + * + * @param packet Packet parsed in to use the payload + * @return null if the packet type is not RACE_STATUS, otherwise an instance of RaceStatusData + * containing the parsed packet data. + */ + public static RaceStatusData extractRaceStatus(StreamPacket packet) { + if (packet.getType() != PacketType.RACE_STATUS) + return null; + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); + long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11)); + int raceStatus = payload[11]; + long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18)); + long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); + long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); + +// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); +// currentTime = format.format((new Date(currentTime))) + + RaceStatusData data = new RaceStatusData( + windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime + ); + + +// long timeTillStart = +// ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000; +// +// if (timeTillStart > 0) { +// timeSinceStart = timeTillStart; +// } else { +// if (raceStatus == 4 || raceStatus == 8) { +// raceFinished = true; +// raceStarted = false; +// } else if (!raceStarted) { +// raceStarted = true; +// raceFinished = false; +// } +// timeSinceStart = timeTillStart; +// } +// + +// + int noBoats = payload[22]; + int raceType = payload[23]; + long boatID, estTimeAtNextMark, estTimeAtFinish; + int leg, boatStatus; + for (int i = 0; i < noBoats; i++) { + boatID = bytesToLong( + Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20))); + boatStatus = (int) payload[28 + (i * 20)]; + +// setBoatLegPosition(boat, (int) payload[29 + (i * 20)]); +// boat.setPenaltiesAwarded((int) payload[30 + (i * 20)]); +// boat.setPenaltiesServed((int) payload[31 + (i * 20)]); + estTimeAtNextMark = bytesToLong( + Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20))); +// boat.setEstimateTimeTillNextMark(estTimeAtNextMark); + estTimeAtFinish = bytesToLong( + Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20))); + leg = (int) payload[29 + (i * 20)]; +// boat.setEstimateTimeAtFinish(estTimeAtFinish); + data.addBoatData(boatID, estTimeAtNextMark, estTimeAtFinish, leg, boatStatus); + } + return data; + } + +// private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){ +// Integer placing = 1; +// if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) { +// for (Yacht boat : boats.values()) { +// if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){ +// placing += 1; +// } +// } +// updatingBoat.setPosition(placing.toString()); +// updatingBoat.setLegNumber(leg); +// boatsPos.putIfAbsent(placing, updatingBoat); +// boatsPos.replace(placing, updatingBoat); +// } else if(updatingBoat.getLegNumber() == null){ +// updatingBoat.setPosition("1"); +// updatingBoat.setLegNumber(leg); +// } +// } + + /** + * Parses and returns the text from a StreamPacket containing text data for display. + * + * @param packet Packet parsed in to use the payload + * @return A list containing all display message text. Is null if the packet is not of type + * DISPLAY_TEXT_MESSAGE. + */ + public static List extractDisplayMessage(StreamPacket packet) { + if (packet.getType() != PacketType.DISPLAY_TEXT_MESSAGE) + return null; + List message = new ArrayList<>(); + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + int numOfLines = payload[3]; + int totalLen = 0; + for (int i = 0; i < numOfLines; i++) { + int lineNum = payload[4 + totalLen]; + int textLength = payload[5 + totalLen]; + byte[] messageTextBytes = Arrays + .copyOfRange(payload, 6 + totalLen, 6 + textLength + totalLen); + message.add(new String(messageTextBytes)); + totalLen += 2 + textLength; + } + return message; + } + + /** + * Parses and returns an XMLParser containing XML data sent in the given StreamPacket. XML data + * can be for races, boats or the regatta. + * + * @param packet Packet parsed in to use the payload + * @return XMLParse containing xmldata. Returns null if the StreamPacket is not of type + * XML_MESSAGE. + */ + public static Document extractXmlMessage(StreamPacket packet) { + if ( packet.getType() != PacketType.RACE_XML && + packet.getType() != PacketType.REGATTA_XML && + packet.getType() != PacketType.BOAT_XML ) + return null; + + byte[] payload = packet.getPayload(); + int messageType = payload[9]; + long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14)); + String xmlMessage = new String( + (Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim(); + + //Create XML document Object + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db; + Document doc = null; + try { + db = dbf.newDocumentBuilder(); + doc = db.parse(new InputSource(new StringReader(xmlMessage))); + } catch (ParserConfigurationException | IOException | SAXException e) { + e.printStackTrace(); + } + return doc; + } + + /** + * Extracts the race start status from the packet and returns it as a long array. + * + * @param packet Packet parsed in to use the payload + * @return An array of form [raceID, raceStartTime, notificationType, timeStamp] or null if + * the packet type is not of RACE_START_STATUS. + */ + public static RaceStartData extractRaceStartStatus(StreamPacket packet) { + if (packet.getType() != PacketType.RACE_START_STATUS) { + return null; + } + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); + long raceStartTime = bytesToLong(Arrays.copyOfRange(payload, 9, 15)); + long raceId = bytesToLong(Arrays.copyOfRange(payload, 15, 19)); + int notificationType = payload[19]; + return new RaceStartData(raceId, raceStartTime, notificationType, timeStamp); + } + + /** + * Parses the the byte array in a StreamPacket for yacht events to retrieve the necessary info + * and returns it a an array of longs. + * + * @param packet Packet parsed in to use the payload + * @return the event data in the form [boatID, incidentID, eventID, timeStamp]. Returns null if + * the packet is not of type YACHT_EVENT_CODE. + */ + public static long[] extractYachtEventCode(StreamPacket packet) { + if (packet.getType() != PacketType.YACHT_EVENT_CODE) + return null; + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); + long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13)); + long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17)); + long incidentId = bytesToLong(Arrays.copyOfRange(payload, 17, 21)); + int eventId = payload[21]; + return new long[] {subjectId, incidentId, eventId, timeStamp}; + } + + /** + * Parses data from a StreamPacket for yacht actions and returns it in a long array. + * + * @param packet Packet parsed in to use the payload + * @return long array of packet data in the form [subjectID, incidentID, eventID, timeStamp]. + * Returns null if the packet is not of type YACHT_ACTION_CODE. + */ + public static long[] extractYachtActionCode(StreamPacket packet) { + if (packet.getType() != PacketType.YACHT_ACTION_CODE) + return null; + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); + long subjectId = bytesToLong(Arrays.copyOfRange(payload, 9, 13)); + long incidentId = bytesToLong(Arrays.copyOfRange(payload, 13, 17)); + int eventId = payload[17]; + return new long[] {subjectId, incidentId, eventId, timeStamp}; + } + + /** + * Strips the message from the chatter text type packets. + * + * @param packet Packet parsed in to use the payload + * @return Chatter text message as a string. Returns null if the packet is not of type + * CHATTER_TEXT. + */ + public static String extractChatterText(StreamPacket packet) { + if (packet.getType() != PacketType.CHATTER_TEXT) + return null; + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + int messageType = payload[1]; + int length = payload[2]; + return new String(Arrays.copyOfRange(payload, 3, 3 + length)); + } + + /** + * Takes the data from a bot location stream packet and parses the id, timeValid, lat, lon, + * heading and groundspeed into a BoatPositionPacket which is returned. + * + * @param packet Packet parsed in to use the payload + * @return BoatPositionPacket containing important boat information. Returns null if the packet + * is not of type BOAT_LOCATION. + */ + public static PositionUpdateData extractBoatLocation(StreamPacket packet) { + if (packet.getType() != PacketType.BOAT_LOCATION) + return null; + byte[] payload = packet.getPayload(); + int deviceType = (int) payload[15]; + long timeValid = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); + long seq = bytesToLong(Arrays.copyOfRange(payload, 11, 15)); + long boatId = bytesToLong(Arrays.copyOfRange(payload, 7, 11)); + long rawLat = bytesToLong(Arrays.copyOfRange(payload, 16, 20)); + long rawLon = bytesToLong(Arrays.copyOfRange(payload, 20, 24)); + //Converts the double to a usable lat/lon + double lat = ((180d * (double) rawLat) / Math.pow(2, 31)); + double lon = ((180d * (double) rawLon) / Math.pow(2, 31)); + double heading = bytesToLong(Arrays.copyOfRange(payload, 28, 30)); + heading = 360.0 / 0xffff * heading; //Convert to degrees. + double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0; + + DeviceType type; + if (deviceType == 1) + type = DeviceType.YACHT_TYPE; + else + type = DeviceType.MARK_TYPE; + + return new PositionUpdateData((int) boatId, type, lat, lon, heading, groundSpeed); + } + + /** + * Processes a stream packet for a mark rounding and returns the boatID, markID and timestamp. + * + * @param packet The packet containing the payload + * @return an array containing longs. The values are [boatID, markID, timeStamp]. Returns null + * if packet is not of type MARK_ROUNDING. + */ + public static MarkRoundingData extractMarkRounding(StreamPacket packet) { + if (packet.getType() != PacketType.MARK_ROUNDING) + return null; + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); + long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13)); + long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17)); + int boatStatus = payload[17]; + int roundingSide = payload[18]; + int markType = payload[19]; + int markId = payload[20]; + + return new MarkRoundingData((int) subjectId, markId, roundingSide, timeStamp); + } + + /** + * Returns a list containing the string value of data within the given stream packet for + * course wind. + * + * @param packet The packet containing the payload + * @return the string values of the wind packet. Returns null if the packet is not of type + * COURSE_WIND. + */ + public static List extractCourseWind(StreamPacket packet) { + if (packet.getType() != PacketType.COURSE_WIND) + return null; + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + int selectedWindId = payload[1]; + int loopCount = payload[2]; + List windInfo = new ArrayList<>(); + for (int i = 0; i < loopCount; i++) { + String wind = "WindId: " + payload[3 + (20 * i)]; + wind += + "\nTime: " + bytesToLong(Arrays.copyOfRange(payload, 4 + (20 * i), 10 + (20 * i))); + wind += "\nRaceId: " + bytesToLong( + Arrays.copyOfRange(payload, 10 + (20 * i), 14 + (20 * i))); + wind += "\nWindDirection: " + bytesToLong( + Arrays.copyOfRange(payload, 14 + (20 * i), 16 + (20 * i))); + wind += "\nWindSpeed: " + bytesToLong( + Arrays.copyOfRange(payload, 16 + (20 * i), 18 + (20 * i))); + wind += "\nBestUpWindAngle: " + bytesToLong( + Arrays.copyOfRange(payload, 18 + (20 * i), 20 + (20 * i))); + wind += "\nBestDownWindAngle: " + bytesToLong( + Arrays.copyOfRange(payload, 20 + (20 * i), 22 + (20 * i))); + wind += "\nFlags: " + String + .format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF)) + .replace(' ', '0'); + windInfo.add(wind); + } + return windInfo; + } + + /** + * Returns the parsed data from a StreamPacket for average wind data. + * + * @param packet The packet containing the payload + * @return The wind data in the form + * [rawPeriod, rawSamplePeriod, period2, speed2, period3, speed3, period4, speed4, timestamp] + * or null if the packet is not of type AVG_WIND. + */ + public static long[] extractAvgWind(StreamPacket packet) { + if (packet.getType() != PacketType.AVG_WIND) + return null; + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7)); + long rawPeriod = bytesToLong(Arrays.copyOfRange(payload, 7, 9)); + long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload, 9, 11)); + long period2 = bytesToLong(Arrays.copyOfRange(payload, 11, 13)); + long speed2 = bytesToLong(Arrays.copyOfRange(payload, 13, 15)); + long period3 = bytesToLong(Arrays.copyOfRange(payload, 15, 17)); + long speed3 = bytesToLong(Arrays.copyOfRange(payload, 17, 19)); + long period4 = bytesToLong(Arrays.copyOfRange(payload, 19, 21)); + long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23)); + return new long[] { + rawPeriod, rawSamplePeriod, period2, speed2, period3, speed3, period4, speed4, timeStamp + }; + } + + + public static void extractBoatAction(StreamPacket packet) { + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1)); + if (actionType == 1) { + System.out.println("VMG"); + } else if (actionType == 2) { + System.out.println("SAILS IN"); + } else if (actionType == 3) { + System.out.println("SAILS OUT"); + } else if (actionType == 4) { + System.out.println("TACK/GYBE"); + } else if (actionType == 5) { + System.out.println("UPWIND"); + } else if (actionType == 6) { + System.out.println("DOWNWIND"); + } + } + + /** + * takes an array of up to 7 bytes and returns a positive + * long constructed from the input bytes + * + * @param bytes the byte array to conver to Long + * @return a positive long if there is less than 7 bytes -1 otherwise + */ + public 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; + } +} + diff --git a/src/main/java/seng302/models/xml/XMLGenerator.java b/src/main/java/seng302/utilities/XMLGenerator.java similarity index 93% rename from src/main/java/seng302/models/xml/XMLGenerator.java rename to src/main/java/seng302/utilities/XMLGenerator.java index 04a5b5fb..3dc99694 100644 --- a/src/main/java/seng302/models/xml/XMLGenerator.java +++ b/src/main/java/seng302/utilities/XMLGenerator.java @@ -1,13 +1,15 @@ -package seng302.models.xml; +package seng302.utilities; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; -import org.apache.commons.io.IOUtils; -import seng302.server.messages.XMLMessageSubType; - -import java.io.*; -import java.net.URISyntaxException; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import seng302.model.stream.xml.generator.Race; +import seng302.model.stream.xml.generator.Regatta; +import seng302.gameServer.server.messages.XMLMessageSubType; /** * An XML generator to generate the Race, Boat, and Regatta XML dynamically diff --git a/src/main/java/seng302/utilities/XMLParser.java b/src/main/java/seng302/utilities/XMLParser.java new file mode 100644 index 00000000..c231535d --- /dev/null +++ b/src/main/java/seng302/utilities/XMLParser.java @@ -0,0 +1,290 @@ +package seng302.utilities; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import seng302.model.Limit; +import seng302.model.Yacht; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Corner; +import seng302.model.mark.Mark; +import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.model.stream.xml.parser.RegattaXMLData; + +/** + * Utilities for parsing XML documents + */ +public class XMLParser { + + /** + * Returns the text content of a given child element tag, assuming it exists, as an Integer. + * + * @param ele Document Element with child elements. + * @param tag Tag to find in document elements child elements. + * @return Text content from tag if found, null otherwise. + */ + private static Integer getElementInt(Element ele, String tag) { + NodeList tagList = ele.getElementsByTagName(tag); + if (tagList.getLength() > 0) { + return Integer.parseInt(tagList.item(0).getTextContent()); + } else { + return null; + } + } + + /** + * Returns the text content of a given child element tag, assuming it exists, as an String. + * + * @param ele Document Element with child elements. + * @param tag Tag to find in document elements child elements. + * @return Text content from tag if found, null otherwise. + */ + private static String getElementString(Element ele, String tag) { + NodeList tagList = ele.getElementsByTagName(tag); + if (tagList.getLength() > 0) { + return tagList.item(0).getTextContent(); + } else { + return null; + } + } + + /** + * Returns the text content of a given child element tag, assuming it exists, as a Double. + * + * @param ele Document Element with child elements. + * @param tag Tag to find in document elements child elements. + * @return Text content from tag if found, null otherwise. + */ + private static Double getElementDouble(Element ele, String tag) { + NodeList tagList = ele.getElementsByTagName(tag); + if (tagList.getLength() > 0) { + return Double.parseDouble(tagList.item(0).getTextContent()); + } else { + return null; + } + } + + /** + * Returns the text content of an attribute of a given Node, assuming it exists, as a String. + * + * @param n A node object that should have some attributes + * @param attr The attribute you want to get from the given node. + * @return The String representation of the text content of an attribute in the given node, else + * returns null. + */ + private static String getNodeAttributeString(Node n, String attr) { + Node attrItem = n.getAttributes().getNamedItem(attr); + if (attrItem != null) { + return attrItem.getTextContent(); + } else { + return null; + } + } + + /** + * Returns the text content of an attribute of a given Node, assuming it exists, as an Integer. + * + * @param n A node object that should have some attributes + * @param attr The attribute you want to get from the given node. + * @return The Integer representation of the text content of an attribute in the given node, + * else returns null. + */ + private static Integer getNodeAttributeInt(Node n, String attr) { + Node attrItem = n.getAttributes().getNamedItem(attr); + if (attrItem != null) { + return Integer.parseInt(attrItem.getTextContent()); + } else { + return null; + } + } + + /** + * Returns the text content of an attribute of a given Node, assuming it exists, as a Double. + * + * @param n A node object that should have some attributes + * @param attr The attribute you want to get from the given node. + * @return The Double representation of the text content of an attribute in the given node, else + * returns null. + */ + private static Double getNodeAttributeDouble(Node n, String attr) { + Node attrItem = n.getAttributes().getNamedItem(attr); + if (attrItem != null) { + return Double.parseDouble(attrItem.getTextContent()); + } else { + return null; + } + } + + /** + * Produces a mapping of boat sourceIDS to boat objects created from the given xml document. + * @param doc XML Document Object + * @return Mapping of sourceIds to Boats. + */ + public static Map parseBoats(Document doc){ + Map competingBoats = new HashMap<>(); + + Element docEle = doc.getDocumentElement(); + + NodeList boatsList = docEle.getElementsByTagName("Boats").item(0).getChildNodes(); + for (int i = 0; i < boatsList.getLength(); i++) { + Node currentBoat = boatsList.item(i); + if (currentBoat.getNodeName().equals("Boat")) { +// Boat boat = new Boat(currentBoat); + Yacht yacht = new Yacht(XMLParser.getNodeAttributeString(currentBoat, "Type"), + XMLParser.getNodeAttributeInt(currentBoat, "SourceID"), + XMLParser.getNodeAttributeString(currentBoat, "HullNum"), + XMLParser.getNodeAttributeString(currentBoat, "ShortName"), + XMLParser.getNodeAttributeString(currentBoat, "BoatName"), + XMLParser.getNodeAttributeString(currentBoat, "Country")); + if (yacht.getBoatType().equals("Yacht")) { + competingBoats.put(yacht.getSourceId(), yacht); + } + } + } + return competingBoats; + } + + /** + * Returns an object containing the data extracted from the given xml formatted document + * + * @param doc XML Document Object + * @return Object containing regatta data + */ + public static RegattaXMLData parseRegatta(Document doc) { + + Element docEle = doc.getDocumentElement(); + Integer regattaID = XMLParser.getElementInt(docEle, "RegattaID"); + String regattaName = XMLParser.getElementString(docEle, "RegattaName"); + String courseName = XMLParser.getElementString(docEle, "CourseName"); + Double centralLat = XMLParser.getElementDouble(docEle, "CentralLatitude"); + Double centralLng = XMLParser.getElementDouble(docEle, "CentralLongitude"); + Integer utcOffset = XMLParser.getElementInt(docEle, "UtcOffset"); + return new RegattaXMLData( + regattaID, regattaName, courseName, centralLat, centralLng, utcOffset + ); + } + + /** + * Returns an object containing the data extracted from the given xml formatted document + * + * @param doc XML document + * @return object containing race data + */ + public static RaceXMLData parseRace(Document doc) { + Element docEle = doc.getDocumentElement(); + return new RaceXMLData( + extractParticpantIDs(docEle), + extractCompoundMarks(docEle), + extractMarkOrder(docEle), + extractCourseLimit(docEle) + ); + } + + /** + * Extracts course limit data + */ + private static List extractCourseLimit(Element docEle) { + List courseLimit = new ArrayList<>(); + NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes(); + for (int i = 0; i < limitList.getLength(); i++) { + Node limitNode = limitList.item(i); + if (limitNode.getNodeName().equals("Limit")) { + courseLimit.add( + new Limit( + XMLParser.getNodeAttributeInt(limitNode, "SeqID"), + XMLParser.getNodeAttributeDouble(limitNode, "Lat"), + XMLParser.getNodeAttributeDouble(limitNode, "Lon") + ) + ); + } + } + return courseLimit; + } + + /** + * Extracts course order data + */ + private static List extractMarkOrder (Element docEle) { + List compoundMarkSequence = new ArrayList<>(); + NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0) + .getChildNodes(); + for (int i = 0; i < cornerList.getLength(); i++) { + Node cornerNode = cornerList.item(i); + if (cornerNode.getNodeName().equals("Corner")) { + compoundMarkSequence.add( + new Corner( + XMLParser.getNodeAttributeInt(cornerNode, "SeqID"), + XMLParser.getNodeAttributeInt(cornerNode, "CompoundMarkID"), + XMLParser.getNodeAttributeString(cornerNode, "Rounding"), + XMLParser.getNodeAttributeInt(cornerNode, "ZoneSize") + ) + ); + } + } + return compoundMarkSequence; + } + + /** + * Extracts course participants data + */ + private static List extractParticpantIDs (Element docEle) { + List boatIDs = new ArrayList<>(); + NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes(); + for (int i = 0; i < pList.getLength(); i++) { + Node pNode = pList.item(i); + if (pNode.getNodeName().equals("Yacht")) { + boatIDs.add(XMLParser.getNodeAttributeInt(pNode, "SourceID")); + } + } + return boatIDs; + } + + /** + * Extracts course mark data + */ + private static List extractCompoundMarks(Element docEle) { + List allMarks = new ArrayList<>(); + NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes(); + CompoundMark cMark; + for (int i = 0; i < cMarkList.getLength(); i++) { + Node cMarkNode = cMarkList.item(i); + if (cMarkNode.getNodeName().equals("CompoundMark")) { + cMark = new CompoundMark( + XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"), + XMLParser.getNodeAttributeString(cMarkNode, "Name") + ); + cMark.addSubMarks(createMarks(cMarkNode)); + allMarks.add(cMark); + } + } + return allMarks; + } + + /** + * Creates marks objects from the given node + */ + private static List createMarks(Node compoundMark) { + List subMarks = new ArrayList<>(); + Integer compoundMarkID = XMLParser.getNodeAttributeInt(compoundMark, "CompoundMarkID"); + String cMarkName = XMLParser.getNodeAttributeString(compoundMark, "Name"); + + NodeList childMarks = compoundMark.getChildNodes(); + for (int i = 0; i < childMarks.getLength(); i++) { + Node markNode = childMarks.item(i); + if (markNode.getNodeName().equals("Mark")) { + Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID"); + String markName = XMLParser.getNodeAttributeString(markNode, "Name"); + Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat"); + Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng"); + Mark mark = new Mark(markName, targetLat, targetLng, sourceID); + subMarks.add(mark); + } + } + return subMarks; + } +} \ No newline at end of file diff --git a/src/main/java/seng302/client/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java similarity index 64% rename from src/main/java/seng302/client/ClientToServerThread.java rename to src/main/java/seng302/visualiser/ClientToServerThread.java index 27dbcd1f..414696c8 100644 --- a/src/main/java/seng302/client/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -1,21 +1,23 @@ -package seng302.client; +package seng302.visualiser; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; -import java.net.UnknownHostException; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.zip.CRC32; import java.util.zip.Checksum; - import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; -import seng302.models.stream.packets.StreamPacket; -import seng302.server.messages.BoatActionMessage; -import seng302.server.messages.Message; +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 @@ -23,18 +25,35 @@ import seng302.server.messages.Message; */ public class ClientToServerThread implements Runnable { + /** + * Functional interface for receiving packets from client socket. + */ + @FunctionalInterface + public interface ClientSocketListener { + void newPacket(); + } + + private class ByteReadException extends Exception { + private ByteReadException(String message) { + super(message); + } + } + private static final int LOG_LEVEL = 1; + private Queue streamPackets = new ConcurrentLinkedQueue<>(); + private List listeners = new ArrayList<>(); private Thread thread; - private Integer ourID; - private Socket socket; private InputStream is; private OutputStream os; - private Boolean updateClient = true; + private int clientId; + +// private Boolean updateClient = true; private ByteArrayOutputStream crcBuffer; + private boolean socketOpen = true; /** * Constructor for ClientToServerThread which takes in ipAddress and portNumber and attempts to @@ -45,19 +64,17 @@ public class ClientToServerThread implements Runnable { * * @param ipAddress a string of ip address to be connected to * @param portNumber an integer port number - * @throws Exception SocketConnection if fail to connect to ip address and port number + * @throws IOException SocketConnection if fail to connect to ip address and port number * combination */ - public ClientToServerThread(String ipAddress, Integer portNumber) throws Exception { + public ClientToServerThread(String ipAddress, Integer portNumber) throws IOException { socket = new Socket(ipAddress, portNumber); is = socket.getInputStream(); os = socket.getOutputStream(); - Integer allocatedID = threeWayHandshake(); if (allocatedID != null) { - ourID = allocatedID; - clientLog("Successful handshake. Allocated ID: " + ourID, 0); - ClientState.setClientSourceId(String.valueOf(ourID)); + clientId = allocatedID; + clientLog("Successful handshake. Allocated ID: " + clientId, 1); } else { clientLog("Unsuccessful handshake", 1); closeSocket(); @@ -90,7 +107,7 @@ public class ClientToServerThread implements Runnable { int sync1; int sync2; // TODO: 14/07/17 wmu16 - Work out how to fix this while loop - while (ClientState.isConnectedToHost()) { + while(socketOpen) { try { crcBuffer = new ByteArrayOutputStream(); sync1 = readByte(); @@ -108,29 +125,32 @@ public class ClientToServerThread implements Runnable { long computedCrc = checksum.getValue(); long packetCrc = Message.bytesToLong(getBytes(4)); if (computedCrc == packetCrc) { - ClientPacketParser - .parsePacket(new StreamPacket(type, payloadLength, timeStamp, payload)); + 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(); + } } else { clientLog("Packet has been dropped", 1); } } - } catch (Exception e) { + } catch (ByteReadException e) { closeSocket(); - Platform.runLater(new Runnable() { - @Override - public void run() { - Alert alert = new Alert(AlertType.ERROR); - alert.setHeaderText("Host has disconnected"); - alert.setContentText("Cannot find Server"); - alert.showAndWait(); - } - }); - clientLog("Disconnected from server", 1); + Platform.runLater(() -> { + Alert alert = new Alert(AlertType.ERROR); + alert.setHeaderText("Host has disconnected"); + alert.setContentText("Cannot find Server"); + alert.showAndWait(); + }); + clientLog(e.getMessage(), 1); return; } +// System.out.println("streamPackets = " + streamPackets.size()); } closeSocket(); - clientLog("Disconnected from server", 0); + clientLog("Closed connection to Server", 0); } @@ -146,7 +166,6 @@ public class ClientToServerThread implements Runnable { ourSourceID = is.read(); } catch (IOException e) { clientLog("Three way handshake failed", 1); - } if (ourSourceID != null) { try { @@ -163,6 +182,7 @@ public class ClientToServerThread implements Runnable { /** * Send the post-start race course information + * @param boatActionMessage The message to send */ public void sendBoatActionMessage(BoatActionMessage boatActionMessage) { try { @@ -173,7 +193,7 @@ public class ClientToServerThread implements Runnable { } - public void closeSocket() { + private void closeSocket() { try { socket.close(); } catch (IOException e) { @@ -181,8 +201,23 @@ public class ClientToServerThread implements Runnable { } } + public void setSocketToClose () { + socketOpen = false; + } - private int readByte() throws Exception { + public Queue getPacketQueue () { + return streamPackets; + } + + public void addStreamObserver (ClientSocketListener streamListener) { + listeners.add(streamListener); + } + + public void removeStreamObserver (ClientSocketListener streamListener) { + listeners.remove(streamListener); + } + + private int readByte() throws ByteReadException { int currentByte = -1; try { currentByte = is.read(); @@ -191,12 +226,12 @@ public class ClientToServerThread implements Runnable { clientLog("Read byte failed", 1); } if (currentByte == -1) { - throw new Exception(); + throw new ByteReadException("InputStream reach end of stream"); } return currentByte; } - private byte[] getBytes(int n) throws Exception { + private byte[] getBytes(int n) throws ByteReadException { byte[] bytes = new byte[n]; for (int i = 0; i < n; i++) { bytes[i] = (byte) readByte(); @@ -204,7 +239,7 @@ public class ClientToServerThread implements Runnable { return bytes; } - private void skipBytes(long n) throws Exception { + private void skipBytes(long n) throws ByteReadException { for (int i = 0; i < n; i++) { readByte(); } @@ -213,4 +248,8 @@ public class ClientToServerThread implements Runnable { public Thread getThread() { return thread; } + + public int getClientId () { + return clientId; + } } diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java new file mode 100644 index 00000000..915eef37 --- /dev/null +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -0,0 +1,325 @@ +package seng302.visualiser; + +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Map; +import java.util.TimeZone; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Pane; +import seng302.gameServer.MainServerThread; +import seng302.model.RaceState; +import seng302.model.Yacht; +import seng302.model.stream.packets.StreamPacket; +import seng302.model.stream.parser.MarkRoundingData; +import seng302.model.stream.parser.PositionUpdateData; +import seng302.model.stream.parser.PositionUpdateData.DeviceType; +import seng302.model.stream.parser.RaceStatusData; +import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.model.stream.xml.parser.RegattaXMLData; +import seng302.gameServer.server.messages.BoatActionMessage; +import seng302.gameServer.server.messages.BoatActionType; +import seng302.utilities.StreamParser; +import seng302.utilities.XMLParser; +import seng302.visualiser.controllers.LobbyController; +import seng302.visualiser.controllers.LobbyController.CloseStatus; +import seng302.visualiser.controllers.RaceViewController; + +/** + * Created by cir27 on 20/07/17. + */ +public class GameClient { + + private Pane holderPane; + private ClientToServerThread socketThread; + private MainServerThread server; + + private RaceViewController raceView; + + private Map allBoatsMap; + private RegattaXMLData regattaData; + private RaceXMLData courseData; + private RaceState raceState = new RaceState(); + + private ObservableList clientLobbyList = FXCollections.observableArrayList(); + + private long lastSendingTime; + private int KEY_STROKE_SENDING_FREQUENCY = 50; + + public GameClient(Pane holder) { + this.holderPane = holder; + } + + public void runAsClient(String ipAddress, Integer portNumber) { + try { + socketThread = new ClientToServerThread(ipAddress, portNumber); + } catch (IOException ioe) { + ioe.printStackTrace(); + System.out.println("Unable to connect to host..."); + } + socketThread.addStreamObserver(this::parsePackets); + LobbyController lobbyController = loadLobby(); + lobbyController.setPlayerListSource(clientLobbyList); + lobbyController.disableReadyButton(); + lobbyController.setTitle("Connected to host - IP : " + ipAddress + " Port : " + portNumber); + lobbyController.addCloseListener((exitCause) -> this.loadStartScreen()); + } + + public void runAsHost(String ipAddress, Integer portNumber) { + server = new MainServerThread(); + try { + socketThread = new ClientToServerThread(ipAddress, portNumber); + } catch (IOException ioe) { + ioe.printStackTrace(); + System.out.println("Unable to make local connection to host..."); + } + socketThread.addStreamObserver(this::parsePackets); + LobbyController lobbyController = loadLobby(); + lobbyController.setPlayerListSource(clientLobbyList); + lobbyController.setTitle("Hosting Lobby - IP : " + ipAddress + " Port : " + portNumber); + lobbyController.addCloseListener(exitCause -> { + if (exitCause == CloseStatus.READY) { + server.startGame(); + } else if (exitCause == CloseStatus.LEAVE) { + loadStartScreen(); + } + }); + } + + private void loadStartScreen() { + socketThread.setSocketToClose(); + socketThread = null; + if (server != null) { + // TODO: 26/07/17 cir27 - handle disconnecting +// server.shutDown(); + server = null; + } + FXMLLoader fxmlLoader = new FXMLLoader( + getClass().getResource("/views/StartScreenView.fxml")); + try { + holderPane.getChildren().clear(); + holderPane.getChildren().add(fxmlLoader.load()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Loads a view of the lobby into the clients pane + * + * @return the lobby controller. + */ + private LobbyController loadLobby() { + FXMLLoader fxmlLoader = new FXMLLoader(GameClient.class.getResource("/views/LobbyView.fxml")); + try { + holderPane.getChildren().clear(); + holderPane.getChildren().add(fxmlLoader.load()); + } catch (IOException e) { + e.printStackTrace(); + } + return fxmlLoader.getController(); + } + + private void loadRaceView() { + FXMLLoader fxmlLoader = new FXMLLoader( + RaceViewController.class.getResource("/views/RaceView.fxml")); + try { + final Node node = fxmlLoader.load(); + Platform.runLater(() -> { + holderPane.getChildren().clear(); + holderPane.getChildren().add(node); + }); + } catch (IOException e) { + e.printStackTrace(); + } + holderPane.getScene().setOnKeyPressed(this::keyPressed); + holderPane.getScene().setOnKeyReleased(this::keyReleased); + raceView = fxmlLoader.getController(); + Yacht player = allBoatsMap.get(socketThread.getClientId()); + raceView.loadRace(allBoatsMap, courseData, raceState, player); + } + + private void parsePackets() { + while (socketThread.getPacketQueue().peek() != null) { + StreamPacket packet = socketThread.getPacketQueue().poll(); + switch (packet.getType()) { + case RACE_STATUS: + processRaceStatusUpdate(StreamParser.extractRaceStatus(packet)); + startRaceIfAllDataReceived(); + break; + + case REGATTA_XML: + regattaData = XMLParser.parseRegatta( + StreamParser.extractXmlMessage(packet) + ); + raceState.setTimeZone( + TimeZone.getTimeZone( + ZoneId.ofOffset("UTC", ZoneOffset.ofHours(regattaData.getUtcOffset())) + ) + ); + break; + + case RACE_XML: + courseData = XMLParser.parseRace( + StreamParser.extractXmlMessage(packet) + ); + if (raceView != null) { + raceView.updateRaceData(courseData); + } + break; + + case BOAT_XML: + System.out.println("GOT SUM BOATS YAY :)"); + allBoatsMap = XMLParser.parseBoats( + StreamParser.extractXmlMessage(packet) + ); + clientLobbyList.clear(); + allBoatsMap.forEach((id, boat) -> { + clientLobbyList.add(id + " " + boat.getBoatName()); +// System.out.println(id + " " + boat.getBoatName()); + + }); +// startRaceIfAllDataReceived(); + break; + + case RACE_START_STATUS: + raceState.updateState(StreamParser.extractRaceStartStatus(packet)); + break; + + case BOAT_LOCATION: + updatePosition(StreamParser.extractBoatLocation(packet)); + break; + + case MARK_ROUNDING: + updateMarkRounding(StreamParser.extractMarkRounding(packet)); + break; + } + } + } + + private void startRaceIfAllDataReceived() { + if (allXMLReceived() && raceView == null) + loadRaceView(); + } + + private boolean allXMLReceived() { + return courseData != null && allBoatsMap != null && regattaData != null; + } + + /** + * Updates the position of a boat. Boat and position are given in the provided data. + */ + private void updatePosition(PositionUpdateData positionData) { + if (positionData.getType() == DeviceType.YACHT_TYPE) { + if (allXMLReceived() && allBoatsMap.containsKey(positionData.getDeviceId())) { + Yacht yacht = allBoatsMap.get(positionData.getDeviceId()); + yacht.updateLocation(positionData.getLat(), + positionData.getLon(), positionData.getHeading(), + positionData.getGroundSpeed()); + } + } else if (positionData.getType() == DeviceType.MARK_TYPE) { + //CompoundMark mark = courseData.getCompoundMarks().get(positionData.getDeviceId()); + } + } + + /** + * Updates the boat as having passed the mark. Boat and mark are given by the ids in the + * provided data. + * + * @param roundingData Contains data for the rounding of a mark. + */ + private void updateMarkRounding(MarkRoundingData roundingData) { + if (allXMLReceived()) { + Yacht yacht = allBoatsMap.get(roundingData.getBoatId()); + yacht.setMarkRoundingTime(roundingData.getTimeStamp()); + yacht.updateTimeSinceLastMarkProperty( + raceState.getRaceTime() - roundingData.getTimeStamp()); + yacht.setLastMarkRounded( + courseData.getCompoundMarks().get( + roundingData.getMarkId() + ) + ); + } + } + + private void processRaceStatusUpdate(RaceStatusData data) { + if (allXMLReceived()) { + raceState.updateState(data); + for (long[] boatData : data.getBoatData()) { + Yacht yacht = allBoatsMap.get((int) boatData[0]); + yacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]); + yacht.setEstimateTimeAtFinish(boatData[2]); + int legNumber = (int) boatData[3]; + yacht.setLegNumber(legNumber); + yacht.setBoatStatus((int) boatData[4]); + if (legNumber != yacht.getLegNumber()) { + int placing = 1; + for (Yacht otherYacht : allBoatsMap.values()) { + if (otherYacht.getSourceId() != boatData[0] && + yacht.getLegNumber() <= otherYacht.getLegNumber()) + placing++; + } + yacht.setPositionInteger(placing); + } + } + } + } + + private void close() { + socketThread.setSocketToClose(); + } + + + /** + * Handle the key-pressed event from the text field. + * @param e The key event triggering this call + */ + public void keyPressed(KeyEvent e) { + BoatActionMessage boatActionMessage; + long currentTime = System.currentTimeMillis(); + if (currentTime - lastSendingTime > KEY_STROKE_SENDING_FREQUENCY) { + lastSendingTime = currentTime; + switch (e.getCode()) { + case SPACE: // align with vmg + boatActionMessage = new BoatActionMessage(BoatActionType.VMG); + socketThread.sendBoatActionMessage(boatActionMessage); + break; + case PAGE_UP: // upwind + boatActionMessage = new BoatActionMessage(BoatActionType.UPWIND); + socketThread.sendBoatActionMessage(boatActionMessage); + break; + case PAGE_DOWN: // downwind + boatActionMessage = new BoatActionMessage(BoatActionType.DOWNWIND); + socketThread.sendBoatActionMessage(boatActionMessage); + break; + case ENTER: // tack/gybe + boatActionMessage = new BoatActionMessage(BoatActionType.TACK_GYBE); + socketThread.sendBoatActionMessage(boatActionMessage); + break; + //TODO Allow a zoom in and zoom out methods + case Z: // zoom in + System.out.println("Zoom in"); + break; + case X: // zoom out + System.out.println("Zoom out"); + break; + } + } + } + + public void keyReleased(KeyEvent e) { + switch (e.getCode()) { + //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) + case SHIFT: // sails in/sails out + BoatActionMessage boatActionMessage = new BoatActionMessage( + BoatActionType.SAILS_IN); + socketThread.sendBoatActionMessage(boatActionMessage); + break; + } + } +} diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java new file mode 100644 index 00000000..73d49b89 --- /dev/null +++ b/src/main/java/seng302/visualiser/GameView.java @@ -0,0 +1,586 @@ +package seng302.visualiser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javafx.animation.AnimationTimer; +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Polygon; +import javafx.scene.text.Text; +import seng302.model.Colors; +import seng302.model.GeoPoint; +import seng302.model.Limit; +import seng302.model.Yacht; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Corner; +import seng302.model.mark.Mark; +import seng302.utilities.GeoUtility; +import seng302.visualiser.fxObjects.AnnotationBox; +import seng302.visualiser.fxObjects.BoatObject; +import seng302.visualiser.fxObjects.CourseBoundary; +import seng302.visualiser.fxObjects.Gate; +import seng302.visualiser.fxObjects.Marker; +import seng302.visualiser.map.Boundary; +import seng302.visualiser.map.CanvasMap; + +/** + * Created by cir27 on 20/07/17. + */ +public class GameView extends Pane { + + private double bufferSize = 50; + private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias. + private double panelHeight = 960; + private double canvasWidth = 1100; + private double canvasHeight = 920; + private boolean horizontalInversion = false; + + private double distanceScaleFactor; + private ScaleDirection scaleDirection; + private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint; + private double referencePointX, referencePointY; + private double metersPerPixelX, metersPerPixelY; + + private Text fpsDisplay = new Text(); + private Polygon raceBorder = new CourseBoundary(); + + /* Note that if either of these is null then values for it have not been added and the other + should be used as the limits of the map. */ + private List borderPoints; + private Map markerObjects; + + private Map boatObjects = new HashMap<>(); + private Map annotations = new HashMap<>(); + private ObservableList gameObjects; + private Group annotationsGroup = new Group(); + private Group wakesGroup = new Group(); + private Group boatObjectGroup = new Group(); + private Group trails = new Group(); + private Group markers = new Group(); + + private ImageView mapImage = new ImageView(); + + //FRAME RATE + + private AnimationTimer timer; + private int NUM_SAMPLES = 10; + private final long[] frameTimes = new long[NUM_SAMPLES]; + private Double frameRate = 60.0; + private int frameTimeIndex = 0; + private boolean arrayFilled = false; + + private enum ScaleDirection { + HORIZONTAL, + VERTICAL + } + + public GameView () { + gameObjects = this.getChildren(); + // create image view for map, bind panel size to image + gameObjects.add(mapImage); + fpsDisplay.setLayoutX(5); + fpsDisplay.setLayoutY(20); + fpsDisplay.setStrokeWidth(2); + gameObjects.add(fpsDisplay); + gameObjects.add(raceBorder); + gameObjects.add(markers); + initializeTimer(); + } + + private void initializeTimer () { + Arrays.fill(frameTimes, 1_000_000_000 / 60); + timer = new AnimationTimer() { + private long lastTime = 0; + private int FPSCount = 30; + private Double frameRate = 60.0; + private int index = 0; + private boolean arrayFilled = false; + private long sum = 1_000_000_000 / 3; + + @Override + public void handle(long now) { + if (lastTime == 0) { + lastTime = now; + } else { + if (now - lastTime >= (1e8 / 60)) { //Fix for framerate going above 60 when minimized + long oldFrameTime = frameTimes[frameTimeIndex]; + frameTimes[frameTimeIndex] = now; + frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length; + if (frameTimeIndex == 0) { + arrayFilled = true; + } + long elapsedNanos; + if (arrayFilled) { + elapsedNanos = now - oldFrameTime; + long elapsedNanosPerFrame = elapsedNanos / frameTimes.length; + frameRate = 1_000_000_000.0 / elapsedNanosPerFrame; + if (FPSCount-- == 0) { + FPSCount = 30; + drawFps(frameRate); + } + } + lastTime = now; + } + } +// Platform.runLater(() -> +// boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation()) +// ); + } + }; + } + + /** + * First find the top right and bottom left points' geo locations, then retrieve + * map from google to display on image view. - Haoming 22/5/2017 + */ + private void drawGoogleMap() { + findMetersPerPixel(); + Point2D topLeftPoint = findScaledXY(maxLatPoint.getLat(), minLonPoint.getLng()); + // distance from top left extreme to panel origin (top left corner) + double distanceFromTopLeftToOrigin = Math.sqrt( + Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math + .pow(topLeftPoint.getY() * metersPerPixelY, 2)); + // angle from top left extreme to panel origin + double bearingFromTopLeftToOrigin = Math + .toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY())); + // the top left extreme + GeoPoint topLeftPos = new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng()); + GeoPoint originPos = GeoUtility + .getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin); + + // distance from origin corner to bottom right corner of the panel + double distanceFromOriginToBottomRight = Math.sqrt( + Math.pow(panelHeight * metersPerPixelY, 2) + Math + .pow(panelWidth * metersPerPixelX, 2)); + double bearingFromOriginToBottomRight = Math + .toDegrees(Math.atan2(panelWidth, -panelHeight)); + GeoPoint bottomRightPos = GeoUtility + .getGeoCoordinate(originPos, bearingFromOriginToBottomRight, + distanceFromOriginToBottomRight); + + Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(), + bottomRightPos.getLat(), originPos.getLng()); + CanvasMap canvasMap = new CanvasMap(boundary); + mapImage.setImage(canvasMap.getMapImage()); + mapImage.fitWidthProperty().bind(((AnchorPane) this.getParent()).heightProperty()); + mapImage.fitHeightProperty().bind(((AnchorPane) this.getParent()).heightProperty()); + } + + /** + * Adds a course to the GameView. The view is scaled accordingly unless a border is set in which + * case the course is added relative ot the border. + * + * @param newCourse the mark objects that make up the course. + * @param sequence The sequence the marks travel through + */ + public void updateCourse(List newCourse, List sequence) { + markerObjects = new HashMap<>(); + final List gates = new ArrayList<>(); + Paint colour = Color.BLACK; + //Creates new markers + for (CompoundMark cMark : newCourse) { + //Set start and end colour + if (cMark.getId() == sequence.get(0).getCompoundMarkID()) { + colour = Color.GREEN; + } else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) { + colour = Color.RED; + } + //Create mark dots + for (Mark mark : cMark.getMarks()) { + makeAndBindMarker(mark, colour); + } + //Create gate line + if (cMark.isGate()) { + for (int i = 1; i < cMark.getMarks().size(); i++) { + gates.add( + makeAndBindGate( + markerObjects.get(cMark.getSubMark(i)), + markerObjects.get(cMark.getSubMark(i+1)), + colour + ) + ); + } + } + colour = Color.BLACK; + } + //Scale race to markers if there is no border. + if (borderPoints == null) { + rescaleRace(new ArrayList<>(markerObjects.keySet())); + } + //Move the Markers to initial position. + markerObjects.forEach(((mark, marker) -> { + Point2D p2d = findScaledXY(mark.getLat(), mark.getLng()); + marker.setCenterX(p2d.getX()); + marker.setCenterY(p2d.getY()); + })); + Platform.runLater(() -> { + markers.getChildren().clear(); + markers.getChildren().addAll(gates); + markers.getChildren().addAll(markerObjects.values()); + }); + } + + /** + * Creates a new Marker and binds it's position to the given Mark. + * + * @param observableMark The mark to bind the marker to. + * @param colour The desired colour of the mark + */ + private void makeAndBindMarker(Mark observableMark, Paint colour) { + Marker marker = new Marker(colour); + markerObjects.put(observableMark, marker); + observableMark.addPositionListener((mark, lat, lon) -> { + Point2D p2d = findScaledXY(lat, lon); + markerObjects.get(mark).setCenterX(p2d.getX()); + markerObjects.get(mark).setCenterY(p2d.getY()); + }); + } + + /** + * Creates a new gate connecting the given marks. + * + * @param m1 The first Mark of the gate. + * @param m2 The second Mark of the gate. + * @param colour The desired colour of the gate. + * @return the new gate. + */ + private Gate makeAndBindGate(Marker m1, Marker m2, Paint colour) { + Gate gate = new Gate(colour); + gate.startXProperty().bind( + m1.centerXProperty() + ); + gate.startYProperty().bind( + m1.centerYProperty() + ); + gate.endXProperty().bind( + m2.centerXProperty() + ); + gate.endYProperty().bind( + m2.centerYProperty() + ); + return gate; + } + + /** + * Adds a border to the GameView and rescales to the size of the border, does not rescale if a + * border already exists. Assumes the border is larger than the course. + * + * @param border the race border to be drawn. + */ + public void updateBorder(List border) { + if (borderPoints == null) { + borderPoints = border; + rescaleRace(new ArrayList<>(borderPoints)); + } + List boundaryPoints = new ArrayList<>(); + for (Limit limit : border) { + Point2D location = findScaledXY(limit.getLat(), limit.getLng()); + boundaryPoints.add(location.getX()); + boundaryPoints.add(location.getY()); + } + raceBorder.getPoints().setAll(boundaryPoints); + } + + /** + * Rescales the race to the size of the window. + * + * @param limitingCoordinates the set of geo points that contains the extremities of the race. + */ + private void rescaleRace(List limitingCoordinates) { + //Check is called once to avoid unnecessarily change the course limits once the race is running + findMinMaxPoint(limitingCoordinates); + double minLonToMaxLon = scaleRaceExtremities(); + calculateReferencePointLocation(minLonToMaxLon); +// drawGoogleMap(); + } + + /** + * Draws all the boats. + * @param yachts The yachts to set in the race + */ + public void setBoats(List yachts) { + BoatObject newBoat; + final List wakes = new ArrayList<>(); + for (Yacht yacht : yachts) { + Paint colour = Colors.getColor(); + newBoat = new BoatObject(); + newBoat.setFill(colour); + boatObjects.put(yacht, newBoat); + createAndBindAnnotationBox(yacht, colour); +// wakesGroup.getChildren().add(newBoat.getWake()); + wakes.add(newBoat.getWake()); + boatObjectGroup.getChildren().add(newBoat); + trails.getChildren().add(newBoat.getTrail()); + // TODO: 1/08/17 Make this less vile to look at. + yacht.addLocationListener((boat, lat, lon, heading, velocity) ->{ + BoatObject bo = boatObjects.get(boat); + Point2D p2d = findScaledXY(lat, lon); + bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity); +// annotations.get(boat).setLayoutX(p2d.getX()); +// annotations.get(boat).setLayoutY(p2d.getY()); +// annotations.get(boat).setLocation(100d, 100d); + annotations.get(boat).setLocation(p2d.getX(), p2d.getY()); + bo.setTrajectory( + heading, + velocity, + metersPerPixelX, + metersPerPixelY); + }); + } + annotationsGroup.getChildren().addAll(annotations.values()); + Platform.runLater(() -> { + gameObjects.addAll(trails); + gameObjects.addAll(wakes); + gameObjects.addAll(annotationsGroup); + gameObjects.addAll(boatObjectGroup); + }); + } + + private void createAndBindAnnotationBox (Yacht yacht, Paint colour) { + AnnotationBox newAnnotation = new AnnotationBox(); + newAnnotation.setFill(colour); + newAnnotation.addAnnotation( + "name", "Player: " + yacht.getShortName() + ); +// newAnnotation.addAnnotation( +// "velocity", +// yacht.getVelocityProperty(), +// (velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue()) +// ); +// newAnnotation.addAnnotation( +// "nextMark", +// yacht.timeTillNextProperty(), +// (time) -> { +// DateFormat format = new SimpleDateFormat("mm:ss"); +// return format.format(time); +// } +// ); +// newAnnotation.addAnnotation( +// "lastMark", +// yacht.timeTillNextProperty(), +// (time) -> { +// DateFormat format = new SimpleDateFormat("mm:ss"); +// return format.format(time); +// } +// ); + annotations.put(yacht, newAnnotation); + } + + private void drawFps(Double fps){ + Platform.runLater(() -> fpsDisplay.setText(String.format("%d FPS", Math.round(fps)))); + } + + /** + * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point + * with the leftmost point, rightmost point, southern most point and northern most point + * respectively. + */ + private void findMinMaxPoint(List points) { + List sortedPoints = new ArrayList<>(points); + sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat)); + minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng()); + GeoPoint maxLat = sortedPoints.get(sortedPoints.size()-1); + maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng()); + + sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng)); + minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng()); + GeoPoint maxLon = sortedPoints.get(sortedPoints.size()-1); + maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng()); + if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) { + horizontalInversion = true; + } + } + + /** + * Calculates the location of a reference point, this is always the point with minimum latitude, + * in relation to the canvas. + * + * @param minLonToMaxLon The horizontal distance between the point of minimum longitude to + * maximum longitude. + */ + private void calculateReferencePointLocation(double minLonToMaxLon) { + GeoPoint referencePoint = minLatPoint; + double referenceAngle; + + if (scaleDirection == ScaleDirection.HORIZONTAL) { + referenceAngle = Math.abs( + GeoUtility.getBearingRad(referencePoint, minLonPoint) + ); + referencePointX = bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility.getDistance(referencePoint, minLonPoint); + referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint)); + referencePointY = canvasHeight - (bufferSize + bufferSize); + referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility.getDistance(referencePoint, maxLatPoint); + referencePointY = referencePointY / 2; + referencePointY += bufferSize; + referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility.getDistance(referencePoint, maxLatPoint); + } else { + referencePointY = canvasHeight - bufferSize; + referenceAngle = Math.abs( + Math.toRadians( + GeoUtility.getDistance(referencePoint, minLonPoint) + ) + ); + referencePointX = bufferSize; + referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility.getDistance(referencePoint, minLonPoint); + referencePointX += ((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor)) / 2; + } + if(horizontalInversion) { + referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize); + } + } + + + /** + * Finds the scale factor necessary to fit all race markers within the onscreen map and assigns + * it to distanceScaleFactor Returns the max horizontal distance of the map. + */ + private double scaleRaceExtremities() { + + double vertAngle = Math.abs( + GeoUtility.getBearingRad(minLatPoint, maxLatPoint) + ); + double vertDistance = + Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint); + double horiAngle = Math.abs( + GeoUtility.getBearingRad(minLonPoint, maxLonPoint) + ); + if (horiAngle <= (Math.PI / 2)) { + horiAngle = (Math.PI / 2) - horiAngle; + } else { + horiAngle = horiAngle - (Math.PI / 2); + } + double horiDistance = + Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint); + + double vertScale = (canvasHeight - (bufferSize + bufferSize)) / vertDistance; + + if ((horiDistance * vertScale) > (canvasWidth - (bufferSize + bufferSize))) { + distanceScaleFactor = (canvasWidth - (bufferSize + bufferSize)) / horiDistance; + scaleDirection = ScaleDirection.HORIZONTAL; + } else { + distanceScaleFactor = vertScale; + scaleDirection = ScaleDirection.VERTICAL; + } + return horiDistance; + } + + private Point2D findScaledXY(GeoPoint unscaled) { + return findScaledXY(unscaled.getLat(), unscaled.getLng()); + } + + private Point2D findScaledXY (double unscaledLat, double unscaledLon) { + double distanceFromReference; + double angleFromReference; + double xAxisLocation = referencePointX; + double yAxisLocation = referencePointY; + + angleFromReference = GeoUtility.getBearingRad( + minLatPoint, new GeoPoint(unscaledLat, unscaledLon) + ); + distanceFromReference = GeoUtility.getDistance( + minLatPoint, new GeoPoint(unscaledLat, unscaledLon) + ); +// System.out.println("distanceFromReference = " + distanceFromReference); + if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { + xAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + yAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + } else if (angleFromReference >= 0) { + angleFromReference = angleFromReference - Math.PI / 2; + xAxisLocation += Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + yAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + } else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) { + angleFromReference = Math.abs(angleFromReference); + xAxisLocation -= Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + yAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + } else { + angleFromReference = Math.abs(angleFromReference) - Math.PI / 2; + xAxisLocation -= Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + yAxisLocation += Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + } + if(horizontalInversion) { + xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize); + } +// System.out.println("yAxisLocation = " + yAxisLocation + " " + unscaledLat); +// System.out.println("xAxisLocation = " + xAxisLocation + " " + unscaledLon); + return new Point2D(xAxisLocation, yAxisLocation); + } + + /** + * Find the number of meters per pixel. + */ + private void findMetersPerPixel() { + Point2D p1, p2; + GeoPoint g1, g2; + double theta, distance, dx, dy, dHorizontal, dVertical; + g1 = new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng()); + g2 = new GeoPoint(minLatPoint.getLat(), maxLatPoint.getLng()); + p1 = findScaledXY(new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng())); + p2 = findScaledXY(new GeoPoint(minLatPoint.getLat(), maxLatPoint.getLng())); + theta = GeoUtility.getBearingRad(g1, g2); + distance = GeoUtility.getDistance(g1, g2); + dHorizontal = Math.abs(Math.sin(theta) * distance); + dVertical = Math.abs(Math.cos(theta) * distance); + dx = Math.abs(p1.getX() - p2.getX()); + dy = Math.abs(p1.getY() - p2.getY()); + metersPerPixelX = dHorizontal / dx; + metersPerPixelY = dVertical / dy; + } + + public void setAnnotationVisibilities (boolean teamName, boolean velocity, boolean estTime, + boolean legTime, boolean trail, boolean wake) { + for (BoatObject boatObject : boatObjects.values()) { + boatObject.setVisibility(teamName, velocity, estTime, legTime, trail, wake); + } + for (AnnotationBox ag : annotations.values()) { + ag.setAnnotationVisibility("name", teamName); + ag.setAnnotationVisibility("velocity", velocity); + ag.setAnnotationVisibility("nextMark", estTime); + ag.setAnnotationVisibility("lastMark", legTime); + } + } + + public void setFPSVisibility (boolean visibility) { + fpsDisplay.setVisible(visibility); + } + + public void selectBoat (Yacht selectedYacht) { + boatObjects.forEach((boat, group) -> + group.setIsSelected(boat == selectedYacht) + ); + } + + public void pauseRace () { + timer.stop(); + } + + public void startRace () { + timer.start(); + } + + public void setBoatAsPlayer (Yacht playerYacht) { + boatObjects.get(playerYacht).setAsPlayer(); + annotations.get(playerYacht).addAnnotation( + "velocity", + playerYacht.getVelocityProperty(), + (velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue()) + ); + Platform.runLater(() -> { + boatObjectGroup.getChildren().remove(boatObjects.get(playerYacht)); + gameObjects.add(boatObjects.get(playerYacht)); + annotationsGroup.getChildren().remove(annotations.get(playerYacht)); + gameObjects.add(annotations.get(playerYacht)); + }); + } +} diff --git a/src/main/java/seng302/controllers/FinishScreenViewController.java b/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java similarity index 77% rename from src/main/java/seng302/controllers/FinishScreenViewController.java rename to src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java index 170de47f..db2a5d17 100644 --- a/src/main/java/seng302/controllers/FinishScreenViewController.java +++ b/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java @@ -1,8 +1,10 @@ -package seng302.controllers; +package seng302.visualiser.controllers; import java.io.IOException; import java.net.URL; import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.ResourceBundle; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -15,9 +17,7 @@ import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; -import seng302.client.ClientPacketParser; -import seng302.models.Yacht; -import seng302.models.stream.XMLParser.RaceXMLObject.Participant; +import seng302.model.Yacht; public class FinishScreenViewController implements Initializable { @@ -34,6 +34,8 @@ public class FinishScreenViewController implements Initializable { @FXML private TableColumn countryCol; + ObservableList data = FXCollections.observableArrayList(); + @Override public void initialize(URL location, ResourceBundle resources) { finishScreenGridPane.getStylesheets() @@ -41,7 +43,6 @@ public class FinishScreenViewController implements Initializable { finishOrderTable.getStylesheets().add(getClass().getResource("/css/master.css").toString()); // set up data for table - ObservableList data = FXCollections.observableArrayList(); finishOrderTable.setItems(data); // setting table col data @@ -57,24 +58,15 @@ public class FinishScreenViewController implements Initializable { countryCol.setCellValueFactory( new PropertyValueFactory<>("country") ); - - // check if the boat is racing - ArrayList participants = ClientPacketParser.getXmlObject().getRaceXML() - .getParticipants(); - ArrayList participantIDs = new ArrayList<>(); - for (Participant p : participants) { - participantIDs.add(p.getsourceID()); - } - - // add data to table - for (Yacht boat : ClientPacketParser.getBoatsPos().values()) { - if (participantIDs.contains(boat.getSourceId())) { - data.add(boat); - } - } finishOrderTable.refresh(); } + public void setFinishers (List participants) { + List sorted = new ArrayList<>(participants); + sorted.sort(Comparator.comparingInt(Yacht::getPositionInteger)); + finishOrderTable.getItems().setAll(sorted); + } + private void setContentPane(String jfxUrl) { try { // get the main controller anchor pane (FinishView -> MainView) diff --git a/src/main/java/seng302/visualiser/controllers/LobbyController.java b/src/main/java/seng302/visualiser/controllers/LobbyController.java new file mode 100644 index 00000000..1a8b13e3 --- /dev/null +++ b/src/main/java/seng302/visualiser/controllers/LobbyController.java @@ -0,0 +1,202 @@ +package seng302.visualiser.controllers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ListView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; +import javafx.scene.text.Text; +import seng302.gameServer.GameStages; +import seng302.gameServer.GameState; + +/** + * A class describing the actions of the lobby screen + * Created by wmu16 on 10/07/17. + */ +public class LobbyController { + + public enum CloseStatus { + LEAVE, + READY + } + + @FunctionalInterface + public interface LobbyCloseListener { + void notify(CloseStatus exitCause); + } + + @FXML + private GridPane lobbyScreen; + @FXML + private Text lobbyIpText; + @FXML + private Button readyButton; + @FXML + private ListView firstListView; + @FXML + private ListView secondListView; + @FXML + private ListView thirdListView; + @FXML + private ListView fourthListView; + @FXML + private ListView fifthListView; + @FXML + private ListView sixthListView; + @FXML + private ListView seventhListView; + @FXML + private ListView eighthListView; + @FXML + private ImageView firstImageView; + @FXML + private ImageView secondImageView; + @FXML + private ImageView thirdImageView; + @FXML + private ImageView fourthImageView; + @FXML + private ImageView fifthImageView; + @FXML + private ImageView sixthImageView; + @FXML + private ImageView seventhImageView; + @FXML + private ImageView eighthImageView; + + private List> competitors = new ArrayList<>(); + private ObservableList firstCompetitor = FXCollections.observableArrayList(); + private ObservableList secondCompetitor = FXCollections.observableArrayList(); + private ObservableList thirdCompetitor = FXCollections.observableArrayList(); + private ObservableList fourthCompetitor = FXCollections.observableArrayList(); + private ObservableList fifthCompetitor = FXCollections.observableArrayList(); + private ObservableList sixthCompetitor = FXCollections.observableArrayList(); + private ObservableList seventhCompetitor = FXCollections.observableArrayList(); + private ObservableList eighthCompetitor = FXCollections.observableArrayList(); + + private List imageViews = new ArrayList<>(); + private List listViews; + + private int MAX_NUM_PLAYERS = 8; + + private List lobbyListeners = new ArrayList<>(); + private ObservableList players = FXCollections.observableArrayList(); + + public void initialize() { + imageViews = new ArrayList<>(); + Collections + .addAll(imageViews, firstImageView, secondImageView, thirdImageView, fourthImageView, + fifthImageView, sixthImageView, seventhImageView, eighthImageView); + listViews = new ArrayList<>(); + Collections.addAll(listViews, firstListView, secondListView, thirdListView, fourthListView, fifthListView, + sixthListView, seventhListView, eighthListView); + competitors = new ArrayList<>(); + Collections.addAll(competitors, firstCompetitor, secondCompetitor, thirdCompetitor, + fourthCompetitor, fifthCompetitor, sixthCompetitor, seventhCompetitor, eighthCompetitor); + + initialiseImageView(); + } + + private void initialiseListView() { + listViews.forEach(listView -> listView.getItems().clear()); + imageViews.forEach(gif -> gif.setVisible(false)); + competitors.forEach(ol -> ol.removeAll()); + for (int i = 0; i < players.size(); i++) { + competitors.get(i).add(players.get(i)); + listViews.get(i).setItems(competitors.get(i)); + imageViews.get(i).setVisible(true); + } + } + + private void initialiseImageView() { + imageViews.add(firstImageView); + imageViews.add(secondImageView); + imageViews.add(thirdImageView); + imageViews.add(fourthImageView); + imageViews.add(fifthImageView); + imageViews.add(sixthImageView); + imageViews.add(seventhImageView); + imageViews.add(eighthImageView); + for (int i = 0; i < MAX_NUM_PLAYERS; i++) { + imageViews.get(i).setImage( + new Image( + RaceViewController.class.getResourceAsStream( + "/pics/sail.png") + ) + ); + } + } + + @FXML + public void leaveLobbyButtonPressed() { + // TODO: 10/07/17 wmu16 - Finish function! +// setContentPane("/views/StartScreenView.fxml"); + GameState.setCurrentStage(GameStages.CANCELLED); + // TODO: 20/07/17 wmu16 - Implement some way of terminating the game +// ClientState.setConnectedToHost(false); + for (LobbyCloseListener readyListener : lobbyListeners) + readyListener.notify(CloseStatus.LEAVE); + + } + + @FXML + public void readyButtonPressed() { + GameState.setCurrentStage(GameStages.RACING); + for (LobbyCloseListener readyListener : lobbyListeners) + readyListener.notify(CloseStatus.READY); + } + + +// private static MediaPlayer mediaPlayer; +// +// private void playTheme() { +// Random random = new Random(System.currentTimeMillis()); +// Integer rand = random.nextInt(); +// if(rand == 10) { +// URL file = getClass().getResource("/music/Disturbed - down with the sickness.mp3"); +// Media hit = new Media(file.toString()); +// mediaPlayer = new MediaPlayer(hit); +// mediaPlayer.play(); +// } else if(rand == 9) { +// URL file = getClass().getResource("/music/Owl City - Fireflies.mp3"); +// Media hit = new Media(file.toString()); +// mediaPlayer = new MediaPlayer(hit); +// mediaPlayer.play(); +// } +// } + +// private void switchToRaceView() { +// if (!switchedPane) { +// switchedPane = true; +// setContentPane("/views/RaceView.fxml"); +// } +// } +// TODO: 26/07/17 cir27 - Could probably be done in a cleaner way. + public void setTitle (String title) { + lobbyIpText.setText(title); + } + + public void addCloseListener(LobbyCloseListener listener) { + lobbyListeners.add(listener); + } + + public void setPlayerListSource (ObservableList players) { + this.players = players; + players.addListener((ListChangeListener) (lcl) -> + Platform.runLater(this::initialiseListView) + ); + Platform.runLater(this::initialiseListView); + } + + public void disableReadyButton () { + readyButton.setDisable(true); + } +} diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java new file mode 100644 index 00000000..a6df4f61 --- /dev/null +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -0,0 +1,598 @@ +package seng302.visualiser.controllers; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Point2D; +import javafx.scene.Scene; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.chart.XYChart.Data; +import javafx.scene.chart.XYChart.Series; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Slider; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Line; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.util.StringConverter; +import seng302.model.RaceState; +import seng302.model.Yacht; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Mark; +import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.visualiser.GameView; +import seng302.visualiser.controllers.annotations.Annotation; +import seng302.visualiser.controllers.annotations.ImportantAnnotationController; +import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate; +import seng302.visualiser.controllers.annotations.ImportantAnnotationsState; +import seng302.visualiser.fxObjects.BoatObject; + +/** + * Controller class that manages the display of a race + */ +public class RaceViewController extends Thread implements ImportantAnnotationDelegate { + + @FXML + private LineChart raceSparkLine; + @FXML + private NumberAxis sparklineYAxis; + @FXML + private VBox positionVbox; + @FXML + private CheckBox toggleFps; + @FXML + private Text timerLabel; + @FXML + private AnchorPane contentAnchorPane; + @FXML + private Text windArrowText, windDirectionText; + @FXML + private Slider annotationSlider; + @FXML + private Button selectAnnotationBtn; + @FXML + private ComboBox yachtSelectionComboBox; + + //Race Data + private Map participants; + private Map markers; + private RaceXMLData courseData; + private GameView gameView; + private RaceState raceState; + + private Timeline timerTimeline; + private Timer timer = new Timer(); + private List> sparkLineData = new ArrayList<>(); + private ImportantAnnotationsState importantAnnotations; + + public void initialize() { + // Load a default important annotation state + importantAnnotations = new ImportantAnnotationsState(); + + //Formatting the y axis of the sparkline + raceSparkLine.getYAxis().setRotate(180); + raceSparkLine.getYAxis().setTickLabelRotation(180); + raceSparkLine.getYAxis().setTranslateX(-5); + raceSparkLine.getYAxis().setAutoRanging(false); + sparklineYAxis.setTickMarkVisible(false); + + positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + + selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); + } + + public void loadRace ( + Map participants, RaceXMLData raceData, RaceState raceState, Yacht player + ) { + this.participants = participants; + this.courseData = raceData; + this.markers = raceData.getCompoundMarks(); + this.raceState = raceState; + + initializeUpdateTimer(); + initialiseFPSCheckBox(); + initialiseAnnotationSlider(); + initialiseBoatSelectionComboBox(); + initialiseSparkLine(); + + gameView = new GameView(); + Platform.runLater(() -> contentAnchorPane.getChildren().add(gameView)); + gameView.setBoats(new ArrayList<>(participants.values())); + gameView.updateBorder(raceData.getCourseLimit()); + gameView.updateCourse( + new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence() + ); + gameView.setBoatAsPlayer(player); + gameView.startRace(); + } + + /** + * The important annotations have been changed, update this view + * + * @param importantAnnotationsState The current state of the selected annotations + */ + public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) { + this.importantAnnotations = importantAnnotationsState; + setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations + } + + + /** + * Loads the "select annotations" view in a new window + */ + private void loadSelectAnnotationView() { + try { + FXMLLoader fxmlLoader = new FXMLLoader(); + Stage stage = new Stage(); + // Set controller + ImportantAnnotationController controller = new ImportantAnnotationController( + this, stage + ); + fxmlLoader.setController(controller); + // Load FXML and set CSS + fxmlLoader.setLocation( + getClass().getResource("/views/importantAnnotationSelectView.fxml") + ); + Scene scene = new Scene(fxmlLoader.load(), 469, 298); + scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + stage.initStyle(StageStyle.UNDECORATED); + stage.setScene(scene); + stage.show(); + controller.loadState(importantAnnotations); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void initialiseFPSCheckBox() { + toggleFps.selectedProperty().addListener((obs, oldVal, newVal) -> + gameView.setFPSVisibility(toggleFps.isSelected()) + ); + } + + private void initialiseAnnotationSlider() { + annotationSlider.setLabelFormatter(new StringConverter() { + @Override + public String toString(Double n) { + if (n == 0) { + return "None"; + } + if (n == 1) { + return "Important"; + } + if (n == 2) { + return "All"; + } + return "All"; + } + + @Override + public Double fromString(String s) { + switch (s) { + case "None": + return 0d; + case "Important": + return 1d; + case "All": + return 2d; + + default: + return 2d; + } + } + }); + annotationSlider.setValue(2); + annotationSlider.valueProperty().addListener((obs, oldVal, newVal) -> + setAnnotations((int) annotationSlider.getValue()) + ); + } + + + /** + * Used to add any new yachts into the race that may have started late or not have had data received yet + */ + private void updateSparkLine(){ + // TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed. + // Collect the racing yachts that aren't already in the chart + sparkLineData.clear(); + List sparkLineCandidates = new ArrayList<>(participants.values()); + // Create a new data series for new yachts + sparkLineCandidates + .stream() + .filter(yacht -> yacht.getPositionInteger() != null) + .forEach(yacht -> { + Series yachtData = new Series<>(); + yachtData.setName(yacht.getSourceId().toString()); + yachtData.getData().add( + new XYChart.Data<>( + Integer.toString(yacht.getLegNumber()), + 1.0 + participants.size() - yacht.getPositionInteger() + ) + ); + sparkLineData.add(yachtData); + }); + + // Lambda function to sort the series in order of leg (later legs shown more to the right) + sparkLineData.sort((o1, o2) -> { + Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue()); + Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue()); + if (leg2 < leg1){ + return 1; + } else { + return -1; + } + }); + // Adds the new data series to the sparkline (and set the colour of the series) + Platform.runLater(() -> { + sparkLineData + .stream() + .filter(spark -> !raceSparkLine.getData().contains(spark)) + .forEach(spark -> { + raceSparkLine.getData().add(spark); + spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName())); + }); + }); + } + + private void initialiseSparkLine() { + sparklineYAxis.setUpperBound(participants.size() + 1); + raceSparkLine.setCreateSymbols(false); + } + + /** + * Updates the yachts sparkline of the desired yacht and using the new leg number + * @param yacht The yacht to be updated on the sparkline + * @param legNumber the leg number that the position will be assigned to + */ + void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){ + for (XYChart.Series positionData : sparkLineData) { + positionData.getData().add( + new Data<>( + Integer.toString(legNumber), + 1.0 + participants.size() - yacht.getPositionInteger() + ) + ); + } +// XYChart.Series positionData = sparkLineData.get(yacht.getSourceID()); +// positionData.getData().add( +// new XYChart.Data<>( +// Integer.toString(legNumber), +// 1.0 + participants.size() - yacht.getPosition() +// ) +// ); + } + + + /** + * gets the rgb string of the yachts colour to use for the chart via css + * @param yachtId id of yacht passed in to get the yachts colour + * @return the colour as an rgb string + */ + private String getBoatColorAsRGB(String yachtId){ + Color color = participants.get(Integer.valueOf(yachtId)).getColour(); + if (color == null){ + return String.format("#%02X%02X%02X",255,255,255); + } + return String.format( "#%02X%02X%02X", + (int)( color.getRed() * 255 ), + (int)( color.getGreen() * 255 ), + (int)( color.getBlue() * 255 ) + ); + } + + + /** + * Initialises a timer which updates elements of the RaceView such as wind direction, yacht + * orderings etc.. which are dependent on the info from the stream parser constantly. + * Updates of each of these attributes are called ONCE EACH SECOND + */ + private void initializeUpdateTimer() { + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + updateRaceTime(); + updateWindDirection(); + updateOrder(); + updateSparkLine(); + } + }, 0, 1000); + } + + /** + * Iterates over all corners until ones SeqID matches with the yachts current leg number. + * Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark + * Returns null if no next mark found. + * @param bg The BoatGroup to find the next mark of + * @return The next Mark or null if none found + */ + private Mark getNextMark(BoatObject bg) { + // TODO: 1/08/17 Move to GameView +// +// Integer legNumber = bg.getYacht().getLegNumber(); +// List markSequence = courseData.getMarkSequence(); +// +// if (legNumber == 0) { +// return null; +// } else if (legNumber == markSequence.size() - 1) { +// return null; +// } +// +// for (Corner corner : markSequence) { +// if (legNumber + 2 == corner.getSeqID()) { +// return courseData.getCompoundMarks().get(corner.getCompoundMarkID()); +// } +// } +// return null; + return null; + } + + + /** + * Updates the wind direction arrow and text as from info from the StreamParser + */ + private void updateWindDirection() { + windDirectionText.setText(String.format("%.1f°", raceState.getWindDirection())); + windArrowText.setRotate(raceState.getWindDirection()); + } + + + /** + * Updates the clock for the race + */ + private void updateRaceTime() { + if (!raceState.isRaceStarted()) { + timerLabel.setFill(Color.RED); + timerLabel.setText("Race Finished!"); + } else { + timerLabel.setText(raceState.getRaceTimeStr()); + } + } + + /** + * Updates the order of the yachts as from the StreamParser and sets them in the yacht order + * section + */ + private void updateOrder() { +// positionVbox.getChildren().removeAll(); +// positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + + // list of racing yacht id + List sorted = new ArrayList<>(participants.values()); + sorted.sort(Comparator.comparingInt(Yacht::getPositionInteger)); + List vboxEntries = new ArrayList<>(); + + for (Yacht yacht : sorted) { +// System.out.println("yacht == null " + String.valueOf(yacht == null)); + if (yacht.getBoatStatus() == 3) { // 3 is finish status + Text textToAdd = new Text(yacht.getPositionInteger() + ". " + + yacht.getShortName() + " (Finished)"); + textToAdd.setFill(Paint.valueOf("#d3d3d3")); + vboxEntries.add(textToAdd); + + } else { + Text textToAdd = new Text(yacht.getPositionInteger() + ". " + + yacht.getShortName() + " "); + textToAdd.setFill(Paint.valueOf("#d3d3d3")); + textToAdd.setStyle(""); + vboxEntries.add(textToAdd); + } +// System.out.println("finished a loop :))))))))))))"); + } + Platform.runLater(() -> + positionVbox.getChildren().setAll(vboxEntries) + ); +// participants.forEach((id, yacht) ->{ +// Text textToAdd = new Text(yacht.getPosition() + ". " + +// yacht.getShortName() + " "); +// textToAdd.setFill(Paint.valueOf("#d3d3d3")); +// textToAdd.setStyle(""); +// positionVbox.getChildren().add(textToAdd); +// }); + } + + + private void updateLaylines(BoatObject bg) { + // TODO: 1/08/17 move to GameView +// +// Mark nextMark = getNextMark(bg); +// Boolean isUpwind = null; +// // Can only calc leg direction if there is a next mark and it is a gate mark +// if (nextMark != null) { +// if (nextMark instanceof GateMark) { +// if (bg.isUpwindLeg(gameViewController, nextMark)) { +// isUpwind = true; +// } else { +// isUpwind = false; +// } +// +// for(MarkObject mg : gameViewController.getMarkGroups()) { +// +// mg.removeLaylines(); +// +// if (mg.getMainMark().getId() == nextMark.getId()) { +// +// SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1(); +// SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2(); +// Point2D markPoint1 = gameViewController +// .findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude()); +// Point2D markPoint2 = gameViewController +// .findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude()); +// HashMap angleAndSpeed; +// if (isUpwind) { +// angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed()); +// } else { +// angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed()); +// } +// +// Double resultingAngle = angleAndSpeed.keySet().iterator().next(); +// +// +// Point2D yachtCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY()); +// Point2D gateMidPoint = markPoint1.midpoint(markPoint2); +// Integer lineFuncResult = GeoUtility.lineFunction(yachtCurrentPos, gateMidPoint, markPoint2); +// Line rightLayline = new Line(); +// Line leftLayline = new Line(); +// if (lineFuncResult == 1) { +// rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection()); +// leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection()); +// } else if (lineFuncResult == -1) { +// rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection()); +// leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection()); +// } +// +// leftLayline.setStrokeWidth(0.5); +// leftLayline.setStroke(bg.getBoat().getColour()); +// +// rightLayline.setStrokeWidth(0.5); +// rightLayline.setStroke(bg.getBoat().getColour()); +// +// bg.setLaylines(leftLayline, rightLayline); +// mg.addLaylines(leftLayline, rightLayline); +// +// } +// } +// } +// } + } + + + private Point2D getPointRotation(Point2D ref, Double distance, Double angle){ + Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle); + Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle); + + return new Point2D(newX, newY); + } + + + public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) { + + Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle); + Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY()); + return line; + + } + + + public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) { + + Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle); + Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY()); + return line; + + } + + + /** + * Initialised the combo box with any yachts currently in the race and adds the required listener + * for the combobox to take action upon selection + */ + private void initialiseBoatSelectionComboBox() { + yachtSelectionComboBox.setItems( + FXCollections.observableArrayList(participants.values()) + ); + //Null check is if the listener is fired but nothing selected + yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> { + if (selectedBoat != null) { + gameView.selectBoat(selectedBoat); + } + }); + } + + /** + * Display the list of yachts in the order they finished the race + */ + private void loadRaceResultView() { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml")); + + try { + contentAnchorPane.getChildren().removeAll(); + contentAnchorPane.getChildren().clear(); + contentAnchorPane.getChildren().addAll((Pane) loader.load()); + + } catch (javafx.fxml.LoadException e) { + System.err.println(e.getCause().toString()); + } catch (IOException e) { + System.err.println(e.toString()); + } + } + + private String getMillisToFormattedTime(long milliseconds) { + return String.format("%02d:%02d:%02d", + TimeUnit.MILLISECONDS.toHours(milliseconds), + TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60, //Modulus 60 minutes per hour + TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60 //Modulus 60 seconds per minute + ); + } + + private void setAnnotations(Integer annotationLevel) { + switch (annotationLevel) { + // No Annotations + case 0: + gameView.setAnnotationVisibilities( + false, false, false, false, false, false + ); + break; + // Important Annotations + case 1: + gameView.setAnnotationVisibilities( + importantAnnotations.getAnnotationState(Annotation.NAME), + importantAnnotations.getAnnotationState(Annotation.SPEED), + importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK), + importantAnnotations.getAnnotationState(Annotation.LEGTIME), + importantAnnotations.getAnnotationState(Annotation.TRACK), + importantAnnotations.getAnnotationState(Annotation.WAKE) + ); + break; + // All Annotations + case 2: + gameView.setAnnotationVisibilities( + true, true, true, true, true, true + ); + break; + } + } + + + /** + * Sets all the annotations of the selected yacht to be visible and all others to be hidden + * + * @param yacht The yacht for which we want to view all annotations + */ + private void setSelectedBoat(Yacht yacht) { +// for (BoatObject bg : gameViewController.getBoatGroups()) { +// //We need to iterate over all race groups to get the matching yacht group belonging to this yacht if we +// //are to toggle its annotations, there is no other backwards knowledge of a yacht to its yachtgroup. +// if (bg.getBoat().getHullID().equals(yacht.getHullID())) { +//// updateLaylines(bg); +// bg.setIsSelected(true); +//// selectedBoat = yacht; +// } else { +// bg.setIsSelected(false); +// } +// } + } + + public void updateRaceData (RaceXMLData raceData) { + this.courseData = raceData; + gameView.updateBorder(raceData.getCourseLimit()); + } +} \ No newline at end of file diff --git a/src/main/java/seng302/visualiser/controllers/StartScreenController.java b/src/main/java/seng302/visualiser/controllers/StartScreenController.java new file mode 100644 index 00000000..87199442 --- /dev/null +++ b/src/main/java/seng302/visualiser/controllers/StartScreenController.java @@ -0,0 +1,168 @@ +package seng302.visualiser.controllers; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.URL; +import java.util.Enumeration; +import java.util.ResourceBundle; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.TextField; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import seng302.gameServer.GameState; +import seng302.visualiser.GameClient; + +/** + * A Class describing the actions of the start screen controller + * Created by wmu16 on 10/07/17. + */ +public class StartScreenController implements Initializable { + + @FXML + private TextField ipTextField; + @FXML + private TextField portTextField; + @FXML + private GridPane startScreen2; + @FXML + private AnchorPane holder; + + GameClient gameClient; + + public void initialize(URL url, ResourceBundle resourceBundle) { +// gameClient = new GameClient(holder); + } +// +// /** +// * Loads the fxml content into the parent pane +// * @param jfxUrl +// * @return the controller of the fxml +// */ +// private Object setContentPane(String jfxUrl) { +// try { +// AnchorPane contentPane = (AnchorPane) startScreen2.getParent(); +// contentPane.getChildren().removeAll(); +// contentPane.getChildren().clear(); +// contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); +// FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl)); +// contentPane.getChildren().addAll((Pane) fxmlLoader.load()); +// +// return fxmlLoader.getController(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// return null; +// } + + + /** + * ATTEMPTS TO: + * Sets up a new game state with your IP address as designated as the host. + * Starts a thread to listen for incoming connections. + * Starts a client to server thread and connects to own ip. + * Switches to the lobby screen + */ + @FXML + public void hostButtonPressed() { + new GameState(getLocalHostIp()); + gameClient = new GameClient(holder); + gameClient.runAsHost(getLocalHostIp(), 4942); +// try { +//// String ipAddress = InetAddress.getLocalHost().getHostAddress(); +//// new GameState(ipAddress); +//// new MainServerThread(); +//// ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950); +//// controller.setClientToServerThread(clientToServerThread); +// // get the lobby controller so that we can pass the game server thread to it +// new GameState(getLocalHostIp()); +// MainServerThread mainServerThread = new MainServerThread(); +//// ClientState.setHost(true); +// // host will connect and handshake to itself after setting up the server +// // TODO: 24/07/17 wmu16 - Make port number some static global type constant? +//// ClientToServerThread clientToServerThread = new ClientToServerThread(ClientState.getHostIp(), 4942); +//// ClientState.setConnectedToHost(true); +//// controller.setClientToServerThread(clientToServerThread); +// LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml"); +// lobbyController.setMainServerThread(mainServerThread); +// } catch (Exception e) { +// Alert alert = new Alert(AlertType.ERROR); +// alert.setHeaderText("Cannot host"); +// alert.setContentText("Oops, failed to host, try to restart."); +// alert.showAndWait(); +// e.printStackTrace(); +// } + } + + /** + * ATTEMPTS TO: + * Connect to an ip address and port using the ip and port specified on start screen. + * Starts a Client To Server Thread to maintain connection to host. + * Switch view to lobby view. + */ + @FXML + public void connectButtonPressed() { + // TODO: 10/07/17 wmu16 - Finish function + gameClient = new GameClient(holder); + gameClient.runAsClient(ipTextField.getText().trim().toLowerCase(), 4942); + +// try { +// String ipAddress = ipTextField.getText().trim().toLowerCase(); +// Integer port = Integer.valueOf(portTextField.getText().trim()); +// +//// ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, port); +//// ClientState.setHost(false); +//// ClientState.setConnectedToHost(true); +// +//// controller.setClientToServerThread(clientToServerThread); +//// setContentPane("/views/LobbyView.fxml"); +// } catch (Exception e) { +// Alert alert = new Alert(AlertType.ERROR); +// alert.setHeaderText("Cannot reach the host"); +// alert.setContentText("Please check your host IP address."); +// alert.showAndWait(); +// } + } + +// public void setController(Controller controller) { +// this.controller = controller; +// } + + /** + * Gets the local host ip address and sets this ip to ClientState. + * Only runs by the host. + * + * @return the localhost ip address + */ + private String getLocalHostIp() { + String ipAddress = null; + try { + Enumeration e = NetworkInterface.getNetworkInterfaces(); + while (e.hasMoreElements()) { + NetworkInterface ni = e.nextElement(); + if (ni.isLoopback()) + continue; + if(ni.isPointToPoint()) + continue; + if(ni.isVirtual()) + continue; + + Enumeration addresses = ni.getInetAddresses(); + while(addresses.hasMoreElements()) { + InetAddress address = addresses.nextElement(); + if(address instanceof Inet4Address) { // skip all ipv6 + ipAddress = address.getHostAddress(); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + if (ipAddress == null) { + System.out.println("[HOST] Cannot obtain local host ip address."); + } +// ClientState.setHostIp(ipAddress); + return ipAddress; + } +} diff --git a/src/main/java/seng302/controllers/annotations/Annotation.java b/src/main/java/seng302/visualiser/controllers/annotations/Annotation.java similarity index 75% rename from src/main/java/seng302/controllers/annotations/Annotation.java rename to src/main/java/seng302/visualiser/controllers/annotations/Annotation.java index 20a2c265..fddd2aa2 100644 --- a/src/main/java/seng302/controllers/annotations/Annotation.java +++ b/src/main/java/seng302/visualiser/controllers/annotations/Annotation.java @@ -1,4 +1,4 @@ -package seng302.controllers.annotations; +package seng302.visualiser.controllers.annotations; /** * Annotations the user can select as important diff --git a/src/main/java/seng302/controllers/annotations/ImportantAnnotationController.java b/src/main/java/seng302/visualiser/controllers/annotations/ImportantAnnotationController.java similarity index 98% rename from src/main/java/seng302/controllers/annotations/ImportantAnnotationController.java rename to src/main/java/seng302/visualiser/controllers/annotations/ImportantAnnotationController.java index 2fe1c774..0c39f226 100644 --- a/src/main/java/seng302/controllers/annotations/ImportantAnnotationController.java +++ b/src/main/java/seng302/visualiser/controllers/annotations/ImportantAnnotationController.java @@ -1,5 +1,7 @@ -package seng302.controllers.annotations; +package seng302.visualiser.controllers.annotations; +import java.net.URL; +import java.util.ResourceBundle; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; @@ -7,8 +9,7 @@ import javafx.scene.control.CheckBox; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; -import java.net.URL;; -import java.util.ResourceBundle; +; public class ImportantAnnotationController implements Initializable { diff --git a/src/main/java/seng302/controllers/annotations/ImportantAnnotationDelegate.java b/src/main/java/seng302/visualiser/controllers/annotations/ImportantAnnotationDelegate.java similarity index 90% rename from src/main/java/seng302/controllers/annotations/ImportantAnnotationDelegate.java rename to src/main/java/seng302/visualiser/controllers/annotations/ImportantAnnotationDelegate.java index ba50726e..a2b5e7a4 100644 --- a/src/main/java/seng302/controllers/annotations/ImportantAnnotationDelegate.java +++ b/src/main/java/seng302/visualiser/controllers/annotations/ImportantAnnotationDelegate.java @@ -1,4 +1,4 @@ -package seng302.controllers.annotations; +package seng302.visualiser.controllers.annotations; /** * An ImportantAnnotationDelegate handles updating the important annotations diff --git a/src/main/java/seng302/controllers/annotations/ImportantAnnotationsState.java b/src/main/java/seng302/visualiser/controllers/annotations/ImportantAnnotationsState.java similarity index 96% rename from src/main/java/seng302/controllers/annotations/ImportantAnnotationsState.java rename to src/main/java/seng302/visualiser/controllers/annotations/ImportantAnnotationsState.java index 5cc97a7f..73dc11c2 100644 --- a/src/main/java/seng302/controllers/annotations/ImportantAnnotationsState.java +++ b/src/main/java/seng302/visualiser/controllers/annotations/ImportantAnnotationsState.java @@ -1,4 +1,4 @@ -package seng302.controllers.annotations; +package seng302.visualiser.controllers.annotations; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/seng302/visualiser/fxObjects/AnnotationBox.java b/src/main/java/seng302/visualiser/fxObjects/AnnotationBox.java new file mode 100644 index 00000000..cdaf329e --- /dev/null +++ b/src/main/java/seng302/visualiser/fxObjects/AnnotationBox.java @@ -0,0 +1,230 @@ +package seng302.visualiser.fxObjects; + +import java.util.HashMap; +import java.util.Map; +import javafx.application.Platform; +import javafx.beans.value.ObservableValue; +import javafx.scene.CacheHint; +import javafx.scene.Group; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.Text; + +/** + * Grouping of string objects over a semi transparent background. + */ +public class AnnotationBox extends Group { + + @FunctionalInterface + public interface AnnotationFormatter { + String transformString (T input); + } + + /** + * Class stores a text object and relationship for updating the text object if needed + * + * @param The type of observable value passed to the annotation, if there is one. + */ + public class Annotation { + private Text text; + private ObservableValue source; + private AnnotationFormatter format; + + /** + * Constructor for observing annotation + * @param textObject the javaFX text object the annotation is displayed in + * @param source observable value that the annotation is taken from + * @param formatter interface describing how to format the source data if needed + */ + public Annotation (Text textObject, ObservableValue source, AnnotationFormatter formatter) { + this.text = textObject; + this.source = source; + this.format = formatter; + source.addListener((obs, oldVal, newVal) -> + Platform.runLater(() -> text.setText(format.transformString(newVal))) + ); + } + + /** + * Constructor for a static annotation + * @param textObject the javaFX text object the annotation is displayed in + * @param annotationText the static value of the test object + */ + public Annotation (Text textObject, String annotationText) { + textObject.setText(annotationText); + text = textObject; + } + + private Text getText () { + return text; + } + } + + //Text offset constants + private static final double X_OFFSET_TEXT = 20d; + private static final double Y_OFFSET_TEXT_INIT = -35d; + private static final double Y_OFFSET_PER_TEXT = 12d; + //Background constants + private static final double TEXT_BUFFER = 3; + private static final double BACKGROUND_X = X_OFFSET_TEXT - TEXT_BUFFER; + private static final double BACKGROUND_Y = Y_OFFSET_TEXT_INIT - TEXT_BUFFER; + private static final double BACKGROUND_H_PER_TEXT = 9.5d; + private static final double BACKGROUND_ARC_SIZE = 10; + + private int visibleAnnotations = 0; + private double backgroundWidth = 145d; + + private Rectangle background = new Rectangle(); + private Paint theme = Color.BLACK; + + private Map annotationsByName = new HashMap<>(); + + /** + * Creates an empty annotation box. The box is offset from (0,0) by (17, -38). + */ + public AnnotationBox() { + this.setCache(true); + background.setX(BACKGROUND_X); + background.setY(BACKGROUND_Y); + background.setWidth(backgroundWidth); + background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * 4); + background.setArcHeight(BACKGROUND_ARC_SIZE); + background.setArcWidth(BACKGROUND_ARC_SIZE); + background.setFill(new Color(1, 1, 1, 0.75)); + background.setStroke(theme); + background.setStrokeWidth(2); + background.setCache(true); + background.setCacheHint(CacheHint.SPEED); + this.getChildren().add(background); + } + + /** + * Adds an annotation to the box. Use the name to reference the annotation for removal or\ + * changing visibility. + * @param annotationName the name of the annotation. + * @param annotation the annotation. + */ + public void addAnnotation (String annotationName, Annotation annotation) { + annotationsByName.put(annotationName, annotation); + Platform.runLater(() -> { + this.getChildren().add(annotation.getText()); + visibleAnnotations++; + update(); + }); + } + + /** + * Adds an annotation with a constant text. + * @param annotationName The name of the annotation. Will be used to reference it later. + * @param annotationText The desired text. + */ + public void addAnnotation (String annotationName, String annotationText) { + Text text = getTextObject(); + addAnnotation(annotationName, new Annotation(text, annotationText)); + } + + /** + * Adds an annotation with the given name. The annotation will contain the value of the given + * ObservableValue. The formatter should return a String and takes an object of the same type as + * the ObservableValue as a parameter. The String is how you want the annotation to look. + * @param annotationName The annotation name. + * @param observable The observable value the annotation will display. + * @param formatter A formatting function for the observable value. + * @param The type of ObservableValue. + */ + public void addAnnotation (String annotationName, ObservableValue observable, + AnnotationFormatter formatter) { + Text newText = getTextObject(); + addAnnotation(annotationName, new Annotation<>(newText, observable, formatter)); + } + + /** + * Sets the visibility of the annotation with the given name if it exists. + * @param annotationName The name of the annotation + * @param visibility the desired visibility + */ + public void setAnnotationVisibility (String annotationName, boolean visibility) { + if (annotationsByName.containsKey(annotationName)) { + Text textField = annotationsByName.get(annotationName).text; + boolean currentState = textField.visibleProperty().get(); + if (visibility != currentState) { + if (visibility) + visibleAnnotations++; + else + visibleAnnotations--; + } + textField.setVisible(visibility); + update(); + } + } + + /** + * Removes the annotation with the given name if it exits. + * @param annotationName The name given when the annotation was created. + */ + public void removeAnnotation (String annotationName) { + if (annotationName.contains(annotationName)) { + Platform.runLater(() -> { + this.getChildren().remove(annotationsByName.remove(annotationName).getText()); + visibleAnnotations--; + update(); + }); + annotationsByName.remove(annotationName); + } + } + + /** + * Moves the annotation. + * @param x x location + * @param y y location + */ + public void setLocation (double x, double y) { + Platform.runLater(()-> this.relocate(x + BACKGROUND_X, y + BACKGROUND_Y)); + } + + /** + * Changes the width of the annotation box. Default is 145. + * @param width new width. + */ + public void setWidth (double width) { + backgroundWidth = width; + Platform.runLater(() -> background.setWidth(backgroundWidth)); + } + + private void update () { + background.setVisible(visibleAnnotations != 0); + background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * visibleAnnotations); + for (int i = 1; i <= visibleAnnotations; i++) { + Text text = (Text) this.getChildren().get(i); + if (text.visibleProperty().get()) { + text.setX(X_OFFSET_TEXT); + text.setY(Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * i); +// }); + } + } + } + + /** + * Returns a text object for an annotation. + * @return The text object + */ + private Text getTextObject() { + Text text = new Text(); + text.setFill(theme); + text.setStrokeWidth(2); + text.setCacheHint(CacheHint.SPEED); + text.setCache(true); + return text; + } + + /** + * Set the colour of the annotation box's border and text colour. + * @param value desired colour. + */ + public void setFill (Paint value) { + theme = value; + background.setStroke(theme); + annotationsByName.forEach((name, annotation) -> annotation.getText().setFill(theme)); + } +} diff --git a/src/main/java/seng302/visualiser/fxObjects/BoatObject.java b/src/main/java/seng302/visualiser/fxObjects/BoatObject.java new file mode 100644 index 00000000..3441ba2d --- /dev/null +++ b/src/main/java/seng302/visualiser/fxObjects/BoatObject.java @@ -0,0 +1,295 @@ +package seng302.visualiser.fxObjects; + +import java.util.ArrayList; +import javafx.application.Platform; +import javafx.geometry.Point2D; +import javafx.scene.CacheHint; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Line; +import javafx.scene.shape.Polygon; +import javafx.scene.shape.Polyline; +import javafx.scene.transform.Rotate; + +/** + * BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 + * dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path, + * a wake object and two text labels to annotate the boat teams name and the boats velocity. The + * boat will update it's position onscreen everytime UpdatePosition is called unless the window is + * minimized in which case it attempts to store animations and apply them when the window is + * maximised. + */ +public class BoatObject extends Group { + + //Constants for drawing + private static final double BOAT_HEIGHT = 15d; + private static final double BOAT_WIDTH = 10d; + + private double xVelocity; + private double yVelocity; + private double lastHeading; + //Graphical objects + private Polyline trail = new Polyline(); + private Polygon boatPoly; + private Wake wake; + private Line leftLayLine; + private Line rightLayline; + private double distanceTravelled, lastRotation; + private Point2D lastPoint; + private Paint colour = Color.BLACK; + private Boolean isSelected, destinationSet; //All boats are initialised as selected + private boolean isPlayer = false; + + /** + * Creates a BoatGroup with the default triangular boat polygon. + */ + public BoatObject() { + this(-BOAT_WIDTH / 2, BOAT_HEIGHT / 2, + 0.0, -BOAT_HEIGHT / 2, + BOAT_WIDTH / 2, BOAT_HEIGHT / 2); + } + + /** + * Creates a BoatGroup with the boat being the default polygon. The head of the boat should be + * at point (0,0). + * + * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat + * polygon. + */ + public BoatObject(double... points) { + initChildren(points); + } + + /** + * Creates the javafx objects that will be the in the group by default. + * + * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat + * polygon. + */ + private void initChildren(double... points) { + boatPoly = new Polygon(points); + boatPoly.setFill(colour); + boatPoly.setFill(this.colour); + boatPoly.setOnMouseEntered(event -> { + boatPoly.setFill(Color.FLORALWHITE); + boatPoly.setStroke(Color.RED); + }); + boatPoly.setOnMouseExited(event -> { + boatPoly.setFill(colour); + boatPoly.setFill(this.colour); + boatPoly.setStroke(Color.BLACK); + }); + boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected)); + boatPoly.setCache(true); + boatPoly.setCacheHint(CacheHint.SPEED); + +// annotationBox = new AnnotationBox(); +// annotationBox.setFill(colour); + + leftLayLine = new Line(); + rightLayline = new Line(); + trail.getStrokeDashArray().setAll(5d, 10d); + trail.setCache(true); + wake = new Wake(0, -BOAT_HEIGHT); + wake.setVisible(true); + super.getChildren().addAll(boatPoly);//, annotationBox); + } + + public void setFill (Paint value) { + this.colour = value; + boatPoly.setFill(colour); + trail.setStroke(colour); + } + + /** + * Moves the boat and its children annotations to coordinates specified + * + * @param x The X coordinate to move the boat to + * @param y The Y coordinate to move the boat to + * @param rotation The rotation by which the boat moves + * @param velocity The velocity the boat is moving + */ + public void moveTo(double x, double y, double rotation, double velocity) { + Double dx = Math.abs(boatPoly.getLayoutX() - x); + Double dy = Math.abs(boatPoly.getLayoutY() - y); + Platform.runLater(() -> { + rotateTo(rotation); + boatPoly.setLayoutX(x); + boatPoly.setLayoutY(y); + wake.setLayoutX(x); + wake.setLayoutY(y); + }); + wake.setRotation(rotation, velocity); +// rotateTo(rotation); +// boatPoly.setLayoutX(x); +// boatPoly.setLayoutY(y); +// wake.setLayoutX(x); +// wake.setLayoutY(y); +// wake.rotate(rotation); + +// wake.setRotation(rotation, groundSpeed); +// isStopped = false; +// destinationSet = true; + lastRotation = rotation; + + distanceTravelled += Math.sqrt((dx * dx) + (dy * dy)); + + if (distanceTravelled > 15 && isPlayer) { + distanceTravelled = 0d; + Platform.runLater(() -> trail.getPoints().addAll(x, y)); + } + } + + private void rotateTo(double rotation) { + boatPoly.getTransforms().setAll(new Rotate(rotation)); + } + + public void updateLocation() { +// double dx = xVelocity / 60; +// double dy = yVelocity / 60; +// +// distanceTravelled += Math.abs(dx) + Math.abs(dy); +// moveGroupBy(dx, dy); +// +// if (distanceTravelled > 70) { +// distanceTravelled = 0d; +// +// if (lastPoint != null) { +// Line l = new Line( +// lastPoint.getX(), +// lastPoint.getY(), +// boatPoly.getLayoutX(), +// boatPoly.getLayoutY() +// ); +// l.getStrokeDashArray().setAll(3d, 7d); +// l.setStroke(colour); +// l.setCache(true); +// l.setCacheHint(CacheHint.SPEED); +// lineGroup.getChildren().add(l); +// } +// lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); +// } +// wake.updatePosition(); + } + +// /** +// * This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next +// * gates position and the current wind +// * If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is +// * going up wind, if they are on different sides of the gate, then the boat is going downwind +// * @param canvasController +// */ +// public Boolean isUpwindLeg(GameViewController canvasController, Mark nextMark) { +// +// Double windAngle = StreamParser.getWindDirection(); +// GateMark thisGateMark = (GateMark) nextMark; +// SingleMark nextMark1 = thisGateMark.getSingleMark1(); +// SingleMark nextMark2 = thisGateMark.getSingleMark2(); +// Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude()); +// Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude()); +// +// Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); +// Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d); +// +// +// Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint); +// Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint); +// +// +// /* +// If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the +// boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going +// with the wind. +// */ +// return boatLineFuncResult.equals(windLineFuncResult); +// return true; +// } + + public void setIsSelected(Boolean isSelected) { + this.isSelected = isSelected; + setLineGroupVisible(isSelected); + setWakeVisible(isSelected); + setLayLinesVisible(isSelected); + } + + public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime, + boolean trail, boolean wake) { +// boatAnnotations.setVisible(teamName, velocity, estTime, legTime); +// this.wake.setVisible(wake); + this.trail.setVisible(trail); + } + + public void setLineGroupVisible(Boolean visible) { + trail.setVisible(visible); + } + + public void setWakeVisible(Boolean visible) { +// wake.setVisible(visible); + } + + public void setLayLinesVisible(Boolean visible) { + leftLayLine.setVisible(visible); + rightLayline.setVisible(visible); + } + + public void setLaylines(Line line1, Line line2) { + this.leftLayLine = line1; + this.rightLayline = line2; + } + + public ArrayList getLaylines() { + ArrayList laylines = new ArrayList<>(); + laylines.add(leftLayLine); + laylines.add(rightLayline); + return laylines; + } + + public Group getWake () { + return wake; + } + + public Node getTrail() { + return trail; + } + + public Double getBoatLayoutX() { + return boatPoly.getLayoutX(); + } + + + public Double getBoatLayoutY() { + return boatPoly.getLayoutY(); + } + + /** + * Sets this boat to appear highlighted + */ + public void setAsPlayer() { + boatPoly.getPoints().setAll( + -BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75, + 0.0, -BOAT_HEIGHT / 1.75, + BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75 + ); + boatPoly.setStroke(Color.BLACK); + boatPoly.setStrokeWidth(3); + isPlayer = true; + } + + public void setTrajectory(double heading, double velocity) { + wake.setRotation(lastHeading - heading, velocity); + rotateTo(heading); + xVelocity = Math.cos(Math.toRadians(heading)) * velocity; + yVelocity = Math.sin(Math.toRadians(heading)) * velocity; + lastHeading = heading; + } + + public void setTrajectory(double heading, double velocity, double scaleFactorX, double scaleFactorY) { +// wake.setRotation(lastHeading - heading, velocity); +// rotateTo(heading); +// xVelocity = Math.cos(Math.toRadians(heading)) * velocity * scaleFactorX; +// yVelocity = Math.sin(Math.toRadians(heading)) * velocity * scaleFactorY; + lastHeading = heading; + } +} \ No newline at end of file diff --git a/src/main/java/seng302/visualiser/fxObjects/CourseBoundary.java b/src/main/java/seng302/visualiser/fxObjects/CourseBoundary.java new file mode 100644 index 00000000..8f07fc5a --- /dev/null +++ b/src/main/java/seng302/visualiser/fxObjects/CourseBoundary.java @@ -0,0 +1,15 @@ +package seng302.visualiser.fxObjects; + +import javafx.scene.paint.Color; +import javafx.scene.shape.Polygon; + +/** + * Polygon with default course border settings. + */ +public class CourseBoundary extends Polygon { + public CourseBoundary() { + this.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1)); + this.setStrokeWidth(3); + this.setFill(new Color(0,0,0,0)); + } +} diff --git a/src/main/java/seng302/visualiser/fxObjects/Gate.java b/src/main/java/seng302/visualiser/fxObjects/Gate.java new file mode 100644 index 00000000..0e6c99e3 --- /dev/null +++ b/src/main/java/seng302/visualiser/fxObjects/Gate.java @@ -0,0 +1,20 @@ +package seng302.visualiser.fxObjects; + +import javafx.scene.paint.Paint; +import javafx.scene.shape.Line; + +/** + * Visual object representing a gate, intended to connect two mark objects. + */ +public class Gate extends Line { + + public Gate () { + super.setStrokeWidth(2); + super.getStrokeDashArray().setAll(2d, 5d); + } + + public Gate (Paint colour) { + this(); + super.setStroke(colour); + } +} diff --git a/src/main/java/seng302/visualiser/fxObjects/Marker.java b/src/main/java/seng302/visualiser/fxObjects/Marker.java new file mode 100644 index 00000000..5697f5ef --- /dev/null +++ b/src/main/java/seng302/visualiser/fxObjects/Marker.java @@ -0,0 +1,19 @@ +package seng302.visualiser.fxObjects; + +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; + +/** + * Visual object for a mark. + */ +public class Marker extends Circle { + + public Marker() { + super.setRadius(5); + } + + public Marker(Paint colour) { + this(); + super.setFill(colour); + } +} \ No newline at end of file diff --git a/src/main/java/seng302/fxObjects/Wake.java b/src/main/java/seng302/visualiser/fxObjects/Wake.java similarity index 83% rename from src/main/java/seng302/fxObjects/Wake.java rename to src/main/java/seng302/visualiser/fxObjects/Wake.java index ae9751d5..4d339dc6 100644 --- a/src/main/java/seng302/fxObjects/Wake.java +++ b/src/main/java/seng302/visualiser/fxObjects/Wake.java @@ -1,5 +1,6 @@ -package seng302.fxObjects; +package seng302.visualiser.fxObjects; +import javafx.application.Platform; import javafx.scene.CacheHint; import javafx.scene.Group; import javafx.scene.paint.Color; @@ -7,7 +8,6 @@ import javafx.scene.shape.Arc; import javafx.scene.shape.ArcType; import javafx.scene.shape.StrokeLineCap; import javafx.scene.transform.Rotate; -import javafx.scene.transform.Scale; /** * A group containing objects used to represent wakes onscreen. Contains functionality for their animation. @@ -42,7 +42,11 @@ public class Wake extends Group { arc.setCache(true); arc.setCacheHint(CacheHint.ROTATE); arc.setType(ArcType.OPEN); - arc.setStroke(new Color(0.18, 0.7, 1.0, 1.0 + (-0.99 / numWakes * i))); + arc.setStroke( + new Color( + 0.18, 0.7, 1.0, 1.0 + (-0.99 / numWakes * i) + ) + ); arc.setStrokeWidth(3.0); arc.setStrokeLineCap(StrokeLineCap.ROUND); arc.setFill(new Color(0.0, 0.0, 0.0, 0.0)); @@ -56,7 +60,15 @@ public class Wake extends Group { void setRotation (double rotation, double velocity) { // if (Math.abs(rotations[0] - rotation) > 20) { - rotate(rotation); + Platform.runLater(() -> { + rotate(rotation); + double rad = (14 / numWakes) + velocity; + for (Arc arc : arcs) { + arc.setRadiusX(rad); + arc.setRadiusY(rad); + rad += (14 / numWakes) + (velocity / 2.5); + } + }); // } else { // rotations[0] = rotation; // ((Rotate) arcs[0].getTransforms().get(0)).setAngle(rotation); @@ -79,12 +91,12 @@ public class Wake extends Group { // } // } - double rad = (14 / numWakes) + velocity; - for (Arc arc : arcs) { - arc.setRadiusX(rad); - arc.setRadiusY(rad); - rad += (14 / numWakes) + (velocity / 2.5); - } +// double rad = (14 / numWakes) + velocity; +// for (Arc arc : arcs) { +// arc.setRadiusX(rad); +// arc.setRadiusY(rad); +// rad += (14 / numWakes) + (velocity / 2.5); +// } } /** diff --git a/src/main/java/seng302/models/map/Boundary.java b/src/main/java/seng302/visualiser/map/Boundary.java similarity index 96% rename from src/main/java/seng302/models/map/Boundary.java rename to src/main/java/seng302/visualiser/map/Boundary.java index 4396d95d..21f2661d 100644 --- a/src/main/java/seng302/models/map/Boundary.java +++ b/src/main/java/seng302/visualiser/map/Boundary.java @@ -1,4 +1,4 @@ -package seng302.models.map; +package seng302.visualiser.map; /** * The Boundary class represents a rectangle territorial boundary on a map. It diff --git a/src/main/java/seng302/models/map/CanvasMap.java b/src/main/java/seng302/visualiser/map/CanvasMap.java similarity index 97% rename from src/main/java/seng302/models/map/CanvasMap.java rename to src/main/java/seng302/visualiser/map/CanvasMap.java index de6403c7..140a0b50 100644 --- a/src/main/java/seng302/models/map/CanvasMap.java +++ b/src/main/java/seng302/visualiser/map/CanvasMap.java @@ -1,11 +1,11 @@ -package seng302.models.map; +package seng302.visualiser.map; +import java.net.URL; import javafx.geometry.Point2D; import javafx.scene.image.Image; -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. diff --git a/src/main/java/seng302/models/map/MercatorProjection.java b/src/main/java/seng302/visualiser/map/MercatorProjection.java similarity index 94% rename from src/main/java/seng302/models/map/MercatorProjection.java rename to src/main/java/seng302/visualiser/map/MercatorProjection.java index 732bc3ee..3f86e628 100644 --- a/src/main/java/seng302/models/map/MercatorProjection.java +++ b/src/main/java/seng302/visualiser/map/MercatorProjection.java @@ -1,7 +1,7 @@ -package seng302.models.map; +package seng302.visualiser.map; import javafx.geometry.Point2D; -import seng302.utilities.GeoPoint; +import seng302.model.GeoPoint; /** * An utility class useful to convert between Geo locations and Mercator projection @@ -28,7 +28,7 @@ public class MercatorProjection { * @param geo GeoPoint (lat, lng) location to be projected * @return the projection Point2D (x, y) on planar */ - public static Point2D toMapPoint(GeoPoint geo) { + static Point2D toMapPoint(GeoPoint geo) { double x, y; Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0); x = (origin.getX() + geo.getLng() * pixelsPerLngDegree); diff --git a/src/main/java/seng302/models/map/TestMapController.java b/src/main/java/seng302/visualiser/map/TestMapController.java similarity index 95% rename from src/main/java/seng302/models/map/TestMapController.java rename to src/main/java/seng302/visualiser/map/TestMapController.java index fc319bcc..cd0b4c60 100644 --- a/src/main/java/seng302/models/map/TestMapController.java +++ b/src/main/java/seng302/visualiser/map/TestMapController.java @@ -1,13 +1,12 @@ -package seng302.models.map; +package seng302.visualiser.map; +import java.net.URL; +import java.util.ResourceBundle; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; -import java.net.URL; -import java.util.ResourceBundle; - public class TestMapController implements Initializable{ @FXML diff --git a/src/main/resources/server_config/race.xml b/src/main/resources/server_config/race.xml index 06f4f626..5798aeb7 100644 --- a/src/main/resources/server_config/race.xml +++ b/src/main/resources/server_config/race.xml @@ -28,30 +28,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -62,13 +38,13 @@ - - - - - - - + + + + + + + diff --git a/src/main/resources/server_config/xml_templates/race.ftlh b/src/main/resources/server_config/xml_templates/race.ftlh index 4349d2e3..e38bf0b7 100644 --- a/src/main/resources/server_config/xml_templates/race.ftlh +++ b/src/main/resources/server_config/xml_templates/race.ftlh @@ -27,31 +27,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -61,13 +37,13 @@ - - - - - - - + + + + + + + diff --git a/src/main/resources/views/CanvasView.fxml b/src/main/resources/views/CanvasView.fxml deleted file mode 100644 index 94c08695..00000000 --- a/src/main/resources/views/CanvasView.fxml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/main/resources/views/FinishScreenView.fxml b/src/main/resources/views/FinishScreenView.fxml index 736c8b74..ab1e5835 100644 --- a/src/main/resources/views/FinishScreenView.fxml +++ b/src/main/resources/views/FinishScreenView.fxml @@ -1,12 +1,16 @@ - - - - - - - + + + + + + + + + + + diff --git a/src/main/resources/views/HostLobbyView.fxml b/src/main/resources/views/HostLobbyView.fxml new file mode 100644 index 00000000..0c186619 --- /dev/null +++ b/src/main/resources/views/HostLobbyView.fxml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/views/TestMapView.fxml b/src/main/resources/views/TestMapView.fxml index 84df98f3..9334d94d 100644 --- a/src/main/resources/views/TestMapView.fxml +++ b/src/main/resources/views/TestMapView.fxml @@ -1,12 +1,9 @@ - - - - - - + + + diff --git a/src/main/resources/views/importantAnnotationSelectView.fxml b/src/main/resources/views/importantAnnotationSelectView.fxml index 8b21c2d9..4079b9ff 100644 --- a/src/main/resources/views/importantAnnotationSelectView.fxml +++ b/src/main/resources/views/importantAnnotationSelectView.fxml @@ -1,10 +1,11 @@ - - - - - + + + + + + diff --git a/src/test/java/seng302/BoatTest.java b/src/test/java/seng302/BoatTest.java index d1a88daf..9ab5b66d 100644 --- a/src/test/java/seng302/BoatTest.java +++ b/src/test/java/seng302/BoatTest.java @@ -1,7 +1,7 @@ //package seng302; // //import org.junit.Test; -//import seng302.models.Boat; +//import seng302.model.Boat; // //import static org.junit.Assert.assertEquals; // diff --git a/src/test/java/seng302/ColorsTest.java b/src/test/java/seng302/ColorsTest.java index 03bc9ad2..d9f1ee4f 100644 --- a/src/test/java/seng302/ColorsTest.java +++ b/src/test/java/seng302/ColorsTest.java @@ -3,10 +3,7 @@ package seng302; import javafx.scene.paint.Color; import org.junit.Assert; import org.junit.Test; -import seng302.models.Colors; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import seng302.model.Colors; public class ColorsTest { diff --git a/src/test/java/seng302/TestGeoUtils.java b/src/test/java/seng302/TestGeoUtils.java index 1660c6be..09232f55 100644 --- a/src/test/java/seng302/TestGeoUtils.java +++ b/src/test/java/seng302/TestGeoUtils.java @@ -1,12 +1,13 @@ 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; -import static org.junit.Assert.*; - /** * Test Class for the GeometryUtils class * Created by wmu16 on 24/05/17. diff --git a/src/test/java/seng302/TestRaceTimer.java b/src/test/java/seng302/TestRaceTimer.java index 542405b1..8fd5b645 100644 --- a/src/test/java/seng302/TestRaceTimer.java +++ b/src/test/java/seng302/TestRaceTimer.java @@ -1,25 +1,22 @@ package seng302; import org.junit.Test; -import seng302.controllers.RaceViewController; - -import static org.junit.Assert.assertTrue; public class TestRaceTimer { @Test public void testPositiveTimeString(){ - RaceViewController controller = new RaceViewController(); - String result = controller.convertTimeToMinutesSeconds(61); - - assertTrue(result.equals("01:01")); +// RaceViewController controller = new RaceViewController(); +// String result = controller.convertTimeToMinutesSeconds(61); +// +// assertTrue(result.equals("01:01")); } @Test public void testNegativeTimeString(){ - RaceViewController controller = new RaceViewController(); - String result = controller.convertTimeToMinutesSeconds(-61); - - assertTrue(result.equals("-01:01")); +// RaceViewController controller = new RaceViewController(); +// String result = controller.convertTimeToMinutesSeconds(-61); +// +// assertTrue(result.equals("-01:01")); } } diff --git a/src/test/java/seng302/server/TestConversions.java b/src/test/java/seng302/gameServer/server/TestConversions.java similarity index 91% rename from src/test/java/seng302/server/TestConversions.java rename to src/test/java/seng302/gameServer/server/TestConversions.java index 91bf44b3..b341def1 100644 --- a/src/test/java/seng302/server/TestConversions.java +++ b/src/test/java/seng302/gameServer/server/TestConversions.java @@ -1,10 +1,10 @@ -package seng302.server; - -import org.junit.Test; -import seng302.server.messages.BoatLocationMessage; +package seng302.gameServer.server; import static junit.framework.TestCase.assertEquals; +import org.junit.Test; +import seng302.gameServer.server.messages.BoatLocationMessage; + /** * Test conversions used by the boat location messages */ diff --git a/src/test/java/seng302/server/TestHeader.java b/src/test/java/seng302/gameServer/server/TestHeader.java similarity index 79% rename from src/test/java/seng302/server/TestHeader.java rename to src/test/java/seng302/gameServer/server/TestHeader.java index 3655bc03..3441e3bc 100644 --- a/src/test/java/seng302/server/TestHeader.java +++ b/src/test/java/seng302/gameServer/server/TestHeader.java @@ -1,10 +1,11 @@ -package seng302.server; - -import org.junit.Test; -import seng302.server.messages.*; +package seng302.gameServer.server; import static junit.framework.TestCase.assertTrue; +import org.junit.Test; +import seng302.gameServer.server.messages.Header; +import seng302.gameServer.server.messages.MessageType; + /** * Tests message header */ diff --git a/src/test/java/seng302/server/TestMessage.java b/src/test/java/seng302/gameServer/server/TestMessage.java similarity index 85% rename from src/test/java/seng302/server/TestMessage.java rename to src/test/java/seng302/gameServer/server/TestMessage.java index 6ba3a735..337202ec 100644 --- a/src/test/java/seng302/server/TestMessage.java +++ b/src/test/java/seng302/gameServer/server/TestMessage.java @@ -1,17 +1,13 @@ -package seng302.server; - -import org.junit.Test; -import seng302.server.messages.*; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; +package seng302.gameServer.server; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; +import org.junit.Test; +import seng302.gameServer.server.messages.Message; +import seng302.gameServer.server.messages.XMLMessage; +import seng302.gameServer.server.messages.XMLMessageSubType; + public class TestMessage { private static int XML_MESSAGE_LEN = 14; private static int CRC_LEN = 4; diff --git a/src/test/java/seng302/server/simulator/GeoUtilityTest.java b/src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java similarity index 94% rename from src/test/java/seng302/server/simulator/GeoUtilityTest.java rename to src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java index a2d22b49..7078eace 100644 --- a/src/test/java/seng302/server/simulator/GeoUtilityTest.java +++ b/src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java @@ -1,11 +1,11 @@ -package seng302.server.simulator; +package seng302.gameServer.server.simulator; + +import static org.junit.Assert.assertEquals; import org.junit.Test; -import seng302.utilities.GeoPoint; +import seng302.model.GeoPoint; import seng302.utilities.GeoUtility; -import static org.junit.Assert.*; - /** * To test methods in GeoUtility. * Created by Haoming on 28/04/17. diff --git a/src/test/java/seng302/model/mark/MarkTest.java b/src/test/java/seng302/model/mark/MarkTest.java new file mode 100644 index 00000000..125f20cc --- /dev/null +++ b/src/test/java/seng302/model/mark/MarkTest.java @@ -0,0 +1,45 @@ +//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/BoatGroupTest.java b/src/test/java/seng302/models/BoatGroupTest.java deleted file mode 100644 index 646ce9b0..00000000 --- a/src/test/java/seng302/models/BoatGroupTest.java +++ /dev/null @@ -1,95 +0,0 @@ -// TODO: 4/05/17 cir27 - Currently the CI cannot build these tests. Unsure of exact issue but believe it may be a driver issue. Find a way to make tests work. - -//package seng302.models; -// -//public class BoatGroupTest { -// -//} -package seng302.models; -/* - * Created by cir27 on 4/05/17. - -public class BoatGroupTest { - BoatGroup boatGroup; - @Before - public void setUp () { - Yacht b = new Yacht("TEST", 0.0, "T" ,0); - boatGroup = new BoatGroup(b, Color.BLACK); - } - - @Test - public void setDestinationFirstUseForcesLocationUpdate () { - boatGroup.setDestination(10, 10, 90, 0); - Polygon bp = (Polygon) boatGroup.getChildren().get(2); - Assert.assertTrue(10 == bp.getLayoutX()); - Assert.assertTrue(10 == bp.getLayoutY()); - } - - @Test - public void setDestinationFutureUseDoesntForce () { - for (int i = 0; i < 60; i++) { - boatGroup.setDestination(200, 200, 90, 0); - } - boatGroup.setDestination(210, 210, 90, 0); - Polygon bp = (Polygon) boatGroup.getChildren().get(2); - Assert.assertTrue(200 == bp.getLayoutX()); - Assert.assertTrue(200 == bp.getLayoutY()); - } - - @Test - public void setDestinationUnrealisticMovementForceUpdate () { - Polygon bp = (Polygon) boatGroup.getChildren().get(2); - double xLocation = bp.getLayoutX(); - double yLocation = bp.getLayoutY(); - boatGroup.setDestination(xLocation + 500, yLocation + 500, 90, 0); - Assert.assertTrue(xLocation + 500 == bp.getLayoutX()); - Assert.assertTrue(yLocation + 500 == bp.getLayoutY()); - } - - @Test - public void setDestinationUnrealisticNegativeForceUpdate () { - Polygon bp = (Polygon) boatGroup.getChildren().get(2); - double xLocation = bp.getLayoutX(); - double yLocation = bp.getLayoutY(); - boatGroup.setDestination(xLocation - 500, yLocation - 500, 90, 0); - Assert.assertTrue(xLocation - 500 == bp.getLayoutX()); - Assert.assertTrue(yLocation - 500 == bp.getLayoutY()); - } - - @Test - public void updatePositionGeneratesExpectedMovement () { - Polygon bp = (Polygon) boatGroup.getChildren().get(2); - double xLocation = bp.getLayoutX(); - double yLocation = bp.getLayoutY(); - int movement = 10; - double delay = RaceObject.getExpectedUpdateInterval(); - double defaultTimePeriod = 1000 / 60; - double expectedMovement = movement / delay * defaultTimePeriod; - for (int i = 0; i < 60; i++) { - boatGroup.setDestination(xLocation, yLocation, 90, 0); - } - boatGroup.setDestination(xLocation + 10, yLocation + 10, 90, 0); - boatGroup.updatePosition(1000/60); - Assert.assertEquals(expectedMovement, bp.getLayoutX() - xLocation, 0.0); - } - - @Test - public void correctRaceID () { - Assert.assertTrue(boatGroup.hasRaceId(0)); - } - - @Test - public void incorrectRaceID () { - Assert.assertTrue(!boatGroup.hasRaceId(2)); - } - - @Test - public void nothingOnWrongId () { - Polygon bp = (Polygon) boatGroup.getChildren().get(2); - double originalX = bp.getLayoutX(); - double originalY = bp.getLayoutY(); - boatGroup.setDestination(10, 10, 90, 12); - Assert.assertTrue(originalX == bp.getLayoutX()); - Assert.assertTrue(originalY == bp.getLayoutY()); - } -}*/ \ No newline at end of file diff --git a/src/test/java/seng302/models/MarkGroupTest.java b/src/test/java/seng302/models/MarkGroupTest.java deleted file mode 100644 index 37482695..00000000 --- a/src/test/java/seng302/models/MarkGroupTest.java +++ /dev/null @@ -1,90 +0,0 @@ -// TODO: 4/05/17 cir27 - Currently the CI cannot build these tests. Unsure of exact issue but believe it may be a driver issue. Find a way to make tests work. -// -//package seng302.models; -// -//public class MarkGroupTest { -// -//} -package seng302.models; -/* -import javafx.scene.shape.Circle; -import seng302.*; -import javafx.geometry.Point2D; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import seng302.models.mark.*; - -/** - * Created by cir27 on 4/05/17. - */ -//public class MarkGroupTest { -// private MarkGroup gateMG; -// private MarkGroup singleMG; -// -// @Before -// public void setUp () { -// Mark single = new SingleMark("SM", 0, 0 , 0); -// Mark gate = new GateMark( -// "GM", -// MarkType.OPEN_GATE, -// new SingleMark("GM1", 0, 0, 1), -// new SingleMark("GM2", 0, 0, 2), -// 0, -// 0); -// gateMG = new MarkGroup(gate, new Point2D(10, 10), new Point2D(20, 20)); -// singleMG = new MarkGroup(single, new Point2D(0, 0)); -// } -// -// @Test -// public void hasIDSingle () { -// Assert.assertTrue(singleMG.hasRaceId(0)); -// Assert.assertTrue(!singleMG.hasRaceId(100,12)); -// } -// -// @Test -// public void hasIdGate () { -// Assert.assertTrue(gateMG.hasRaceId(1)); -// Assert.assertTrue(gateMG.hasRaceId(2)); -// Assert.assertTrue(!gateMG.hasRaceId(100,12)); -// } -// -// @Test -// public void nothingOnWrongId () { -// double originalX = singleMG.getChildren().get(0).getLayoutX(); -// double originalY = singleMG.getChildren().get(0).getLayoutY(); -// singleMG.setDestination(10, 10, 0, 4); -// singleMG.updatePosition(400); -// Assert.assertTrue(originalX == singleMG.getChildren().get(0).getLayoutX()); -// Assert.assertTrue(originalY == singleMG.getChildren().get(0).getLayoutY()); -// } -// -// @Test -// public void correctMovementCorrectIdSingle () { -// double originalX = singleMG.getChildren().get(0).getLayoutX(); -// double originalY = singleMG.getChildren().get(0).getLayoutY(); -// long timeinterval = 1000/60; -// double expectedChange = 10 / 200 * timeinterval; -// singleMG.setDestination(originalX + 10, originalY + 10, 0, 0); -// singleMG.updatePosition(timeinterval); -// Assert.assertTrue(originalX + expectedChange == singleMG.getChildren().get(0).getLayoutX()); -// Assert.assertTrue(originalY + expectedChange == singleMG.getChildren().get(0).getLayoutY()); -// } -// -// @Test -// public void correctMovementCorrectIDGate () { -// double originalX1 = gateMG.getChildren().get(0).getLayoutX(); -// double originalY1 = gateMG.getChildren().get(0).getLayoutY(); -// double originalX2 = gateMG.getChildren().get(1).getLayoutX(); -// double originalY2 = gateMG.getChildren().get(1).getLayoutY(); -// long timeinterval = 1000/60; -// double expectedChange = 10 / 200 * timeinterval; -// gateMG.setDestination(originalX1 + 10, originalY1 + 10, 0, 1); -// gateMG.setDestination(originalX2 + 10, originalY2 + 10, 0, 2); -// gateMG.updatePosition(timeinterval); -// Assert.assertTrue(originalX1 + expectedChange == gateMG.getChildren().get(0).getLayoutX()); -// Assert.assertTrue(originalY1 + expectedChange == gateMG.getChildren().get(0).getLayoutY()); -// Assert.assertTrue(originalX2 + expectedChange == gateMG.getChildren().get(1).getLayoutX()); -// Assert.assertTrue(originalY2 + expectedChange == gateMG.getChildren().get(1).getLayoutY()); -// } -//} diff --git a/src/test/java/seng302/models/YachtTest.java b/src/test/java/seng302/models/YachtTest.java index b2e3ca8f..ba6de8c0 100644 --- a/src/test/java/seng302/models/YachtTest.java +++ b/src/test/java/seng302/models/YachtTest.java @@ -1,17 +1,17 @@ package seng302.models; - import java.util.ArrayList; import java.util.List; import org.junit.Before; -import org.junit.Test; -import seng302.utilities.GeoPoint; +import seng302.model.PolarTable; +import seng302.model.Yacht; +import seng302.model.GeoPoint; public class YachtTest { Double windDir; Double windSpd; - List yachts = new ArrayList(); + List yachts = new ArrayList<>(); @Before public void setUp() { diff --git a/src/test/java/seng302/models/mark/MarkTest.java b/src/test/java/seng302/models/mark/MarkTest.java deleted file mode 100644 index 8615be5f..00000000 --- a/src/test/java/seng302/models/mark/MarkTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package seng302.models.mark; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * 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/stream/TeamsParserTest.java b/src/test/java/seng302/models/stream/TeamsParserTest.java deleted file mode 100644 index 60cf52e8..00000000 --- a/src/test/java/seng302/models/stream/TeamsParserTest.java +++ /dev/null @@ -1,36 +0,0 @@ -//package seng302.models.parsers; -// -//import org.junit.Before; -//import org.junit.Test; -//import seng302.models.Boat; -//import seng302.models.Yacht; -// -//import java.util.ArrayList; -// -//import static org.junit.Assert.*; -// -///** -// * Created by Haoming on 18/03/17. -// */ -//public class TeamsParserTest { -// -// private TeamsParser tp; -// @Before -// public void readFile() { -// tp = new TeamsParser("/config/teams.xml"); -// } -// -// @Test -// public void getBoats() throws Exception { -// ArrayList boats = tp.getBoats(); -// -// assertEquals(6, boats.size(), 1e-10); -// -// assertEquals("Oracle Team USA", boats.get(0).getBoatName()); -// //assertEquals(30.9, boats.get(0).getVelocity(), 1e-10); -// -// assertEquals("Groupama Team France", boats.get(5).getBoatName()); -// //assertEquals(45.6, boats.get(5).getVelocity(), 1e-10); -// } -// -//} \ No newline at end of file diff --git a/src/test/java/seng302/models/map/MercatorProjectionTest.java b/src/test/java/seng302/visualiser/map/MercatorProjectionTest.java similarity index 93% rename from src/test/java/seng302/models/map/MercatorProjectionTest.java rename to src/test/java/seng302/visualiser/map/MercatorProjectionTest.java index 118d3bd8..03dcaccd 100644 --- a/src/test/java/seng302/models/map/MercatorProjectionTest.java +++ b/src/test/java/seng302/visualiser/map/MercatorProjectionTest.java @@ -1,11 +1,9 @@ -package seng302.models.map; +package seng302.visualiser.map; + +import static org.junit.Assert.assertEquals; import org.junit.Test; -import seng302.utilities.GeoPoint; - -import java.awt.geom.Point2D; - -import static org.junit.Assert.*; +import seng302.model.GeoPoint; /** * Unit test for Mercator Project class. diff --git a/src/test/java/seng302/visualizer/annotations/TestImportantAnnotationState.java b/src/test/java/seng302/visualizer/annotations/TestImportantAnnotationState.java index 72ef4a57..4c3e6101 100644 --- a/src/test/java/seng302/visualizer/annotations/TestImportantAnnotationState.java +++ b/src/test/java/seng302/visualizer/annotations/TestImportantAnnotationState.java @@ -1,12 +1,12 @@ package seng302.visualizer.annotations; +import static org.junit.Assert.assertEquals; + import org.junit.After; import org.junit.Before; import org.junit.Test; -import seng302.controllers.annotations.Annotation; -import seng302.controllers.annotations.ImportantAnnotationsState; - -import static org.junit.Assert.assertEquals; +import seng302.visualiser.controllers.annotations.Annotation; +import seng302.visualiser.controllers.annotations.ImportantAnnotationsState; public class TestImportantAnnotationState { private ImportantAnnotationsState importantAnnotationsState;