diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 00000000..f0fdafc3 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,17 @@ +engines: + pmd: + enabled: true + channel: "beta" + + fixme: + enabled: true + config: + strings: + - FIXME + - TODO + - BUG + - FIX + +ratings: + paths: + - "**.java" diff --git a/pom.xml b/pom.xml index 8d10c6fc..296a4f01 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,13 @@ 2.7.13 test + + + + org.freemarker + freemarker + 2.3.26-incubating + diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index c796998a..7069d719 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -4,76 +4,37 @@ import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; +import javafx.scene.image.Image; import javafx.stage.Stage; +import seng302.client.ClientPacketParser; import seng302.models.PolarTable; -import seng302.models.stream.StreamParser; import seng302.models.stream.StreamReceiver; -import seng302.server.ServerThread; public class App extends Application { @Override public void start(Stage primaryStage) throws Exception { - PolarTable.parsePolarFile(getClass().getResource("/config/acc_polars.csv").getFile()); + PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml")); primaryStage.setTitle("RaceVision"); primaryStage.setScene(new Scene(root, 1530, 960)); primaryStage.setMaxWidth(1530); primaryStage.setMaxHeight(960); + primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png"))); // primaryStage.setMaximized(true); primaryStage.show(); primaryStage.setOnCloseRequest(e -> { - StreamParser.appClose(); + ClientPacketParser.appClose(); StreamReceiver.noMoreBytes(); System.exit(0); }); + } public static void main(String[] args) { - StreamReceiver sr = null; - - new ServerThread("Racevision Test Server"); - - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (args.length == 1 && args[0].equals("-standalone")) { - return; - } - - if (args.length == 3 && args[0].equals("-server")) { - - sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream"); - - } else if (args.length == 2 && args[0].equals("-server")) { - switch (args[1]) { - case "internal": - sr = new StreamReceiver("localhost", 4949, "RaceStream"); - break; - case "staffserver": - sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); - break; - case "official": - sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); - break; - } - } - //Change the StreamReceiver in this else block to change the default data source. - else{ - sr = new StreamReceiver("localhost", 4949, "RaceStream"); - } - - sr.start(); - StreamParser streamParser = new StreamParser("StreamParser"); - streamParser.start(); - launch(args); - } } diff --git a/src/main/java/seng302/GeometryUtils.java b/src/main/java/seng302/GeometryUtils.java deleted file mode 100644 index ce8b9fe4..00000000 --- a/src/main/java/seng302/GeometryUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -package seng302; - -import javafx.geometry.Point2D; - -/** - * A Class for performing geometric calculations on the canvas - * Created by wmu16 on 24/05/17. - */ -public final class GeometryUtils { - - - /** - * Performs the line function on two points of a line and a test point to test which side of the line that point is - * on. If the return value is - * return 1, then the point is on one side of the line, - * return -1 then the point is on the other side of the line - * return 0 then the point is exactly on the line. - * @param linePoint1 One point of the line - * @param linePoint2 Second point of the line - * @param testPoint The point to test with this line - * @return A return value indicating which side of the line the point is on - */ - public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { - - Double x = testPoint.getX(); - Double y = testPoint.getY(); - Double x1 = linePoint1.getX(); - Double y1 = linePoint1.getY(); - Double x2 = linePoint2.getX(); - Double y2 = linePoint2.getY(); - - Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function - - if (result > 0) { - return 1; - } - else if (result < 0) { - return -1; - } - else { - return 0; - } - } - - - /** - * Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin - * point - * @param originPoint The point with which to use as the base for our vector addition - * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!) - * @param vectorLength The length out on this angle from the origin point to create the new point - * @return a Point2D - */ - public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) { - - Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); - Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); - - return new Point2D(endPointX, endPointY); - - } - -} diff --git a/src/main/java/seng302/models/stream/StreamParser.java b/src/main/java/seng302/client/ClientPacketParser.java similarity index 88% rename from src/main/java/seng302/models/stream/StreamParser.java rename to src/main/java/seng302/client/ClientPacketParser.java index e7286561..4198dc55 100644 --- a/src/main/java/seng302/models/stream/StreamParser.java +++ b/src/main/java/seng302/client/ClientPacketParser.java @@ -1,4 +1,4 @@ -package seng302.models.stream; +package seng302.client; import java.io.IOException; @@ -11,7 +11,6 @@ import java.util.Comparator; import java.util.Date; import java.util.Map; import java.util.TimeZone; -import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.PriorityBlockingQueue; @@ -23,6 +22,7 @@ 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; @@ -32,15 +32,13 @@ import seng302.models.stream.packets.StreamPacket; * that are threadsafe so the visualiser can always access the latest speed and position available * Created by kre39 on 23/04/17. */ -public class StreamParser extends Thread { +public class ClientPacketParser { public static ConcurrentHashMap> markLocations = new ConcurrentHashMap<>(); public static ConcurrentHashMap> boatLocations = new ConcurrentHashMap<>(); - private String threadName; - private Thread t; private static boolean newRaceXmlReceived = false; private static boolean raceStarted = false; - private static XMLParser xmlObject; + private static XMLParser xmlObject = new XMLParser(); private static boolean raceFinished = false; private static boolean streamStatus = false; private static long timeSinceStart = -1; @@ -48,64 +46,26 @@ public class StreamParser extends Thread { private static Map boatsPos = new ConcurrentSkipListMap<>(); private static double windDirection = 0; private static Double windSpeed = 0d; - private static Long currentTimeLong; + private static Long currentTimeLong; private static String currentTimeString; private static boolean appRunning; - + private static Map clientStateBoats = new ConcurrentHashMap<>(); //CONVERSION CONSTANTS - private static final Double MS_TO_KNOTS = 1.94384; + 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 - * - * @param threadName name of the thread */ - public StreamParser(String threadName) { - this.threadName = threadName; + public ClientPacketParser() { } - - /** - * Used to within threading so when the stream parser thread runs, it will keep looking for a - * packet to process until it is unable to find anymore packets - */ - public void run() { - appRunning = true; - try { - streamStatus = true; - xmlObject = new XMLParser(); - while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) { - Thread.sleep(1); - } - while (appRunning) { - StreamPacket packet = StreamReceiver.packetBuffer.take(); - parsePacket(packet); - Thread.sleep(1); - while (StreamReceiver.packetBuffer.peek() == null) { - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Used to start the stream parser thread when multithreading - */ - public void start() { - if (t == null) { - t = new Thread(this, threadName); - t.start(); - } - } - /** * 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 */ - private static void parsePacket(StreamPacket packet) { + public static void parsePacket(StreamPacket packet) { try { switch (packet.getType()) { case HEARTBEAT: @@ -145,8 +105,6 @@ public class StreamParser extends Thread { case AVG_WIND: extractAvgWind(packet); break; - default: - break; } } catch (NullPointerException e) { System.out.println("Error parsing packet"); @@ -212,8 +170,10 @@ public class StreamParser extends Thread { if (raceStatus == 4 || raceStatus == 8) { raceFinished = true; raceStarted = false; + ClientState.setRaceStarted(false); } else if (!raceStarted) { raceStarted = true; + ClientState.setRaceStarted(true); raceFinished = false; } timeSinceStart = timeTillStart; @@ -225,41 +185,40 @@ public class StreamParser extends Thread { int noBoats = payload[22]; int raceType = payload[23]; + clientStateBoats = ClientState.getBoats(); for (int i = 0; i < noBoats; i++) { long boatStatusSourceID = bytesToLong( Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20))); - Yacht boat = boats.get((int) boatStatusSourceID); - boat.setBoatStatus((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)]); - Long estTimeAtNextMark = bytesToLong( + 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))); - boat.setEstimateTimeAtNextMark(estTimeAtNextMark); - Long estTimeAtFinish = bytesToLong( + 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); -// boatsPos.put(estTimeAtFinish, boat); -// String boatStatus = "SourceID: " + boatStatusSourceID; -// boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)]; -// boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)]; -// boatStatus += "\nPenaltiesAwarded: " + (int)payload[29 + (i * 20)]; -// boatStatus += "\nPenaltiesServed: " + (int)payload[30 + (i * 20)]; -// boatStatus += "\nEstTimeAtNextMark: " + bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20))); -// boatStatus += "\nEstTimeAtFinish: " + bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20))); -// boatStatuses.add(boatStatus); + + Yacht clientBoat = clientStateBoats.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 + if (raceStatus == 3) { + ClientState.setRaceStarted(true); } -// if (isRaceStarted()) { -// int pos = 1; -// for (Yacht yacht : boatsPos.values()) { -// yacht.setPosition(String.valueOf(pos)); -// pos++; -// } -// } else { -// for (Yacht yacht : boatsPos.values()) { -// yacht.setPosition("-"); -// } -// } } private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){ @@ -306,9 +265,8 @@ public class StreamParser extends Thread { * @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( @@ -328,9 +286,10 @@ public class StreamParser extends Thread { xmlObject.constructXML(doc, messageType); if (messageType == 7) { //7 is the boat XML boats = xmlObject.getBoatXML().getCompetingBoats(); + ClientState.setBoats(xmlObject.getBoatXML().getCompetingBoats()); + ClientState.setDirtyState(true); } if (messageType == 6) { //6 is race info xml - newRaceXmlReceived = true; } } @@ -392,6 +351,7 @@ public class StreamParser extends Thread { int messageType = payload[1]; int length = payload[2]; String message = new String(Arrays.copyOfRange(payload, 3, 3 + length)); + System.out.println(message); } /** @@ -412,9 +372,9 @@ public class StreamParser extends Thread { //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); diff --git a/src/main/java/seng302/client/ClientState.java b/src/main/java/seng302/client/ClientState.java new file mode 100644 index 00000000..64512a1b --- /dev/null +++ b/src/main/java/seng302/client/ClientState.java @@ -0,0 +1,78 @@ +package seng302.client; + +import com.sun.org.apache.xpath.internal.operations.Bool; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import seng302.models.Yacht; + +/** + * Used by the client to store static variables to be used in game. + */ +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 dirtyState = true; + private static String clientSourceId = ""; + + 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 isDirtyState() { + return dirtyState; + } + + public static void setDirtyState(Boolean dirtyState) { + ClientState.dirtyState = dirtyState; + } + + 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 new file mode 100644 index 00000000..67cf1dbf --- /dev/null +++ b/src/main/java/seng302/client/ClientStateQueryingRunnable.java @@ -0,0 +1,43 @@ +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() {} + + @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) { + e.printStackTrace(); + } + + if (ClientState.isRaceStarted() && ClientState.isConnectedToHost()) { + setChanged(); + notifyObservers("game started"); + terminate(); + } + + if (ClientState.isDirtyState()) { + setChanged(); + notifyObservers("update players"); + ClientState.setDirtyState(false); + } + } + } + + public void terminate() { + terminate = true; + } +} diff --git a/src/main/java/seng302/client/ClientToServerThread.java b/src/main/java/seng302/client/ClientToServerThread.java new file mode 100644 index 00000000..1e76bd07 --- /dev/null +++ b/src/main/java/seng302/client/ClientToServerThread.java @@ -0,0 +1,192 @@ +package seng302.client; + +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.zip.CRC32; +import java.util.zip.Checksum; + +import seng302.models.stream.packets.StreamPacket; +import seng302.server.messages.BoatActionMessage; +import seng302.server.messages.Message; + +/** + * Created by kre39 on 13/07/17. + */ +public class ClientToServerThread implements Runnable { + + private static final int LOG_LEVEL = 1; + + private Thread thread; + + private Integer ourID; + + private Socket socket; + private InputStream is; + private OutputStream os; + + private Boolean updateClient = true; + private ByteArrayOutputStream crcBuffer; + + public ClientToServerThread(String ipAddress, Integer portNumber) throws Exception{ + 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, 1); + ClientState.setClientSourceId(String.valueOf(ourID)); + } else { + clientLog("Unsuccessful handshake", 1); + closeSocket(); + return; + } + + thread = new Thread(this); + thread.start(); + + } + + static void clientLog(String message, int logLevel){ + if(logLevel <= LOG_LEVEL){ + System.out.println("[CLIENT " + LocalDateTime.now().toLocalTime().toString() + "] " + message); + } + } + + public void run() { + int sync1; + int sync2; + // TODO: 14/07/17 wmu16 - Work out how to fix this while loop + while(ClientState.isConnectedToHost()) { + try { + //Perform a write if it is time to as delegated by the MainServerThread + if (updateClient) { + // TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream +// try { +// GameState.outputState(os); +// } catch (IOException e) { +// System.out.println("IO error in server thread upon writing to output stream"); +// } + updateClient = false; + } + crcBuffer = new ByteArrayOutputStream(); + sync1 = readByte(); + sync2 = readByte(); + //checking if it is the start of the packet + if(sync1 == 0x47 && sync2 == 0x83) { + int type = readByte(); + //No. of milliseconds since Jan 1st 1970 + long timeStamp = Message.bytesToLong(getBytes(6)); + skipBytes(4); + long payloadLength = Message.bytesToLong(getBytes(2)); + byte[] payload = getBytes((int) payloadLength); + Checksum checksum = new CRC32(); + checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size()); + long computedCrc = checksum.getValue(); + long packetCrc = Message.bytesToLong(getBytes(4)); + if (computedCrc == packetCrc) { + ClientPacketParser + .parsePacket(new StreamPacket(type, payloadLength, timeStamp, payload)); + // TODO: 17/07/17 wmu16 - Fix this or maybe we dont need to go through the main server at all!?!? +// packetBufferDelegate.addToBuffer(new StreamPacket(type, payloadLength, timeStamp, payload)); + } else { + clientLog("Packet has been dropped", 1); + } + } + } catch (Exception e) { + closeSocket(); + e.printStackTrace(); + return; + } + } + closeSocket(); + clientLog("Disconnected from server", 0); + } + + + /** + * Listens for an allocated sourceID and returns it to the server if recieved + * @return the sourceID allocated to us by the server + */ + private Integer threeWayHandshake() { + Integer ourSourceID = null; + while (true) { + try { + ourSourceID = is.read(); + } catch (IOException e) { + e.printStackTrace(); + } + if (ourSourceID != null) { + try { + os.write(ourSourceID); + return ourSourceID; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + } + } + + + + /** + * Send the post-start race course information + */ + public void sendBoatActionMessage(BoatActionMessage boatActionMessage) { + try { + os.write(boatActionMessage.getBuffer()); + } catch (IOException e) { + clientLog("COULD NOT WRITE TO SERVER", 0); + e.printStackTrace(); + } + } + + + public void closeSocket() { + try { + socket.close(); + } catch (IOException e) { + clientLog("Failed to close the socket", 0); + } + } + + + private int readByte() throws Exception { + int currentByte = -1; + try { + currentByte = is.read(); + crcBuffer.write(currentByte); + } catch (IOException e) { + e.printStackTrace(); + } + if (currentByte == -1){ + throw new Exception(); + } + return currentByte; + } + + private byte[] getBytes(int n) throws Exception{ + byte[] bytes = new byte[n]; + for (int i = 0; i < n; i++){ + bytes[i] = (byte) readByte(); + } + return bytes; + } + + private void skipBytes(long n) throws Exception{ + for (int i=0; i < n; i++){ + readByte(); + } + } + + public Thread getThread() { + return thread; + } +} diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 0dc8de48..06558675 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -21,9 +21,8 @@ import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; -import seng302.fxObjects.BoatAnnotations; +import seng302.client.ClientPacketParser; import seng302.fxObjects.BoatGroup; -import seng302.fxObjects.Wake; import seng302.models.Colors; import seng302.models.Yacht; import seng302.models.mark.GateMark; @@ -33,19 +32,12 @@ import seng302.models.mark.MarkType; import seng302.models.mark.SingleMark; import seng302.models.map.Boundary; import seng302.models.map.CanvasMap; -import seng302.models.stream.StreamParser; 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.server.simulator.GeoUtility; -import seng302.server.simulator.mark.Position; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.PriorityBlockingQueue; +import seng302.utilities.GeoPoint; +import seng302.utilities.GeoUtility; /** * Created by ptg19 on 15/03/17. @@ -118,6 +110,7 @@ public class CanvasController { // Bind canvas size to stack pane size. canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH)); canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT)); + } public void initializeCanvas() { @@ -162,13 +155,13 @@ public class CanvasController { raceViewController.updateSparkLine(); } updateGroups(); - if (StreamParser.isRaceFinished()) { + if (ClientPacketParser.isRaceFinished()) { this.stop(); } lastTime = now; } } - if (StreamParser.isRaceFinished()) { + if (ClientPacketParser.isRaceFinished()) { this.stop(); switchToFinishScreen(); } @@ -208,8 +201,8 @@ public class CanvasController { double bearingFromTopLeftToOrigin = Math .toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY())); // the top left extreme - Position topLeftPos = new Position(maxLatPoint.getLatitude(), minLonPoint.getLongitude()); - Position originPos = GeoUtility + 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 @@ -218,7 +211,7 @@ public class CanvasController { .pow(PANEL_WIDTH * metersPerPixelX, 2)); double bearingFromOriginToBottomRight = Math .toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT)); - Position bottomRightPos = GeoUtility + GeoPoint bottomRightPos = GeoUtility .getGeoCoordinate(originPos, bearingFromOriginToBottomRight, distanceFromOriginToBottomRight); @@ -237,7 +230,7 @@ public class CanvasController { * in a compound mark etc.. */ private void addRaceBorder() { - XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML(); + XMLParser.RaceXMLObject raceXMLObject = ClientPacketParser.getXmlObject().getRaceXML(); ArrayList courseLimits = raceXMLObject.getCourseLimit(); raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1)); raceBorder.setStrokeWidth(3); @@ -255,7 +248,7 @@ public class CanvasController { 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 (StreamParser.boatLocations.containsKey(boatGroup.getRaceId())) { + if (ClientPacketParser.boatLocations.containsKey(boatGroup.getRaceId())) { if (boatGroup.isStopped()) { updateBoatGroup(boatGroup); } @@ -264,7 +257,7 @@ public class CanvasController { } for (MarkGroup markGroup : markGroups) { for (Long id : markGroup.getRaceIds()) { - if (StreamParser.markLocations.containsKey(id)) { + if (ClientPacketParser.markLocations.containsKey(id)) { updateMarkGroup(id, markGroup); } } @@ -273,13 +266,14 @@ public class CanvasController { } private void checkForCourseChanges() { - if (StreamParser.isNewRaceXmlReceived()){ + if (ClientPacketParser.isNewRaceXmlReceived()) { addRaceBorder(); } } private void updateBoatGroup(BoatGroup boatGroup) { - PriorityBlockingQueue movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId()); + 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 { @@ -297,7 +291,8 @@ public class CanvasController { } void updateMarkGroup (long raceId, MarkGroup markGroup) { - PriorityBlockingQueue movementQueue = StreamParser.markLocations.get(raceId); + PriorityBlockingQueue movementQueue = ClientPacketParser.markLocations + .get(raceId); if (movementQueue.size() > 0){ try { BoatPositionPacket positionPacket = movementQueue.take(); @@ -313,12 +308,12 @@ public class CanvasController { * Draws all the boats. */ private void initializeBoats() { - Map boats = StreamParser.getBoats(); + Map boats = ClientPacketParser.getBoats(); Group wakes = new Group(); Group trails = new Group(); Group annotations = new Group(); - ArrayList participants = StreamParser.getXmlObject().getRaceXML() + ArrayList participants = ClientPacketParser.getXmlObject().getRaceXML() .getParticipants(); ArrayList participantIDs = new ArrayList<>(); for (Participant p : participants) { @@ -326,7 +321,7 @@ public class CanvasController { } for (Yacht boat : boats.values()) { - if (participantIDs.contains(boat.getSourceID())) { + if (participantIDs.contains(boat.getSourceId())) { boat.setColour(Colors.getColor()); BoatGroup boatGroup = new BoatGroup(boat, boat.getColour()); boatGroups.add(boatGroup); @@ -342,7 +337,8 @@ public class CanvasController { } private void initializeMarks() { - List allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks(); + List allMarks = ClientPacketParser.getXmlObject().getRaceXML() + .getNonDupCompoundMarks(); for (Mark mark : allMarks) { if (mark.getMarkType() == MarkType.SINGLE_MARK) { SingleMark sMark = (SingleMark) mark; @@ -408,7 +404,7 @@ public class CanvasController { */ private void fitMarksToCanvas() { //Check is called once to avoid unnecessarily change the course limits once the race is running - StreamParser.isNewRaceXmlReceived(); + ClientPacketParser.isNewRaceXmlReceived(); findMinMaxPoint(); double minLonToMaxLon = scaleRaceExtremities(); calculateReferencePointLocation(minLonToMaxLon); @@ -424,7 +420,8 @@ public class CanvasController { */ private void findMinMaxPoint() { List sortedPoints = new ArrayList<>(); - for (Limit limit : StreamParser.getXmlObject().getRaceXML().getCourseLimit()) { + + for (Limit limit : ClientPacketParser.getXmlObject().getRaceXML().getCourseLimit()) { sortedPoints.add(limit); } sortedPoints.sort(Comparator.comparingDouble(Limit::getLat)); @@ -584,4 +581,5 @@ public class CanvasController { 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 index 73b3766b..550f6f81 100644 --- a/src/main/java/seng302/controllers/Controller.java +++ b/src/main/java/seng302/controllers/Controller.java @@ -1,39 +1,99 @@ package seng302.controllers; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.fxml.Initializable; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Pane; - import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; -import seng302.models.stream.StreamParser; +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.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; - private void setContentPane(String jfxUrl) { + private Object setContentPane(String jfxUrl) { try { contentPane.getChildren().removeAll(); contentPane.getChildren().clear(); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - contentPane.getChildren() - .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); + 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) { contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - setContentPane("/views/StartScreenView.fxml"); - StreamParser.boatLocations.clear(); + StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml"); + startScreenController.setController(this); + ClientPacketParser.boatLocations.clear(); + + lastSendingTime = System.currentTimeMillis(); + } + + /** 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) { + 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/FinishScreenViewController.java b/src/main/java/seng302/controllers/FinishScreenViewController.java index a2d79f36..ab3adf0d 100644 --- a/src/main/java/seng302/controllers/FinishScreenViewController.java +++ b/src/main/java/seng302/controllers/FinishScreenViewController.java @@ -15,8 +15,8 @@ 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.StreamParser; import seng302.models.stream.XMLParser.RaceXMLObject.Participant; public class FinishScreenViewController implements Initializable { @@ -59,7 +59,7 @@ public class FinishScreenViewController implements Initializable { ); // check if the boat is racing - ArrayList participants = StreamParser.getXmlObject().getRaceXML() + ArrayList participants = ClientPacketParser.getXmlObject().getRaceXML() .getParticipants(); ArrayList participantIDs = new ArrayList<>(); for (Participant p : participants) { @@ -67,8 +67,8 @@ public class FinishScreenViewController implements Initializable { } // add data to table - for (Yacht boat : StreamParser.getBoatsPos().values()) { - if (participantIDs.contains(boat.getSourceID())) { + for (Yacht boat : ClientPacketParser.getBoatsPos().values()) { + if (participantIDs.contains(boat.getSourceId())) { data.add(boat); } } diff --git a/src/main/java/seng302/controllers/LobbyController.java b/src/main/java/seng302/controllers/LobbyController.java new file mode 100644 index 00000000..81ba9e9a --- /dev/null +++ b/src/main/java/seng302/controllers/LobbyController.java @@ -0,0 +1,233 @@ +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 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) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @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: "); + readyButton.setDisable(true); + } + + 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(); + } + + @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(); + } + } + }); + } + + 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(String.format("Player ID: %d", ids.get(i))); + listViews.get(i).setItems(competitors.get(i)); + imageViews.get(i).setVisible(true); + } + } + + 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() { + // 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); + } + + @FXML + public void readyButtonPressed() { +// setContentPane("/views/RaceView.fxml"); +// playTheme(); + GameState.setCurrentStage(GameStages.RACING); + mainServerThread.startGame(); + } + + +// 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"); + } + } + + public void setMainServerThread(MainServerThread mainServerThread) { + this.mainServerThread = mainServerThread; + } +} diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 741d856c..436da210 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -27,7 +27,8 @@ import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.util.Duration; import javafx.util.StringConverter; -import seng302.GeometryUtils; +import seng302.client.ClientPacketParser; +import seng302.utilities.GeoUtility; import seng302.controllers.annotations.Annotation; import seng302.controllers.annotations.ImportantAnnotationController; import seng302.controllers.annotations.ImportantAnnotationDelegate; @@ -38,7 +39,6 @@ import seng302.models.*; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.SingleMark; -import seng302.models.stream.StreamParser; import seng302.models.stream.XMLParser; import java.io.IOException; @@ -51,6 +51,8 @@ import java.util.stream.Collectors; */ public class RaceViewController extends Thread implements ImportantAnnotationDelegate { + @FXML + private Text windSpeedText; @FXML private LineChart raceSparkLine; @FXML @@ -93,7 +95,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel raceSparkLine.getYAxis().setTranslateX(-5); raceSparkLine.getYAxis().setAutoRanging(false); sparklineYAxis.setTickMarkVisible(false); - startingBoats = new ArrayList<>(StreamParser.getBoats().values()); + startingBoats = new ArrayList<>(ClientPacketParser.getBoats().values()); includedCanvasController.setup(this); includedCanvasController.initializeCanvas(); @@ -103,6 +105,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel initialiseBoatSelectionComboBox(); includedCanvasController.timer.start(); selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); + } @@ -136,7 +139,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel 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(); @@ -199,7 +201,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel */ void updateSparkLine(){ // Collect the racing boats that aren't already in the chart - ArrayList sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID()) + 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 @@ -212,7 +214,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel 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); + sparkLineData.put(yacht.getSourceId(), yachtData); }); // Lambda function to sort the series in order of leg (later legs shown more to the right) @@ -242,7 +244,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel * @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()); + XYChart.Series positionData = sparkLineData.get(yacht.getSourceId()); positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); } @@ -283,7 +285,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel event -> { updateRaceTime(); updateWindDirection(); - updateOrder(); +// updateOrder(); updateBoatSelectionComboBox(); }) ); @@ -303,7 +305,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel private Mark getNextMark(BoatGroup bg) { Integer legNumber = bg.getBoat().getLegNumber(); - List markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence(); + List markSequence = ClientPacketParser.getXmlObject() + .getRaceXML().getCompoundMarkSequence(); if (legNumber == 0) { return null; @@ -315,7 +318,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel if (legNumber + 2 == corner.getSeqID()) { Integer thisCompoundMarkID = corner.getCompoundMarkID(); - for (Mark mark : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) { + for (Mark mark : ClientPacketParser.getXmlObject().getRaceXML() + .getAllCompoundMarks()) { if (mark.getCompoundMarkID() == thisCompoundMarkID) { return mark; } @@ -328,11 +332,12 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel /** - * Updates the wind direction arrow and text as from info from the StreamParser + * Updates the wind direction arrow and text as from info from the ClientPacketParser */ private void updateWindDirection() { - windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection())); - windArrowText.setRotate(StreamParser.getWindDirection()); + windDirectionText.setText(String.format("%.1f°", ClientPacketParser.getWindDirection())); + windArrowText.setRotate(ClientPacketParser.getWindDirection()); + windSpeedText.setText(String.format("%.1f Knots", ClientPacketParser.getWindSpeed())); } @@ -340,7 +345,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel * Updates the clock for the race */ private void updateRaceTime() { - if (StreamParser.isRaceFinished()) { + if (ClientPacketParser.isRaceFinished()) { timerLabel.setFill(Color.RED); timerLabel.setText("Race Finished!"); } else { @@ -350,18 +355,18 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel /** - * Grabs the boats currently in the race as from the StreamParser and sets them to be selectable + * 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(StreamParser.getBoatsPos().values()); + .observableArrayList(ClientPacketParser.getBoatsPos().values()); boatSelectionComboBox.setItems(observableBoats); } /** - * Updates the order of the boats as from the StreamParser and sets them in the boat order + * Updates the order of the boats as from the ClientPacketParser and sets them in the boat order * section */ private void updateOrder() { @@ -370,16 +375,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); // list of racing boat id - ArrayList participants = StreamParser.getXmlObject().getRaceXML() + ArrayList participants = ClientPacketParser.getXmlObject().getRaceXML() .getParticipants(); ArrayList participantIDs = new ArrayList<>(); for (Participant p : participants) { participantIDs.add(p.getsourceID()); } - if (StreamParser.isRaceStarted()) { - for (Yacht boat : StreamParser.getBoatsPos().values()) { - if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing + if (ClientPacketParser.isRaceStarted()) { + for (Yacht boat : ClientPacketParser.getBoatsPos().values()) { + if (participantIDs.contains(boat.getSourceId())) { // check if the boat is racing if (boat.getBoatStatus() == 3) { // 3 is finish status Text textToAdd = new Text(boat.getPosition() + ". " + boat.getShortName() + " (Finished)"); @@ -396,8 +401,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } } } else { - for (Yacht boat : StreamParser.getBoats().values()) { - if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing + 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")); @@ -434,9 +439,11 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude()); HashMap angleAndSpeed; if (isUpwind) { - angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed()); + angleAndSpeed = PolarTable + .getOptimalUpwindVMG(ClientPacketParser.getWindSpeed()); } else { - angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed()); + angleAndSpeed = PolarTable + .getOptimalDownwindVMG(ClientPacketParser.getWindSpeed()); } Double resultingAngle = angleAndSpeed.keySet().iterator().next(); @@ -444,15 +451,23 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY()); Point2D gateMidPoint = markPoint1.midpoint(markPoint2); - Integer lineFuncResult = GeometryUtils.lineFunction(boatCurrentPos, gateMidPoint, markPoint2); + Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, 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()); + rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, + ClientPacketParser + .getWindDirection()); + leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, + ClientPacketParser + .getWindDirection()); } else if (lineFuncResult == -1) { - rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection()); - leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection()); + rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, + ClientPacketParser + .getWindDirection()); + leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, + ClientPacketParser + .getWindDirection()); } leftLayline.setStrokeWidth(0.5); @@ -548,16 +563,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel private String getTimeSinceStartOfRace() { String timerString = "0:00"; - if (StreamParser.getTimeSinceStart() > 0) { - String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60); + 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 * StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60); + String timerMinute = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() / 60); + String timerSecond = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() % 60); if (timerSecond.length() == 1) { timerSecond = "0" + timerSecond; } @@ -637,4 +652,5 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel 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 index 931874d5..b2503027 100644 --- a/src/main/java/seng302/controllers/StartScreenController.java +++ b/src/main/java/seng302/controllers/StartScreenController.java @@ -1,191 +1,162 @@ package seng302.controllers; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.ResourceBundle; -import java.util.Timer; -import java.util.TimerTask; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import java.net.Inet4Address; +import java.net.NetworkInterface; +import java.util.Enumeration; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; -import javafx.fxml.Initializable; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.cell.PropertyValueFactory; +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 javafx.scene.paint.Color; -import seng302.models.Yacht; -import seng302.models.stream.StreamParser; -import seng302.models.stream.XMLParser.RaceXMLObject.Participant; +import seng302.client.ClientState; +import seng302.client.ClientToServerThread; +import seng302.gameServer.GameState; +import seng302.gameServer.MainServerThread; -public class StartScreenController implements Initializable { +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 GridPane gridPane; + private TextField ipTextField; @FXML - private Label timeTillLive; + private TextField portTextField; @FXML - private Button streamButton; - @FXML - private Button switchToRaceViewButton; - @FXML - private TableView teamList; - @FXML - private TableColumn boatNameCol; - @FXML - private TableColumn shortNameCol; - @FXML - private TableColumn countryCol; - @FXML - private TableColumn posCol; - @FXML - private Label realTime; + private GridPane startScreen2; - private boolean switchedToRaceView = false; + private Controller controller; - private void setContentPane(String jfxUrl) { + /** + * Loads the fxml content into the parent pane + * @param jfxUrl + * @return the controller of the fxml + */ + private Object setContentPane(String jfxUrl) { try { - // get the main controller anchor pane (MainView.fxml) - AnchorPane contentPane = (AnchorPane) gridPane.getParent(); + AnchorPane contentPane = (AnchorPane) startScreen2.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))); + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl)); + contentPane.getChildren().addAll((Pane) fxmlLoader.load()); + + return fxmlLoader.getController(); } catch (javafx.fxml.LoadException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } + return null; } - @Override - public void initialize(URL location, ResourceBundle resources) { - gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + + /** + * 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); + } 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(); + } + + } /** - * Running a timer to update the livestream status on welcome screen. Update interval is 1 - * second. + * 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. */ - public void startStream() { - // reset boolean for switch to race view - switchedToRaceView = false; + @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()); - if (StreamParser.isStreamStatus()) { - streamButton.setVisible(false); - realTime.setVisible(true); - timeTillLive.setVisible(true); - timeTillLive.setTextFill(Color.GREEN); - timeTillLive.setText("Connecting..."); - Timer timer = new Timer(); - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - Platform.runLater(() -> { - if (StreamParser.isRaceStarted()) { - if (!switchedToRaceView) { - switchToRaceView(); - } - timer.cancel(); - } - if (StreamParser.isRaceFinished()) { - realTime.setText(StreamParser.getCurrentTimeString()); - timeTillLive.setTextFill(Color.RED); - timeTillLive.setText("Race finished! Waiting for new race..."); - switchToRaceViewButton.setDisable(true); - } else if (StreamParser.getTimeSinceStart() > 0) { - realTime.setText(StreamParser.getCurrentTimeString()); - updateTeamList(); - timeTillLive.setTextFill(Color.RED); - switchToRaceViewButton.setDisable(false); - String timerMinute = Long - .toString(StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long - .toString(StreamParser.getTimeSinceStart() % 60); - if (timerSecond.length() == 1) { - timerSecond = "0" + timerSecond; - } - String timerString = "-" + timerMinute + ":" + timerSecond; - timeTillLive.setText(timerString); - } else { - realTime.setText(StreamParser.getCurrentTimeString()); - updateTeamList(); - timeTillLive.setTextFill(Color.BLACK); - switchToRaceViewButton.setDisable(false); - String timerMinute = Long - .toString(-1 * StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long - .toString(-1 * StreamParser.getTimeSinceStart() % 60); - if (timerSecond.length() == 1) { - timerSecond = "0" + timerSecond; - } - String timerString = timerMinute + ":" + timerSecond; - timeTillLive.setText(timerString); - } - }); - } - }, 0, 1000); - } else { - timeTillLive.setText("Stream not available."); - timeTillLive.setTextFill(Color.RED); + 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 switchToRaceView() { - StreamParser.boatLocations.clear(); - switchedToRaceView = true; - setContentPane("/views/RaceView.fxml"); + public void setController(Controller controller) { + this.controller = controller; } - private void updateTeamList() { - ObservableList data = FXCollections.observableArrayList(); + /** + * 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; - teamList.setItems(data); - - boatNameCol.setCellValueFactory( - new PropertyValueFactory<>("boatName") - ); - shortNameCol.setCellValueFactory( - new PropertyValueFactory<>("shortName") - ); - countryCol.setCellValueFactory( - new PropertyValueFactory<>("country") - ); - posCol.setCellValueFactory( - new PropertyValueFactory<>("position") - ); - - // check if the boat is racing - ArrayList participants = StreamParser.getXmlObject().getRaceXML() - .getParticipants(); - ArrayList participantIDs = new ArrayList<>(); - for (Participant p : participants) { - participantIDs.add(p.getsourceID()); - } - - // add boats to the start screen list - if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos() - for (Yacht boat : StreamParser.getBoatsPos().values()) { - if (participantIDs.contains(boat.getSourceID())) { - data.add(boat); - } - } - } else { // else use StreamParser.getBoats() - for (Yacht boat : StreamParser.getBoats().values()) { - if (participantIDs.contains(boat.getSourceID())) { - data.add(boat); + 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(); } - teamList.refresh(); + 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 index fbba2257..26931ce9 100644 --- a/src/main/java/seng302/fxObjects/BoatAnnotations.java +++ b/src/main/java/seng302/fxObjects/BoatAnnotations.java @@ -5,8 +5,8 @@ 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 seng302.models.stream.StreamParser; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -83,12 +83,12 @@ public class BoatAnnotations extends Group{ } void update () { - velocityObject.setText(String.format(String.format("%.2f m/s", boat.getVelocity()))); + 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() - StreamParser.getCurrentTimeLong()); + .format(boat.getTimeTillNext() - ClientPacketParser.getCurrentTimeLong()); estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark); } else { estTimeToNextMarkObject.setText("Next mark: -"); @@ -97,7 +97,7 @@ public class BoatAnnotations extends Group{ if (boat.getMarkRoundTime() != null) { DateFormat format = new SimpleDateFormat("mm:ss"); String elapsedTime = format - .format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundTime()); + .format(ClientPacketParser.getCurrentTimeLong() - boat.getMarkRoundTime()); legTimeObject.setText("Last mark: " + elapsedTime); }else { legTimeObject.setText("Last mark: - "); diff --git a/src/main/java/seng302/fxObjects/BoatGroup.java b/src/main/java/seng302/fxObjects/BoatGroup.java index d5ab53b2..7a11d181 100644 --- a/src/main/java/seng302/fxObjects/BoatGroup.java +++ b/src/main/java/seng302/fxObjects/BoatGroup.java @@ -1,25 +1,21 @@ package seng302.fxObjects; import java.util.ArrayList; -import javafx.event.EventHandler; + import javafx.geometry.Point2D; import javafx.scene.CacheHint; import javafx.scene.Group; import javafx.scene.paint.Color; import javafx.scene.shape.Line; import javafx.scene.shape.Polygon; -import javafx.scene.text.Text; import javafx.scene.transform.Rotate; +import seng302.client.ClientPacketParser; import seng302.models.Yacht; -import seng302.GeometryUtils; +import seng302.utilities.GeoUtility; import seng302.controllers.CanvasController; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.SingleMark; -import seng302.models.stream.StreamParser; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; /** * BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 @@ -176,27 +172,27 @@ public class BoatGroup extends Group { isStopped = true; } - 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(boat.getColour()); - l.setCache(true); - l.setCacheHint(CacheHint.SPEED); - lineGroup.getChildren().add(l); - } - - if (destinationSet) { - lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); - } - } +// 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(boat.getColour()); +// l.setCache(true); +// l.setCacheHint(CacheHint.SPEED); +// lineGroup.getChildren().add(l); +// } +// +// if (destinationSet) { +// lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); +// } +// } wake.updatePosition(); } @@ -242,7 +238,7 @@ public class BoatGroup extends Group { */ public Boolean isUpwindLeg(CanvasController canvasController, Mark nextMark) { - Double windAngle = StreamParser.getWindDirection(); + Double windAngle = ClientPacketParser.getWindDirection(); GateMark thisGateMark = (GateMark) nextMark; SingleMark nextMark1 = thisGateMark.getSingleMark1(); SingleMark nextMark2 = thisGateMark.getSingleMark2(); @@ -250,11 +246,11 @@ public class BoatGroup extends Group { Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude()); Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); - Point2D windTestPoint = GeometryUtils.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d); + Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d); - Integer boatLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint); - Integer windLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint); + Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint); + Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint); /* @@ -320,7 +316,7 @@ public class BoatGroup extends Group { * @return An array containing all ID's associated with this RaceObject. */ public long getRaceId() { - return boat.getSourceID(); + return boat.getSourceId(); } public Group getWake () { diff --git a/src/main/java/seng302/fxObjects/MarkGroup.java b/src/main/java/seng302/fxObjects/MarkGroup.java index 2215aef7..597338a1 100644 --- a/src/main/java/seng302/fxObjects/MarkGroup.java +++ b/src/main/java/seng302/fxObjects/MarkGroup.java @@ -12,7 +12,6 @@ import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.MarkType; import seng302.models.mark.SingleMark; -import seng302.GeometryUtils; /** * Grouping of javaFX objects needed to represent a Mark on screen. diff --git a/src/main/java/seng302/gameServer/ClientConnectionDelegate.java b/src/main/java/seng302/gameServer/ClientConnectionDelegate.java new file mode 100644 index 00000000..fab71cd7 --- /dev/null +++ b/src/main/java/seng302/gameServer/ClientConnectionDelegate.java @@ -0,0 +1,17 @@ +package seng302.gameServer; + +import seng302.models.Player; + +public interface ClientConnectionDelegate { + /** + * A player has connected to the server + * @param serverToClientThread The player that has connected + */ + void clientConnected(ServerToClientThread serverToClientThread); + + /** + * A player has disconnected from the server + * @param player The player that has disconnected + */ + void clientDisconnected(Player player); +} diff --git a/src/main/java/seng302/gameServer/GameStages.java b/src/main/java/seng302/gameServer/GameStages.java new file mode 100644 index 00000000..ef436f64 --- /dev/null +++ b/src/main/java/seng302/gameServer/GameStages.java @@ -0,0 +1,24 @@ +package seng302.gameServer; + +/** + * An enum describing the states of the game + * Created by wmu16 on 11/07/17. + */ +public enum GameStages { + + LOBBYING(0), + PRE_RACE(1), + RACING(2), + FINISHED(3), + CANCELLED(4); + + private long code; + + GameStages(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java new file mode 100644 index 00000000..326caf52 --- /dev/null +++ b/src/main/java/seng302/gameServer/GameState.java @@ -0,0 +1,154 @@ +package seng302.gameServer; + +import java.util.*; + +import seng302.client.ClientPacketParser; +import seng302.models.Player; + +import seng302.models.Yacht; +import seng302.server.messages.BoatActionType; + +/** + * A Static class to hold information about the current state of the game (model) + * Created by wmu16 on 10/07/17. + */ +public class GameState { + + private static Long previousUpdateTime; + public static Double windDirection; + private static Double windSpeed; + + private static String hostIpAddress; + private static List players; + private static Map yachts; + private static Boolean isRaceStarted; + private static GameStages currentStage; + + public GameState(String hostIpAddress) { + windDirection = 170d; + windSpeed = 10000d; + yachts = new HashMap<>(); + players = new ArrayList<>(); + + + GameState.hostIpAddress = hostIpAddress; + players = new ArrayList<>(); + currentStage = GameStages.LOBBYING; + isRaceStarted = false; + yachts = new HashMap<>(); + //set this when game stage changes to prerace + previousUpdateTime = System.currentTimeMillis(); + yachts = new HashMap<>(); + } + + public static String getHostIpAddress() { + return hostIpAddress; + } + + public static List getPlayers() { + return players; + } + + public static void addPlayer(Player player) { + players.add(player); + } + + public static void removePlayer(Player player) { + players.remove(player); + } + + public static void addYacht(Integer sourceId, Yacht yacht) { + yachts.put(sourceId, yacht); + } + + public static void removeYacht(Integer yachtId) { + yachts.remove(yachtId); + } + + public static Boolean getIsRaceStarted() { + return isRaceStarted; + } + + public static GameStages getCurrentStage() { + return currentStage; + } + + public static void setCurrentStage(GameStages currentStage) { + GameState.currentStage = currentStage; + } + + public static Double getWindDirection() { + return windDirection; + } + + public static Double getWindSpeedMMS() { + return windSpeed; + } + + public static Double getWindSpeedKnots() { + return windSpeed / 1000 * ClientPacketParser.MS_TO_KNOTS; + } + + public static Map getYachts() { + return yachts; + } + + public static void updateBoat(Integer sourceId, BoatActionType actionType) { + Yacht playerYacht = yachts.get(sourceId); +// System.out.println("-----------------------"); + switch (actionType) { + case VMG: + playerYacht.turnToVMG(); +// System.out.println("Snapping to VMG"); + // TODO: 22/07/17 wmu16 - Add in the vmg calculation code here + break; + case SAILS_IN: + playerYacht.toggleSailIn(); +// System.out.println("Toggling Sails"); + break; + case SAILS_OUT: + playerYacht.toggleSailIn(); +// System.out.println("Toggling Sails"); + break; + case TACK_GYBE: + playerYacht.tackGybe(windDirection); +// System.out.println("Tack/Gybe"); + break; + case UPWIND: + playerYacht.turnUpwind(); +// System.out.println("Moving upwind"); + break; + case DOWNWIND: + playerYacht.turnDownwind(); +// System.out.println("Moving downwind"); + break; + } + + System.out.println("-----------------------"); + System.out.println("Sails are in: " + playerYacht.getSailIn()); + System.out.println("Heading: " + playerYacht.getHeading()); + System.out.println("Velocity: " + playerYacht.getVelocityMMS() / 1000); + System.out.println("Lat: " + playerYacht.getLocation().getLat()); + System.out.println("Lng: " + playerYacht.getLocation().getLng()); + System.out.println("-----------------------\n"); + } + + public static void update() { + + Long timeInterval = System.currentTimeMillis() - previousUpdateTime; + previousUpdateTime = System.currentTimeMillis(); + for (Yacht yacht : yachts.values()) { + yacht.update(timeInterval); + } + } + + + /** + * Generates a new ID based off the size of current players + 1 + * @return a playerID to be allocated to a new connetion + */ + public static Integer getUniquePlayerID() { + // TODO: 22/07/17 wmu16 - This may not be robust enough and may have to be improved on. + return yachts.size() + 1; + } +} diff --git a/src/main/java/seng302/gameServer/HeartbeatThread.java b/src/main/java/seng302/gameServer/HeartbeatThread.java new file mode 100644 index 00000000..0b7ce19a --- /dev/null +++ b/src/main/java/seng302/gameServer/HeartbeatThread.java @@ -0,0 +1,81 @@ +package seng302.gameServer; + +import seng302.models.Player; +import seng302.server.messages.Heartbeat; +import seng302.server.messages.Message; + +import java.io.IOException; +import java.util.*; + +/** + * Send Heartbeat messages to connected player at a specified interval + * Will call .clientDisconnected on the delegate when a heartbeat message + * cannot be sent to a player + */ +public class HeartbeatThread extends Thread{ + private final int HEARTBEAT_PERIOD = 200; + private ClientConnectionDelegate delegate; + private Integer seqNum; + private Stack disconnectedPlayers; + + public HeartbeatThread(ClientConnectionDelegate delegate){ + this.delegate = delegate; + seqNum = 0; + disconnectedPlayers = new Stack<>(); + } + + /** + * A player has lost connection to the server + * The player is added to a stack so that the delegate + * can be notified + * + * @param player The player that has disconnected + */ + private void playerLostConnection(Player player){ + disconnectedPlayers.push(player); + } + + /** + * Sends a heartbeat message to each connected player + * The delegate is notified if a player has disconnected + */ + private void sendHeartbeatToAllPlayers(){ + Message heartbeat = new Heartbeat(seqNum); + + for (Player player : GameState.getPlayers()){ + if (!player.getSocket().isConnected()) { + playerLostConnection(player); + } + + try { + player.getSocket().getOutputStream().write(heartbeat.getBuffer()); + } catch (IOException e) { + playerLostConnection(player); + } + } + + updateDelegate(); + seqNum++; + } + + /** + * Notifies the delegate about + * each disconnected player + */ + private void updateDelegate() { + while (!disconnectedPlayers.empty()){ + delegate.clientDisconnected(disconnectedPlayers.pop()); + } + } + + public void run(){ + Timer t = new Timer(); + + t.schedule(new TimerTask() { + @Override + public void run() { + sendHeartbeatToAllPlayers(); + } + }, 0, HEARTBEAT_PERIOD); + } +} diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java new file mode 100644 index 00000000..d2cc3473 --- /dev/null +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -0,0 +1,162 @@ +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.util.ArrayList; +import java.util.concurrent.PriorityBlockingQueue; + +/** + * A class describing the overall server, which creates and collects server threads for each client + * Created by wmu16 on 13/07/17. + */ +public class MainServerThread extends Observable implements Runnable, PacketBufferDelegate, ClientConnectionDelegate{ + + private static final int PORT = 4942; + private static final Integer MAX_NUM_PLAYERS = 3; + private static final Integer UPDATES_PER_SECOND = 2; + private static final int LOG_LEVEL = 1; + + private Thread thread; + + private ServerSocket serverSocket = null; + private Socket socket; + private ArrayList serverToClientThreads = new ArrayList<>(); + + private PriorityBlockingQueue packetBuffer; + + + public MainServerThread() { + try { + serverSocket = new ServerSocket(PORT); + } catch (IOException e) { + serverLog("IO error in server thread handler upon trying to make new server socket", 0); + } + + packetBuffer = new PriorityBlockingQueue<>(); + + thread = new Thread(this); + thread.start(); + } + + + public void run() { + ServerListenThread serverListenThread; + HeartbeatThread heartbeatThread; + + serverListenThread = new ServerListenThread(serverSocket, this); + heartbeatThread = new HeartbeatThread(this); + + heartbeatThread.start(); + serverListenThread.start(); + + + //You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app. + while (!thread.isInterrupted()) { + try { + Thread.sleep(1000 / UPDATES_PER_SECOND); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (GameState.getCurrentStage() == GameStages.PRE_RACE) { + GameState.update(); + } + + //RACING + if (GameState.getCurrentStage() == GameStages.RACING) { + GameState.update(); + updateClients(); + } + + //FINISHED + else if (GameState.getCurrentStage() == GameStages.FINISHED) { + + } + + while (!packetBuffer.isEmpty()){ + try { + StreamPacket packet = packetBuffer.take(); + ClientPacketParser.parsePacket(packet); + } catch (InterruptedException e) { + continue; + } + } + } + + // TODO: 14/07/17 wmu16 - Send out disconnect packet to clients + try { + serverSocket.close(); + return; + } catch (IOException e) { + System.out.println("IO error in server thread handler upon closing socket"); + } + } + + public void updateClients() { + for (ServerToClientThread serverToClientThread : serverToClientThreads) { + serverToClientThread.updateClient(); + } + } + + + static void serverLog(String message, int logLevel){ + if(logLevel <= LOG_LEVEL){ + System.out.println("[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message); + } + } + + @Override + public boolean addToBuffer(StreamPacket streamPacket) { + return packetBuffer.add(streamPacket); + } + + /** + * A client has tried to connect to the server + * @param serverToClientThread The player that connected + */ + @Override + public void clientConnected(ServerToClientThread serverToClientThread) { + serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0); + serverToClientThreads.add(serverToClientThread); + this.addObserver(serverToClientThread); + setChanged(); + notifyObservers(); + } + + /** + * A player has left the game, remove the player from the GameState + * @param player The player that left + */ + @Override + public void clientDisconnected(Player player) { + try { + player.getSocket().close(); + } catch (Exception e) { + serverLog("Cannot disconnect the socket for the disconnected player.", 0); + } + serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0); + GameState.removeYacht(player.getYacht().getSourceId()); + GameState.removePlayer(player); + for (ServerToClientThread serverToClientThread : serverToClientThreads) { + if (serverToClientThread.getSocket() == player.getSocket()) { + this.deleteObserver(serverToClientThread); + } + } + setChanged(); + notifyObservers(); + } + + public void startGame() { + for (ServerToClientThread serverToClientThread : serverToClientThreads) { + serverToClientThread.sendRaceStatusMessage(); + } + } +} diff --git a/src/main/java/seng302/gameServer/ServerListenThread.java b/src/main/java/seng302/gameServer/ServerListenThread.java new file mode 100644 index 00000000..f36d08df --- /dev/null +++ b/src/main/java/seng302/gameServer/ServerListenThread.java @@ -0,0 +1,44 @@ +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 + * Created by wmu16 on 11/07/17. + */ +public class ServerListenThread extends Thread{ + private ServerSocket serverSocket; + private ClientConnectionDelegate delegate; + + public ServerListenThread(ServerSocket serverSocket, ClientConnectionDelegate delegate){ + this.serverSocket = serverSocket; + this.delegate = delegate; + } + + /** + * Listens for a connection and upon finding one, creates a Player object and adds it to the universal GameState + */ + private void acceptConnection() { + try { + Socket thisClient = serverSocket.accept(); + if (thisClient != null){ + ServerToClientThread thisConnection = new ServerToClientThread(thisClient); + delegate.clientConnected(thisConnection); + } + } catch (IOException e) { + e.getMessage(); + } + } + + public void run(){ + while (true){ + acceptConnection(); + } + } +} diff --git a/src/main/java/seng302/gameServer/ServerPacketParser.java b/src/main/java/seng302/gameServer/ServerPacketParser.java new file mode 100644 index 00000000..155ebe04 --- /dev/null +++ b/src/main/java/seng302/gameServer/ServerPacketParser.java @@ -0,0 +1,37 @@ +package seng302.gameServer; + +import java.util.Arrays; +import seng302.models.stream.packets.StreamPacket; +import seng302.server.messages.BoatActionType; + + +public class ServerPacketParser { + + + public static BoatActionType extractBoatAction(StreamPacket packet) { + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long actionTypeValue = bytesToLong(Arrays.copyOfRange(payload, 0, 1)); + return BoatActionType.getType((int) actionTypeValue); + } + + /** + * takes an array of up to 7 bytes and returns a positive + * long constructed from the input bytes + * + * @return a positive long if there is less than 7 bytes -1 otherwise + */ + private static long bytesToLong(byte[] bytes) { + long partialLong = 0; + int index = 0; + for (byte b : bytes) { + if (index > 6) { + return -1; + } + partialLong = partialLong | (b & 0xFFL) << (index * 8); + index++; + } + return partialLong; + } +} + diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java new file mode 100644 index 00000000..5931aa40 --- /dev/null +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -0,0 +1,350 @@ +package seng302.gameServer; + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.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; + +/** + * A class describing a single connection to a Client for the purposes of sending and receiving on + * its own thread. All server threads created and owned by the server thread handler which can + * trigger client updates on its threads Created by wmu16 on 13/07/17. + */ +public class ServerToClientThread implements Runnable, Observer { + + private static final Integer LOG_LEVEL = 1; + private static final Integer MAX_ID_ATTEMPTS = 10; + + private Thread thread; + + private InputStream is; + private OutputStream os; + private Socket socket; + + private ByteArrayOutputStream crcBuffer; + + private Boolean userIdentified = false; + private Boolean connected = true; + private Boolean updateClient = true; +// private Boolean initialisedRace = true; + + private Integer seqNo; + private Integer sourceId; + + private XMLGenerator xml; + + public ServerToClientThread(Socket socket) { + this.socket = socket; + try { + is = socket.getInputStream(); + os = socket.getOutputStream(); + } catch (IOException e) { + System.out.println("IO error in server thread upon grabbing streams"); + } + //Attempt threeway handshake with connection + sourceId = GameState.getUniquePlayerID(); + if (threeWayHandshake(sourceId)) { + serverLog("Successful handshake. Client allocated id: " + sourceId, 1); + Yacht yacht = new Yacht("Yacht", sourceId, sourceId.toString(), "Kapa", "Kappa", "NZ"); +// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0); + GameState.addYacht(sourceId, yacht); + GameState.addPlayer(new Player(socket, yacht)); + } else { + serverLog("Unsuccessful handshake. Connection rejected", 1); + closeSocket(); + return; + } + + seqNo = 0; + thread = new Thread(this); + thread.start(); + } + + static void serverLog(String message, int logLevel) { + if (logLevel <= LOG_LEVEL) { + System.out.println( + "[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message); + } + } + + @Override + public void update(Observable o, Object arg) { + sendSetupMessages(); + } + + public void run() { + int sync1; + int sync2; + // TODO: 14/07/17 wmu16 - Work out how to fix this while loop + + while (socket.isConnected()) { + + try { +// if (initialisedRace) { +// sendSetupMessages(); +// initialisedRace = false; +// } + + //Perform a write if it is time to as delegated by the MainServerThread + if (updateClient) { + // TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream +// ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me"); +// sendMessage(chatterMessage); +// try { +// GameState.outputState(os); +// } catch (IOException e) { +// System.out.println("IO error in server thread upon writing to output stream"); +// } +// sendBoatLocationPackets(); + updateClient = false; + } + + crcBuffer = new ByteArrayOutputStream(); + sync1 = readByte(); + sync2 = readByte(); + //checking if it is the start of the packet + if (sync1 == 0x47 && sync2 == 0x83) { + int type = readByte(); + //No. of milliseconds since Jan 1st 1970 + long timeStamp = Message.bytesToLong(getBytes(6)); + skipBytes(4); + long payloadLength = Message.bytesToLong(getBytes(2)); + byte[] payload = getBytes((int) payloadLength); + Checksum checksum = new CRC32(); + checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size()); + long computedCrc = checksum.getValue(); + long packetCrc = Message.bytesToLong(getBytes(4)); + if (computedCrc == packetCrc) { + //System.out.println("RECEIVED A PACKET"); + switch (PacketType.assignPacketType(type)) { + case BOAT_ACTION: + BoatActionType actionType = ServerPacketParser + .extractBoatAction( + new StreamPacket(type, payloadLength, timeStamp, payload)); + GameState.updateBoat(sourceId, actionType); + break; + } + } else { + serverLog("Packet has been dropped", 1); + } + } + } catch (Exception e) { + // TODO: 24/07/17 zyt10 - fix a logic here when a client disconnected +// serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1); +// e.printStackTrace(); + closeSocket(); + return; + } + } + + } + + private void sendSetupMessages() { + xml = new XMLGenerator(); + Race race = new Race(); + + for (Yacht yacht : GameState.getYachts().values()) { + race.addBoat(yacht); + } + + //@TODO calculate lat/lng values + xml.setRegatta(new Regatta("RaceVision Test Game", 57.6679590, 11.8503233)); + xml.setRace(race); + + XMLMessage xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA, + xml.getRegattaAsXml().length()); + sendMessage(xmlMessage); + + xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT, + xml.getBoatsAsXml().length()); + sendMessage(xmlMessage); + + xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE, + xml.getRaceAsXml().length()); + sendMessage(xmlMessage); +// System.out.println("Sent xml messages for " + thread.getName()); + } + + public void updateClient() { + sendBoatLocationPackets(); + updateClient = true; + } + + + /** + * Tries to confirm the connection just accepted. + * Sends ID, expects that ID echoed for confirmation, + * if so, sends a confirmation packet back to that connection + * Creates a player instance with that ID and this thread and adds it to the GameState + * If not, close the socket and end the threads execution + * + * @param id the id to try and assign to the connection + * @return A boolean indicating if it was a successful handshake + */ + private Boolean threeWayHandshake(Integer id) { + Integer confirmationID = null; + Integer identificationAttempt = 0; + while (!userIdentified) { + try { + os.write(id); //Send out new ID looking for echo + confirmationID = is.read(); + } catch (IOException e) { + e.printStackTrace(); + } + + if (id.equals(confirmationID)) { //ID is echoed back. Connection is a client + return true; + } else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home. + return false; + } + identificationAttempt++; + } + + return true; + } + + private void closeSocket() { + try { + socket.close(); + } catch (IOException e) { + System.out.println("IO error in server thread upon trying to close socket"); + } + } + + + private int readByte() throws Exception { + int currentByte = -1; + try { + // @TODO @FIX ConnectionReset Exception when a client disconnects before it is garbage collected + currentByte = is.read(); + crcBuffer.write(currentByte); + } catch (IOException e) { + e.printStackTrace(); + } + if (currentByte == -1) { + throw new Exception(); + } + return currentByte; + } + + private byte[] getBytes(int n) throws Exception { + byte[] bytes = new byte[n]; + for (int i = 0; i < n; i++) { + bytes[i] = (byte) readByte(); + } + return bytes; + } + + private void skipBytes(long n) throws Exception { + for (int i = 0; i < n; i++) { + readByte(); + } + } + + public void sendMessage(Message message) { + try { + os.write(message.getBuffer()); + } catch (SocketException e) { + //serverLog("Player " + sourceId + " side socket disconnected", 0); + return; + } catch (IOException e) { + e.printStackTrace(); + } + } + + private int getSeqNo() { + seqNo++; + return seqNo; + } + + + private void sendBoatLocationPackets() { + ArrayList yachts = new ArrayList<>(GameState.getYachts().values()); + for (Yacht yacht : yachts) { +// System.out.println("[SERVER] Lat: " + yacht.getLocation().getLat() + " Lon: " + yacht.getLocation().getLng()); + BoatLocationMessage boatLocationMessage = + new BoatLocationMessage( + yacht.getSourceId(), + getSeqNo(), + yacht.getLocation().getLat(), + yacht.getLocation().getLng(), + yacht.getHeading(), + (long) yacht.getVelocityMMS()); + + sendMessage(boatLocationMessage); + } + } + + public Thread getThread() { + return thread; + } + + public void sendRaceStatusMessage() { + // variables taken from GameServerThread + int TIME_TILL_RACE_START = 20 * 1000; + long startTime = System.currentTimeMillis() + TIME_TILL_RACE_START; + + List boatSubMessages = new ArrayList<>(); + BoatStatus boatStatus; + RaceStatus raceStatus; + + for (Player player : GameState.getPlayers()) { + Yacht y = player.getYacht(); + + if (GameState.getCurrentStage() == GameStages.PRE_RACE) { + boatStatus = BoatStatus.PRESTART; + } else if (GameState.getCurrentStage() == GameStages.RACING) { + boatStatus = BoatStatus.RACING; + } else { + boatStatus = BoatStatus.UNDEFINED; + } + + BoatSubMessage m = new BoatSubMessage(y.getSourceId(), boatStatus, 0, 0, 0, 1234l, + 1234l); + boatSubMessages.add(m); + } + + if (GameState.getCurrentStage() == GameStages.RACING) { + raceStatus = RaceStatus.STARTED; + } else { + raceStatus = RaceStatus.WARNING; + } + + sendMessage(new RaceStatusMessage(1, raceStatus, startTime, GameState.getWindDirection(), + GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(), + RaceType.MATCH_RACE, 1, boatSubMessages)); + } + + public Socket getSocket() { + return socket; + } +} diff --git a/src/main/java/seng302/models/Player.java b/src/main/java/seng302/models/Player.java new file mode 100644 index 00000000..71260d9c --- /dev/null +++ b/src/main/java/seng302/models/Player.java @@ -0,0 +1,72 @@ +package seng302.models; + +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 + * Created by wmu16 on 10/07/17. + */ +public class Player { + + private Socket socket; + private Yacht yacht; + private Integer lastMarkPassed; + + + public Player(Socket socket, Yacht yacht) { + this.socket = socket; + this.yacht = yacht; + } + + public Socket getSocket() { + return socket; + } + + public Integer getLastMarkPassed() { + return lastMarkPassed; + } + + public void setLastMarkPassed(Integer lastMarkPassed) { + this.lastMarkPassed = lastMarkPassed; + } + + public Yacht getYacht() { + return yacht; + } + + @Override + public String toString() { + String playerAddress = null; + + if (socket == null){ + return "Disconnected Player"; + } + + playerAddress = socket.getRemoteSocketAddress().toString(); + + + return playerAddress; + } + + @Override + public boolean equals(Object obj) { + if (obj == null){ + return false; + } + + if (!(obj instanceof Player)){ + return false; + } + + return ((Player) obj).socket.equals(socket); + } + + @Override + public int hashCode(){ + return socket.hashCode(); + } +} diff --git a/src/main/java/seng302/models/PolarTable.java b/src/main/java/seng302/models/PolarTable.java index 168d2291..997b0356 100644 --- a/src/main/java/seng302/models/PolarTable.java +++ b/src/main/java/seng302/models/PolarTable.java @@ -1,6 +1,10 @@ package seng302.models; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.HashMap; @@ -24,9 +28,8 @@ 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 file containing the polar csv information */ - public static void parsePolarFile(String file) { + public static void parsePolarFile(InputStream polarFile) { polarTable = new HashMap<>(); upwindOptimal = new HashMap<>(); downwindOptimal = new HashMap<>(); @@ -34,7 +37,7 @@ public final class PolarTable { String line; Boolean isHeaderLine = true; - try (BufferedReader br = new BufferedReader(new FileReader(file))) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) { while ((line = br.readLine()) != null) { String[] thisLine = line.split(","); @@ -69,6 +72,8 @@ public final class PolarTable { } catch (IOException e) { e.printStackTrace(); } + + } @@ -122,7 +127,7 @@ public final class PolarTable { */ public static HashMap getOptimalUpwindVMG(Double thisWindSpeed) { - Double polarWindSpeed = getClosestMatch(thisWindSpeed); + Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed); return upwindOptimal.get(polarWindSpeed); } @@ -134,30 +139,47 @@ public final class PolarTable { */ public static HashMap getOptimalDownwindVMG(Double thisWindSpeed) { - Double polarWindSpeed = getClosestMatch(thisWindSpeed); + Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed); return downwindOptimal.get(polarWindSpeed); } - private static Double getClosestMatch(Double thisWindSpeed) { + public static Double getBoatSpeed(Double thisWindSpeed, Double thisHeading) { - ArrayList windValues = new ArrayList<>(polarTable.keySet()); + Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed); + Double polarAngle = getClosestAngleInPolar(polarTable.get(polarWindSpeed), thisHeading); - Double lowerVal = windValues.get(0); - Double upperVal = windValues.get(1); + return polarTable.get(polarWindSpeed).get(polarAngle); + } - for(int i = 0; i < windValues.size() - 1; i++) { - lowerVal = windValues.get(i); - upperVal = windValues.get(i+1); - if (thisWindSpeed <= upperVal) { - break; + + public static Double getClosestWindSpeedInPolar(Double thisWindSpeed) { + Double smallestDif = Double.POSITIVE_INFINITY; + Double closestWind = 0d; + + for (Double polarWindSpeed : polarTable.keySet()) { + Double difference = Math.abs(polarWindSpeed - thisWindSpeed); + if (difference < smallestDif) { + smallestDif = difference; + closestWind = polarWindSpeed; } } + return closestWind; + } - Double lowerDiff = Math.abs(lowerVal - thisWindSpeed); - Double upperDiff = Math.abs(upperVal - thisWindSpeed); - return (lowerDiff <= upperDiff) ? lowerVal : upperVal; + public static Double getClosestAngleInPolar(HashMap thisWindSpeedPolar, Double thisHeading) { + Double smallestDif = Double.POSITIVE_INFINITY; + Double closestAngle = 0d; + + for (Double polarAngle : thisWindSpeedPolar.keySet()) { + Double difference = Math.abs(polarAngle - thisHeading); + if (difference < smallestDif) { + smallestDif = difference; + closestAngle = polarAngle; + } + } + return closestAngle; } } \ No newline at end of file diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index c11c7407..5cb9b837 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -1,11 +1,17 @@ package seng302.models; -import javafx.scene.paint.Color; -import seng302.models.mark.Mark; -import seng302.controllers.RaceViewController; +import static seng302.utilities.GeoUtility.getGeoCoordinate; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; + +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; /** * Yacht class for the racing boat. @@ -15,15 +21,25 @@ import java.text.SimpleDateFormat; */ public class Yacht { + private final Double TURN_STEP = 5.0; + + private Double lastHeading; + private Boolean sailIn; + + // Used in boat group private Color colour; private String boatType; - private Integer sourceID; + private Integer sourceId; private String hullID; //matches HullNum in the XML spec. private String shortName; private String boatName; private String country; + + // Situational data + + // Boat status private Integer boatStatus; private Integer legNumber; @@ -31,7 +47,9 @@ public class Yacht { private Integer penaltiesServed; private Long estimateTimeAtFinish; private String position; - private double velocity; + private GeoPoint location; + private Double heading; + private Double velocity; private Long timeTillNext; private Long markRoundTime; @@ -40,13 +58,30 @@ public class Yacht { private Mark nextMark; + /** + * @param location latlon location of the boat stored in a geopoint + * @param heading heading of the boat in degrees from 0 to 365 with 0 being north + */ + public Yacht(GeoPoint location, Double heading) { + this.location = location; + this.heading = heading; + this.velocity = 0.0; + this.sailIn = false; + } + + /** * Used in EventTest and RaceTest. * * @param boatName Create a yacht object with name. */ - public Yacht(String boatName) { + public Yacht(String boatName, String shortName, GeoPoint location, Double heading) { this.boatName = boatName; + this.shortName = shortName; + this.location = location; + this.heading = heading; + this.velocity = 0.0; + this.sailIn = false; } /** @@ -60,29 +95,127 @@ public class Yacht { this.boatName = boatName; this.velocity = boatVelocity; this.shortName = shortName; - this.sourceID = id; + this.sourceId = id; + this.sailIn = false; } - public Yacht(String boatType, Integer sourceID, String hullID, String shortName, - String boatName, String country) { + + public Yacht(String boatType, Integer sourceId, String hullID, String shortName, + String boatName, String country) { this.boatType = boatType; - this.sourceID = sourceID; + this.sourceId = sourceId; this.hullID = hullID; 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 + this.velocity = 0d; //in mms-1 } + /** + * @param timeInterval since last update in milliseconds + */ + public void update(Long timeInterval) { + 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 / ClientPacketParser.MS_TO_KNOTS * 1000; + Double metersCovered = velocity * secondsElapsed; + location = getGeoCoordinate(location, heading, metersCovered); + } else { + velocity = 0d; + } + } + + + public Double getHeading() { + return heading; + } + + public void adjustHeading(Double amount) { + Double newVal = heading + amount; + lastHeading = heading; + // TODO: 24/07/17 wmu16 - '%' in java does remainder, we need modulo. All this must be changed here, this is why we have neg values! + heading = (double) Math.floorMod(newVal.longValue(), 360L); + } + + public void tackGybe(Double windDirection) { + Double normalizedHeading = heading - GameState.windDirection; + normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360); + adjustHeading(-2 * normalizedHeading); + } + + public void toggleSailIn() { + sailIn = !sailIn; + } + + public void turnUpwind() { + Double normalizedHeading = heading - GameState.windDirection; + normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360); + if (normalizedHeading == 0) { + if (lastHeading < 180) { + adjustHeading(-TURN_STEP); + } else { + adjustHeading(TURN_STEP); + } + } else if (normalizedHeading == 180) { + if (lastHeading < 180) { + adjustHeading(TURN_STEP); + } else { + adjustHeading(-TURN_STEP); + } + } else if (normalizedHeading < 180) { + adjustHeading(-TURN_STEP); + } else { + adjustHeading(TURN_STEP); + } + } + + public void turnDownwind() { + Double normalizedHeading = heading - GameState.windDirection; + normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360); + if (normalizedHeading == 0) { + if (lastHeading < 180) { + adjustHeading(TURN_STEP); + } else { + adjustHeading(-TURN_STEP); + } + } else if (normalizedHeading == 180) { + if (lastHeading < 180) { + adjustHeading(-TURN_STEP); + } else { + adjustHeading(TURN_STEP); + } + } else if (normalizedHeading < 180) { + adjustHeading(TURN_STEP); + } else { + adjustHeading(-TURN_STEP); + } + } + + public void turnToVMG() { + // TODO: 25/07/17 wmu16 - Fix this so it grabs the optimal value from the optimal Polar + } + + + public String getBoatType() { return boatType; } - public Integer getSourceID() { - return sourceID; + public Integer getSourceId() { + //@TODO Remove and merge with Creating Game Loop + if (sourceId == null) return 0; + return sourceId; } public String getHullID() { + if (hullID == null) return ""; return hullID; } @@ -95,6 +228,7 @@ public class Yacht { } public String getCountry() { + if (country == null) return ""; return country; } @@ -111,7 +245,8 @@ public class Yacht { } public void setLegNumber(Integer legNumber) { - if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus(sourceID)) { + if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus( + sourceId)) { RaceViewController.updateYachtPositionSparkline(this, legNumber); } this.legNumber = legNumber; @@ -171,10 +306,14 @@ public class Yacht { this.markRoundTime = markRoundingTime; } - public double getVelocity() { + public double getVelocityMMS() { return velocity; } + public Double getVelocityKnots() { + return velocity / 1000 * ClientPacketParser.MS_TO_KNOTS; + } + public Long getTimeTillNext() { return timeTillNext; } @@ -191,17 +330,25 @@ public class Yacht { this.lastMarkRounded = lastMarkRounded; } + public void setNextMark(Mark nextMark) { + this.nextMark = nextMark; + } + + public Mark getNextMark(){ + return nextMark; + } + + public Boolean getSailIn() { + return sailIn; + } + @Override public String toString() { return boatName; } - public void setNextMark(Mark nextMark) { - this.nextMark = nextMark; - } - - public Mark getNextMark(){ - return nextMark; - } + public GeoPoint getLocation() { + return location; + } } diff --git a/src/main/java/seng302/models/map/CanvasMap.java b/src/main/java/seng302/models/map/CanvasMap.java index 162358a1..ade3e3da 100644 --- a/src/main/java/seng302/models/map/CanvasMap.java +++ b/src/main/java/seng302/models/map/CanvasMap.java @@ -1,6 +1,8 @@ package seng302.models.map; +import javafx.geometry.Point2D; import javafx.scene.image.Image; +import seng302.utilities.GeoPoint; import javax.net.ssl.HttpsURLConnection; import java.net.URL; @@ -65,10 +67,10 @@ public class CanvasMap { private MapSize getMapSize(int zoom, Boundary boundary) { double scale = Math.pow(2, zoom); - MapGeo geoSW = new MapGeo(boundary.getSouthLat(), boundary.getWestLng()); - MapGeo geoNE = new MapGeo(boundary.getNorthLat(), boundary.getEastLng()); - MapPoint pointSW = MercatorProjection.toMapPoint(geoSW); - MapPoint pointNE = MercatorProjection.toMapPoint(geoNE); + GeoPoint geoSW = new GeoPoint(boundary.getSouthLat(), boundary.getWestLng()); + GeoPoint geoNE = new GeoPoint(boundary.getNorthLat(), boundary.getEastLng()); + Point2D pointSW = MercatorProjection.toMapPoint(geoSW); + Point2D pointNE = MercatorProjection.toMapPoint(geoNE); return new MapSize(Math.abs(pointNE.getX() - pointSW.getX()) * scale, Math.abs(pointNE.getY() - pointSW.getY()) * scale); } diff --git a/src/main/java/seng302/models/map/MapGeo.java b/src/main/java/seng302/models/map/MapGeo.java deleted file mode 100644 index 43d02565..00000000 --- a/src/main/java/seng302/models/map/MapGeo.java +++ /dev/null @@ -1,31 +0,0 @@ -package seng302.models.map; - -/** - * A class represent Geo location (latitude, longitude). - * Created by Haoming on 15/5/2017 - */ -class MapGeo { - - private double lat, lng; - - MapGeo(double lat, double lng) { - this.lat = lat; - this.lng = lng; - } - - double getLat() { - return lat; - } - - void setLat(double lat) { - this.lat = lat; - } - - double getLng() { - return lng; - } - - void setLng(double lng) { - this.lng = lng; - } -} diff --git a/src/main/java/seng302/models/map/MapPoint.java b/src/main/java/seng302/models/map/MapPoint.java deleted file mode 100644 index 41be919a..00000000 --- a/src/main/java/seng302/models/map/MapPoint.java +++ /dev/null @@ -1,31 +0,0 @@ -package seng302.models.map; - -/** - * A class represent euclidean planar point (x, y) - * Created by Haoming on 15/5/2017 - */ -class MapPoint { - - private double x, y; - - MapPoint(double x, double y) { - this.x = x; - this.y = y; - } - - double getX() { - return x; - } - - void setX(double x) { - this.x = x; - } - - double getY() { - return y; - } - - void setY(double y) { - this.y = y; - } -} diff --git a/src/main/java/seng302/models/map/MercatorProjection.java b/src/main/java/seng302/models/map/MercatorProjection.java index b4bf647d..732bc3ee 100644 --- a/src/main/java/seng302/models/map/MercatorProjection.java +++ b/src/main/java/seng302/models/map/MercatorProjection.java @@ -1,5 +1,8 @@ package seng302.models.map; +import javafx.geometry.Point2D; +import seng302.utilities.GeoPoint; + /** * An utility class useful to convert between Geo locations and Mercator projection * planar coordinates. @@ -22,31 +25,31 @@ public class MercatorProjection { /** * Projects a Geo Location (lat, lng) on a planar - * @param geo MapGeo (lat, lng) location to be projected - * @return the projection GeoPoint (x, y) on planar + * @param geo GeoPoint (lat, lng) location to be projected + * @return the projection Point2D (x, y) on planar */ - public static MapPoint toMapPoint(MapGeo geo) { - MapPoint point = new MapPoint(0, 0); - MapPoint origin = new MapPoint(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0); - point.setX(origin.getX() + geo.getLng() * pixelsPerLngDegree); + public 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); // NOTE(appleton): Truncating to 0.9999 effectively limits latitude to // 89.189. This is about a third of a tile past the edge of the world tile. double sinY = bound(Math.sin(Math.toRadians(geo.getLat()))); - point.setY(origin.getY() + 0.5 * Math.log((1 + sinY) / (1 - sinY)) * (-pixelsPerLngRadian)); - return point; + y = origin.getY() + 0.5 * Math.log((1 + sinY) / (1 - sinY)) * (-pixelsPerLngRadian); + return new Point2D(x, y); } /** * Converts the planar projection (x, y) back to Geo Location (lat, lng) - * @param point MapPoint (x, y) to be converted back + * @param point Point2D (x, y) to be converted back * @return the original Geo location converted from the given projection point */ - public static MapGeo toMapGeo(MapPoint point) { - MapPoint origin = new MapPoint(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0); + public static GeoPoint toMapGeo(Point2D point) { + Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0); double lng = (point.getX() - origin.getX()) / pixelsPerLngDegree; double latRadians = (point.getY() - origin.getY()) / (-pixelsPerLngRadian); double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2.0); - return new MapGeo(lat, lng); + return new GeoPoint(lat, lng); } } diff --git a/src/main/java/seng302/models/stream/PacketBufferDelegate.java b/src/main/java/seng302/models/stream/PacketBufferDelegate.java new file mode 100644 index 00000000..847b0de5 --- /dev/null +++ b/src/main/java/seng302/models/stream/PacketBufferDelegate.java @@ -0,0 +1,7 @@ +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/StreamReceiver.java b/src/main/java/seng302/models/stream/StreamReceiver.java index b1ddb996..8763a1e4 100644 --- a/src/main/java/seng302/models/stream/StreamReceiver.java +++ b/src/main/java/seng302/models/stream/StreamReceiver.java @@ -1,11 +1,15 @@ package seng302.models.stream; import seng302.models.stream.packets.StreamPacket; +import seng302.server.messages.BoatActionMessage; +import seng302.server.messages.BoatActionType; +import seng302.server.messages.Heartbeat; +import seng302.server.messages.Message; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; import java.util.Comparator; import java.util.concurrent.PriorityBlockingQueue; import java.util.zip.CRC32; @@ -13,9 +17,10 @@ import java.util.zip.Checksum; public class StreamReceiver extends Thread { - private InputStream stream; + private InputStream inputStream; + private OutputStream outputStream; private Socket host; - private ByteArrayOutputStream crcBuffer; + private ByteArrayOutputStream crcBuffer; private Thread t; private String threadName; public static PriorityBlockingQueue packetBuffer; @@ -58,49 +63,43 @@ public class StreamReceiver extends Thread { public void connect(){ - try { - stream = host.getInputStream(); - } catch (IOException e) { - e.printStackTrace(); - System.exit(1); - } - int sync1; - int sync2; - moreBytes = true; - while(moreBytes) { - try { - crcBuffer = new ByteArrayOutputStream(); - sync1 = readByte(); - sync2 = readByte(); - //checking if it is the start of the packet - if(sync1 == 0x47 && sync2 == 0x83) { - int type = readByte(); - //No. of milliseconds since Jan 1st 1970 - long timeStamp = bytesToLong(getBytes(6)); - skipBytes(4); - long payloadLength = bytesToLong(getBytes(2)); - byte[] payload = getBytes((int) payloadLength); - Checksum checksum = new CRC32(); - checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size()); - long computedCrc = checksum.getValue(); - long packetCrc = bytesToLong(getBytes(4)); - if (computedCrc == packetCrc) { - packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload)); - } else { - System.err.println("Packet has been dropped"); - } - } - } catch (Exception e) { - moreBytes = false; - } - } +// int sync1; +// int sync2; +// moreBytes = true; +// while(moreBytes) { +// try { +// crcBuffer = new ByteArrayOutputStream(); +// sync1 = readByte(); +// sync2 = readByte(); +// //checking if it is the start of the packet +// if(sync1 == 0x47 && sync2 == 0x83) { +// int type = readByte(); +// //No. of milliseconds since Jan 1st 1970 +// long timeStamp = bytesToLong(getBytes(6)); +// skipBytes(4); +// long payloadLength = bytesToLong(getBytes(2)); +// byte[] payload = getBytes((int) payloadLength); +// Checksum checksum = new CRC32(); +// checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size()); +// long computedCrc = checksum.getValue(); +// long packetCrc = bytesToLong(getBytes(4)); +// if (computedCrc == packetCrc) { +// packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload)); +// } else { +// System.err.println("Packet has been dropped"); +// } +// } +// } catch (Exception e) { +// moreBytes = false; +// } +// } } private int readByte() throws Exception { int currentByte = -1; try { - currentByte = stream.read(); + currentByte = inputStream.read(); crcBuffer.write(currentByte); } catch (IOException e) { e.printStackTrace(); diff --git a/src/main/java/seng302/models/stream/XMLParser.java b/src/main/java/seng302/models/stream/XMLParser.java index 99ce72c8..733bcb54 100644 --- a/src/main/java/seng302/models/stream/XMLParser.java +++ b/src/main/java/seng302/models/stream/XMLParser.java @@ -558,7 +558,7 @@ public class XMLParser { getNodeAttributeString(currentBoat, "Country")); this.boats.add(boat); if (boat.getBoatType().equals("Yacht")) { - competingBoats.put(boat.getSourceID(), boat); + competingBoats.put(boat.getSourceId(), boat); } } } diff --git a/src/main/java/seng302/models/stream/packets/PacketType.java b/src/main/java/seng302/models/stream/packets/PacketType.java index 0fd0be84..6737d53f 100644 --- a/src/main/java/seng302/models/stream/packets/PacketType.java +++ b/src/main/java/seng302/models/stream/packets/PacketType.java @@ -16,6 +16,7 @@ public enum PacketType { MARK_ROUNDING, COURSE_WIND, AVG_WIND, + BOAT_ACTION, OTHER; public static PacketType assignPacketType(int packetType){ @@ -44,6 +45,8 @@ public enum PacketType { return COURSE_WIND; case 47: return AVG_WIND; + case 100: + return BOAT_ACTION; default: } return OTHER; diff --git a/src/main/java/seng302/models/xml/Race.java b/src/main/java/seng302/models/xml/Race.java new file mode 100644 index 00000000..9be61f37 --- /dev/null +++ b/src/main/java/seng302/models/xml/Race.java @@ -0,0 +1,53 @@ +package seng302.models.xml; + +import seng302.models.Yacht; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A Race object that can be parsed into XML + */ +public class Race { + private List yachts; + private LocalDateTime startTime; + + public Race(){ + yachts = new ArrayList<>(); + startTime = LocalDateTime.now(); + } + + /** + * Add a boat to the race + * @param yacht The boat to add + */ + public void addBoat(Yacht yacht){ + yachts.add(yacht); + } + + /** + * Get a list of boats in the race + * @return A List of boats + */ + public List getBoats(){ + return Collections.unmodifiableList(yachts); + } + + /** + * Set the time until the race starts + * @param seconds The time in seconds until the race starts + */ + public void setRaceStartDelay(Integer seconds){ + startTime = startTime.plusMinutes(seconds); + } + + /** + * Get the time the race starts + * @return The time the race starts + */ + public String getRaceStartTime(){ + return startTime.toString(); + } +} diff --git a/src/main/java/seng302/models/xml/Regatta.java b/src/main/java/seng302/models/xml/Regatta.java new file mode 100644 index 00000000..733b7a0a --- /dev/null +++ b/src/main/java/seng302/models/xml/Regatta.java @@ -0,0 +1,77 @@ +package seng302.models.xml; + +/** + * A Race regatta that can be parsed into XML + */ +public class Regatta { + private final Double DEFAULT_ALTITUDE = 0d; + private final Integer DEFAULT_REGATTA_ID = 0; + + private Integer id; + private String name; + private String courseName; + + private Double latitude; + private Double longitude; + private Double altitude; + + private Integer utcOffset; + private Double magneticVariation; + + public Regatta(String name, Double latitude, Double longitude) { + this.name = name; + this.id = DEFAULT_REGATTA_ID; + this.courseName = name; + + this.latitude = latitude; + this.longitude = longitude; + this.altitude = DEFAULT_ALTITUDE; + + this.utcOffset = 0; + this.magneticVariation = 0d; + } + + public void setMagneticVariation(Double magneticVariation){ + this.magneticVariation = magneticVariation; + } + + public void setUtcOffset(Integer offset){ + this.utcOffset = offset; + } + + /* + NOTE!! The following getters must follow the JavaBean standard (getPropertyName()), and must be public. + */ + + public String getName(){ + return name; + } + + public String getCourseName(){ + return courseName; + } + + public Integer getRegattaId(){ + return id; + } + + public Double getLatitude() { + return latitude; + } + + public Double getLongitude() { + return longitude; + } + + public Double getAltitude() { + return altitude; + } + + public Integer getUtcOffset(){ + return utcOffset; + } + + public Double getMagneticVariation(){ + return magneticVariation; + } +} diff --git a/src/main/java/seng302/models/xml/XMLGenerator.java b/src/main/java/seng302/models/xml/XMLGenerator.java new file mode 100644 index 00000000..04a5b5fb --- /dev/null +++ b/src/main/java/seng302/models/xml/XMLGenerator.java @@ -0,0 +1,161 @@ +package seng302.models.xml; + +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; + +/** + * An XML generator to generate the Race, Boat, and Regatta XML dynamically + */ +public class XMLGenerator { + private static final String XML_TEMPLATE_DIR = "/server_config/xml_templates"; + private static final String REGATTA_TEMPLATE_NAME = "regatta.ftlh"; + private static final String BOATS_TEMPLATE_NAME = "boats.ftlh"; + private static final String RACE_TEMPLATE_NAME = "race.ftlh"; + private Configuration configuration; + private Regatta regatta; + private Race race; + + /** + * Set up a configuration instance for Apache Freemake + */ + private void setupConfiguration() { + configuration = new Configuration(Configuration.VERSION_2_3_26); + + try { + configuration.setClassForTemplateLoading(getClass(), XML_TEMPLATE_DIR); + } catch (NullPointerException e){ + System.out.println("[FATAL] Server could not load XML Template directory, ensure this directory isn't empty"); + } + } + + /** + * Create an instance of the XML Generator + */ + public XMLGenerator(){ + setupConfiguration(); + } + + /** + * Set the race regatta to send to players + * Note: This must be set before a regatta message can be generated + * @param regatta The race regatta + */ + public void setRegatta(Regatta regatta){ + this.regatta = regatta; + } + + /** + * Set the race to send to players + * Note: This must be set before a boat or race message can be generated + * @param race The race + */ + public void setRace(Race race){ + this.race = race; + } + + /** + * Parse an XML template and generate the output as a string + * @param templateName The templates file name + * @param type The XML message sub type + */ + private String parseToXmlString(String templateName, XMLMessageSubType type) throws IOException, TemplateException { + Template template; + ByteArrayOutputStream os = new ByteArrayOutputStream(); + OutputStreamWriter writer = new OutputStreamWriter(os); + + template = configuration.getTemplate(templateName); + + switch (type) { + case REGATTA: + template.process(regatta, writer); + break; + + case BOAT: + template.process(race, writer); + break; + + case RACE: + template.process(race, writer); + break; + + default: + throw new UnsupportedOperationException(); + } + + try { + return os.toString("UTF-8"); + } catch (UnsupportedEncodingException e) { + System.out.println("[FATAL] UTF-8 Not supported"); + return null; + } + } + + /** + * Get the race regatta as a string + * Note: Regatta must be set before calling this + * @return String containing the regatta XML, null if there was an error + */ + public String getRegattaAsXml(){ + String result = null; + + if (regatta == null) return null; + + try { + result = parseToXmlString(REGATTA_TEMPLATE_NAME, XMLMessageSubType.REGATTA); + } catch (TemplateException e) { + System.out.println("[FATAL] Error parsing regatta"); + } catch (IOException e) { + System.out.println("[FATAL] Error reading regatta"); + } + + return result; + } + + /** + * Get the boats XML as a string + * Note: Race must be set before calling this + * @return String containing the boats XML, null if there was an error + */ + public String getBoatsAsXml() { + String result = null; + + if (race == null) return null; + + try { + result = parseToXmlString(BOATS_TEMPLATE_NAME, XMLMessageSubType.BOAT); + } catch (TemplateException e) { + System.out.println("[FATAL] Error parsing boats"); + } catch (IOException e) { + System.out.println("[FATAL] Error reading boats"); + } + + return result; + } + + /** + * Get the race XML as a string + * Note: Race must be set before calling this + * @return String containing the race XML, null if there was an error + */ + public String getRaceAsXml() { + String result = null; + + if (race == null) return null; + + try { + result = parseToXmlString(RACE_TEMPLATE_NAME, XMLMessageSubType.RACE); + } catch (TemplateException e) { + System.out.println("[FATAL] Error parsing race"); + } catch (IOException e) { + System.out.println("[FATAL] Error reading race"); + } + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java deleted file mode 100644 index 3fbf79ae..00000000 --- a/src/main/java/seng302/server/ServerThread.java +++ /dev/null @@ -1,382 +0,0 @@ -package seng302.server; - -import seng302.server.simulator.mark.CompoundMark; -import seng302.server.simulator.mark.Mark; -import seng302.server.messages.*; -import seng302.server.simulator.Boat; -import seng302.server.simulator.Simulator; - -import java.io.IOException; -import java.io.InputStream; -import java.util.*; - -public class ServerThread implements Runnable, Observer { - private StreamingServerSocket server; - private long startTime; - private boolean raceStarted = false; - private Map boatsFinished = new HashMap<>(); - private List boats; - private Simulator raceSimulator; - private boolean sendingRaceFinishedLocationMessages = true; - - private final int HEARTBEAT_PERIOD = 5000; - private final int RACE_STATUS_PERIOD = 1000/2; - private final int RACE_START_STATUS_PERIOD = 1000; - private final int BOAT_LOCATION_PERIOD = 1000/5; - private final int PORT_NUMBER = 4949; - private final int TIME_TILL_RACE_START = 20*1000; - private static final int LOG_LEVEL = 1; - - public ServerThread(String threadName){ - Thread runner = new Thread(this, threadName); - runner.setDaemon(true); - - raceSimulator = new Simulator(BOAT_LOCATION_PERIOD); - raceSimulator.addObserver(this); - // run race simulator, so it can send boats' static location. - Thread raceSimulatorThread = new Thread(raceSimulator, "Race Simulator"); - - boats = raceSimulator.getBoats(); - - for (Boat b : boats){ - boatsFinished.put(b.getSourceID(), false); - } - - runner.start(); - raceSimulatorThread.start(); - } - - static void serverLog(String message, int logLevel){ - if(logLevel <= LOG_LEVEL){ - System.out.println("[SERVER] " + message); - } - } - - /** - * Creates and returns an XML Message from the file specified - * @param fileName The source XML file - * @param type The XML Message type - * @return The XML Message - */ - private Message getXmlMessage(String fileName, XMLMessageSubType type){ - String fileContents = null; - - try { - InputStream thisStream = this.getClass().getResourceAsStream(fileName); - fileContents = new String(org.apache.commons.io.IOUtils.toByteArray(thisStream)); - } catch (IOException e) { - e.printStackTrace(); - } catch (NullPointerException e){ - return null; - } - - if (fileContents != null){ - return new XMLMessage(fileContents, type, server.getSequenceNumber()); - } - - return null; - } - - /** - * @return Get a race status message for the current race - */ - private Message getRaceStatusMessage(){ - List boatSubMessages = new ArrayList<>(); - BoatStatus boatStatus; - RaceStatus raceStatus; - boolean thereAreBoatsNotFinished = false; - - for (Boat b : boats){ - if (!raceStarted){ - boatStatus = BoatStatus.PRESTART; - thereAreBoatsNotFinished = true; - } - else if(boatsFinished.get(b.getSourceID())){ - boatStatus = BoatStatus.FINISHED; - } - else{ - boatStatus = BoatStatus.PRESTART; - thereAreBoatsNotFinished = true; - } - - BoatSubMessage m = new BoatSubMessage(b.getSourceID(), boatStatus, b.getLastPassedCorner().getSeqID(), 0, 0, b.getEstimatedTimeTillFinish(), b.getEstimatedTimeTillFinish()); - boatSubMessages.add(m); - } - - if (thereAreBoatsNotFinished){ - if (raceStarted){ - raceStatus = RaceStatus.STARTED; - } - else{ - long currentTime = System.currentTimeMillis(); - long timeDifference = startTime - currentTime; - - if (timeDifference > 60*3){ - raceStatus = RaceStatus.PRESTART; - } - else if (timeDifference > 60){ - raceStatus = RaceStatus.WARNING; - } - else{ - raceStatus = RaceStatus.PREPARATORY; - } - } - } - else{ - raceStatus = RaceStatus.TERMINATED; - } - - return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.SOUTH, - 100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages); - } - - /** - * Starts an instance of the race simulator - */ - private void startRaceSim(){ - // set race started to true, so the simulator will start moving boats - raceSimulator.setRaceStarted(true); - } - - /** - * Starts sending heartbeat messages to the client - */ - private void startSendingHeartbeats() { - Timer t = new Timer(); - - t.schedule(new TimerTask() { - @Override - public void run() { - Message heartbeat = new Heartbeat(server.getSequenceNumber()); - - try { - server.send(heartbeat); - } catch (IOException e) { - e.printStackTrace(); - } - } - }, 0, HEARTBEAT_PERIOD); - } - - /** - * Start sending race start status messages until race starts - */ - private void startSendingRaceStartStatusMessages(){ - Timer t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - Message raceStartStatusMessage = new RaceStartStatusMessage(server.getSequenceNumber(), startTime , 1, - RaceStartNotificationType.SET_RACE_START_TIME); - try { - if (startTime < System.currentTimeMillis() && !raceStarted){ - startRaceSim(); - raceStarted = true; - } - else{ - server.send(raceStartStatusMessage); - } - - } catch (IOException e) { - e.printStackTrace(); - } - } - }, 0, RACE_START_STATUS_PERIOD); - } - - /** - * Start sending race start status messages until race starts - */ - private void startSendingRaceStatusMessages(){ - Timer t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - Message raceStatusMessage = getRaceStatusMessage(); - try { - server.send(raceStatusMessage); - } catch (IOException e) { - e.printStackTrace(); - } - } - }, 0, RACE_STATUS_PERIOD); - } - - /** - * Sends the race, boat, and regatta XML files to the client - */ - private void sendXml(){ - try{ - Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE); - Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT); - Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA); - - if (raceData != null){ - server.send(raceData); - } - if (boatData != null){ - server.send(boatData); - } - if (regatta != null){ - server.send(regatta); - } - } catch (IOException e) { - serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); - } - } - - /** - * Send the post-start race course information - */ - private void sendPostStartCourseXml(){ - Timer t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - try { - Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE); - if (raceData != null) { - server.send(raceData); - } - }catch (IOException e) { - serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); - } - } - },25000); - //Delays the new course xml data for 25 seconds so the boats are able to pass the starting line - } - - /** - * Starts sending boat location messages containing the mark positions - * Marks are flipped by 90 degrees from their original position - */ - private void startUpdatingMarkPositions(){ - Timer t = new Timer(); - t.schedule(new TimerTask() { - - /** - * Send the mark location message - * @param m The mark to send - * @param offset How far to move the marks from their original position - */ - private void sendMark(Mark m, Double offset){ - Message markLocation = new BoatLocationMessage(m.getSourceID(), server.getSequenceNumber(), - m.getLat()-offset, m.getLng()+offset*2, 0, 0); - - try { - server.send(markLocation); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void run() { - for (CompoundMark m : raceSimulator.getMarks()){ - if (m == null){ - continue; - } - - Mark mark1 = m.getMark1(); - Mark mark2 = m.getMark2(); - - if (mark1 != null){ - sendMark(mark1, 0.0002); - } - - if (mark2 != null){ - sendMark(mark2, 0.0005); - } - - } - } - }, 21000, 1000); - } - - public void run() { - try{ - server = new StreamingServerSocket(PORT_NUMBER); - } - catch (IOException e){ - serverLog("Failed to bind socket: " + e.getMessage(), 0); - } - - // Wait for client to connect - server.start(); - - startTime = System.currentTimeMillis() + TIME_TILL_RACE_START; - - startSendingHeartbeats(); - sendXml(); - startSendingRaceStartStatusMessages(); - startSendingRaceStatusMessages(); - sendPostStartCourseXml(); - startUpdatingMarkPositions(); - } - - /** - * Start sending static boat position updates when race has finished - */ - private void startSendingRaceFinishedBoatPositions(){ - Timer t = new Timer(); - t.schedule(new TimerTask() { - @Override - public void run() { - try { - for (Boat b : raceSimulator.getBoats()){ - Message m = new BoatLocationMessage(b.getSourceID(), server.getSequenceNumber(), b.getLat(), - b.getLng(), b.getLastPassedCorner().getBearingToNextCorner(), - ((long) 0)); - - server.send(m); - } - - } catch (IOException e) { - e.printStackTrace(); - } - } - }, 0, BOAT_LOCATION_PERIOD); - } - - /** - * Send a boat location message when they are updated by the simulator - * @param o . - * @param arg . - */ - @Override - @SuppressWarnings("unchecked") - public void update(Observable o, Object arg) { - // Only send if server started - // TODO: I don't understand why i need to check server is null or not ... confused - haoming 2/5/17 - if(server == null || !server.isStarted()){ - return; - } - - int numOfBoatsFinished = 0; - for (Boat boat : (List) arg){ - try { - if (boat.isFinished()) { - numOfBoatsFinished ++; - if (!boatsFinished.get(boat.getSourceID())) { - boatsFinished.put(boat.getSourceID(), true); - } - } - Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(), - boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(), - ((long) boat.getSpeed())); - server.send(m); - } catch (IOException e) { - serverLog("Couldn't send a boat status message", 3); - return; - } - catch (NullPointerException e){ - e.printStackTrace(); - } - } - - if (numOfBoatsFinished == ((List) arg).size()) { - startSendingRaceFinishedBoatPositions(); - } - - } -} diff --git a/src/main/java/seng302/server/StreamingServerSocket.java b/src/main/java/seng302/server/StreamingServerSocket.java deleted file mode 100644 index 35297f9f..00000000 --- a/src/main/java/seng302/server/StreamingServerSocket.java +++ /dev/null @@ -1,61 +0,0 @@ -package seng302.server; - -import seng302.server.messages.Message; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.nio.channels.Channels; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; -import java.util.ArrayList; -import java.util.List; - -class StreamingServerSocket { - private ServerSocketChannel socket; - private SocketChannel client; - private short seqNum; - private boolean isServerStarted; - - StreamingServerSocket(int port) throws IOException{ - socket = ServerSocketChannel.open(); - socket.socket().bind(new InetSocketAddress("localhost", port)); - //socket.setSoTimeout(10000); - seqNum = 0; - isServerStarted = false; - } - - void start(){ - try { - client = socket.accept(); - } catch (IOException e) { - e.getMessage(); - } - if (client.socket() == null){ - start(); - } - else{ - isServerStarted = true; - } - } - - void send(Message message) throws IOException{ - if (client == null){ - return; - } - - message.send(client); - - seqNum++; - } - - public short getSequenceNumber(){ - return seqNum; - } - - public boolean isStarted(){ - return isServerStarted; - } -} diff --git a/src/main/java/seng302/server/messages/BoatActionMessage.java b/src/main/java/seng302/server/messages/BoatActionMessage.java new file mode 100644 index 00000000..cf4ea918 --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatActionMessage.java @@ -0,0 +1,33 @@ +package seng302.server.messages; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * Created by kre39 on 12/07/17. + */ +public class BoatActionMessage extends Message{ + private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION; + private final int MESSAGE_SIZE = 1; + private BoatActionType actionType; + + public BoatActionMessage(BoatActionType actionType) { + this.actionType = actionType; + setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id + allocateBuffer(); + writeHeaderToBuffer(); + // Write message fields + putInt(actionType.getValue(), 1); + writeCRC(); + rewind(); + + } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + + +} diff --git a/src/main/java/seng302/server/messages/BoatActionType.java b/src/main/java/seng302/server/messages/BoatActionType.java new file mode 100644 index 00000000..f8318af7 --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatActionType.java @@ -0,0 +1,38 @@ +package seng302.server.messages; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by kre39 on 12/07/17. + */ +public enum BoatActionType { + + VMG(1), + SAILS_IN(2), + SAILS_OUT(3), + TACK_GYBE(4), + UPWIND(5), + DOWNWIND(6); + + private final int type; + private static final Map intToTypeMap = new HashMap<>(); + + static { + for (BoatActionType type : BoatActionType.values()) { + intToTypeMap.put(type.getValue(), type); + } + } + + BoatActionType(int type){ + this.type = type; + } + + public static BoatActionType getType(int value) { + return intToTypeMap.get(value); + } + + public int getValue() { + return this.type; + } +} diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java index 5e605170..ec0a4c0e 100644 --- a/src/main/java/seng302/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -1,7 +1,7 @@ package seng302.server.messages; import java.io.IOException; -import java.nio.channels.SocketChannel; +import java.io.OutputStream; public class BoatLocationMessage extends Message { private final int MESSAGE_SIZE = 56; @@ -39,7 +39,6 @@ public class BoatLocationMessage extends Message { * @param boatSpeed The boats speed */ public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){ - boatSpeed /= 10; messageVersionNumber = 1; time = System.currentTimeMillis(); this.sourceId = sourceId; @@ -64,6 +63,36 @@ public class BoatLocationMessage extends Message { this.rudderAngle = 0; setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize())); + allocateBuffer(); + writeHeaderToBuffer(); + + long headingToSend = (long)((heading/360.0) * 65535.0); + + putByte((byte) messageVersionNumber); + putInt(time, 6); + putInt((int) sourceId, 4); + putUnsignedInt((int) sequenceNum, 4); + putByte((byte) deviceType.getCode()); + putInt((int) latLonToBinaryPackedLong(latitude), 4); + putInt((int) latLonToBinaryPackedLong(longitude), 4); + putInt((int) altitude, 4); + putInt(headingToSend, 2); + putInt((int) pitch, 2); + putInt((int) roll, 2); + putInt((int) boatSpeed, 2); + putUnsignedInt((int) COG, 2); + putUnsignedInt((int) SOG, 2); + putUnsignedInt((int) apparentWindSpeed, 2); + putInt((int) apparentWindAngle, 2); + putUnsignedInt((int) trueWindSpeed, 2); + putUnsignedInt((int) trueWindDirection, 2); + putInt((int) trueWindAngle, 2); + putUnsignedInt((int) currentDrift, 2); + putUnsignedInt((int) currentSet, 2); + putInt((int) rudderAngle, 2); + + writeCRC(); + rewind(); } /** @@ -124,41 +153,4 @@ public class BoatLocationMessage extends Message { public int getSize() { return MESSAGE_SIZE; } - - - @Override - public void send(SocketChannel outputStream) throws IOException{ - allocateBuffer(); - writeHeaderToBuffer(); - - long headingToSend = (long)((heading/360.0) * 65535.0); - - putByte((byte) messageVersionNumber); - putInt(time, 6); - putInt((int) sourceId, 4); - putUnsignedInt((int) sequenceNum, 4); - putByte((byte) deviceType.getCode()); - putInt((int) latLonToBinaryPackedLong(latitude), 4); - putInt((int) latLonToBinaryPackedLong(longitude), 4); - putInt((int) altitude, 4); - putInt(headingToSend, 2); - putInt((int) pitch, 2); - putInt((int) roll, 2); - putInt((int) boatSpeed, 2); - putUnsignedInt((int) COG, 2); - putUnsignedInt((int) SOG, 2); - putUnsignedInt((int) apparentWindSpeed, 2); - putInt((int) apparentWindAngle, 2); - putUnsignedInt((int) trueWindSpeed, 2); - putUnsignedInt((int) trueWindDirection, 2); - putInt((int) trueWindAngle, 2); - putUnsignedInt((int) currentDrift, 2); - putUnsignedInt((int) currentSet, 2); - putInt((int) rudderAngle, 2); - - writeCRC(); - rewind(); - - outputStream.write(getBuffer()); - } } diff --git a/src/main/java/seng302/server/messages/ChatterMessage.java b/src/main/java/seng302/server/messages/ChatterMessage.java new file mode 100644 index 00000000..8480a9d5 --- /dev/null +++ b/src/main/java/seng302/server/messages/ChatterMessage.java @@ -0,0 +1,38 @@ +package seng302.server.messages; + +/** + * Created by kre39 on 20/07/17. + */ +public class ChatterMessage extends Message { + + private final long MESSAGE_VERSION_NUMBER = 1; + private final int MESSAGE_SIZE = 3; + private int message_type; + private int message_size = 21; + private String message; + + public ChatterMessage(int message_type, int message_size, String message) { + this.message_type = message_type; + this.message_size = message_size; + this.message = message; + + setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize())); + allocateBuffer(); + writeHeaderToBuffer(); + + putByte((byte) MESSAGE_VERSION_NUMBER); + putInt(message_type, 1); + putInt(message_size, 1); + putBytes(message.getBytes()); + + writeCRC(); + rewind(); + } + + @Override + public int getSize() { + return MESSAGE_SIZE + message_size; + } + + +} diff --git a/src/main/java/seng302/server/messages/Header.java b/src/main/java/seng302/server/messages/Header.java index c4dc6251..2b520611 100644 --- a/src/main/java/seng302/server/messages/Header.java +++ b/src/main/java/seng302/server/messages/Header.java @@ -43,10 +43,21 @@ public class Header { buff.position(buffPos); } + /** + * Reset the buffer + */ + public void reset(){ + buffPos = 0; + buff.clear(); + buff.position(buffPos); + } + /** * @return a ByteBuffer containing the message header */ public ByteBuffer getByteBuffer(){ + reset(); + putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1); putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2); diff --git a/src/main/java/seng302/server/messages/Heartbeat.java b/src/main/java/seng302/server/messages/Heartbeat.java index 8e619107..c86baac3 100644 --- a/src/main/java/seng302/server/messages/Heartbeat.java +++ b/src/main/java/seng302/server/messages/Heartbeat.java @@ -1,32 +1,16 @@ package seng302.server.messages; -import java.io.DataOutputStream; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; -import java.util.zip.CRC32; +import java.io.OutputStream; public class Heartbeat extends Message { private final int MESSAGE_SIZE = 4; - private int seqNo; /** * Heartbeat from the AC35 Streaming data spec * @param seqNo Increment every time a message is sent */ public Heartbeat(int seqNo){ - this.seqNo = seqNo; - } - - @Override - public int getSize() { - return MESSAGE_SIZE; - } - - @Override - public void send(SocketChannel outputStream) throws IOException { setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize())); allocateBuffer(); @@ -36,7 +20,11 @@ public class Heartbeat extends Message { writeCRC(); rewind(); - - outputStream.write(getBuffer()); } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + } \ No newline at end of file diff --git a/src/main/java/seng302/server/messages/MarkRoundingMessage.java b/src/main/java/seng302/server/messages/MarkRoundingMessage.java index 750efb22..5a085255 100644 --- a/src/main/java/seng302/server/messages/MarkRoundingMessage.java +++ b/src/main/java/seng302/server/messages/MarkRoundingMessage.java @@ -1,10 +1,7 @@ package seng302.server.messages; -import java.io.DataOutputStream; import java.io.IOException; -import java.nio.channels.Channels; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; +import java.io.OutputStream; public class MarkRoundingMessage extends Message{ private final long MESSAGE_VERSION_NUMBER = 1; @@ -33,15 +30,6 @@ public class MarkRoundingMessage extends Message{ this.markId = markId; setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize())); - } - - @Override - public int getSize() { - return MESSAGE_SIZE; - } - - @Override - public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -56,7 +44,10 @@ public class MarkRoundingMessage extends Message{ writeCRC(); rewind(); + } - outputStream.write(getBuffer()); + @Override + public int getSize() { + return MESSAGE_SIZE; } } diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java index e1bf2b53..398628ab 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/server/messages/Message.java @@ -1,9 +1,9 @@ package seng302.server.messages; import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.channels.SocketChannel; import java.util.Arrays; import java.util.zip.CRC32; @@ -33,11 +33,6 @@ public abstract class Message { */ public abstract int getSize(); - /** - * Send the message as through the outputStream - */ - public abstract void send(SocketChannel outputStream) throws IOException; - /** * Allocate byte buffer to correct size */ @@ -45,6 +40,7 @@ public abstract class Message { buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE); buffer.order(ByteOrder.LITTLE_ENDIAN); bufferPosition = 0; + buffer.position(bufferPosition); } /** @@ -161,10 +157,10 @@ public abstract class Message { } /** - * @return The current buffer + * @return The current buffer as a byte array */ - public ByteBuffer getBuffer(){ - return buffer; + public byte[] getBuffer(){ + return buffer.array(); } /** @@ -193,6 +189,25 @@ public abstract class Message { return data; } + /** + * takes an array of up to 7 bytes in little endian format and + * returns a positive long constructed from the input bytes + * + * @return a positive long if there is less than 8 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; + } + /** * Reverse an array of bytes * @param data The byte[] to reverse diff --git a/src/main/java/seng302/server/messages/MessageType.java b/src/main/java/seng302/server/messages/MessageType.java index be856dac..fccf8d45 100644 --- a/src/main/java/seng302/server/messages/MessageType.java +++ b/src/main/java/seng302/server/messages/MessageType.java @@ -16,7 +16,8 @@ public enum MessageType { BOAT_LOCATION(37), MARK_ROUNDING(38), COURSE_WIND(44), - AVERAGE_WIND(47); + AVERAGE_WIND(47), + BOAT_ACTION(100); private int code; diff --git a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java index 368a18fd..24158d62 100644 --- a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java @@ -1,10 +1,7 @@ package seng302.server.messages; -import java.io.DataOutputStream; import java.io.IOException; -import java.nio.channels.Channels; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; +import java.io.OutputStream; public class RaceStartStatusMessage extends Message { private final int MESSAGE_SIZE = 20; @@ -32,15 +29,6 @@ public class RaceStartStatusMessage extends Message { this.raceId = raceId; setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize())); - } - - @Override - public int getSize() { - return MESSAGE_SIZE; - } - - @Override - public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -53,7 +41,11 @@ public class RaceStartStatusMessage extends Message { writeCRC(); rewind(); - - outputStream.write(getBuffer()); } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + } diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java index 32ea9abd..0310216e 100644 --- a/src/main/java/seng302/server/messages/RaceStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -1,7 +1,7 @@ package seng302.server.messages; import java.io.IOException; -import java.nio.channels.SocketChannel; +import java.io.OutputStream; import java.util.List; import java.util.zip.CRC32; @@ -9,12 +9,14 @@ public class RaceStatusMessage extends Message{ private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS; private final int MESSAGE_VERSION = 2; //Always set to 1 private final int MESSAGE_BASE_SIZE = 24; + private final double windDirFactor = 0x4000 / 90; + private long currentTime; private long raceId; private RaceStatus raceStatus; private long expectedStartTime; - private WindDirection raceWindDirection; + private double raceWindDirection; private long windSpeed; private long numBoatsInRace; private RaceType raceType; @@ -33,13 +35,13 @@ public class RaceStatusMessage extends Message{ * @param sourceId The source of this message * @param boats A list of boat status sub messages */ - public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection, + public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, double raceWindDirection, long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List boats){ currentTime = System.currentTimeMillis(); this.raceId = raceId; this.raceStatus = raceStatus; this.expectedStartTime = expectedStartTime; - this.raceWindDirection = raceWindDirection; + this.raceWindDirection = raceWindDirection * windDirFactor; this.windSpeed = windSpeed; this.numBoatsInRace = numBoatsInRace; this.raceType = raceType; @@ -47,22 +49,6 @@ public class RaceStatusMessage extends Message{ crc = new CRC32(); setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize())); - } - - /** - * @return the size of this message in bytes - */ - @Override - public int getSize() { - return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace)); - } - - /** - * Send this message as a stream of bytes - * @param outputStream The output stream to send the message - */ - @Override - public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -71,7 +57,7 @@ public class RaceStatusMessage extends Message{ putInt((int) raceId, 4); putByte((byte) raceStatus.getCode()); putInt(expectedStartTime, 6); - putInt((int) raceWindDirection.getCode(), 2); + putInt((int) this.raceWindDirection, 2); putInt((int) windSpeed, 2); putByte((byte) numBoatsInRace); putByte((byte) raceType.getCode()); @@ -82,7 +68,14 @@ public class RaceStatusMessage extends Message{ writeCRC(); rewind(); - - outputStream.write(getBuffer()); } + + /** + * @return the size of this message in bytes + */ + @Override + public int getSize() { + return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace)); + } + } diff --git a/src/main/java/seng302/server/messages/WindDirection.java b/src/main/java/seng302/server/messages/WindDirection.java deleted file mode 100644 index c0b8d767..00000000 --- a/src/main/java/seng302/server/messages/WindDirection.java +++ /dev/null @@ -1,20 +0,0 @@ -package seng302.server.messages; - -/** - * Enum containing the supported wind directions - */ -public enum WindDirection { - NORTH(0x0000L), - EAST(0x4000L), - SOUTH(0x8000L); - - private long code; - - WindDirection(long code) { - this.code = code; - } - - public long getCode() { - return code; - } -} diff --git a/src/main/java/seng302/server/messages/XMLMessage.java b/src/main/java/seng302/server/messages/XMLMessage.java index 2cf3a5b5..57d10a00 100644 --- a/src/main/java/seng302/server/messages/XMLMessage.java +++ b/src/main/java/seng302/server/messages/XMLMessage.java @@ -1,12 +1,7 @@ package seng302.server.messages; -import java.io.DataOutputStream; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; -import java.util.zip.CRC32; +import java.io.OutputStream; public class XMLMessage extends Message{ private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE; @@ -35,20 +30,6 @@ public class XMLMessage extends Message{ sequence = sequenceNum; setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); - } - - /** - * @return The length of this message - */ - public int getSize(){ - return MESSAGE_SIZE + content.length(); - } - - /** - * Send this message as a stream of bytes - * @param outputStream The output stream to send the message - */ - public void send(SocketChannel outputStream) throws IOException { allocateBuffer(); writeHeaderToBuffer(); @@ -63,7 +44,12 @@ public class XMLMessage extends Message{ writeCRC(); rewind(); + } - outputStream.write(getBuffer()); + /** + * @return The length of this message + */ + public int getSize(){ + return MESSAGE_SIZE + content.length(); } } diff --git a/src/main/java/seng302/server/simulator/Boat.java b/src/main/java/seng302/server/simulator/Boat.java index 1768402b..435a70b6 100644 --- a/src/main/java/seng302/server/simulator/Boat.java +++ b/src/main/java/seng302/server/simulator/Boat.java @@ -1,7 +1,8 @@ package seng302.server.simulator; import seng302.server.simulator.mark.Corner; -import seng302.server.simulator.mark.Position; +import seng302.utilities.GeoPoint; +import seng302.utilities.GeoUtility; public class Boat { @@ -29,8 +30,8 @@ public class Boat { */ public void move(double heading, double duration) { Double distance = speed * duration / 1000000; // convert mm to meter - Position originPos = new Position(lat, lng); - Position newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance); + GeoPoint originPos = new GeoPoint(lat, lng); + GeoPoint newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance); this.lat = newPos.getLat(); this.lng = newPos.getLng(); } diff --git a/src/main/java/seng302/server/simulator/Simulator.java b/src/main/java/seng302/server/simulator/Simulator.java index d31c2cfd..9b1dc00b 100644 --- a/src/main/java/seng302/server/simulator/Simulator.java +++ b/src/main/java/seng302/server/simulator/Simulator.java @@ -3,8 +3,9 @@ package seng302.server.simulator; import seng302.server.simulator.mark.CompoundMark; import seng302.server.simulator.mark.Corner; import seng302.server.simulator.mark.Mark; -import seng302.server.simulator.mark.Position; import seng302.server.simulator.parsers.RaceParser; +import seng302.utilities.GeoPoint; +import seng302.utilities.GeoUtility; import java.util.HashSet; import java.util.List; @@ -80,8 +81,8 @@ public class Simulator extends Observable implements Runnable { boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration); - Position boatPos = new Position(boat.getLat(), boat.getLng()); - Position lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getMark1(); + GeoPoint boatPos = new GeoPoint(boat.getLat(), boat.getLng()); + GeoPoint lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getMark1(); double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos); // if a boat passes its heading mark @@ -97,13 +98,13 @@ public class Simulator extends Observable implements Runnable { } // move compensate distance for the mark just passed - Position pos = GeoUtility.getGeoCoordinate( + GeoPoint pos = GeoUtility.getGeoCoordinate( boat.getLastPassedCorner().getCompoundMark().getMark1(), boat.getLastPassedCorner().getBearingToNextCorner(), compensateDistance); boat.setLat(pos.getLat()); boat.setLng(pos.getLng()); - distanceFromLastMark = GeoUtility.getDistance(new Position(boat.getLat(), boat.getLng()), + distanceFromLastMark = GeoUtility.getDistance(new GeoPoint(boat.getLat(), boat.getLng()), boat.getLastPassedCorner().getCompoundMark().getMark1()); } } diff --git a/src/main/java/seng302/server/simulator/mark/Mark.java b/src/main/java/seng302/server/simulator/mark/Mark.java index 41f00bb6..0b6f1f3b 100644 --- a/src/main/java/seng302/server/simulator/mark/Mark.java +++ b/src/main/java/seng302/server/simulator/mark/Mark.java @@ -1,10 +1,12 @@ package seng302.server.simulator.mark; +import seng302.utilities.GeoPoint; + /** * An abstract class to represent general marks * Created by Haoming Yin (hyi25) on 17/3/17. */ -public class Mark extends Position { +public class Mark extends GeoPoint { private int seqID; private String name; @@ -22,7 +24,7 @@ public class Mark extends Position { */ @Override public String toString() { - return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, lat, lng); + return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, getLat(), getLng()); } public int getSeqID() { diff --git a/src/main/java/seng302/server/simulator/mark/Position.java b/src/main/java/seng302/utilities/GeoPoint.java similarity index 56% rename from src/main/java/seng302/server/simulator/mark/Position.java rename to src/main/java/seng302/utilities/GeoPoint.java index 74200e9d..a3d5c54b 100644 --- a/src/main/java/seng302/server/simulator/mark/Position.java +++ b/src/main/java/seng302/utilities/GeoPoint.java @@ -1,18 +1,18 @@ -package seng302.server.simulator.mark; +package seng302.utilities; -public class Position { +/** + * A class represent Geo location (latitude, longitude). + * Created by Haoming on 15/5/2017 + */ +public class GeoPoint { double lat, lng; - public Position(double lat, double lng) { + public GeoPoint(double lat, double lng) { this.lat = lat; this.lng = lng; } - public String toString() { - return String.format("Position at lat:%f lng:%f.", lat, lng); - } - public double getLat() { return lat; } diff --git a/src/main/java/seng302/server/simulator/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java similarity index 56% rename from src/main/java/seng302/server/simulator/GeoUtility.java rename to src/main/java/seng302/utilities/GeoUtility.java index dff67e50..0e17ff1a 100644 --- a/src/main/java/seng302/server/simulator/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -1,6 +1,6 @@ -package seng302.server.simulator; +package seng302.utilities; -import seng302.server.simulator.mark.Position; +import javafx.geometry.Point2D; public class GeoUtility { @@ -13,7 +13,7 @@ public class GeoUtility { * @param p2 second geographical position * @return the distance in meter between two points in meters */ - public static Double getDistance(Position p1, Position p2) { + public static Double getDistance(GeoPoint p1, GeoPoint p2) { double dLat = Math.toRadians(p2.getLat() - p1.getLat()); double dLon = Math.toRadians(p2.getLng() - p1.getLng()); @@ -42,7 +42,7 @@ public class GeoUtility { * (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60° * and end up on a heading of 120° */ - public static Double getBearing(Position p1, Position p2) { + public static Double getBearing(GeoPoint p1, GeoPoint p2) { double dLon = Math.toRadians(p2.getLng() - p1.getLng()); @@ -64,7 +64,7 @@ public class GeoUtility { * @param distance the distance in meter, from original position to the new position * @return the new position */ - public static Position getGeoCoordinate(Position origin, Double bearing, Double distance) { + public static GeoPoint getGeoCoordinate(GeoPoint origin, Double bearing, Double distance) { double b = Math.toRadians(bearing); // bearing to radians double d = distance / 1000.0; // distance to km @@ -77,6 +77,57 @@ public class GeoUtility { + Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat), Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); - return new Position(Math.toDegrees(endLat), Math.toDegrees(endLng)); + return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); + } + + /** + * Performs the line function on two points of a line and a test point to test which side of the line that point is + * on. If the return value is + * return 1, then the point is on one side of the line, + * return -1 then the point is on the other side of the line + * return 0 then the point is exactly on the line. + * @param linePoint1 One point of the line + * @param linePoint2 Second point of the line + * @param testPoint The point to test with this line + * @return A return value indicating which side of the line the point is on + */ + public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { + + Double x = testPoint.getX(); + Double y = testPoint.getY(); + Double x1 = linePoint1.getX(); + Double y1 = linePoint1.getY(); + Double x2 = linePoint2.getX(); + Double y2 = linePoint2.getY(); + + Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function + + if (result > 0) { + return 1; + } + else if (result < 0) { + return -1; + } + else { + return 0; + } + } + + + /** + * Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin + * point + * @param originPoint The point with which to use as the base for our vector addition + * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!) + * @param vectorLength The length out on this angle from the origin point to create the new point + * @return a Point2D + */ + public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) { + + Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); + Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); + + return new Point2D(endPointX, endPointY); + } } diff --git a/src/main/resources/PP.png b/src/main/resources/PP.png new file mode 100644 index 00000000..1cc88a95 Binary files /dev/null and b/src/main/resources/PP.png differ diff --git a/src/main/resources/config/course.xml b/src/main/resources/config/course.xml index cec726ad..180f692a 100644 --- a/src/main/resources/config/course.xml +++ b/src/main/resources/config/course.xml @@ -12,8 +12,8 @@ Start2 - 57.6706330 - 11.8281330 + 57.6703330 + 11.8271333 123 diff --git a/src/main/resources/css/master.css b/src/main/resources/css/master.css index 42fc44c7..7620d831 100644 --- a/src/main/resources/css/master.css +++ b/src/main/resources/css/master.css @@ -168,7 +168,7 @@ Remove scroll bars .ui-table *.scroll-bar:vertical *.increment-arrow, .ui-table *.scroll-bar:vertical *.decrement-arrow { - -fx-background-color: null; + -fx-background-color: #0e6d6c; -fx-background-radius: 0; -fx-background-insets: 0; -fx-padding: 0; @@ -177,7 +177,7 @@ Remove scroll bars .ui-table *.scroll-bar:vertical *.increment-button, .ui-table *.scroll-bar:vertical *.decrement-button { - -fx-background-color: null; + -fx-background-color: #0e6d6c; -fx-background-radius: 0; -fx-background-insets: 0; -fx-padding: 0; diff --git a/src/main/resources/music/Disturbed - down with the sickness.mp3 b/src/main/resources/music/Disturbed - down with the sickness.mp3 new file mode 100644 index 00000000..375b140b Binary files /dev/null and b/src/main/resources/music/Disturbed - down with the sickness.mp3 differ diff --git a/src/main/resources/music/Owl City - Fireflies.mp3 b/src/main/resources/music/Owl City - Fireflies.mp3 new file mode 100644 index 00000000..fce20269 Binary files /dev/null and b/src/main/resources/music/Owl City - Fireflies.mp3 differ diff --git a/src/main/resources/pics/alistair.gif b/src/main/resources/pics/alistair.gif new file mode 100644 index 00000000..ce003789 Binary files /dev/null and b/src/main/resources/pics/alistair.gif differ diff --git a/src/main/resources/pics/calum.gif b/src/main/resources/pics/calum.gif new file mode 100644 index 00000000..03d495ed Binary files /dev/null and b/src/main/resources/pics/calum.gif differ diff --git a/src/main/resources/pics/haoming.gif b/src/main/resources/pics/haoming.gif new file mode 100644 index 00000000..33b26de6 Binary files /dev/null and b/src/main/resources/pics/haoming.gif differ diff --git a/src/main/resources/pics/kusal.gif b/src/main/resources/pics/kusal.gif new file mode 100644 index 00000000..1372928c Binary files /dev/null and b/src/main/resources/pics/kusal.gif differ diff --git a/src/main/resources/pics/michael.gif b/src/main/resources/pics/michael.gif new file mode 100644 index 00000000..83ea1dff Binary files /dev/null and b/src/main/resources/pics/michael.gif differ diff --git a/src/main/resources/pics/parrot.gif b/src/main/resources/pics/parrot.gif new file mode 100644 index 00000000..458ad859 Binary files /dev/null and b/src/main/resources/pics/parrot.gif differ diff --git a/src/main/resources/pics/peter.gif b/src/main/resources/pics/peter.gif new file mode 100644 index 00000000..470b46cf Binary files /dev/null and b/src/main/resources/pics/peter.gif differ diff --git a/src/main/resources/pics/ryan.gif b/src/main/resources/pics/ryan.gif new file mode 100644 index 00000000..b518c777 Binary files /dev/null and b/src/main/resources/pics/ryan.gif differ diff --git a/src/main/resources/pics/sail.png b/src/main/resources/pics/sail.png new file mode 100644 index 00000000..7335918c Binary files /dev/null and b/src/main/resources/pics/sail.png differ diff --git a/src/main/resources/pics/will.gif b/src/main/resources/pics/will.gif new file mode 100644 index 00000000..e7b762de Binary files /dev/null and b/src/main/resources/pics/will.gif differ diff --git a/src/main/resources/server_config/boats1.xml b/src/main/resources/server_config/boats1.xml new file mode 100644 index 00000000..401e7bf6 --- /dev/null +++ b/src/main/resources/server_config/boats1.xml @@ -0,0 +1,171 @@ + + + 2015-08-28T17:32:59+0100 + 12 + 219 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/server_config/boats2.xml b/src/main/resources/server_config/boats2.xml new file mode 100644 index 00000000..c7255771 --- /dev/null +++ b/src/main/resources/server_config/boats2.xml @@ -0,0 +1,161 @@ + + + 2015-08-28T17:32:59+0100 + 12 + 219 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/server_config/boats3.xml b/src/main/resources/server_config/boats3.xml new file mode 100644 index 00000000..401e7bf6 --- /dev/null +++ b/src/main/resources/server_config/boats3.xml @@ -0,0 +1,171 @@ + + + 2015-08-28T17:32:59+0100 + 12 + 219 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/server_config/xml_templates/boats.ftlh b/src/main/resources/server_config/xml_templates/boats.ftlh new file mode 100644 index 00000000..f9a1e2d0 --- /dev/null +++ b/src/main/resources/server_config/xml_templates/boats.ftlh @@ -0,0 +1,22 @@ + + 2012-05-17T07:49:40+0200 + 12 + + + + + + + + <#-- Not used --> + + + <#list boats as boat> + + + + + + + \ No newline at end of file diff --git a/src/main/resources/server_config/xml_templates/race.ftlh b/src/main/resources/server_config/xml_templates/race.ftlh new file mode 100644 index 00000000..4349d2e3 --- /dev/null +++ b/src/main/resources/server_config/xml_templates/race.ftlh @@ -0,0 +1,86 @@ + + + ${raceStartTime} + + 15082901 + Fleet + + + <#list boats as boat> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/server_config/xml_templates/regatta.ftlh b/src/main/resources/server_config/xml_templates/regatta.ftlh new file mode 100644 index 00000000..25543c15 --- /dev/null +++ b/src/main/resources/server_config/xml_templates/regatta.ftlh @@ -0,0 +1,11 @@ + + + ${regattaId} + ${name} + ${courseName} + ${latitude} + ${longitude} + ${altitude} + ${utcOffset} + ${magneticVariation} + \ No newline at end of file diff --git a/src/main/resources/views/LobbyView.fxml b/src/main/resources/views/LobbyView.fxml new file mode 100644 index 00000000..e867a519 --- /dev/null +++ b/src/main/resources/views/LobbyView.fxml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/seng302/TestGeoUtils.java b/src/test/java/seng302/TestGeoUtils.java index 633cec99..1660c6be 100644 --- a/src/test/java/seng302/TestGeoUtils.java +++ b/src/test/java/seng302/TestGeoUtils.java @@ -3,6 +3,7 @@ package seng302; import javafx.geometry.Point2D; import org.junit.Before; import org.junit.Test; +import seng302.utilities.GeoUtility; import static org.junit.Assert.*; @@ -34,9 +35,9 @@ public class TestGeoUtils { @Test public void testLineFunction() { - Integer lineFunctionResult1 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint1); - Integer lineFunctionResult2 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint2); - Integer lineFunctionResult3 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint3); + Integer lineFunctionResult1 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint1); + Integer lineFunctionResult2 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint2); + Integer lineFunctionResult3 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint3); //Point1 and Point2 are on opposite sides assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2)); @@ -50,13 +51,13 @@ public class TestGeoUtils { public void testMakeArbitraryVectorPoint() { //Make a point (1,0) from point (0,0) - Point2D newPoint = GeometryUtils.makeArbitraryVectorPoint(linePoint1, 0d, 1d); + Point2D newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 0d, 1d); Point2D expected = new Point2D(1,0); assertEquals(expected.getX(), newPoint.getX(), 1E-6); assertEquals(expected.getY(), newPoint.getY(), 1E-6); - newPoint = GeometryUtils.makeArbitraryVectorPoint(linePoint1, 90d, 1d); + newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 90d, 1d); expected = new Point2D(0, 1); assertEquals(expected.getX(), newPoint.getX(), 1E-6); diff --git a/src/test/java/seng302/models/YachtTest.java b/src/test/java/seng302/models/YachtTest.java new file mode 100644 index 00000000..ab467522 --- /dev/null +++ b/src/test/java/seng302/models/YachtTest.java @@ -0,0 +1,27 @@ +package seng302.models; + + +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import seng302.utilities.GeoPoint; + +public class YachtTest { + + Double windDir; + Double windSpd; + List yachts = new ArrayList(); + + @Before + public void setUp() { + PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); + windDir = 90d; + windSpd = 10d; + + yachts.add(new Yacht("Yacht 1", "Y1", new GeoPoint(-30.0, 20.0), 160.0)); + yachts.add(new Yacht("Yacht 2", "Y2", new GeoPoint(-40.0, -20.0), 100.0)); + yachts.add(new Yacht("Yacht 3", "Y3", new GeoPoint(-35.0, -15.5), 20.0)); + } + +} diff --git a/src/test/java/seng302/models/map/MercatorProjectionTest.java b/src/test/java/seng302/models/map/MercatorProjectionTest.java index a4350c18..118d3bd8 100644 --- a/src/test/java/seng302/models/map/MercatorProjectionTest.java +++ b/src/test/java/seng302/models/map/MercatorProjectionTest.java @@ -1,6 +1,9 @@ package seng302.models.map; import org.junit.Test; +import seng302.utilities.GeoPoint; + +import java.awt.geom.Point2D; import static org.junit.Assert.*; @@ -11,30 +14,30 @@ import static org.junit.Assert.*; public class MercatorProjectionTest { @Test public void toMapPoint() throws Exception { - MapGeo geo1 = new MapGeo(12.485394, 19.38947); - MapPoint actualPoint1 = MercatorProjection.toMapPoint(geo1); - MapPoint expectedPoint1 = new MapPoint(141.78806755555556, 119.0503853635612); + GeoPoint geo1 = new GeoPoint(12.485394, 19.38947); + javafx.geometry.Point2D actualPoint1 = MercatorProjection.toMapPoint(geo1); + javafx.geometry.Point2D expectedPoint1 = new javafx.geometry.Point2D(141.78806755555556, 119.0503853635612); assertEquals(expectedPoint1.getX(), actualPoint1.getX(), 0.0001); assertEquals(expectedPoint1.getY(), actualPoint1.getY(), 0.0001); - MapGeo geo2 = new MapGeo(77.456432, -23.456462); - MapPoint actualPoint2 = MercatorProjection.toMapPoint(geo2); - MapPoint expectedPoint2 = new MapPoint(111.31984924444444, 38.03143323746788); + GeoPoint geo2 = new GeoPoint(77.456432, -23.456462); + javafx.geometry.Point2D actualPoint2 = MercatorProjection.toMapPoint(geo2); + javafx.geometry.Point2D expectedPoint2 = new javafx.geometry.Point2D(111.31984924444444, 38.03143323746788); assertEquals(expectedPoint2.getX(), actualPoint2.getX(), 0.0001); assertEquals(expectedPoint2.getY(), actualPoint2.getY(), 0.0001); } @Test public void toMapGeo() throws Exception { - MapPoint point1 = new MapPoint(123.1234, 25.4565); - MapGeo actualGeo1 = MercatorProjection.toMapGeo(point1); - MapGeo expectedGeo1 = new MapGeo(80.77043127275441, -6.857718749999995); + javafx.geometry.Point2D point1 = new javafx.geometry.Point2D(123.1234, 25.4565); + GeoPoint actualGeo1 = MercatorProjection.toMapGeo(point1); + GeoPoint expectedGeo1 = new GeoPoint(80.77043127275441, -6.857718749999995); assertEquals(expectedGeo1.getLat(), actualGeo1.getLat(), 0.0001); assertEquals(expectedGeo1.getLng(), actualGeo1.getLng(), 0.0001); - MapPoint point2 = new MapPoint(1.235, 255.4565); - MapGeo actualGeo2 = MercatorProjection.toMapGeo(point2); - MapGeo expectedGeo2 = new MapGeo(-84.98475532898011, -178.26328125); + javafx.geometry.Point2D point2 = new javafx.geometry.Point2D(1.235, 255.4565); + GeoPoint actualGeo2 = MercatorProjection.toMapGeo(point2); + GeoPoint expectedGeo2 = new GeoPoint(-84.98475532898011, -178.26328125); assertEquals(expectedGeo2.getLat(), actualGeo2.getLat(), 0.0001); assertEquals(expectedGeo2.getLng(), actualGeo2.getLng(), 0.0001); } diff --git a/src/test/java/seng302/models/stream/StreamReceiverTest.java b/src/test/java/seng302/models/stream/StreamReceiverTest.java index 96f89b93..082f9128 100644 --- a/src/test/java/seng302/models/stream/StreamReceiverTest.java +++ b/src/test/java/seng302/models/stream/StreamReceiverTest.java @@ -61,16 +61,16 @@ public class StreamReceiverTest { assert pq.size() == 0; } - @Test - public void connectReadsAPacket() throws Exception { - Socket host=mock(Socket.class); - InputStream stream = new ByteArrayInputStream(workingPacket); - when(host.getInputStream()).thenReturn(stream); - StreamReceiver streamReceiver = new StreamReceiver(host, pq); - - streamReceiver.connect(); - assert pq.size() == 1; - } +// @Test +// public void connectReadsAPacket() throws Exception { +// Socket host=mock(Socket.class); +// InputStream stream = new ByteArrayInputStream(workingPacket); +// when(host.getInputStream()).thenReturn(stream); +// StreamReceiver streamReceiver = new StreamReceiver(host, pq); +// +// streamReceiver.connect(); +// assert pq.size() == 1; +// } @Test public void connectDropsAMismatchedCrc() throws Exception { diff --git a/src/test/java/seng302/server/simulator/GeoUtilityTest.java b/src/test/java/seng302/server/simulator/GeoUtilityTest.java index 4cdf01df..a2d22b49 100644 --- a/src/test/java/seng302/server/simulator/GeoUtilityTest.java +++ b/src/test/java/seng302/server/simulator/GeoUtilityTest.java @@ -1,7 +1,8 @@ package seng302.server.simulator; import org.junit.Test; -import seng302.server.simulator.mark.Position; +import seng302.utilities.GeoPoint; +import seng302.utilities.GeoUtility; import static org.junit.Assert.*; @@ -11,10 +12,10 @@ import static org.junit.Assert.*; */ public class GeoUtilityTest { - private Position p1 = new Position(57.670333, 11.827833); - private Position p2 = new Position(57.671524, 11.844495); - private Position p3 = new Position(57.670822, 11.843392); - private Position p4 = new Position(25.694829, 98.392049); + private GeoPoint p1 = new GeoPoint(57.670333, 11.827833); + private GeoPoint p2 = new GeoPoint(57.671524, 11.844495); + private GeoPoint p3 = new GeoPoint(57.670822, 11.843392); + private GeoPoint p4 = new GeoPoint(25.694829, 98.392049); private double toleranceRate = 0.01; @@ -54,7 +55,7 @@ public class GeoUtilityTest { @Test public void getGeoCoordinate() throws Exception { - Position expected, actual; + GeoPoint expected, actual; actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0); expected = p2;