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>
+
+
+
+
+ #list>
+
+
\ 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>
+
+ #list>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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/main/resources/views/MainView.fxml b/src/main/resources/views/MainView.fxml
index dab6e135..87cc7443 100644
--- a/src/main/resources/views/MainView.fxml
+++ b/src/main/resources/views/MainView.fxml
@@ -7,4 +7,5 @@
-
+
+
diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml
index d777cece..ea453f32 100644
--- a/src/main/resources/views/RaceView.fxml
+++ b/src/main/resources/views/RaceView.fxml
@@ -35,7 +35,12 @@
-
+
+
+
+
+
+
@@ -48,9 +53,9 @@
-
+
-
+
diff --git a/src/main/resources/views/StartScreenView.fxml b/src/main/resources/views/StartScreenView.fxml
index e7dd3dea..bfbcf4ee 100644
--- a/src/main/resources/views/StartScreenView.fxml
+++ b/src/main/resources/views/StartScreenView.fxml
@@ -1,60 +1,59 @@
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
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;